From 36b99265274f5e34a9507a29bf4ed13507d6be47 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 12 Oct 2022 12:21:48 +0000 Subject: [PATCH 001/176] chore(release): Bumped to Version 14.3.0 # [14.3.0](https://github.com/frappe/erpnext/compare/v14.2.3...v14.3.0) (2022-10-12) ### Bug Fixes * consider outgoingrate while valuation rate calculate ([423c019](https://github.com/frappe/erpnext/commit/423c0190e29a99af1aef223d3f4d8d7e6dbfcf0c)) * consider sales rate as incoming rate for transit warehouse in purchase flow ([948b231](https://github.com/frappe/erpnext/commit/948b231e92755b725785865ec2e0ae385c5efb7f)) * Do not add tax withheld vouchers post tax withheding in one document ([bd8e61b](https://github.com/frappe/erpnext/commit/bd8e61b2dc0bee580b19925c5ecb20dc4f837118)) * Explicitly update modified (backport [#32519](https://github.com/frappe/erpnext/issues/32519)) ([#32575](https://github.com/frappe/erpnext/issues/32575)) ([154904e](https://github.com/frappe/erpnext/commit/154904e9609a572f1c6d9f0cbffec220881dbb33)) * Hanlde rounding loss for internal transfer ([8eda2db](https://github.com/frappe/erpnext/commit/8eda2dbdf8c26798af0859baaac4458330096ef5)) * Incoming rate precision fixes for intra company transfer ([b653f43](https://github.com/frappe/erpnext/commit/b653f43b70f76e3620948fd483a6f3f7eb201dac)) * make readings status mandatory in Quality Inspection ([50d790c](https://github.com/frappe/erpnext/commit/50d790c919b4b653844e93797e3f7ab3b4aee5c2)) * minor cleanup ([236a5f3](https://github.com/frappe/erpnext/commit/236a5f37c758652092336888451c788bfddfd75b)) * more fields reordering related to Tab Break ([db4fbc9](https://github.com/frappe/erpnext/commit/db4fbc9e58e193439024ca309210b0f2fbdf1d71)) * PO cancel post advance payment cancel against PO ([bda25c4](https://github.com/frappe/erpnext/commit/bda25c4d5d2b6e30f6c4e29897a5404d31a41100)) * removed unnecessary imports ([31782d6](https://github.com/frappe/erpnext/commit/31782d6f71d8066f64f90ce56e29d1832ae32235)) * set Quality Inspection status based on readings status ([e25db8b](https://github.com/frappe/erpnext/commit/e25db8b1b5d4cd118c4e6a3fe49f5262373e2aea)) * single column indexes (backport [#32425](https://github.com/frappe/erpnext/issues/32425)) ([#32513](https://github.com/frappe/erpnext/issues/32513)) ([2f1b8af](https://github.com/frappe/erpnext/commit/2f1b8af13c2b5f155225859ea65770b7e5a9ba69)) * Tax withholding related fixes ([d5f6938](https://github.com/frappe/erpnext/commit/d5f693806b4e7699b07594ea1565b24809b1d82e)) * test case ([0b26131](https://github.com/frappe/erpnext/commit/0b26131b65e7b68b5ebc7fcde164f2937958c0e7)) * **test:** `test_rejected_qi_validation` ([943e619](https://github.com/frappe/erpnext/commit/943e6192cc4da77ffa4672a0fc5c67adb59684c7)) * TooManyWritesError during reposting of stock ([73742ff](https://github.com/frappe/erpnext/commit/73742ff56566170bcf37058fc315d94eccae1eff)) * unlink payment on invoice cancellation ([8ba503b](https://github.com/frappe/erpnext/commit/8ba503bf0e2a6ffe9f5b3e5210c3a9dc9ec4942b)) * use naming_series in budget ([0a24859](https://github.com/frappe/erpnext/commit/0a24859c9c00e8f0ef857e4859d1e483f94150b6)) * value error on pos submit ([4b65dd7](https://github.com/frappe/erpnext/commit/4b65dd7f2a55076ff9922e7113e704d1bfaf089a)) ### Features * **JE:** trigger account field when fetched from template ([17dd1ab](https://github.com/frappe/erpnext/commit/17dd1abb34af9ee6a01080e15491faa7596cd923)), closes [#32409](https://github.com/frappe/erpnext/issues/32409) * provision to return non consumed components against the work order ([d0f3818](https://github.com/frappe/erpnext/commit/d0f3818060de365df90b6eda24d3d217af9a616f)) * Tab Break in Material Request ([79151be](https://github.com/frappe/erpnext/commit/79151be8ce1442cc232ab6eb2a74e84659dd684d)) * Tab Break in Purchase Invoice ([89314d4](https://github.com/frappe/erpnext/commit/89314d4171e7ef736ab1c77c199aa17034f80eae)) * Tab Break in Purchase Order ([5a8329a](https://github.com/frappe/erpnext/commit/5a8329add1cc8457108ed2ff7d2821bf07a4efa6)) * Tab Break in Purchase Receipt ([b092791](https://github.com/frappe/erpnext/commit/b092791ac803c7f229ea5c8202b04cd1e5ffe06d)) * Tab Break in Quotation ([b0a9f79](https://github.com/frappe/erpnext/commit/b0a9f79e181826eef48a62fc28a17d73abdbb129)) * Tab Break in Sales Order, Delivery Note, Sales Invoice and Purchase Order ([31cd96b](https://github.com/frappe/erpnext/commit/31cd96bc89f9f1b0ca29359ceb9ae542e32289ff)) * Tab Break in Supplier Quotation ([22ad3ad](https://github.com/frappe/erpnext/commit/22ad3ad6f39a2c717d44fea2d039bbe76a750aaf)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 7a447fef30e..8866fa756cd 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.2.3" +__version__ = "14.3.0" def get_default_company(user=None): From 9703771b757ec38c907d8f4a5527b622f30110c4 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 18 Oct 2022 18:03:17 +0000 Subject: [PATCH 002/176] chore(release): Bumped to Version 14.3.1 ## [14.3.1](https://github.com/frappe/erpnext/compare/v14.3.0...v14.3.1) (2022-10-18) ### Bug Fixes * `Brand Defaults` filters ([cb0c4b5](https://github.com/frappe/erpnext/commit/cb0c4b566455a71577e084fa910e4ce15b114984)) * add project settings to projects workspace (backport [#32568](https://github.com/frappe/erpnext/issues/32568)) ([#32600](https://github.com/frappe/erpnext/issues/32600)) ([af4dafd](https://github.com/frappe/erpnext/commit/af4dafdc64c53883c55c73620926fea00c021591)) * delete old ple's on item value repost ([62cabdf](https://github.com/frappe/erpnext/commit/62cabdf792d98c39bedddb20f6d1e641ed2b40c5)) * don't try to update youtube data if disabled in settings (backport [#32588](https://github.com/frappe/erpnext/issues/32588)) ([#32589](https://github.com/frappe/erpnext/issues/32589)) ([ed85683](https://github.com/frappe/erpnext/commit/ed85683fea42bd9cb96e9a28a37446db48a38033)) * group warehouse filter not working for Batchwise Balance history report ([faedd85](https://github.com/frappe/erpnext/commit/faedd85b660fc0647ab877fc99ddd41634809f44)) * Ignore linked purchase invoice on cancel ([cc938fb](https://github.com/frappe/erpnext/commit/cc938fb028ca26df9f1f764664375885149d0421)) * linter ([831f60f](https://github.com/frappe/erpnext/commit/831f60f439a71fb39c9d2ef287bcaf3fe49a6d38)) * Party account for multi-order invoices ([eaea846](https://github.com/frappe/erpnext/commit/eaea8469fc092458454f7859574833efa29211f4)) * pricing rule item code UOM apply & conversions (backport [#32566](https://github.com/frappe/erpnext/issues/32566)) ([#32637](https://github.com/frappe/erpnext/issues/32637)) ([ffd82f3](https://github.com/frappe/erpnext/commit/ffd82f330233ed00bbafbeabef9ceef21a491f4f)) * Renamed Dashboard tab label to Connections ([dac5989](https://github.com/frappe/erpnext/commit/dac5989d72732a83c4e1c45851be635b860f50cb)) * Renamed Notes section to Comments ([9e94dd9](https://github.com/frappe/erpnext/commit/9e94dd9203396f745ab32740942e169536e1fac7)) * type-cast while saving an item (backport [#32549](https://github.com/frappe/erpnext/issues/32549)) ([#32578](https://github.com/frappe/erpnext/issues/32578)) ([829a0ff](https://github.com/frappe/erpnext/commit/829a0ff827d81beca6bb98caff0c9295da204e2c)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 8866fa756cd..94d6b34de46 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.3.0" +__version__ = "14.3.1" def get_default_company(user=None): From 123d720616fb2f3901329789c77990ed93092f23 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 26 Oct 2022 05:09:19 +0000 Subject: [PATCH 003/176] chore(release): Bumped to Version 14.4.0 # [14.4.0](https://github.com/frappe/erpnext/compare/v14.3.1...v14.4.0) (2022-10-26) ### Bug Fixes * Advance paid amount in orders (backport [#32642](https://github.com/frappe/erpnext/issues/32642)) ([#32648](https://github.com/frappe/erpnext/issues/32648)) ([8a88105](https://github.com/frappe/erpnext/commit/8a88105aed3de147a8fc176467435292870195c9)) * allow to create Sales Order from expired Quotation ([#32641](https://github.com/frappe/erpnext/issues/32641)) ([ccc58f4](https://github.com/frappe/erpnext/commit/ccc58f48e38f1aaeaf23410e8b3a8a45dbcd9eea)) * Billing Address for inter-company purchase docs ([f8934fa](https://github.com/frappe/erpnext/commit/f8934faa73c020660c189a9b458a94afaebae892)) * BOM cost update message ([e539579](https://github.com/frappe/erpnext/commit/e539579fb4e3e3147517aaafb076e55097730dfa)) * dont update item info twice ([8876904](https://github.com/frappe/erpnext/commit/887690449dbc56d3e801ccb4ab3d649e78278a44)) * incorrect qty in material request ([da538a3](https://github.com/frappe/erpnext/commit/da538a37addaec6911d30dd3aec361f93d58df35)) * number of months subscription plan ([fff9e76](https://github.com/frappe/erpnext/commit/fff9e7671844d8b6df9ff9a25dfdbfb5b5b58ba1)) * overlap error not raised for job card in case of workstation with production capacity ([ed2a093](https://github.com/frappe/erpnext/commit/ed2a093e493ea41efbfc9b47c9c32856d4fdd9de)) * party type and party mandatory on updating outstanding ([9a5e238](https://github.com/frappe/erpnext/commit/9a5e238702fa7f7469e5269e84e1c274f92e0821)) * searchfield not working for cuctsomer, supplier as per customize form ([fb1c307](https://github.com/frappe/erpnext/commit/fb1c30718ba5f1cf9dcbf5c5f21b603bca10b4a8)) * unset contact details ([d7a65b1](https://github.com/frappe/erpnext/commit/d7a65b1d419fb9c8131a48ada866f1fe8ed5037a)) ### Features * Basic Payment Ledger report ([5cb9f7b](https://github.com/frappe/erpnext/commit/5cb9f7b3a451d609872384005238b252d02cc882)) * Repayment schedule types for term loans ([6ce32fd](https://github.com/frappe/erpnext/commit/6ce32fd5a983739186d690f3209dbc69d6e888f0)) ### Performance Improvements * cache barcode scan result (backport [#32629](https://github.com/frappe/erpnext/issues/32629)) ([#32672](https://github.com/frappe/erpnext/issues/32672)) ([3300856](https://github.com/frappe/erpnext/commit/3300856fb462a9aa1d146f50d157f5a862df6e97)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 94d6b34de46..ea34b889777 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.3.1" +__version__ = "14.4.0" def get_default_company(user=None): From ce5dbf891a211de2d0ec6072b6d7ff25e6530dc3 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 1 Nov 2022 17:19:41 +0000 Subject: [PATCH 004/176] chore(release): Bumped to Version 14.5.0 # [14.5.0](https://github.com/frappe/erpnext/compare/v14.4.0...v14.5.0) (2022-11-01) ### Bug Fixes * add `Sales Order` reference in Material Request Dashboard ([fc63892](https://github.com/frappe/erpnext/commit/fc6389280ce790aa98c345578d3da39579c2cf25)) * Add condition for discount section collapse ([953f78d](https://github.com/frappe/erpnext/commit/953f78d6a9ac4e346f7a0ae71aaf10be95ae0cd9)) * Budget validation for main cost center ([89a1c83](https://github.com/frappe/erpnext/commit/89a1c83431950129bd305c35f012e07843763098)) * Clear invoice table post importing invoices ([6eafff8](https://github.com/frappe/erpnext/commit/6eafff86941d49983b6c7934a580479b3781f5e4)) * Company bank account filter in Bank Clearance ([797512c](https://github.com/frappe/erpnext/commit/797512ca131ea70e844a5b9bf2b38f64605e945a)) * Curreny in SOA print for multi-currency party ([195500c](https://github.com/frappe/erpnext/commit/195500cb32b274b21ff63525bbe898f022bf1845)) * duplicate custom fields for inventory dimension ([1152ac3](https://github.com/frappe/erpnext/commit/1152ac3ff10c73acddb0ec9973baa2cada2e9345)) * Filter fixes in Accounts Payable report ([29197dc](https://github.com/frappe/erpnext/commit/29197dcd7fd56ad5ab962cb77362940f6b3c4bf0)) * Issues while cancel/amending Purchase Invoice with TDS enabled ([74a6479](https://github.com/frappe/erpnext/commit/74a6479f707c7c849b5aac626e698dca94111a03)) * key error in filter access ([6114241](https://github.com/frappe/erpnext/commit/6114241ff2545cbede7bf55ccfa5b68dba07955b)) * Mode of payment for returns in POS Sales Invoice ([a260426](https://github.com/frappe/erpnext/commit/a260426dd471d7b186fb1f2a9352e48d06b0cd4e)) * Pass project to stock entry items ([4035873](https://github.com/frappe/erpnext/commit/403587329508822ec33cae87c86de562d26277e5)) * pro_rata_amount calculation in assets tests ([d1b2786](https://github.com/frappe/erpnext/commit/d1b2786f24559c46fbee02e354016243f9c0ecfe)) * Reference due date field type in Journal Entry Accounts table ([faf25c0](https://github.com/frappe/erpnext/commit/faf25c0b950e72a164acaa892c1124e956110a09)) * Reset advance paid amount on Oreder cancel and amend ([34bd783](https://github.com/frappe/erpnext/commit/34bd7837e27cbc57ebedbdfaa1dc5745a4f36869)) * Total Sales amount update in project via Sales Order ([d742e6d](https://github.com/frappe/erpnext/commit/d742e6d56b8c66df9f582a763da13a716faf4850)) ### Features * additional filters on Payment terms report ([a03ec0a](https://github.com/frappe/erpnext/commit/a03ec0afb382e506bfdf2af109b89644d82c1fd7)) * **pricing rule:** free qty rounding and recursion qty ([#32577](https://github.com/frappe/erpnext/issues/32577)) ([9b66020](https://github.com/frappe/erpnext/commit/9b66020fc70209196cda3d9b6e6a654b3b32d8c1)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ea34b889777..2c7ca0af0d1 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.4.0" +__version__ = "14.5.0" def get_default_company(user=None): From dd4dbd4b000e6ccc3ee3e93fefdd2ce3a03b1c7d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 3 Nov 2022 11:24:58 +0530 Subject: [PATCH 005/176] fix: not able to select customer / supplier (cherry picked from commit b0fc568c80ec5bead83fc0bc61be78e95ba24813) (cherry picked from commit 6989cdf4f2c327bcc7565ce70a51b794a83f5c94) --- erpnext/buying/doctype/supplier/test_supplier.py | 4 ++++ erpnext/controllers/queries.py | 4 ++-- erpnext/selling/doctype/customer/test_customer.py | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index e2dbf21be2c..b9fc344647b 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -156,6 +156,8 @@ class TestSupplier(FrappeTestCase): def test_serach_fields_for_supplier(self): from erpnext.controllers.queries import supplier_query + frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series") + supplier_name = create_supplier(supplier_name="Test Supplier 1").name make_property_setter( @@ -187,6 +189,8 @@ class TestSupplier(FrappeTestCase): self.assertEqual(data[0].supplier_type, "Company") self.assertTrue("supplier_type" in data[0]) + frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name") + def create_supplier(**args): args = frappe._dict(args) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 3bdc0170682..b0cf7241669 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -85,7 +85,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict= fields = ["name"] if cust_master_name != "Customer Name": - fields = ["customer_name"] + fields.append("customer_name") fields = get_fields(doctype, fields) searchfields = frappe.get_meta(doctype).get_search_fields() @@ -123,7 +123,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict= fields = ["name"] if supp_master_name != "Supplier Name": - fields = ["supplier_name"] + fields.append("supplier_name") fields = get_fields(doctype, fields) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 691adccd4dd..a621c737ed3 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -345,6 +345,8 @@ class TestCustomer(FrappeTestCase): def test_serach_fields_for_customer(self): from erpnext.controllers.queries import customer_query + frappe.db.set_value("Selling Settings", None, "cust_master_name", "Naming Series") + make_property_setter( "Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype" ) @@ -369,6 +371,8 @@ class TestCustomer(FrappeTestCase): self.assertEqual(data[0].territory, "_Test Territory") self.assertTrue("territory" in data[0]) + frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name") + def get_customer_dict(customer_name): return { From 3967773fbeddfd05b53f722f1b51ea813a57b3c1 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 3 Nov 2022 08:07:36 +0000 Subject: [PATCH 006/176] chore(release): Bumped to Version 14.5.1 ## [14.5.1](https://github.com/frappe/erpnext/compare/v14.5.0...v14.5.1) (2022-11-03) ### Bug Fixes * not able to select customer / supplier ([dd4dbd4](https://github.com/frappe/erpnext/commit/dd4dbd4b000e6ccc3ee3e93fefdd2ce3a03b1c7d)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2c7ca0af0d1..6295cc7e96d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.5.0" +__version__ = "14.5.1" def get_default_company(user=None): From e917212849cc5444a80d3e28a0e9a1ee9495d0fa Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 8 Nov 2022 12:15:30 +0000 Subject: [PATCH 007/176] chore(release): Bumped to Version 14.6.0 # [14.6.0](https://github.com/frappe/erpnext/compare/v14.5.1...v14.6.0) (2022-11-08) ### Bug Fixes * `Material Consumption` option in case of `Skip Transfer to WIP` in WO ([8c856cd](https://github.com/frappe/erpnext/commit/8c856cd5fc41c8603edf85e66db8ed1879b0ebb5)) * add translate function to name of chart labels in budget_variance_report.py ([16f364d](https://github.com/frappe/erpnext/commit/16f364da37c4a67cdee1a00714a3560b155ef9fe)) * add translate function to name of chart labels in deferred_revenue_and_expense.py ([b8caa58](https://github.com/frappe/erpnext/commit/b8caa587d2296cf856122bb2f57a85263a9a69f1)) * add translate function to period in stock_analytics.py ([b0c06d5](https://github.com/frappe/erpnext/commit/b0c06d5a04adb12704fa1c10e35cca329dedc594)) * add translate function to period in sales_analytics.py ([e681f06](https://github.com/frappe/erpnext/commit/e681f068832384ee55e8eb170ce6a637a902a81d)) * add translate function to string on budget_variance_report.js to match the variance word translated ([595aaad](https://github.com/frappe/erpnext/commit/595aaad99d5956f6345a6dadfd1994a12ac0bf26)) * Auto advance allocation against partial invoices ([b7763d9](https://github.com/frappe/erpnext/commit/b7763d953ad11f5770c03fb5376f6da0cc93d78e)) * auto increment qty if item table has no items ([d8e403b](https://github.com/frappe/erpnext/commit/d8e403bf5de17cdbda349a0f16680c2af482dd06)) * conflicts ([ab87a95](https://github.com/frappe/erpnext/commit/ab87a950e522bed10ca85951cae8bf89e483b7fa)) * correct linters ([8f6f9a4](https://github.com/frappe/erpnext/commit/8f6f9a429a3131ee3bdab4836775223bb4a4b1b4)) * correct linters ([440e208](https://github.com/frappe/erpnext/commit/440e20859f1b26e253141652d9a4c1bca06165de)) * correct linters ([5acc9be](https://github.com/frappe/erpnext/commit/5acc9be5c931aaa22b17be36b581068501d10c87)) * Create POS Opening Entry POS Profile filter. ([60af9c0](https://github.com/frappe/erpnext/commit/60af9c0516935963538a50a623ab32052099eda2)) * Disable tax included prices for internal transfers ([#32794](https://github.com/frappe/erpnext/issues/32794)) ([6838e5e](https://github.com/frappe/erpnext/commit/6838e5ea3b3c3c2a948ab7569dee8a3745b6ad2a)) * for asset's purchase_date, if bill_date is set, use that instead of posting_date ([01a1c96](https://github.com/frappe/erpnext/commit/01a1c963148b60d23dbdd66638d6b8d2a5456b2c)) * get `consumed_qty` based on `received_qty` in SCR ([ea9a502](https://github.com/frappe/erpnext/commit/ea9a50278d1a067275294db2f37fbbc51b0d5887)) * Increase columns width in Warehouse wise Item Balance Age and Value ([0b09c31](https://github.com/frappe/erpnext/commit/0b09c31cb0cb174a79d2e26d520b869e31d02328)) * linter ([af60c8f](https://github.com/frappe/erpnext/commit/af60c8f7599ab03d920a6fe7ef2e7a05c4018bb1)) * make `BOM` required in SCR Item ([3f79a05](https://github.com/frappe/erpnext/commit/3f79a057e42cf09f75913801cb5d6133f7b6175f)) * make `consumed_qty` editable when backflush based on Material Transfer ([2c5a8c4](https://github.com/frappe/erpnext/commit/2c5a8c43f6515e8dc46a092093fdb2e565a6cc99)) * make `consumed_qty` read-only in SCR Supplied Items ([68229f0](https://github.com/frappe/erpnext/commit/68229f06d18eac15b699372a4fe2dfdef867716d)) * map `BOM` while mapping the return SCR ([e629cba](https://github.com/frappe/erpnext/commit/e629cba2b71668f526edf008c1880b5ec3b0671d)) * mysql syntax issue ([4d9bbd4](https://github.com/frappe/erpnext/commit/4d9bbd4c9c06811086e0ae4e2c16763fe7d171a3)) * not able to select customer / supplier ([6989cdf](https://github.com/frappe/erpnext/commit/6989cdf4f2c327bcc7565ce70a51b794a83f5c94)) * refactor code for better translatable string ([2dc24f2](https://github.com/frappe/erpnext/commit/2dc24f22ea6762e9e6fb4f1a9bb73dca45433ed7)) * refactor code for better translatable string in stock_ageing.py ([0ead516](https://github.com/frappe/erpnext/commit/0ead51642f718d08c7345ff78ffc844fd1de5071)) * rename test method ([97445d9](https://github.com/frappe/erpnext/commit/97445d951646c4b663e000109efb338999f3e72d)) * Scan Barcode UX ([1944f4d](https://github.com/frappe/erpnext/commit/1944f4df4d3d2ef87da41c29e5fe0a00699c87ca)) * set `received_qty` before_validate SCR ([e316558](https://github.com/frappe/erpnext/commit/e31655828668cdaf6647f76185ba3ee0bf4400bf)) * test cases ([0feec4c](https://github.com/frappe/erpnext/commit/0feec4ca8a630471371e83fb4005037d0c6e1f41)) * trailing whitespace ([31bada9](https://github.com/frappe/erpnext/commit/31bada9205e2557e564a305d284be3cb135f2c05)) * update advance paid in SO/PO from Payment Ledger ([a561432](https://github.com/frappe/erpnext/commit/a561432908191bd903a8041cee128c0b81c5087c)) * use `flt` instead of `cint` in `get_batch_no` ([6510464](https://github.com/frappe/erpnext/commit/65104644822683c5cd4774d7411fded24f0e4f1e)) ### Features * Item Wise TDS Calculation ([b9fb104](https://github.com/frappe/erpnext/commit/b9fb1045d7f4331c064c491072199f65376159af)) ### Performance Improvements * use `get_cached_value` instead of `db.get_value` in controllers ([#32776](https://github.com/frappe/erpnext/issues/32776)) ([34ca17a](https://github.com/frappe/erpnext/commit/34ca17ab11cc5150ed951b0222e0c1c55905c27a)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6295cc7e96d..4a05dd4e58f 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.5.1" +__version__ = "14.6.0" def get_default_company(user=None): From 964abce057f222af6b44099f8dfb07273abfbc63 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 15 Nov 2022 12:59:04 +0000 Subject: [PATCH 008/176] chore(release): Bumped to Version 14.7.0 # [14.7.0](https://github.com/frappe/erpnext/compare/v14.6.0...v14.7.0) (2022-11-15) ### Bug Fixes * add translate function to valitate company msg in chart of accounts importer ([8de4430](https://github.com/frappe/erpnext/commit/8de4430662961036398f38a34ff197d71bbebffb)) * check type for reference name ([a305793](https://github.com/frappe/erpnext/commit/a30579393ed966d46bb5e91f39347a840b220bb2)) * don't set WIP Warehouse if is checked in WO ([f923183](https://github.com/frappe/erpnext/commit/f923183b64757c2bbff33f0ff8298c9d70ec3079)) * GP incorrect buying amount if no upd on SI and Delivery Note ([2d8f00a](https://github.com/frappe/erpnext/commit/2d8f00afade29fbfe8a175b319e979fb8de255b6)) * incorrect fix of conversion factor in PP ([c48d00a](https://github.com/frappe/erpnext/commit/c48d00ad7751468df456bbf06fed97c26fa2bddd)) * Label for applicable dimension table ([eb4f8e4](https://github.com/frappe/erpnext/commit/eb4f8e4bd8012f9a258181c735b3150c7374e716)) * Maintain same rate between Quotation and Sales Order ([6c155d2](https://github.com/frappe/erpnext/commit/6c155d2825ac111b795ee7dd899a53b967a0f1f8)) * Project filter in timesheet ([37bed12](https://github.com/frappe/erpnext/commit/37bed12df463a16baac209d1a34fa97fdfe7fd80)) * Purchase Receipt timeout error ([0d5b726](https://github.com/frappe/erpnext/commit/0d5b7269d409dde23d36a27af14f7f30510802d5)) * repayment schedule regeneration ([2f5033b](https://github.com/frappe/erpnext/commit/2f5033b70f7d887e3605bef811b526ab764d3f2f)) * set `WIP Warehouse` in Job Card ([c294652](https://github.com/frappe/erpnext/commit/c294652dab2ef4a5d182fa2779789a6badd58032)) * set stock UOM in args to ensure item price is fetched ([a4187b9](https://github.com/frappe/erpnext/commit/a4187b9d8f9cefa51591e5ad09bb4787e515036a)) * test cases ([071ee5d](https://github.com/frappe/erpnext/commit/071ee5d81c08b0045b6822959d6b6d3dfc42ec55)) * **ux:** Tab break in Customer and Supplier form ([eeaa932](https://github.com/frappe/erpnext/commit/eeaa9329f6db11705e227ec3a6a347c686a777a1)) * Write Off section visibility for non POS Invoices ([07badbc](https://github.com/frappe/erpnext/commit/07badbc0f214ef1cca98215db4770e7d265523b4)) ### Features * Repost Payment Ledger entries for vouchers ([de59b50](https://github.com/frappe/erpnext/commit/de59b5040739bbc0cc3e5a3fcef42f2253eb3f96)) ### Reverts * Revert "fix: get `consumed_qty` based on `received_qty` in SCR" ([7fd6c43](https://github.com/frappe/erpnext/commit/7fd6c43752ce94e8821b7bf62a4b2d1f7e34f126)) * Revert "fix: set `received_qty` before_validate SCR" ([0ecb44d](https://github.com/frappe/erpnext/commit/0ecb44d40c22858a99cd7c0af44253aa114b20fa)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4a05dd4e58f..19fa0849ecc 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.6.0" +__version__ = "14.7.0" def get_default_company(user=None): From df743aec29e6545cf8fb5a303d1a9cc281eae04f Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 22 Nov 2022 16:00:31 +0000 Subject: [PATCH 009/176] chore(release): Bumped to Version 14.8.0 # [14.8.0](https://github.com/frappe/erpnext/compare/v14.7.0...v14.8.0) (2022-11-22) ### Bug Fixes * Accounting Dimension filtering for Sales and Purchase Report ([b782209](https://github.com/frappe/erpnext/commit/b78220957b2362f0f05205b6c53075b6402bfda2)) * add missing commas and brackets ([ecd4eab](https://github.com/frappe/erpnext/commit/ecd4eab2da1c89f05acef5bf3eccf521ba114774)) * always send account currency in response ([f2fde83](https://github.com/frappe/erpnext/commit/f2fde8327d0cdedd78024a7ca8b9896b47f659ae)) * Bulk payment generation against invoices ([57b00e3](https://github.com/frappe/erpnext/commit/57b00e3b16d6150c8f1263506c73eebc2c4ac0bc)) * cast POS query inputs to integers (backport [#32975](https://github.com/frappe/erpnext/issues/32975)) ([#32977](https://github.com/frappe/erpnext/issues/32977)) ([55e1592](https://github.com/frappe/erpnext/commit/55e1592b709d49b5483f0e4a66d1d3edf7a84a0d)) * don't set `rejected-qty` in return SCR ([4de02dc](https://github.com/frappe/erpnext/commit/4de02dc2588d11b415157315f069461eedd53764)) * Don't show payment button for invoices on hold ([7487acd](https://github.com/frappe/erpnext/commit/7487acdeb6d6003450d09e6c2033c248a0d9446c)) * hide reject-fields in return SCR ([71d6f2a](https://github.com/frappe/erpnext/commit/71d6f2a4901a6af6f05a9cb20a6d784c1b49dbe8)) * incorrect currency in Exchange rate revaluation ([a26470a](https://github.com/frappe/erpnext/commit/a26470a65f10ecc33beaae1d4afbdbb62248e9ed)) * Internal Transfer Material Request cycle and tracking fixed till purchase receipt ([eaf0950](https://github.com/frappe/erpnext/commit/eaf09503a998ef27aec916b1b3acf7ec720bec07)) * link to brand doctype. ([b428307](https://github.com/frappe/erpnext/commit/b428307e9f4c3013b8bc8e14ba4379ac9d447ca3)) * linters failing ([24aafb3](https://github.com/frappe/erpnext/commit/24aafb3866174214087da2c38728d321b3d04968)) * make `is_internal_supplier` read-only ([caef140](https://github.com/frappe/erpnext/commit/caef140a9b515d1d25c04ede6c5baa3935667e50)) * minor change ([ac3120b](https://github.com/frappe/erpnext/commit/ac3120b6f9e9d4e4f8ca64e01b8b434c1ad41293)) * minor issue fixed ([c356d2c](https://github.com/frappe/erpnext/commit/c356d2cabdf79bae32c52afe7cf7406efdc47577)) * naming ([a198a55](https://github.com/frappe/erpnext/commit/a198a55d2d16e3c050d288701b1acd83dce3c0e0)) * Opening journal entry templates ([6aada76](https://github.com/frappe/erpnext/commit/6aada762970eef2e6ab2d84582678382305f07e6)) * **pos:** item selector image border radius ([aaed4ab](https://github.com/frappe/erpnext/commit/aaed4ab9585c2fcea72436ae3e1ea199e17a66e4)) * precision in asset tests ([444f241](https://github.com/frappe/erpnext/commit/444f241263e6ba89e316e06b9f67302c698da343)) * **realtime:** Restrict updates to only last modified or current user ([#33034](https://github.com/frappe/erpnext/issues/33034)) ([9e8a835](https://github.com/frappe/erpnext/commit/9e8a8356e9ede1e19e68bde1c7f8021a6842feb9)) * Remove unnecessary filters from Journal Entry ([387665d](https://github.com/frappe/erpnext/commit/387665d221d28450a4f7a49e71fcabc0c56062da)) * test case added for MR internal Transfer ([2a892f5](https://github.com/frappe/erpnext/commit/2a892f5c523171814511c1ed983212746ba930bd)) * test case updated for mr ([6ddf273](https://github.com/frappe/erpnext/commit/6ddf27380f845c8d0f5b9a574cafbe53d807abee)) * Timesheet timer button ([53cf6b8](https://github.com/frappe/erpnext/commit/53cf6b8c89a75d6959dbe727b2527820091f6982)) * update advace paid in SO/PO in account currency ([14235f2](https://github.com/frappe/erpnext/commit/14235f24b2905fc026f3a42e6e17e6e60dc253e2)) * use `list()` on self mutating iteration ([eb968d7](https://github.com/frappe/erpnext/commit/eb968d7f0247e40f149a279250e3f4dbab89619e)) * Viewing account ledger from party master ([da2dfcc](https://github.com/frappe/erpnext/commit/da2dfcc10b33bb6ba75dd4652ddc1caf71dd3325)) ### Features * Workstation Type for BOM ([8323775](https://github.com/frappe/erpnext/commit/8323775282d99aeb6d6362fccff703aaaa41a9c9)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 19fa0849ecc..5e58d0c32a4 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.7.0" +__version__ = "14.8.0" def get_default_company(user=None): From 7d63bcbeb66cd7ef39d785526e4d243542230b3e Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 29 Nov 2022 13:19:34 +0000 Subject: [PATCH 010/176] chore(release): Bumped to Version 14.9.0 # [14.9.0](https://github.com/frappe/erpnext/compare/v14.8.0...v14.9.0) (2022-11-29) ### Bug Fixes * `production_item` filter in `Job Card Summary Report` ([c9f4f60](https://github.com/frappe/erpnext/commit/c9f4f6042584632cac884b1e2c687a8633435a24)) * `Work Order` filter typo in `Job Card Summary Report` ([b157193](https://github.com/frappe/erpnext/commit/b1571932d033dbf87c1fc84612d2797d70e65795)) * Auto repeat date validations ([2aada1a](https://github.com/frappe/erpnext/commit/2aada1a0d95d9fde65314d1078eb850dedce7801)) * check for session user rather than owner ([c9c7222](https://github.com/frappe/erpnext/commit/c9c7222ded872f504f8b0b7b9d2d99f5b9e844dd)) * company name with `,` in `Job Card Summary Report` ([9c6d020](https://github.com/frappe/erpnext/commit/9c6d020e499375dd441899b37263a1431abd2c34)) * company name with `,` in `Work Order Summary Report` ([bc649b3](https://github.com/frappe/erpnext/commit/bc649b371f450284e22de436fbddd80ec0c00f19)) * create rounding gl entry for PCV during gle post processing ([fd4bcd9](https://github.com/frappe/erpnext/commit/fd4bcd9f7f1b603394a8ab4290fb04c7ceafbf24)) * Debit and Credit not equal while submitting PI containing asset item ([c11a31b](https://github.com/frappe/erpnext/commit/c11a31b39045e9d54a4e335cef92b0e11f5862a5)) * disbursable amount on currrent security price ([5edaf83](https://github.com/frappe/erpnext/commit/5edaf83733d788f9a2a6aa86a6b703e7a24699cf)) * Dispatch address display ([ef687e2](https://github.com/frappe/erpnext/commit/ef687e22b8bff7e9d8e350451c436dd2601b8ed2)) * incorrect balance qty ([f92b501](https://github.com/frappe/erpnext/commit/f92b5011dace89d76c5038cd91dea567ee286408)) * job card for quantity UX ([59e2ab7](https://github.com/frappe/erpnext/commit/59e2ab702f10ea63f6b1dad173152dc1e2fc2f5e)) * MR Item `description` and `item_name` gets reset on `qty` change ([85d108b](https://github.com/frappe/erpnext/commit/85d108b8100f78745fcf2fc0c90d32a94a7b8503)) * only show serial no batch selector only once ([8d0f715](https://github.com/frappe/erpnext/commit/8d0f715087baf8940cc72986094d33c910e3c58b)) * opportunity list doesn't show assigned user (backport [#33110](https://github.com/frappe/erpnext/issues/33110)) ([#33131](https://github.com/frappe/erpnext/issues/33131)) ([0ba2a4d](https://github.com/frappe/erpnext/commit/0ba2a4d084cd3cde5c3d1c793690059066cd53eb)) * **pos:** filter on customer groups ([c1ae5b0](https://github.com/frappe/erpnext/commit/c1ae5b0af5aa49f53ed64cf45ae99c1845b25234)) * **pos:** warehouse should be in company ([c03ec80](https://github.com/frappe/erpnext/commit/c03ec80d1add7d2b854cf2252cc43c1a51e67c81)) * precision in asset test_scrap_asset ([0fe5e9a](https://github.com/frappe/erpnext/commit/0fe5e9a9d19c2f3ecaefa3d1811c90fd9a342f63)) * production plan UX ([4607590](https://github.com/frappe/erpnext/commit/46075901baa47c87f94452fbbe65d0ff605193f0)) * remove duplicate schema ([cdd13cd](https://github.com/frappe/erpnext/commit/cdd13cd95fe3344ab342de98436c1f125d6b8182)) * remove obsolete comment ([978924a](https://github.com/frappe/erpnext/commit/978924a7e496bb684c1349ada566d93a6adf9c0a)) * reposting error `AttributeError: 'datetime.timedelta' object has no attribute 'replace'` ([4b0c9b6](https://github.com/frappe/erpnext/commit/4b0c9b61157c01a29ee80aff15d0f6e8be6cdc5f)) * reset `voucher_type` and `voucher_no` if `based_on` is set to `Item and Warehouse` ([e530f0b](https://github.com/frappe/erpnext/commit/e530f0b2fbfe876ce164b984a8cd2c55b4799d55)) * UX for inventory dimension ([f1dd4d0](https://github.com/frappe/erpnext/commit/f1dd4d0449ac602d19f90ef1d1e652711ad49f52)) * **ux:** Action buttons in Bank Reconciliation ([93b8cc3](https://github.com/frappe/erpnext/commit/93b8cc3042fe22a6412bd5f084b6cd3ef36db714)) * validation msg in stock entry ([65ac84e](https://github.com/frappe/erpnext/commit/65ac84e020ffdbb6cbdf8f52f500dd2917b292ce)) * Valuation Rate column UX in stock ledger report ([5c065e8](https://github.com/frappe/erpnext/commit/5c065e8a64b5e7a6c29dcf73f5dcb861bd897425)) ### Features * add connections to Incoterm doctype ([89b9a06](https://github.com/frappe/erpnext/commit/89b9a06204b350810f2e6de13db1231666c40cab)) * add doctype Incoterm ([b711931](https://github.com/frappe/erpnext/commit/b7119318a60f00732e5f21b44e2d345793872dfc)) * add german translations for incoterm titles ([f7988de](https://github.com/frappe/erpnext/commit/f7988dea1b9aa216a07f8e45e7880a20767d2890)) * add incoterm to purchasing transactions ([88346b1](https://github.com/frappe/erpnext/commit/88346b17e93193631086ba3a353b6957cb2494a9)) * add incoterm to sales transactions ([fcfe0cb](https://github.com/frappe/erpnext/commit/fcfe0cb9e9afddc9363d6ef278a8253009a20a70)) * create Incoterm records after install ([93e029d](https://github.com/frappe/erpnext/commit/93e029df915521a226b7e9711adf328f6af3e1c6)) * create incoterms and migrate shipments ([014896a](https://github.com/frappe/erpnext/commit/014896a59519672dc24ebc5f63cc997ce5758e0d)) * german tax templates ([5652af0](https://github.com/frappe/erpnext/commit/5652af04ba7587de7fecbf78578a11b7403d363c)) * item wise tds calculation for purchase order ([5f8f574](https://github.com/frappe/erpnext/commit/5f8f574e20579dcebd332c2144173800a775b27e)) * item wise tds calculation for purchase order. ([2bd8bd2](https://github.com/frappe/erpnext/commit/2bd8bd224ba7f0414cdd74126012887da1d10445)) * item wise tds in purchase order ([ba36435](https://github.com/frappe/erpnext/commit/ba3643514ee2011b24736c2b39d71e0107cf22d4)) * make Material Request for sub-assembly items ([5fc4dfa](https://github.com/frappe/erpnext/commit/5fc4dfaad6289d6fdb6ac6544a028968b573c184)) * **pos:** invoice: fitler warehouse by company ([c379baf](https://github.com/frappe/erpnext/commit/c379baf7a2d7f038e7eff5b204a9704434f2e5ed)) * validate repost item valuation against accounts freeze date ([04f50ea](https://github.com/frappe/erpnext/commit/04f50ea76a34ab95c83b4d62c560f0b1fab53ec9)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 5e58d0c32a4..c2041d240bd 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.8.0" +__version__ = "14.9.0" def get_default_company(user=None): From 1e351a9e0bbe17b82e7a09347a2313aaf3f85763 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 6 Dec 2022 14:37:42 +0000 Subject: [PATCH 011/176] chore(release): Bumped to Version 14.10.0 # [14.10.0](https://github.com/frappe/erpnext/compare/v14.9.0...v14.10.0) (2022-12-06) ### Bug Fixes * add company filter in RFQ Items ([7c1595e](https://github.com/frappe/erpnext/commit/7c1595e2c2ba2192183ede916bea8dfbdbb37433)) * Allow item rate udpates for non-stock invoices ([0307656](https://github.com/frappe/erpnext/commit/030765618bb6ec2b3a03f87cca54d533df65305a)) * Bundle item rates ([82cdf49](https://github.com/frappe/erpnext/commit/82cdf49d3268ebf58540cbccdadc2d2295a03c64)) * data import mandatory account_head, charge_type ([653cb9f](https://github.com/frappe/erpnext/commit/653cb9fc3b9c23e19d66cda12441e432a9fa2f10)) * default clear repost logs ([e039a14](https://github.com/frappe/erpnext/commit/e039a14f6fa92e9687dbae9102b5aed6decf97e9)) * Due date for month end payment term ([9b40b7f](https://github.com/frappe/erpnext/commit/9b40b7f2403c6ee80d6725d5b63e49146d99ad48)) * Error on making stock entry from material request ([701ccc3](https://github.com/frappe/erpnext/commit/701ccc36c83d7aed49331f5bd1af20b359a4a0e5)) * key error on p/l and balance sheet reports on foreign currency ([23a0a53](https://github.com/frappe/erpnext/commit/23a0a5337e6ab1d65e32a305c4ac08f98bfa436e)) * key error while filtering on date range and different currency ([08bca7d](https://github.com/frappe/erpnext/commit/08bca7d252e085b543dc3f49343582794ac6a02a)) * **lint:** trailing whitespace ([fae941c](https://github.com/frappe/erpnext/commit/fae941c4e3934dc3543161e2e299420e7d5fd51f)) * non empty FG batch picked while completing work order ([e5e6b5d](https://github.com/frappe/erpnext/commit/e5e6b5d81c949e0e8ea2e44fdfeb7d236b68e1a3)) * **pos:** partial return amount update ([91920f4](https://github.com/frappe/erpnext/commit/91920f4d847fe83d0c86f42380baf420f7508a20)) * reload currency exchange settings ([5b5a85e](https://github.com/frappe/erpnext/commit/5b5a85ee3bd7fc00adb7b8b9b7c0974f5c2e82c1)) * replace sql code with fields list in get_cached_value ([e18d0ec](https://github.com/frappe/erpnext/commit/e18d0eca3c8f917867e52ddb0837509b65ccb2b6)) * Tax withholding net total for PI in reports ([f87c3c6](https://github.com/frappe/erpnext/commit/f87c3c615734e8a307eb7d5470aa11f57f3d7abd)) * test case ([39680ed](https://github.com/frappe/erpnext/commit/39680ed28bf80320063dff04bb10c1d906632bed)) * type error on Sales Pipeline Analytics ([3af2b9b](https://github.com/frappe/erpnext/commit/3af2b9b4232bd2909ee5a565108b1df324934b94)) * use is_last_day_of_the_month in test_scrap_asset ([fc175e2](https://github.com/frappe/erpnext/commit/fc175e2b1fcae9f82da29e8454abc0608766be3e)) * **UX:** Alert on change of item rate in Stock Entry ([b2da76f](https://github.com/frappe/erpnext/commit/b2da76f02c864064d4c8629eb07f41a8027c4a04)) ### Features * warehouse wise stock balance ([7027fda](https://github.com/frappe/erpnext/commit/7027fdae3912351759a8ba6807215e94094c3a79)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index c2041d240bd..3ef6b49f4e1 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.9.0" +__version__ = "14.10.0" def get_default_company(user=None): From ac1af3bce98e46187776de1d69fcbfe916c49605 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 13 Dec 2022 12:30:55 +0000 Subject: [PATCH 012/176] chore(release): Bumped to Version 14.10.1 ## [14.10.1](https://github.com/frappe/erpnext/compare/v14.10.0...v14.10.1) (2022-12-13) ### Bug Fixes * `Enough Parts to Build` in `BOM Stock Report` ([3b9e9d2](https://github.com/frappe/erpnext/commit/3b9e9d2c6e4cbdace2067176f18ace3e2908b9f0)) * `Material Request` reference in internal `Sales Order` ([416d178](https://github.com/frappe/erpnext/commit/416d17820944e926a0ac336f4f7748baebfbe5e7)) * add translation variable order ([ef933a8](https://github.com/frappe/erpnext/commit/ef933a8231cc219e5e81d8c8cae7b8b61b6d1dc3)) * Buying and selling check in pricing rule ([f5205a5](https://github.com/frappe/erpnext/commit/f5205a5b5d27769da3f2c60f0250ee20bd2b6b13)) * **ecommerce:** remove query parameters from referer ([40621b9](https://github.com/frappe/erpnext/commit/40621b99c83b8002e76e7e79bd0f526ef846c3fc)) * handle_post_depr_entries_fail, show error alert and send email ([b661f57](https://github.com/frappe/erpnext/commit/b661f5758a8fe2ea2bf63289c9b3201d44abbc1b)) * incorrect balance on parent company due to key mismatch ([436e93c](https://github.com/frappe/erpnext/commit/436e93c129551af187b62e6af7f216b09a46af39)) * index error on customer master ([02cc618](https://github.com/frappe/erpnext/commit/02cc618a1fc52de4fa3e5aaf413f1e045eb35a24)) * Maintain Same Rate Throughout Sales Cycle doesn't work ([5398cf8](https://github.com/frappe/erpnext/commit/5398cf8f227e740dc035eca4d1f44f21109dea1d)), closes [#32923](https://github.com/frappe/erpnext/issues/32923) * order status in `Production Planning Report` ([a657db6](https://github.com/frappe/erpnext/commit/a657db66b4057ce37d8a5fbb56f6ba9757e4425c)) * Permission issue in Tax Detail report ([7a5b80d](https://github.com/frappe/erpnext/commit/7a5b80dfbc5c819db0b80ae82efef2dd5469d4a8)) * **pos:** variable typo: `s_pos` -> `is_pos` ([afbd48f](https://github.com/frappe/erpnext/commit/afbd48f26e67235866c289492b3664a6d402af31)) * Reapply pricing rule on qty change ([c726c16](https://github.com/frappe/erpnext/commit/c726c167026ad99fbb0bfb142a2c441be6a2581f)) * Remove free items ([5e5937d](https://github.com/frappe/erpnext/commit/5e5937d6d020ae910ab9064419bb2700cd0d12c8)) * total value in Warehouse Wise Stock Balance ([c5a54d7](https://github.com/frappe/erpnext/commit/c5a54d79120cad19bbdcc43e550565fbac125375)) ### Performance Improvements * add indexes on payment entry reference (backport [#33288](https://github.com/frappe/erpnext/issues/33288)) ([#33289](https://github.com/frappe/erpnext/issues/33289)) ([ce63086](https://github.com/frappe/erpnext/commit/ce630868132ac1dabf2278dd292a80f214260299)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 3ef6b49f4e1..383be7bb582 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.10.0" +__version__ = "14.10.1" def get_default_company(user=None): From 29bb873347df3e5fe93722bdae20132b0403fd9c Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 20 Dec 2022 14:01:35 +0000 Subject: [PATCH 013/176] chore(release): Bumped to Version 14.11.0 # [14.11.0](https://github.com/frappe/erpnext/compare/v14.10.1...v14.11.0) (2022-12-20) ### Bug Fixes * Consolidated financial report ([16ce411](https://github.com/frappe/erpnext/commit/16ce411b8f9838c67667f6f34477288f6338daf3)) * Cost center filter not working in cash flow report ([ce5065b](https://github.com/frappe/erpnext/commit/ce5065b13216ef77327829208d3ac679745fc24a)) * Cost Center for tax withholding invoices ([728643a](https://github.com/frappe/erpnext/commit/728643aa4a8abacf4fc5ac4ad4d7fbe982c08fd2)) * cost_center filter fix for 'Get Outstanding Invoice' in PE ([ff61997](https://github.com/frappe/erpnext/commit/ff61997d251c8f8b3b0eb727517873a0b82d570a)) * cost_center filter gives incorrect output ([9b2b281](https://github.com/frappe/erpnext/commit/9b2b2812ca50f981bdf2f440363a6c0672aafa65)) * daily scheduler to identify and fix stock transfer entries having incorrect valuation ([deb3efd](https://github.com/frappe/erpnext/commit/deb3efdd9ab1b95e3d2e6ab0d3a8079c61d20798)) * disabled items showing in the report 'Itemwise Recommended Reorder Level ([493509e](https://github.com/frappe/erpnext/commit/493509e42d9b39ac2e957333631af0a21c152360)) * get_serial_nos_for_fg() missing 1 required positional argument: 'args' ([bddb5b8](https://github.com/frappe/erpnext/commit/bddb5b8d25ebd9ec934abdd9c0bd9fa90c9f2612)) * incorrect type hints (backport [#33381](https://github.com/frappe/erpnext/issues/33381)) ([#33384](https://github.com/frappe/erpnext/issues/33384)) ([fa77259](https://github.com/frappe/erpnext/commit/fa77259f8d0f78ed7ed9a07eb0e1d764105bfb0e)) * Payment Request flow fixes from Order to Payment Entry ([a01db8f](https://github.com/frappe/erpnext/commit/a01db8fc38378756246dded7cf1fc5fe2098e470)) * remove unnecessary permissions from Appointment and Appointment Booking Settings ([#33358](https://github.com/frappe/erpnext/issues/33358)) ([#33395](https://github.com/frappe/erpnext/issues/33395)) ([6ef7eaf](https://github.com/frappe/erpnext/commit/6ef7eaf82e93278c6e72e0cbaa285f913b9830c4)) * translation for warning on Overbilling/-receipt/-delivery ([ba51d50](https://github.com/frappe/erpnext/commit/ba51d50fef754a1e5853062cd6b54f54b5c391a9)) * Unable to import COA through importer ([f8c09ee](https://github.com/frappe/erpnext/commit/f8c09ee720cce35447e2b339d172bf9045e8c68b)) * unsupported operand type(s) for +: 'int' and 'NoneType' ([7bdfb3d](https://github.com/frappe/erpnext/commit/7bdfb3d181798411d3d00613ad2994dacd7d29dc)) * unsupported operand type(s) for +=: 'int' and 'NoneType' ([88dc81b](https://github.com/frappe/erpnext/commit/88dc81b7d45ac086fc96cd54f3e76b3ea6daad13)) * use highest precision for exchange rate. ([4a8a84d](https://github.com/frappe/erpnext/commit/4a8a84d6f06f08c43f50cbc418733742a5d187da)) ### Features * Ignore company related doctype for other apps via hooks ([cd5a2af](https://github.com/frappe/erpnext/commit/cd5a2af27241cc148c2a655a1789597f7699d827)) * improve visibility of default values ([7ff50b9](https://github.com/frappe/erpnext/commit/7ff50b9446a14301ecdfa2f94e42b4e2e61bad7b)) * incoterm named place ([bfe57ac](https://github.com/frappe/erpnext/commit/bfe57acdbf067602ea4b765c675dc720517b0ab7)) * more control when printing RFQ ([07cda0a](https://github.com/frappe/erpnext/commit/07cda0aeb57ca0814c494ff42c7f791146f59275)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 383be7bb582..246b8d36ef0 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.10.1" +__version__ = "14.11.0" def get_default_company(user=None): From abfb3bf1c6753cf1e2f3f2a167f625cb8a2606ad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 15:01:44 +0530 Subject: [PATCH 014/176] fix: Random behaviour while picking items using picklist (backport #33449) (#33451) fix: Random behaviour while picking items using picklist (#33449) (cherry picked from commit 8263bf9a9a716f7651386633fd6f26686f2008bd) Co-authored-by: Deepesh Garg --- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index aff5e0539c7..8704b6718b9 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -441,7 +441,7 @@ def get_available_item_locations_for_batched_item( sle.`batch_no`, sle.`item_code` HAVING `qty` > 0 - ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation` + ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse` """.format( warehouse_condition=warehouse_condition ), From 0dbac5b6897b8a680b97aa8be256d2ed466d79e8 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 27 Dec 2022 09:35:01 +0000 Subject: [PATCH 015/176] chore(release): Bumped to Version 14.11.1 ## [14.11.1](https://github.com/frappe/erpnext/compare/v14.11.0...v14.11.1) (2022-12-27) ### Bug Fixes * Random behaviour while picking items using picklist (backport [#33449](https://github.com/frappe/erpnext/issues/33449)) ([#33451](https://github.com/frappe/erpnext/issues/33451)) ([abfb3bf](https://github.com/frappe/erpnext/commit/abfb3bf1c6753cf1e2f3f2a167f625cb8a2606ad)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 246b8d36ef0..f85a404de53 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.11.0" +__version__ = "14.11.1" def get_default_company(user=None): From 24d161d6708404d225b1619e36b49bcd3f7ea574 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 27 Dec 2022 18:26:24 +0530 Subject: [PATCH 016/176] chore: release v14 (#33452) * fix: typerror on multi warehouse in Packed Items DN(with bundled item with varying warehouses)-> Sales Invoice. (cherry picked from commit e684eb32d0cf62f67f2b1de30ec7368d36708321) * test: type error on bundled products with different warehouses (cherry picked from commit 5918bb03f7db388a27cb9319530b56c383304242) * fix: payment terms and sales partner filter issue in AR/AP report (cherry picked from commit 13c4420f42bb483bbbed7eddf168f4cb62554ab6) * fix: timeout error while submitting stock entry Co-authored-by: Ankush Menat (cherry picked from commit a05c47e49990ad00dc1b33b5d58688bca4b6b021) * fix: `shipping_address` in PO (cherry picked from commit 7e1b6b3c2aa114331ad9085cfefee340b8ca2ad0) * refactor: Customer and Supplier Ledger summary will have hidden fields for better handling of user permission (#33433) * feat: Accounting Dimension updation in Payment Request and Entry (#33411) * fix: `shipping_address` for non-drop shipping item (cherry picked from commit 67a7ccf3cead654e66a3d1b5ccb253d90b19c43b) * fix: Random behaviour while picking items using picklist (backport #33449) (#33450) fix: Random behaviour while picking items using picklist (#33449) (cherry picked from commit 8263bf9a9a716f7651386633fd6f26686f2008bd) Co-authored-by: Deepesh Garg * fix: Multiple rows for same warehouse and batches in pick list (backport #33456) (#33458) fix: Multiple rows for same warehouse and batches in pick list (#33456) (cherry picked from commit d2686ce75bb39bffff4fd1b56ad4880444efb72e) Co-authored-by: Deepesh Garg * fix: Default dimensions on fetching items from BOM (backport #33439) (#33459) fix: Default dimensions on fetching items from BOM (#33439) (cherry picked from commit 0b75aa53907e67d440884a2ea0084044265994a5) Co-authored-by: Deepesh Garg Co-authored-by: ruthra kumar Co-authored-by: Rohit Waghchaure Co-authored-by: s-aga-r Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Deepesh Garg --- .../doctype/payment_entry/payment_entry.py | 14 +++ .../payment_request/payment_request.json | 28 +++++- .../payment_request/payment_request.py | 25 +++++ .../accounts_receivable.py | 16 ++-- .../customer_ledger_summary.py | 72 ++++++++++++++ .../report/gross_profit/gross_profit.py | 1 + .../report/gross_profit/test_gross_profit.py | 95 +++++++++++++++++++ .../supplier_ledger_summary.js | 18 ---- .../purchase_order/purchase_order.json | 16 ++-- erpnext/patches.txt | 3 +- ...counting_dimensions_for_payment_request.py | 31 ++++++ .../doctype/sales_order/sales_order.py | 19 +++- .../customer_group/customer_group.json | 14 ++- .../supplier_group/supplier_group.json | 16 +++- .../setup/doctype/territory/territory.json | 14 ++- erpnext/stock/doctype/bin/bin.py | 1 + erpnext/stock/doctype/pick_list/pick_list.js | 10 +- erpnext/stock/doctype/pick_list/pick_list.py | 22 ++++- .../stock/doctype/stock_entry/stock_entry.js | 15 ++- erpnext/stock/stock_ledger.py | 23 ++++- 20 files changed, 400 insertions(+), 53 deletions(-) create mode 100644 erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1a761b424ad..5dbc91654f9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1758,6 +1758,8 @@ def get_payment_entry( pe.setup_party_account_field() pe.set_missing_values() + update_accounting_dimensions(pe, doc) + if party_account and bank: pe.set_exchange_rate(ref_doc=reference_doc) pe.set_amounts() @@ -1775,6 +1777,18 @@ def get_payment_entry( return pe +def update_accounting_dimensions(pe, doc): + """ + Updates accounting dimensions in Payment Entry based on the accounting dimensions in the reference document + """ + from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, + ) + + for dimension in get_accounting_dimensions(): + pe.set(dimension, doc.get(dimension)) + + def get_bank_cash_account(doc, bank_account): bank = get_default_bank_cash_account( doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 2f3516e135a..381f3fb531a 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -32,6 +32,10 @@ "iban", "branch_code", "swift_number", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", "recipient_and_message", "print_format", "email_to", @@ -362,13 +366,35 @@ "label": "Payment Channel", "options": "\nEmail\nPhone", "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "in_create": 1, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-09-30 16:19:43.680025", + "modified": "2022-12-21 16:56:40.115737", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index e09da678071..f63fba1b716 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -11,6 +11,9 @@ from frappe.utils import flt, get_url, nowdate from frappe.utils.background_jobs import enqueue from payments.utils import get_payment_gateway_controller +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) from erpnext.accounts.doctype.payment_entry.payment_entry import ( get_company_defaults, get_payment_entry, @@ -263,6 +266,17 @@ class PaymentRequest(Document): } ) + # Update dimensions + payment_entry.update( + { + "cost_center": self.get("cost_center"), + "project": self.get("project"), + } + ) + + for dimension in get_accounting_dimensions(): + payment_entry.update({dimension: self.get(dimension)}) + if payment_entry.difference_amount: company_details = get_company_defaults(ref_doc.company) @@ -442,6 +456,17 @@ def make_payment_request(**args): } ) + # Update dimensions + pr.update( + { + "cost_center": ref_doc.get("cost_center"), + "project": ref_doc.get("project"), + } + ) + + for dimension in get_accounting_dimensions(): + pr.update({dimension: ref_doc.get(dimension)}) + if args.order_type == "Shopping Cart" or args.mute_email: pr.flags.mute_email = True diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a195c575866..e73d5ba390d 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -794,19 +794,19 @@ class ReceivablePayableReport(object): if self.filters.get("payment_terms_template"): self.qb_selection_filter.append( - self.ple.party_isin( - qb.from_(self.customer).where( - self.customer.payment_terms == self.filters.get("payment_terms_template") - ) + self.ple.party.isin( + qb.from_(self.customer) + .select(self.customer.name) + .where(self.customer.payment_terms == self.filters.get("payment_terms_template")) ) ) if self.filters.get("sales_partner"): self.qb_selection_filter.append( - self.ple.party_isin( - qb.from_(self.customer).where( - self.customer.default_sales_partner == self.filters.get("payment_terms_template") - ) + self.ple.party.isin( + qb.from_(self.customer) + .select(self.customer.name) + .where(self.customer.default_sales_partner == self.filters.get("payment_terms_template")) ) ) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index cafe95b3603..d870a1aaf83 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -26,6 +26,7 @@ class PartyLedgerSummaryReport(object): ) self.get_gl_entries() + self.get_additional_columns() self.get_return_invoices() self.get_party_adjustment_amounts() @@ -33,6 +34,42 @@ class PartyLedgerSummaryReport(object): data = self.get_data() return columns, data + def get_additional_columns(self): + """ + Additional Columns for 'User Permission' based access control + """ + from frappe import qb + + if self.filters.party_type == "Customer": + self.territories = frappe._dict({}) + self.customer_group = frappe._dict({}) + + customer = qb.DocType("Customer") + result = ( + frappe.qb.from_(customer) + .select( + customer.name, customer.territory, customer.customer_group, customer.default_sales_partner + ) + .where((customer.disabled == 0)) + .run(as_dict=True) + ) + + for x in result: + self.territories[x.name] = x.territory + self.customer_group[x.name] = x.customer_group + else: + self.supplier_group = frappe._dict({}) + supplier = qb.DocType("Supplier") + result = ( + frappe.qb.from_(supplier) + .select(supplier.name, supplier.supplier_group) + .where((supplier.disabled == 0)) + .run(as_dict=True) + ) + + for x in result: + self.supplier_group[x.name] = x.supplier_group + def get_columns(self): columns = [ { @@ -116,6 +153,35 @@ class PartyLedgerSummaryReport(object): }, ] + # Hidden columns for handling 'User Permissions' + if self.filters.party_type == "Customer": + columns += [ + { + "label": _("Territory"), + "fieldname": "territory", + "fieldtype": "Link", + "options": "Territory", + "hidden": 1, + }, + { + "label": _("Customer Group"), + "fieldname": "customer_group", + "fieldtype": "Link", + "options": "Customer Group", + "hidden": 1, + }, + ] + else: + columns += [ + { + "label": _("Supplier Group"), + "fieldname": "supplier_group", + "fieldtype": "Link", + "options": "Supplier Group", + "hidden": 1, + } + ] + return columns def get_data(self): @@ -143,6 +209,12 @@ class PartyLedgerSummaryReport(object): ), ) + if self.filters.party_type == "Customer": + self.party_data[gle.party].update({"territory": self.territories.get(gle.party)}) + self.party_data[gle.party].update({"customer_group": self.customer_group.get(gle.party)}) + else: + self.party_data[gle.party].update({"supplier_group": self.supplier_group.get(gle.party)}) + amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) self.party_data[gle.party].closing_balance += amount diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index dacc809da0d..ba947f392f8 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -607,6 +607,7 @@ class GrossProfitGenerator(object): return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) else: return flt(row.qty) * self.get_average_buying_rate(row, item_code) + return 0.0 def get_buying_amount(self, row, item_code): # IMP NOTE diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 0ea6b5c8a44..fa11a41df4a 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -6,6 +6,8 @@ from frappe.utils import add_days, flt, nowdate from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.gross_profit.gross_profit import execute +from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice +from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -14,6 +16,7 @@ class TestGrossProfit(FrappeTestCase): def setUp(self): self.create_company() self.create_item() + self.create_bundle() self.create_customer() self.create_sales_invoice() self.clear_old_entries() @@ -42,6 +45,7 @@ class TestGrossProfit(FrappeTestCase): self.company = company.name self.cost_center = company.cost_center self.warehouse = "Stores - " + abbr + self.finished_warehouse = "Finished Goods - " + abbr self.income_account = "Sales - " + abbr self.expense_account = "Cost of Goods Sold - " + abbr self.debit_to = "Debtors - " + abbr @@ -53,6 +57,23 @@ class TestGrossProfit(FrappeTestCase): ) self.item = item if isinstance(item, str) else item.item_code + def create_bundle(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + + item2 = create_item( + item_code="_Test GP Item 2", is_stock_item=1, company=self.company, warehouse=self.warehouse + ) + self.item2 = item2 if isinstance(item2, str) else item2.item_code + + # This will be parent item + bundle = create_item( + item_code="_Test GP bundle", is_stock_item=0, company=self.company, warehouse=self.warehouse + ) + self.bundle = bundle if isinstance(bundle, str) else bundle.item_code + + # Create Product Bundle + self.product_bundle = make_product_bundle(parent=self.bundle, items=[self.item, self.item2]) + def create_customer(self): name = "_Test GP Customer" if frappe.db.exists("Customer", name): @@ -93,6 +114,28 @@ class TestGrossProfit(FrappeTestCase): ) return sinv + def create_delivery_note( + self, item=None, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False + ): + """ + Helper function to populate default values in Delivery Note + """ + dnote = create_delivery_note( + company=self.company, + customer=self.customer, + currency="INR", + item=item or self.item, + qty=qty, + rate=rate, + cost_center=self.cost_center, + warehouse=self.warehouse, + return_against=None, + expense_account=self.expense_account, + do_not_save=do_not_save, + do_not_submit=do_not_submit, + ) + return dnote + def clear_old_entries(self): doctype_list = [ "Sales Invoice", @@ -207,3 +250,55 @@ class TestGrossProfit(FrappeTestCase): } gp_entry = [x for x in data if x.parent_invoice == sinv.name] self.assertDictContainsSubset(expected_entry_with_dn, gp_entry[0]) + + def test_bundled_delivery_note_with_different_warehouses(self): + """ + Test Delivery Note with bundled item. Packed Item from the bundle having different warehouses + """ + se = make_stock_entry( + company=self.company, + item_code=self.item, + target=self.warehouse, + qty=1, + basic_rate=100, + do_not_submit=True, + ) + item = se.items[0] + se.append( + "items", + { + "item_code": self.item2, + "s_warehouse": "", + "t_warehouse": self.finished_warehouse, + "qty": 1, + "basic_rate": 100, + "conversion_factor": item.conversion_factor or 1.0, + "transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0), + "serial_no": item.serial_no, + "batch_no": item.batch_no, + "cost_center": item.cost_center, + "expense_account": item.expense_account, + }, + ) + se = se.save().submit() + + # Make a Delivery note with Product bundle + # Packed Items will have different warehouses + dnote = self.create_delivery_note(item=self.bundle, qty=1, rate=200, do_not_submit=True) + dnote.packed_items[1].warehouse = self.finished_warehouse + dnote = dnote.submit() + + # make Sales Invoice for above delivery note + sinv = make_sales_invoice(dnote.name) + sinv = sinv.save().submit() + + filters = frappe._dict( + company=self.company, + from_date=nowdate(), + to_date=nowdate(), + group_by="Invoice", + sales_invoice=sinv.name, + ) + + columns, data = execute(filters=filters) + self.assertGreater(len(data), 0) diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index f81297760ed..5dc4c3d1c15 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -63,24 +63,6 @@ frappe.query_reports["Supplier Ledger Summary"] = { "fieldtype": "Link", "options": "Payment Terms Template" }, - { - "fieldname":"territory", - "label": __("Territory"), - "fieldtype": "Link", - "options": "Territory" - }, - { - "fieldname":"sales_partner", - "label": __("Sales Partner"), - "fieldtype": "Link", - "options": "Sales Partner" - }, - { - "fieldname":"sales_person", - "label": __("Sales Person"), - "fieldtype": "Link", - "options": "Sales Person" - }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ce7de874c56..e1dd6797815 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -108,7 +108,7 @@ "contact_display", "contact_mobile", "contact_email", - "company_shipping_address_section", + "shipping_address_section", "shipping_address", "column_break_99", "shipping_address_display", @@ -385,7 +385,7 @@ { "fieldname": "shipping_address", "fieldtype": "Link", - "label": "Company Shipping Address", + "label": "Shipping Address", "options": "Address", "print_hide": 1 }, @@ -1207,11 +1207,6 @@ "fieldtype": "Tab Break", "label": "Address & Contact" }, - { - "fieldname": "company_shipping_address_section", - "fieldtype": "Section Break", - "label": "Company Shipping Address" - }, { "fieldname": "company_billing_address_section", "fieldtype": "Section Break", @@ -1263,13 +1258,18 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:36:37.455134", + "modified": "2022-12-25 18:08:59.074182", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5e83f837614..f7d2dedb1b3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -319,4 +319,5 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization erpnext.patches.v13_0.update_schedule_type_in_loans erpnext.patches.v14_0.update_partial_tds_fields erpnext.patches.v14_0.create_incoterms_and_migrate_shipment -erpnext.patches.v14_0.setup_clear_repost_logs \ No newline at end of file +erpnext.patches.v14_0.setup_clear_repost_logs +erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request \ No newline at end of file diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py b/erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py new file mode 100644 index 00000000000..bede419ad29 --- /dev/null +++ b/erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py @@ -0,0 +1,31 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + accounting_dimensions = frappe.db.get_all( + "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"] + ) + + if not accounting_dimensions: + return + + doctype = "Payment Request" + + for d in accounting_dimensions: + field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) + + if field: + continue + + df = { + "fieldname": d.fieldname, + "label": d.label, + "fieldtype": "Link", + "options": d.document_type, + "insert_after": "accounting_dimensions_section", + } + + create_custom_field(doctype, df, ignore_validate=True) + + frappe.clear_cache(doctype=doctype) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 0013c95032f..7c0601e3dd5 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1024,6 +1024,15 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): ] items_to_map = list(set(items_to_map)) + def is_drop_ship_order(target): + drop_ship = True + for item in target.items: + if not item.delivered_by_supplier: + drop_ship = False + break + + return drop_ship + def set_missing_values(source, target): target.supplier = "" target.apply_discount_on = "" @@ -1031,8 +1040,14 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): target.discount_amount = 0.0 target.inter_company_order_reference = "" target.shipping_rule = "" - target.customer = "" - target.customer_name = "" + + if is_drop_ship_order(target): + target.customer = source.customer + target.customer_name = source.customer_name + target.shipping_address = source.shipping_address_name + else: + target.customer = target.customer_name = target.shipping_address = None + target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json index 0e2ed9efcf8..d6a431ea616 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.json +++ b/erpnext/setup/doctype/customer_group/customer_group.json @@ -139,10 +139,11 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2021-02-08 17:01:52.162202", + "modified": "2022-12-24 11:15:17.142746", "modified_by": "Administrator", "module": "Setup", "name": "Customer Group", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_customer_group", "owner": "Administrator", "permissions": [ @@ -198,10 +199,19 @@ "role": "Customer", "select": 1, "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 } ], "search_fields": "parent_customer_group", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.json b/erpnext/setup/doctype/supplier_group/supplier_group.json index 9119bb947cb..b3ed608cd03 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.json +++ b/erpnext/setup/doctype/supplier_group/supplier_group.json @@ -6,6 +6,7 @@ "creation": "2013-01-10 16:34:24", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "supplier_group_name", "parent_supplier_group", @@ -106,10 +107,11 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:10:49.228407", + "modified": "2022-12-24 11:16:12.486719", "modified_by": "Administrator", "module": "Setup", "name": "Supplier Group", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_supplier_group", "owner": "Administrator", "permissions": [ @@ -156,8 +158,18 @@ "permlevel": 1, "read": 1, "role": "Purchase User" + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 } ], "show_name_in_global_search": 1, - "sort_order": "ASC" + "sort_field": "modified", + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/setup/doctype/territory/territory.json b/erpnext/setup/doctype/territory/territory.json index a25bda054b9..c3a49933746 100644 --- a/erpnext/setup/doctype/territory/territory.json +++ b/erpnext/setup/doctype/territory/territory.json @@ -123,11 +123,12 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2021-02-08 17:10:03.767426", + "modified": "2022-12-24 11:16:39.964956", "modified_by": "Administrator", "module": "Setup", "name": "Territory", "name_case": "Title Case", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_territory", "owner": "Administrator", "permissions": [ @@ -175,10 +176,19 @@ "role": "Customer", "select": 1, "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 } ], "search_fields": "parent_territory,territory_manager", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index c28f45aed41..9f409d4b96a 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -162,6 +162,7 @@ def update_qty(bin_name, args): .where((sle.item_code == args.get("item_code")) & (sle.warehouse == args.get("warehouse"))) .orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=Order.desc) .orderby(sle.creation, order=Order.desc) + .limit(1) .run() ) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 799406cd79e..8213adb89bf 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -51,7 +51,15 @@ frappe.ui.form.on('Pick List', { if (!(frm.doc.locations && frm.doc.locations.length)) { frappe.msgprint(__('Add items in the Item Locations table')); } else { - frm.call('set_item_locations', {save: save}); + frappe.call({ + method: "set_item_locations", + doc: frm.doc, + args: { + "save": save, + }, + freeze: 1, + freeze_message: __("Setting Item Locations..."), + }); } }, get_item_locations: (frm) => { diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 8704b6718b9..953fca7419c 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -135,6 +135,7 @@ class PickList(Document): # reset self.delete_key("locations") + updated_locations = frappe._dict() for item_doc in items: item_code = item_doc.item_code @@ -155,7 +156,26 @@ class PickList(Document): for row in locations: location = item_doc.as_dict() location.update(row) - self.append("locations", location) + key = ( + location.item_code, + location.warehouse, + location.uom, + location.batch_no, + location.serial_no, + location.sales_order_item or location.material_request_item, + ) + + if key not in updated_locations: + updated_locations.setdefault(key, location) + else: + updated_locations[key].qty += location.qty + updated_locations[key].stock_qty += location.stock_qty + + for location in updated_locations.values(): + if location.picked_qty > location.stock_qty: + location.picked_qty = location.stock_qty + + self.append("locations", location) # If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red # and give feedback to the user. This is to avoid empty Pick Lists. diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index b9102445e01..d4b4efa4cdd 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -112,6 +112,10 @@ frappe.ui.form.on('Stock Entry', { } }); attach_bom_items(frm.doc.bom_no); + + if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + } }, setup_quality_inspection: function(frm) { @@ -326,7 +330,11 @@ frappe.ui.form.on('Stock Entry', { } frm.trigger("setup_quality_inspection"); - attach_bom_items(frm.doc.bom_no) + attach_bom_items(frm.doc.bom_no); + + if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + } }, before_save: function(frm) { @@ -939,7 +947,10 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle method: "get_items", callback: function(r) { if(!r.exc) refresh_field("items"); - if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no) + if(me.frm.doc.bom_no) { + attach_bom_items(me.frm.doc.bom_no); + erpnext.accounts.dimensions.update_dimension(me.frm, me.frm.doctype); + } } }); } diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e7f55e9b35d..55a11a18671 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -470,8 +470,10 @@ class update_entries_after(object): item_code = %(item_code)s and warehouse = %(warehouse)s and is_cancelled = 0 - and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) - + and ( + posting_date = %(posting_date)s and + time_format(posting_time, %(time_format)s) = time_format(%(posting_time)s, %(time_format)s) + ) order by creation ASC for update @@ -1070,7 +1072,13 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): and warehouse = %(warehouse)s and is_cancelled = 0 {voucher_condition} - and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + and ( + posting_date < %(posting_date)s or + ( + posting_date = %(posting_date)s and + time_format(posting_time, %(time_format)s) < time_format(%(posting_time)s, %(time_format)s) + ) + ) order by timestamp(posting_date, posting_time) desc, creation desc limit 1 for update""".format( @@ -1355,8 +1363,13 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): and warehouse = %(warehouse)s and voucher_no != %(voucher_no)s and is_cancelled = 0 - and timestamp(posting_date, time_format(posting_time, %(time_format)s)) - > timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + and ( + posting_date > %(posting_date)s or + ( + posting_date = %(posting_date)s and + time_format(posting_time, %(time_format)s) > time_format(%(posting_time)s, %(time_format)s) + ) + ) {datetime_limit_condition} """, args, From aab856ceb167896ad686ac8ab88600bacd5b33c5 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 4 Jan 2023 02:54:39 +0000 Subject: [PATCH 017/176] chore(release): Bumped to Version 14.12.0 # [14.12.0](https://github.com/frappe/erpnext/compare/v14.11.1...v14.12.0) (2023-01-04) ### Bug Fixes * [concurrency issue] incorrect picked qty in sales order ([e7254fd](https://github.com/frappe/erpnext/commit/e7254fd161c42dee123e4f998340b55c35c4ecb6)) * `fg_item_qty` in non-subcontracted PO ([6e15331](https://github.com/frappe/erpnext/commit/6e15331fd4c906223f9d4605a12373aef4a5f061)) * `shipping_address` for non-drop shipping item ([a7a3654](https://github.com/frappe/erpnext/commit/a7a3654541b75854bdc69a822845e23e4088dee5)) * `shipping_address` in PO ([448fbe5](https://github.com/frappe/erpnext/commit/448fbe55826c06fd85fc95c84b2c678ca19fb61f)) * add missing 'ordered_qty' to get_bin_details ([55e8e45](https://github.com/frappe/erpnext/commit/55e8e45d52970cefb5d7ce63d6cbbc16b73d46c8)) * consider child nodes while getting bin details ([e296408](https://github.com/frappe/erpnext/commit/e2964088b76bbd6dbbd6c016d4dad4850d33b322)) * Conversion factor error for invoices without item code (petty expenses) ([#32714](https://github.com/frappe/erpnext/issues/32714)) ([ba5a149](https://github.com/frappe/erpnext/commit/ba5a149a6b75280ba0984a1435dc92a7af327c39)) * Customer Primary Contact (backport [#33424](https://github.com/frappe/erpnext/issues/33424)) ([#33440](https://github.com/frappe/erpnext/issues/33440)) ([73c9820](https://github.com/frappe/erpnext/commit/73c9820e825e30cb88495c6735339882699a18f7)) * Default dimensions on fetching items from BOM (backport [#33439](https://github.com/frappe/erpnext/issues/33439)) ([#33459](https://github.com/frappe/erpnext/issues/33459)) ([a332b22](https://github.com/frappe/erpnext/commit/a332b229cfb09a2f83d96f1cdb74cc862516b0f4)) * Deferred revenue date comparison ([#33515](https://github.com/frappe/erpnext/issues/33515)) ([027510b](https://github.com/frappe/erpnext/commit/027510b629f612887c20479e7b04fde4ddd83d97)) * ERR journals reported in AR/AP ([823b352](https://github.com/frappe/erpnext/commit/823b352c57af2e2e698c5b2debe615b4605ff651)) * Get payment entry button not visible in Bank Clearance doc (backport [#33518](https://github.com/frappe/erpnext/issues/33518)) ([#33525](https://github.com/frappe/erpnext/issues/33525)) ([fcf052d](https://github.com/frappe/erpnext/commit/fcf052d3c48c64ee46a0a9f8d7391186b15db0d0)) * Missing opening entry in general ledger (backport [#33519](https://github.com/frappe/erpnext/issues/33519)) ([#33526](https://github.com/frappe/erpnext/issues/33526)) ([8e375db](https://github.com/frappe/erpnext/commit/8e375db0b248952e3292069c17bd639c2b0a893c)) * Multi-currency issues in Bank Reconciliation Tool ([#33488](https://github.com/frappe/erpnext/issues/33488)) ([d030852](https://github.com/frappe/erpnext/commit/d03085259d1f8e7f21256451be076ccd9134718f)) * Multiple rows for same warehouse and batches in pick list (backport [#33456](https://github.com/frappe/erpnext/issues/33456)) ([#33458](https://github.com/frappe/erpnext/issues/33458)) ([a166a76](https://github.com/frappe/erpnext/commit/a166a7676847cc8fe34bb879bfcbf59adeb85e47)) * payment terms and sales partner filter issue in AR/AP report ([0f6790b](https://github.com/frappe/erpnext/commit/0f6790be11d9c191cc8640d4ab5827cd86072173)) * **pricing rule:** consider child tables in condition (backport [#33469](https://github.com/frappe/erpnext/issues/33469)) ([#33470](https://github.com/frappe/erpnext/issues/33470)) ([3bceb47](https://github.com/frappe/erpnext/commit/3bceb475427ed13fc2cf57ebe8c6adfc8820fa57)) * Random behaviour while picking items using picklist (backport [#33449](https://github.com/frappe/erpnext/issues/33449)) ([#33450](https://github.com/frappe/erpnext/issues/33450)) ([1edde9c](https://github.com/frappe/erpnext/commit/1edde9c9e05868166b0ba929b373f4968238442d)) * set `supplier` details while mapping SE(Send to Subcontractor) ([06e13b6](https://github.com/frappe/erpnext/commit/06e13b64a429af702c79e0e5a4d7cf0e2668a18c)) * timeout error while submitting stock entry ([f30f77c](https://github.com/frappe/erpnext/commit/f30f77cde693d3df259257b0c9ad8ee836d82310)) * typerror on multi warehouse in Packed Items ([9b24940](https://github.com/frappe/erpnext/commit/9b24940059c5a762cdc60af638d479f2bec346d5)) * use base_net_amount in case of missing stock qty ([#33457](https://github.com/frappe/erpnext/issues/33457)) ([88ca780](https://github.com/frappe/erpnext/commit/88ca7806af440d7ba09382cd5c197c18b0af603a)) ### Features * Accounting Dimension updation in Payment Request and Entry ([#33411](https://github.com/frappe/erpnext/issues/33411)) ([e7704b2](https://github.com/frappe/erpnext/commit/e7704b2321c6c4fdaf4850ba9b37554daf092304)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f85a404de53..4aa7b4fd90d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.11.1" +__version__ = "14.12.0" def get_default_company(user=None): From 259639a4563107a15d87289287de77a2a7b6b611 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 3 Jan 2023 16:55:15 +0530 Subject: [PATCH 018/176] fix: Exchange gain and loss booking on multi-currency invoice reconciliation (#32900) * fix: Exchange gain and loss booking on multi-curreny invoice reconciliation * test: Update test cases * chore: Ignore SQL linting rule * chore: Joural Entry for exchange gainand loss booking * chore: Journal entry for exchange gain loss booking * test: Update test case * chore: Default exchange gain and loss account (cherry picked from commit 9a3d947e893a787834bf12a9cf50c4af9e449f40) --- .../doctype/journal_entry/journal_entry.py | 18 +- .../payment_reconciliation.js | 16 +- .../payment_reconciliation.py | 132 ++++++++++-- .../test_payment_reconciliation.py | 198 +++++++++++++++--- .../payment_reconciliation_allocation.json | 26 ++- .../payment_reconciliation_invoice.json | 12 +- .../payment_reconciliation_payment.json | 14 +- 7 files changed, 349 insertions(+), 67 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 5a1b6ba1712..88b030cae3d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -592,15 +592,15 @@ class JournalEntry(AccountsController): d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field) else: for d in self.get("accounts"): - if flt(d.debit > 0): + if flt(d.debit) > 0: accounts_debited.append(d.party or d.account) if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) for d in self.get("accounts"): - if flt(d.debit > 0): + if flt(d.debit) > 0: d.against_account = ", ".join(list(set(accounts_credited))) - if flt(d.credit > 0): + if flt(d.credit) > 0: d.against_account = ", ".join(list(set(accounts_debited))) def validate_debit_credit_amount(self): @@ -762,7 +762,7 @@ class JournalEntry(AccountsController): pay_to_recd_from = d.party if pay_to_recd_from and pay_to_recd_from == d.party: - party_amount += d.debit_in_account_currency or d.credit_in_account_currency + party_amount += flt(d.debit_in_account_currency) or flt(d.credit_in_account_currency) party_account_currency = d.account_currency elif frappe.db.get_value("Account", d.account, "account_type") in ["Bank", "Cash"]: @@ -840,7 +840,7 @@ class JournalEntry(AccountsController): make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding) @frappe.whitelist() - def get_balance(self): + def get_balance(self, difference_account=None): if not self.get("accounts"): msgprint(_("'Entries' cannot be empty"), raise_exception=True) else: @@ -855,7 +855,13 @@ class JournalEntry(AccountsController): blank_row = d if not blank_row: - blank_row = self.append("accounts", {}) + blank_row = self.append( + "accounts", + { + "account": difference_account, + "cost_center": erpnext.get_default_cost_center(self.company), + }, + ) blank_row.exchange_rate = 1 if diff > 0: diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 0b334ae076d..d986f320669 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -170,7 +170,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } reconcile() { - var show_dialog = this.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); + var show_dialog = this.frm.doc.allocation.filter(d => d.difference_amount); if (show_dialog && show_dialog.length) { @@ -179,8 +179,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo title: __("Select Difference Account"), fields: [ { - fieldname: "allocation", fieldtype: "Table", label: __("Allocation"), - data: this.data, in_place_edit: true, + fieldname: "allocation", + fieldtype: "Table", + label: __("Allocation"), + data: this.data, + in_place_edit: true, + cannot_add_rows: true, get_data: () => { return this.data; }, @@ -218,6 +222,10 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo read_only: 1 }] }, + { + fieldtype: 'HTML', + options: " New Journal Entry will be posted for the difference amount " + } ], primary_action: () => { const args = dialog.get_values()["allocation"]; @@ -234,7 +242,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }); this.frm.doc.allocation.forEach(d => { - if (d.difference_amount && !d.difference_account) { + if (d.difference_amount) { dialog.fields_dict.allocation.df.data.push({ 'docname': d.name, 'reference_name': d.reference_name, diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index ff212f2a35f..ac033f7db60 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -14,7 +14,6 @@ from erpnext.accounts.utils import ( QueryPaymentLedger, get_outstanding_invoices, reconcile_against_document, - update_reference_in_payment_entry, ) from erpnext.controllers.accounts_controller import get_advance_payment_entries @@ -80,12 +79,13 @@ class PaymentReconciliation(Document): "t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1" ) + # nosemgrep journal_entries = frappe.db.sql( """ select "Journal Entry" as reference_type, t1.name as reference_name, t1.posting_date, t1.remark as remarks, t2.name as reference_row, - {dr_or_cr} as amount, t2.is_advance, + {dr_or_cr} as amount, t2.is_advance, t2.exchange_rate, t2.account_currency as currency from `tabJournal Entry` t1, `tabJournal Entry Account` t2 @@ -215,26 +215,26 @@ class PaymentReconciliation(Document): inv.currency = entry.get("currency") inv.outstanding_amount = flt(entry.get("outstanding_amount")) - def get_difference_amount(self, allocated_entry): - if allocated_entry.get("reference_type") != "Payment Entry": - return + def get_difference_amount(self, payment_entry, invoice, allocated_amount): + difference_amount = 0 + if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get( + "exchange_rate", 1 + ): + allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount + allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount + difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate - dr_or_cr = ( - "credit_in_account_currency" - if erpnext.get_party_account_type(self.party_type) == "Receivable" - else "debit_in_account_currency" - ) - - row = self.get_payment_details(allocated_entry, dr_or_cr) - - doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name) - update_reference_in_payment_entry(row, doc, do_not_save=True) - - return doc.difference_amount + return difference_amount @frappe.whitelist() def allocate_entries(self, args): self.validate_entries() + + invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices")) + default_exchange_gain_loss_account = frappe.get_cached_value( + "Company", self.company, "exchange_gain_loss_account" + ) + entries = [] for pay in args.get("payments"): pay.update({"unreconciled_amount": pay.get("amount")}) @@ -248,7 +248,10 @@ class PaymentReconciliation(Document): inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount")) pay["amount"] = 0 - res.difference_amount = self.get_difference_amount(res) + inv["exchange_rate"] = invoice_exchange_map.get(inv.get("invoice_number")) + res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"]) + res.difference_account = default_exchange_gain_loss_account + res.exchange_rate = inv.get("exchange_rate") if pay.get("amount") == 0: entries.append(res) @@ -278,6 +281,7 @@ class PaymentReconciliation(Document): "amount": pay.get("amount"), "allocated_amount": allocated_amount, "difference_amount": pay.get("difference_amount"), + "currency": inv.get("currency"), } ) @@ -300,7 +304,11 @@ class PaymentReconciliation(Document): else: reconciled_entry = entry_list - reconciled_entry.append(self.get_payment_details(row, dr_or_cr)) + payment_details = self.get_payment_details(row, dr_or_cr) + reconciled_entry.append(payment_details) + + if payment_details.difference_amount: + self.make_difference_entry(payment_details) if entry_list: reconcile_against_document(entry_list) @@ -311,6 +319,56 @@ class PaymentReconciliation(Document): msgprint(_("Successfully Reconciled")) self.get_unreconciled_entries() + def make_difference_entry(self, row): + journal_entry = frappe.new_doc("Journal Entry") + journal_entry.voucher_type = "Exchange Gain Or Loss" + journal_entry.company = self.company + journal_entry.posting_date = nowdate() + journal_entry.multi_currency = 1 + + party_account_currency = frappe.get_cached_value( + "Account", self.receivable_payable_account, "account_currency" + ) + difference_account_currency = frappe.get_cached_value( + "Account", row.difference_account, "account_currency" + ) + + # Account Currency has balance + dr_or_cr = "debit" if self.party_type == "Customer" else "debit" + reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" + + journal_account = frappe._dict( + { + "account": self.receivable_payable_account, + "party_type": self.party_type, + "party": self.party, + "account_currency": party_account_currency, + "exchange_rate": 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "reference_type": row.against_voucher_type, + "reference_name": row.against_voucher, + dr_or_cr: flt(row.difference_amount), + dr_or_cr + "_in_account_currency": 0, + } + ) + + journal_entry.append("accounts", journal_account) + + journal_account = frappe._dict( + { + "account": row.difference_account, + "account_currency": difference_account_currency, + "exchange_rate": 1, + "cost_center": erpnext.get_default_cost_center(self.company), + reverse_dr_or_cr + "_in_account_currency": flt(row.difference_amount), + } + ) + + journal_entry.append("accounts", journal_account) + + journal_entry.save() + journal_entry.submit() + def get_payment_details(self, row, dr_or_cr): return frappe._dict( { @@ -320,6 +378,7 @@ class PaymentReconciliation(Document): "against_voucher_type": row.get("invoice_type"), "against_voucher": row.get("invoice_number"), "account": self.receivable_payable_account, + "exchange_rate": row.get("exchange_rate"), "party_type": self.party_type, "party": self.party, "is_advance": row.get("is_advance"), @@ -344,6 +403,41 @@ class PaymentReconciliation(Document): if not self.get("payments"): frappe.throw(_("No records found in the Payments table")) + def get_invoice_exchange_map(self, invoices): + sales_invoices = [ + d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Sales Invoice" + ] + purchase_invoices = [ + d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Purchase Invoice" + ] + invoice_exchange_map = frappe._dict() + + if sales_invoices: + sales_invoice_map = frappe._dict( + frappe.db.get_all( + "Sales Invoice", + filters={"name": ("in", sales_invoices)}, + fields=["name", "conversion_rate"], + as_list=1, + ) + ) + + invoice_exchange_map.update(sales_invoice_map) + + if purchase_invoices: + purchase_invoice_map = frappe._dict( + frappe.db.get_all( + "Purchase Invoice", + filters={"name": ("in", purchase_invoices)}, + fields=["name", "conversion_rate"], + as_list=1, + ) + ) + + invoice_exchange_map.update(purchase_invoice_map) + + return invoice_exchange_map + def validate_allocation(self): unreconciled_invoices = frappe._dict() diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 6030134fff2..2ba90b4da9f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -6,7 +6,7 @@ import unittest import frappe from frappe import qb from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, nowdate +from frappe.utils import add_days, flt, nowdate from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -75,33 +75,11 @@ class TestPaymentReconciliation(FrappeTestCase): self.item = item if isinstance(item, str) else item.item_code def create_customer(self): - if frappe.db.exists("Customer", "_Test PR Customer"): - self.customer = "_Test PR Customer" - else: - customer = frappe.new_doc("Customer") - customer.customer_name = "_Test PR Customer" - customer.type = "Individual" - customer.save() - self.customer = customer.name - - if frappe.db.exists("Customer", "_Test PR Customer 2"): - self.customer2 = "_Test PR Customer 2" - else: - customer = frappe.new_doc("Customer") - customer.customer_name = "_Test PR Customer 2" - customer.type = "Individual" - customer.save() - self.customer2 = customer.name - - if frappe.db.exists("Customer", "_Test PR Customer 3"): - self.customer3 = "_Test PR Customer 3" - else: - customer = frappe.new_doc("Customer") - customer.customer_name = "_Test PR Customer 3" - customer.type = "Individual" - customer.default_currency = "EUR" - customer.save() - self.customer3 = customer.name + self.customer = make_customer("_Test PR Customer") + self.customer2 = make_customer("_Test PR Customer 2") + self.customer3 = make_customer("_Test PR Customer 3", "EUR") + self.customer4 = make_customer("_Test PR Customer 4", "EUR") + self.customer5 = make_customer("_Test PR Customer 5", "EUR") def create_account(self): account_name = "Debtors EUR" @@ -598,6 +576,156 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pr.payments[0].amount, amount) self.assertEqual(pr.payments[0].currency, "EUR") + def test_difference_amount_via_journal_entry(self): + # Make Sale Invoice + si = self.create_sales_invoice( + qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True + ) + si.customer = self.customer4 + si.currency = "EUR" + si.conversion_rate = 85 + si.debit_to = self.debtors_eur + si.save().submit() + + # Make payment using Journal Entry + je1 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 100, nowdate()) + je1.multi_currency = 1 + je1.accounts[0].exchange_rate = 1 + je1.accounts[0].credit_in_account_currency = 0 + je1.accounts[0].credit = 0 + je1.accounts[0].debit_in_account_currency = 8000 + je1.accounts[0].debit = 8000 + je1.accounts[1].party_type = "Customer" + je1.accounts[1].party = self.customer4 + je1.accounts[1].exchange_rate = 80 + je1.accounts[1].credit_in_account_currency = 100 + je1.accounts[1].credit = 8000 + je1.accounts[1].debit_in_account_currency = 0 + je1.accounts[1].debit = 0 + je1.save() + je1.submit() + + je2 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 200, nowdate()) + je2.multi_currency = 1 + je2.accounts[0].exchange_rate = 1 + je2.accounts[0].credit_in_account_currency = 0 + je2.accounts[0].credit = 0 + je2.accounts[0].debit_in_account_currency = 16000 + je2.accounts[0].debit = 16000 + je2.accounts[1].party_type = "Customer" + je2.accounts[1].party = self.customer4 + je2.accounts[1].exchange_rate = 80 + je2.accounts[1].credit_in_account_currency = 200 + je1.accounts[1].credit = 16000 + je1.accounts[1].debit_in_account_currency = 0 + je1.accounts[1].debit = 0 + je2.save() + je2.submit() + + pr = self.create_payment_reconciliation() + pr.party = self.customer4 + pr.receivable_payable_account = self.debtors_eur + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 2) + + # Test exact payment allocation + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[0].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + self.assertEqual(pr.allocation[0].allocated_amount, 100) + self.assertEqual(pr.allocation[0].difference_amount, -500) + + # Test partial payment allocation (with excess payment entry) + pr.set("allocation", []) + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[1].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].difference_account = "Exchange Gain/Loss - _PR" + + self.assertEqual(pr.allocation[0].allocated_amount, 100) + self.assertEqual(pr.allocation[0].difference_amount, -500) + + # Check if difference journal entry gets generated for difference amount after reconciliation + pr.reconcile() + total_debit_amount = frappe.db.get_all( + "Journal Entry Account", + {"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name}, + "sum(debit) as amount", + group_by="reference_name", + )[0].amount + + self.assertEqual(flt(total_debit_amount, 2), -500) + + def test_difference_amount_via_payment_entry(self): + # Make Sale Invoice + si = self.create_sales_invoice( + qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True + ) + si.customer = self.customer5 + si.currency = "EUR" + si.conversion_rate = 85 + si.debit_to = self.debtors_eur + si.save().submit() + + # Make payment using Payment Entry + pe1 = create_payment_entry( + company=self.company, + payment_type="Receive", + party_type="Customer", + party=self.customer5, + paid_from=self.debtors_eur, + paid_to=self.bank, + paid_amount=100, + ) + + pe1.source_exchange_rate = 80 + pe1.received_amount = 8000 + pe1.save() + pe1.submit() + + pe2 = create_payment_entry( + company=self.company, + payment_type="Receive", + party_type="Customer", + party=self.customer5, + paid_from=self.debtors_eur, + paid_to=self.bank, + paid_amount=200, + ) + + pe2.source_exchange_rate = 80 + pe2.received_amount = 16000 + pe2.save() + pe2.submit() + + pr = self.create_payment_reconciliation() + pr.party = self.customer5 + pr.receivable_payable_account = self.debtors_eur + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 2) + + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[0].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + self.assertEqual(pr.allocation[0].allocated_amount, 100) + self.assertEqual(pr.allocation[0].difference_amount, -500) + + pr.set("allocation", []) + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[1].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + self.assertEqual(pr.allocation[0].allocated_amount, 100) + self.assertEqual(pr.allocation[0].difference_amount, -500) + def test_differing_cost_center_on_invoice_and_payment(self): """ Cost Center filter should not affect outstanding amount calculation @@ -618,3 +746,17 @@ class TestPaymentReconciliation(FrappeTestCase): # check PR tool output self.assertEqual(len(pr.get("invoices")), 0) self.assertEqual(len(pr.get("payments")), 0) + + +def make_customer(customer_name, currency=None): + if not frappe.db.exists("Customer", customer_name): + customer = frappe.new_doc("Customer") + customer.customer_name = customer_name + customer.type = "Individual" + + if currency: + customer.default_currency = currency + customer.save() + return customer.name + else: + return customer_name diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 6a21692c6ac..0f7e47acfee 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -20,7 +20,9 @@ "section_break_5", "difference_amount", "column_break_7", - "difference_account" + "difference_account", + "exchange_rate", + "currency" ], "fields": [ { @@ -37,7 +39,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Allocated Amount", - "options": "Currency", + "options": "currency", "reqd": 1 }, { @@ -112,7 +114,7 @@ "fieldtype": "Currency", "hidden": 1, "label": "Unreconciled Amount", - "options": "Currency", + "options": "currency", "read_only": 1 }, { @@ -120,7 +122,7 @@ "fieldtype": "Currency", "hidden": 1, "label": "Amount", - "options": "Currency", + "options": "currency", "read_only": 1 }, { @@ -129,11 +131,24 @@ "hidden": 1, "label": "Reference Row", "read_only": 1 + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-10-06 11:48:59.616562", + "modified": "2022-12-24 21:01:14.882747", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", @@ -141,5 +156,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json index 00c9e1240c5..c4dbd7e8441 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json @@ -11,7 +11,8 @@ "col_break1", "amount", "outstanding_amount", - "currency" + "currency", + "exchange_rate" ], "fields": [ { @@ -62,11 +63,17 @@ "hidden": 1, "label": "Currency", "options": "Currency" + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "hidden": 1, + "label": "Exchange Rate" } ], "istable": 1, "links": [], - "modified": "2021-08-24 22:42:40.923179", + "modified": "2022-11-08 18:18:02.502149", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Invoice", @@ -75,5 +82,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json index add07e870d8..d300ea97abc 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -15,7 +15,8 @@ "difference_amount", "sec_break1", "remark", - "currency" + "currency", + "exchange_rate" ], "fields": [ { @@ -91,11 +92,17 @@ "label": "Difference Amount", "options": "currency", "read_only": 1 + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "hidden": 1, + "label": "Exchange Rate" } ], "istable": 1, "links": [], - "modified": "2021-08-30 10:51:48.140062", + "modified": "2022-11-08 18:18:36.268760", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Payment", @@ -103,5 +110,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file From 1b3df094afe4456f5f1d7b915e3fe465f9841518 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 10 Jan 2023 17:48:26 +0000 Subject: [PATCH 019/176] chore(release): Bumped to Version 14.12.1 ## [14.12.1](https://github.com/frappe/erpnext/compare/v14.12.0...v14.12.1) (2023-01-10) ### Bug Fixes * **accounts:** currency fields no longer read as strings by validation function in Payment Entry ([#33535](https://github.com/frappe/erpnext/issues/33535)) ([44a95da](https://github.com/frappe/erpnext/commit/44a95da8ab7cd97cf26bf4bc5b55b5b8309895cb)) * better handling of duplicate bundle items ([b96a97f](https://github.com/frappe/erpnext/commit/b96a97f6b4811ad499940a2525357539b9eca46c)) * customer/supplier quick entry dialog ([#33496](https://github.com/frappe/erpnext/issues/33496)) ([914e2fd](https://github.com/frappe/erpnext/commit/914e2fdded1bfa155b3100b22ac68d762415063c)) * don't check other warehouse ledgers to calculate valuation rate ([ab0a2b4](https://github.com/frappe/erpnext/commit/ab0a2b427291cd381f885dc46b62dcd67bada195)) * Exchange gain and loss booking on multi-currency invoice reconciliation ([#32900](https://github.com/frappe/erpnext/issues/32900)) ([fe82ebc](https://github.com/frappe/erpnext/commit/fe82ebcc38b1eed26a6b722aadfa304f895694b3)) * Exchange gain and loss booking on multi-currency invoice reconciliation ([#32900](https://github.com/frappe/erpnext/issues/32900)) ([259639a](https://github.com/frappe/erpnext/commit/259639a4563107a15d87289287de77a2a7b6b611)) * Incorrect exchange rate in payment entries ([#33481](https://github.com/frappe/erpnext/issues/33481)) ([e995e95](https://github.com/frappe/erpnext/commit/e995e952b576a0aa1e4415e7dda78ea4518bca49)) * incorrect status in the work order ([2658fc9](https://github.com/frappe/erpnext/commit/2658fc9f9b022cac6806ef4aa1152c392c9afc0f)) * incorrect warehouse and selling amount on bundled products ([#33549](https://github.com/frappe/erpnext/issues/33549)) ([c6c3ac3](https://github.com/frappe/erpnext/commit/c6c3ac3e55e5a2b4e17793a120c4a9a840a43269)) * RFQ emails not sent with pdf attachment ([#33604](https://github.com/frappe/erpnext/issues/33604)) ([34df9ab](https://github.com/frappe/erpnext/commit/34df9ab7d52349f9b071609e1d4bc8d4acea88d7)) * **stock entry:** wrong valuation rate in repack ([#33579](https://github.com/frappe/erpnext/issues/33579)) ([a92b4e7](https://github.com/frappe/erpnext/commit/a92b4e7255e173873fa285dc2f5fca414dc601b8)) * Timeout error while saving the purchase invoice ([#33577](https://github.com/frappe/erpnext/issues/33577)) ([d2e3701](https://github.com/frappe/erpnext/commit/d2e3701b1a0221b6638f433f6acfd72fed526e06)) ### Performance Improvements * Drop `name` part from posting sort index ([#33551](https://github.com/frappe/erpnext/issues/33551)) ([f501575](https://github.com/frappe/erpnext/commit/f5015750e4451201c9f9b444a1e2590e8fb892eb)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4aa7b4fd90d..e3f67077086 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.12.0" +__version__ = "14.12.1" def get_default_company(user=None): From e910c949f7d0ad3de2501546aadf333492f2b92f Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 17 Jan 2023 16:02:54 +0000 Subject: [PATCH 020/176] chore(release): Bumped to Version 14.13.0 # [14.13.0](https://github.com/frappe/erpnext/compare/v14.12.1...v14.13.0) (2023-01-17) ### Bug Fixes * allow to create sales order from expired quotation ([#33582](https://github.com/frappe/erpnext/issues/33582)) ([fe51343](https://github.com/frappe/erpnext/commit/fe513433b282ccce57745686254a26eeb66eaf3f)) * asset repair link ([bc55f44](https://github.com/frappe/erpnext/commit/bc55f44de6719483b158dec193293d61eed04905)) * asset value in fixed asset register ([#33608](https://github.com/frappe/erpnext/issues/33608)) ([4d2497f](https://github.com/frappe/erpnext/commit/4d2497faf1d3987d0b87c6b48bafbd4c3e2df852)) * attribute error while submitting Repost PLE ([0431a57](https://github.com/frappe/erpnext/commit/0431a57ff067e492fdedd3bd767c7acb1402e00d)) * better comparision of difference value between stock and account ([5869fcb](https://github.com/frappe/erpnext/commit/5869fcbd861fbd5202c24badd06fa2a80b4bc820)) * minor filter issue while reconciliation tool from bench console ([bddf330](https://github.com/frappe/erpnext/commit/bddf3307542cdf98d16016c94d11a775ace019db)) * Missing constructor args in Bank Reco Tool ([#33705](https://github.com/frappe/erpnext/issues/33705)) ([f88c8c4](https://github.com/frappe/erpnext/commit/f88c8c48c98fa44e2058bc66f6e63c4ca5153d7e)) * only group similar items in print format if group_same_items is checked in pick list (backport [#33627](https://github.com/frappe/erpnext/issues/33627)) ([#33630](https://github.com/frappe/erpnext/issues/33630)) ([28f2d35](https://github.com/frappe/erpnext/commit/28f2d357abb6f59762ff2d721944dc897709e21f)) * patch item_reposting_for_incorrect_sl_and_gl ([1928195](https://github.com/frappe/erpnext/commit/192819516783c7575b30c8757a71b9765e6045f9)) * Rate from LDC in TDS reports (backport [#33699](https://github.com/frappe/erpnext/issues/33699)) ([#33700](https://github.com/frappe/erpnext/issues/33700)) ([9fa4c1a](https://github.com/frappe/erpnext/commit/9fa4c1a3bd4c6a4122f85d57c7beafa926ce44d1)) * Return against internal purchase invoice (backport [#33635](https://github.com/frappe/erpnext/issues/33635)) ([#33658](https://github.com/frappe/erpnext/issues/33658)) ([35fbd67](https://github.com/frappe/erpnext/commit/35fbd67a938b7ffb5bbb7a5a39b4c9a9558acd33)) * Sales ORder Connections on Material Request ([8a04031](https://github.com/frappe/erpnext/commit/8a0403119f246de5b79eb860ee429ec21d65cc1c)) * Updating SO throws ordered_qty not allowed to change after submission ([f915c18](https://github.com/frappe/erpnext/commit/f915c181376f1c3e90ffd3ccde1ddf8a3c60ec66)) * zero rm-cost in SCR ([2dfbc6e](https://github.com/frappe/erpnext/commit/2dfbc6e4ebb39efe8c497205c3351cf0f602622d)) ### Features * Date filters on bank reconciliation tool ([#33271](https://github.com/frappe/erpnext/issues/33271)) ([91b08f1](https://github.com/frappe/erpnext/commit/91b08f179a4e29f3b9400190faa50154cc587709)) * provision to select date type based on filter ([4d65d6f](https://github.com/frappe/erpnext/commit/4d65d6f9bd73008db5682027e1ceb44e44d64e82)) ### Performance Improvements * improve reconciliation speed on JE's with 1000's of rows ([8a498ed](https://github.com/frappe/erpnext/commit/8a498ed029b9ffc2adc8eadade070f154ff5d768)) ### Reverts * Reverting changes done on 33495 ([#33662](https://github.com/frappe/erpnext/issues/33662)) ([23b9f66](https://github.com/frappe/erpnext/commit/23b9f661b641f9727cfdbad24f58110d839da5a9)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index e3f67077086..b1d128aac00 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.12.1" +__version__ = "14.13.0" def get_default_company(user=None): From 9fe9d7de6bcf9565ea2bc6645685c80d0e233b94 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Jan 2023 04:00:05 +0000 Subject: [PATCH 021/176] chore(release): Bumped to Version 14.14.0 # [14.14.0](https://github.com/frappe/erpnext/compare/v14.13.0...v14.14.0) (2023-01-25) ### Bug Fixes * Better budget exceeding validation messages ([#33713](https://github.com/frappe/erpnext/issues/33713)) ([4be8375](https://github.com/frappe/erpnext/commit/4be8375e36d7a1ccd1337d3be84c30afe346a127)) * bom.json updated ([9469488](https://github.com/frappe/erpnext/commit/9469488254262f41aac96125eefc486e8fb8896d)) * calculate correct amount for qty == 0 ([#33739](https://github.com/frappe/erpnext/issues/33739)) ([1c1c903](https://github.com/frappe/erpnext/commit/1c1c903fee23e13da8a8415957e9106bfff0745a)) * don't add template item in sales/purchase transaction ([8c12f7f](https://github.com/frappe/erpnext/commit/8c12f7f2f25d74ea323cd191f1c3c08c11002ed6)) * **ecommerce:** breadcrumb: fallback to `/all-products` ([#33718](https://github.com/frappe/erpnext/issues/33718)) ([1a33324](https://github.com/frappe/erpnext/commit/1a33324b4a0af094653ff47f90637c652c5c2266)) * fb issue in asset chart, asset split and reverse_depreciation_entry_made_after_disposal ([dffdc67](https://github.com/frappe/erpnext/commit/dffdc67455ac07ebb4f66bc85c3274acb6290d0b)) * hide with_operation on selection on fg_based and vice versa ([8ee6db3](https://github.com/frappe/erpnext/commit/8ee6db3b7bb4b61bbfaf385657a553e5163f23e7)) * incorrect `rate` and `amount` in MR Item ([#33547](https://github.com/frappe/erpnext/issues/33547)) ([ff731ea](https://github.com/frappe/erpnext/commit/ff731ea70a0d8232f0d3f64f4349b36f0f85b780)) * incorrect actual qty for the packed item ([bcd1fca](https://github.com/frappe/erpnext/commit/bcd1fca37bcae71c517c7fcc53314655df42f535)) * incorrect row order and accumulated_depreciation when schedule with multiple FBs is scrapped ([96f9b34](https://github.com/frappe/erpnext/commit/96f9b34b19f324b8d083666e9b6e4973953d7932)) * linting ([dedc9ec](https://github.com/frappe/erpnext/commit/dedc9ecc7ca806fae8c2d87cb442ac2e2d027a0a)) * local variable 'stock_rbnb' referenced before assignment ([21cf929](https://github.com/frappe/erpnext/commit/21cf929c7ac4a41cd0527ceb622d74e0940828d1)) * minor change in bom.js added ([19aa237](https://github.com/frappe/erpnext/commit/19aa23765a84b9441e5925274fbaa679a84f82e6)) * minor changes added ([7fd8cef](https://github.com/frappe/erpnext/commit/7fd8cef4d3667ca24c61375d7f26996784d22565)) * **minor:** Label updates in Statement of Accounts ([#33639](https://github.com/frappe/erpnext/issues/33639)) ([8af9a2f](https://github.com/frappe/erpnext/commit/8af9a2fad1a1f5153929c03f78bf908c76802cb6)) * missing constant definition ([219aa81](https://github.com/frappe/erpnext/commit/219aa81eb61851ba9f62745b8e4168ab1efb1e94)) * not able to change default BOM in the Subcontracting Order ([ed1aed2](https://github.com/frappe/erpnext/commit/ed1aed22c079a2a0c38b35a9fa67aa2da0338b35)) * Patch to update reference_due_date in Journal Entry ([#33616](https://github.com/frappe/erpnext/issues/33616)) ([0740120](https://github.com/frappe/erpnext/commit/0740120914866f317daa15959283a184606ac487)) * **pricing rule:** free item duplication ([#33746](https://github.com/frappe/erpnext/issues/33746)) ([5a49884](https://github.com/frappe/erpnext/commit/5a4988463674528ae0e8c7ea4fa9656645a44d09)) * rewrite logic for duplicate check in Item Attribute ([6544cb8](https://github.com/frappe/erpnext/commit/6544cb882285d01e7bfa4e60e04901fb870eddb0)) * Short closed order, receipt, and delivery note status on cancellation ([#33743](https://github.com/frappe/erpnext/issues/33743)) ([89f1eef](https://github.com/frappe/erpnext/commit/89f1eefe2ba02f9d30a2f1675f4984091aa01d03)) * TDS deduction in payment entry ([#33747](https://github.com/frappe/erpnext/issues/33747)) ([f9a43e5](https://github.com/frappe/erpnext/commit/f9a43e5470f153bdf94fa827b688462e8f1cc3f6)) * test case added for FG_BASED OPERTING COST ([30af8c3](https://github.com/frappe/erpnext/commit/30af8c3acb7017c96a297e50099652b6eda18a1b)) * the frappe throw message is corrected in the group task validation ([cf43930](https://github.com/frappe/erpnext/commit/cf439301f6ce1df5ac962b388bac0210079fac7e)) * use hash based naming for tax withheld vouchers child table (backport [#33643](https://github.com/frappe/erpnext/issues/33643)) ([#33748](https://github.com/frappe/erpnext/issues/33748)) ([cf6d454](https://github.com/frappe/erpnext/commit/cf6d4546063f66a36dede61941800c18e19fbed2)) * web supplier quotation ([ceef2d6](https://github.com/frappe/erpnext/commit/ceef2d6553046afb110e24e3939626226ca8369d)) ### Features * Add operating cost based on bom quanity without creating job card ([0035ee2](https://github.com/frappe/erpnext/commit/0035ee2a74a8286330bb859b1ddb7dbb946fa5c9)) * get items from Transit Stock Entry ([31fd6f3](https://github.com/frappe/erpnext/commit/31fd6f300f48f8463a244213b3cb34c269698a18)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b1d128aac00..0cb2b7ef2a0 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.13.0" +__version__ = "14.14.0" def get_default_company(user=None): From c8226ff64240bbed254aa37d77a885247d31a527 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 31 Jan 2023 06:20:11 +0000 Subject: [PATCH 022/176] chore(release): Bumped to Version 14.15.0 # [14.15.0](https://github.com/frappe/erpnext/compare/v14.14.0...v14.15.0) (2023-01-31) ### Bug Fixes * Amount validation in Payment Request against Purchase Order ([#33855](https://github.com/frappe/erpnext/issues/33855)) ([5605f1e](https://github.com/frappe/erpnext/commit/5605f1e3efe931a5240e8878a911a48bf62713c6)) * Currency symbol for tax withholding net total field ([#33850](https://github.com/frappe/erpnext/issues/33850)) ([f54e862](https://github.com/frappe/erpnext/commit/f54e8625f6d10bb8f2547314d1a4b16411c02047)) * disfuctional cost center filter on Journal Entries ([#33815](https://github.com/frappe/erpnext/issues/33815)) ([58c3e16](https://github.com/frappe/erpnext/commit/58c3e16fec68da1e784800daee76e0f13edd4ed7)) * disposal_was_made_on_original_schedule_date ([4586806](https://github.com/frappe/erpnext/commit/4586806ed12fc3817563c3b2cf660b611dd50815)) * double salutation on quotation print ([#33834](https://github.com/frappe/erpnext/issues/33834)) ([0fcf364](https://github.com/frappe/erpnext/commit/0fcf364aaada1d08bef06369ccbdcabfb5ad2935)) * Fetch commission rate from sales partner ([#33851](https://github.com/frappe/erpnext/issues/33851)) ([868c8d6](https://github.com/frappe/erpnext/commit/868c8d65aec951248944698463af62bbd53a38eb)) * **gp:** fetch buying amount from dn related to so ([f5bde9c](https://github.com/frappe/erpnext/commit/f5bde9cf6d5ec61b3e8c011c9dfe289dba54fb9a)) * GST Category validation broken for pos unregistered customer who dont have address. ([#33800](https://github.com/frappe/erpnext/issues/33800)) ([f124dd3](https://github.com/frappe/erpnext/commit/f124dd31120b52a4de15e8ba612ba82e5ed521d0)) * Ignore linked JE on JE cancellation ([#33852](https://github.com/frappe/erpnext/issues/33852)) ([a0e1ee0](https://github.com/frappe/erpnext/commit/a0e1ee0450478782a2ca320fbc3080703c954b2a)) * item rate not fetching ([b98d351](https://github.com/frappe/erpnext/commit/b98d3514ab1bef6a6a26f2b5c0a3c9dde0f0b6fc)) * Lead to customer creation ([#33859](https://github.com/frappe/erpnext/issues/33859)) ([44692e9](https://github.com/frappe/erpnext/commit/44692e9b575e0af44d6d928a227e595953f71216)) * manual depr entry not updating asset value [v14] ([#33788](https://github.com/frappe/erpnext/issues/33788)) ([f487eae](https://github.com/frappe/erpnext/commit/f487eae28e0ec5fe77cfaff8b1edd16a98992e7f)) * **patch:** validation error on cost center allocation migration ([#33835](https://github.com/frappe/erpnext/issues/33835)) ([5d4967c](https://github.com/frappe/erpnext/commit/5d4967ceeefe9504f421d2230a08bb23f57eca5b)) * use correct filter name in `item_query` (backport [#33814](https://github.com/frappe/erpnext/issues/33814)) ([#33816](https://github.com/frappe/erpnext/issues/33816)) ([f7eabca](https://github.com/frappe/erpnext/commit/f7eabcafde23e02b1db7209549462abbbf4aafde)) ### Features * **gp:** test for inv and dn related via so ([7a793ea](https://github.com/frappe/erpnext/commit/7a793ea58888801e1852756f102455255958be5b)) ### Performance Improvements * show update items dialog ([ac2ebfb](https://github.com/frappe/erpnext/commit/ac2ebfbf5942dc127aec8ed05bba864007a89258)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 0cb2b7ef2a0..f59e1633b71 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.14.0" +__version__ = "14.15.0" def get_default_company(user=None): From 4c238f1871cb5ae5db0669f6fb7daa2e8a684db9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 1 Feb 2023 15:38:12 +0530 Subject: [PATCH 023/176] fix: incorrect actual qty in Bin (cherry picked from commit f8c852c54ccf7a33d26e15378b76557ceffd77e5) (cherry picked from commit 01ff6a1f1995e619de90ba861c7b6dd0bf79af94) --- erpnext/stock/doctype/bin/bin.py | 7 ++++++- erpnext/stock/stock_ledger.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 9f409d4b96a..72654e6f816 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -159,13 +159,18 @@ def update_qty(bin_name, args): last_sle_qty = ( frappe.qb.from_(sle) .select(sle.qty_after_transaction) - .where((sle.item_code == args.get("item_code")) & (sle.warehouse == args.get("warehouse"))) + .where( + (sle.item_code == args.get("item_code")) + & (sle.warehouse == args.get("warehouse")) + & (sle.is_cancelled == 0) + ) .orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=Order.desc) .orderby(sle.creation, order=Order.desc) .limit(1) .run() ) + actual_qty = 0.0 if last_sle_qty: actual_qty = last_sle_qty[0][0] diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5d75bfd05a3..d8b12ed5b92 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1179,7 +1179,7 @@ def get_stock_ledger_entries( def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): return frappe.db.get_value( "Stock Ledger Entry", - {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle]}, + {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle], "is_cancelled": 0}, [ "item_code", "warehouse", From 171df324074f22b76c1db242580aa6a7a3257580 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 1 Feb 2023 17:46:44 +0000 Subject: [PATCH 024/176] chore(release): Bumped to Version 14.15.1 ## [14.15.1](https://github.com/frappe/erpnext/compare/v14.15.0...v14.15.1) (2023-02-01) ### Bug Fixes * incorrect actual qty in Bin ([4c238f1](https://github.com/frappe/erpnext/commit/4c238f1871cb5ae5db0669f6fb7daa2e8a684db9)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f59e1633b71..2fca8ec32c7 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.15.0" +__version__ = "14.15.1" def get_default_company(user=None): From 26c9782960f342f4619022cc2cd73cf8a6b72519 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 14 Feb 2023 10:38:44 +0000 Subject: [PATCH 025/176] chore(release): Bumped to Version 14.16.0 # [14.16.0](https://github.com/frappe/erpnext/compare/v14.15.1...v14.16.0) (2023-02-14) ### Bug Fixes * `amount` in `Material Request` ([f1dd923](https://github.com/frappe/erpnext/commit/f1dd923a501989d0718a7ddc84b1f10397e77711)) * `get_picked_items_details` ([7afbd92](https://github.com/frappe/erpnext/commit/7afbd9201d328fa13170453bd120c6c04a7cec04)) * `pymysql.err.ProgrammingError` ([aa3dd33](https://github.com/frappe/erpnext/commit/aa3dd33f5626b8cbae54ba4f9ca97e1acefc9779)) * Add missing 1 required positional argument: 'bill_date' ([ced9274](https://github.com/frappe/erpnext/commit/ced9274d1b394a3be8c83f47db1b5d2bace36ad9)) * add payment hook to point of sale JS ([#33988](https://github.com/frappe/erpnext/issues/33988)) ([49fd712](https://github.com/frappe/erpnext/commit/49fd712966e270f094ff00089b46f194b061e4d6)) * allow PI cancel if linked asset is cancelled ([c98b2b5](https://github.com/frappe/erpnext/commit/c98b2b59181546f246912fcd8c649ae773b68fab)) * Amount for debit and credit notes with 0 qty line items ([#33902](https://github.com/frappe/erpnext/issues/33902)) ([87a8c17](https://github.com/frappe/erpnext/commit/87a8c17314c0268109d1a60d8a90c470e2548c3f)) * Amount validation in Payment Request against Purchase Order ([#34042](https://github.com/frappe/erpnext/issues/34042)) ([c7c6123](https://github.com/frappe/erpnext/commit/c7c61239a3b95439f8de2342f9e080fed4fec592)) * BOM import failed as importer use same label field for Raw MaterialsItem table and Scrap Item table ([47d17f4](https://github.com/frappe/erpnext/commit/47d17f413639ef3601cc6c562cbbe3c2634ad837)) * Concurrency issues in Sales and Purchase returns ([#34019](https://github.com/frappe/erpnext/issues/34019)) ([087333a](https://github.com/frappe/erpnext/commit/087333abcbe0bca99d49bd2fe88766a8e751e7be)) * consider `stock_qty` if `picked_qty` is zero ([df72e4a](https://github.com/frappe/erpnext/commit/df72e4a2217337d3815808a7bf7fba75eab643ea)) * consider existing pick list ([466a791](https://github.com/frappe/erpnext/commit/466a791f68643cae1d2f1323039e786aace9be77)) * currency formatting in item-wise sales history ([#33903](https://github.com/frappe/erpnext/issues/33903)) ([8e2d7bb](https://github.com/frappe/erpnext/commit/8e2d7bb44a383ed4c7770a2e5c8de2930c0ffc10)) * default due_date was wrong calculated on template "_Test Payment Term Template 1" (last day of next month) ([c8c9c50](https://github.com/frappe/erpnext/commit/c8c9c509932c6ccdc8350d3b77d94244fa3a515b)) * **ecommerce:** throw invalid doctype error in shop by category ([#33901](https://github.com/frappe/erpnext/issues/33901)) ([1d0e71b](https://github.com/frappe/erpnext/commit/1d0e71bfe585c41bfcdc2ed29ade0887b466e10b)) * failed test, convert date time to string ([7228a49](https://github.com/frappe/erpnext/commit/7228a492ef2390b2acb08426b363633690098ecf)) * german chart of accounts "SKR03" ([#33909](https://github.com/frappe/erpnext/issues/33909)) ([02c4c55](https://github.com/frappe/erpnext/commit/02c4c55adc87aa7423fcf2fff05568eccf135cd2)) * Ignore mandatory fields while creating tax templates for new companies ([#34005](https://github.com/frappe/erpnext/issues/34005)) ([b0ed3c8](https://github.com/frappe/erpnext/commit/b0ed3c8aedc3ecf43d957b9a894491f180b77144)) * Ignore Payment Ledger Entry on dunning cancel (backport [#34025](https://github.com/frappe/erpnext/issues/34025)) ([#34028](https://github.com/frappe/erpnext/issues/34028)) ([699e93e](https://github.com/frappe/erpnext/commit/699e93e17f61b9530c0670980de52b0f54473aec)) * incorrect actual qty in Bin ([01ff6a1](https://github.com/frappe/erpnext/commit/01ff6a1f1995e619de90ba861c7b6dd0bf79af94)) * IntegrityError while cancelling journals against cr note ([c71d035](https://github.com/frappe/erpnext/commit/c71d03555f41fe01c6eed9c3cf0184b3e9d58dd5)) * list view for Terms and Conditions ([#33925](https://github.com/frappe/erpnext/issues/33925)) ([bb8e232](https://github.com/frappe/erpnext/commit/bb8e232aea98db7158c64e8f5a0ef9bdc57293a9)) * negative stock error ([e0cd6c2](https://github.com/frappe/erpnext/commit/e0cd6c20a3a317f567778e22c8f8b0df19c1891c)) * set per_billed based on hours when amounts are zero ([#33984](https://github.com/frappe/erpnext/issues/33984)) ([5270fbe](https://github.com/frappe/erpnext/commit/5270fbe01a3e83a432610db94f4bc49359eb8821)) * should never get cutomer price on purchase document ([#34002](https://github.com/frappe/erpnext/issues/34002)) ([6fe7600](https://github.com/frappe/erpnext/commit/6fe7600844dc6ac9f4e83eaa9e8d414350591d02)), closes [#33998](https://github.com/frappe/erpnext/issues/33998) * stock entry from item dashboard (stock levels) ([04a474d](https://github.com/frappe/erpnext/commit/04a474d0a12fa51211b5878a19ada7f8e7848475)) * **test:** `test_pick_list_for_items_with_multiple_UOM()` ([7124c0c](https://github.com/frappe/erpnext/commit/7124c0ca30f26c715766b6d1363a4f041e082fac)) * unwanted difference amount calculation on cr note and invoice with same currency ([#34020](https://github.com/frappe/erpnext/issues/34020)) ([cbafc51](https://github.com/frappe/erpnext/commit/cbafc51e753b03ae9a56f08046e1c598f41877ab)) ### Features * Add filters in Loan Interest Report ([#33907](https://github.com/frappe/erpnext/issues/33907)) ([52bfb66](https://github.com/frappe/erpnext/commit/52bfb667294b39bcd621b536459f0d2b0e43a824)) * add incoterm named place to RFQ ([68a1615](https://github.com/frappe/erpnext/commit/68a1615eae49a0d237bb358d650b3c8e80806da2)) * mandatory and mandatory depends on in inventory dimension ([3aca84c](https://github.com/frappe/erpnext/commit/3aca84c43f4e843802f94ee1e44e57090385c50b)) * Setting to allow Sales Order creation against expired quotation ([#33952](https://github.com/frappe/erpnext/issues/33952)) ([4d0e27e](https://github.com/frappe/erpnext/commit/4d0e27ed2b219e15ad8738d41fea57c4e1a6430c)) ### Performance Improvements * reduce memory usage by paging through records ([3ce8dc7](https://github.com/frappe/erpnext/commit/3ce8dc70cbdfab46789480b1e70f818bc6a328a3)) * reduce memory usage while migrating remarks ([c191a3f](https://github.com/frappe/erpnext/commit/c191a3f7c6ec48269f9b0b6c774297ced9db129a)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2fca8ec32c7..ed5c841843d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.15.1" +__version__ = "14.16.0" def get_default_company(user=None): From 4a209fcb7cb7b2c4cee1680643df155491ed294d Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Mon, 13 Feb 2023 17:22:54 +0530 Subject: [PATCH 026/176] fix: opening_accumulated_depreciation and precision in charts (cherry picked from commit 47cc8ab6c6e6ad3cf13ba750e9410696be827c23) --- erpnext/assets/doctype/asset/asset.js | 39 ++++++++++--------- erpnext/assets/doctype/asset/asset.py | 8 ++-- .../fixed_asset_register.py | 10 ++--- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 276cc925208..bffe1995457 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -210,52 +210,53 @@ frappe.ui.form.on('Asset', { return } - var x_intervals = [frm.doc.purchase_date]; + var x_intervals = [frappe.format(frm.doc.purchase_date, { fieldtype: 'Date' })]; var asset_values = [frm.doc.gross_purchase_amount]; - var last_depreciation_date = frm.doc.purchase_date; - - if(frm.doc.opening_accumulated_depreciation) { - last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date, - -1*frm.doc.frequency_of_depreciation); - - x_intervals.push(last_depreciation_date); - asset_values.push(flt(frm.doc.gross_purchase_amount) - - flt(frm.doc.opening_accumulated_depreciation)); - } if(frm.doc.calculate_depreciation) { + if(frm.doc.opening_accumulated_depreciation) { + var depreciation_date = frappe.datetime.add_months( + frm.doc.finance_books[0].depreciation_start_date, + -1 * frm.doc.finance_books[0].frequency_of_depreciation + ); + x_intervals.push(frappe.format(depreciation_date, { fieldtype: 'Date' })); + asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount'))); + } + $.each(frm.doc.schedules || [], function(i, v) { - x_intervals.push(v.schedule_date); - var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount); + x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' })); + var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount')); if(v.journal_entry) { - last_depreciation_date = v.schedule_date; asset_values.push(asset_value); } else { if (in_list(["Scrapped", "Sold"], frm.doc.status)) { asset_values.push(null); } else { - asset_values.push(asset_value) + asset_values.push(asset_value); } } }); } else { + if(frm.doc.opening_accumulated_depreciation) { + x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' })); + asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount'))); + } + let depr_entries = (await frappe.call({ method: "get_manual_depreciation_entries", doc: frm.doc, })).message; $.each(depr_entries || [], function(i, v) { - x_intervals.push(v.posting_date); - last_depreciation_date = v.posting_date; + x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' })); let last_asset_value = asset_values[asset_values.length - 1] asset_values.push(last_asset_value - v.value); }); } if(in_list(["Scrapped", "Sold"], frm.doc.status)) { - x_intervals.push(frm.doc.disposal_date); + x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: 'Date' })); asset_values.push(0); - last_depreciation_date = frm.doc.disposal_date; } frm.dashboard.render_graph({ diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index cd9f3b420d1..d3eb958fa6d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -693,14 +693,16 @@ class Asset(AccountsController): def get_value_after_depreciation(self, finance_book=None): if not self.calculate_depreciation: - return self.value_after_depreciation + return flt(self.value_after_depreciation, self.precision("gross_purchase_amount")) if not finance_book: - return self.get("finance_books")[0].value_after_depreciation + return flt( + self.get("finance_books")[0].value_after_depreciation, self.precision("gross_purchase_amount") + ) for row in self.get("finance_books"): if finance_book == row.finance_book: - return row.value_after_depreciation + return flt(row.value_after_depreciation, self.precision("gross_purchase_amount")) def get_default_finance_book_idx(self): if not self.get("default_finance_book") and self.company: 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 5bfd4840d73..0ab3e16e424 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.query_builder.functions import Sum -from frappe.utils import cstr, formatdate, getdate +from frappe.utils import cstr, flt, formatdate, getdate from erpnext.accounts.report.financial_statements import ( get_fiscal_year_data, @@ -102,13 +102,9 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - finance_book_filter = ("is", "not set") - if filters.finance_book: - finance_book_filter = ("=", filters.finance_book) - assets_linked_to_fb = frappe.db.get_all( doctype="Asset Finance Book", - filters={"finance_book": finance_book_filter}, + filters={"finance_book": filters.finance_book or ("is", "not set")}, pluck="parent", ) @@ -194,7 +190,7 @@ def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters): else: depr_amount = get_manual_depreciation_amount_of_asset(asset, filters) - return depr_amount + return flt(depr_amount, 2) def get_finance_book_value_map(filters): From be2ddd153641709f5d4d3ce43550323df5c7cb5e Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Mon, 13 Feb 2023 17:28:46 +0530 Subject: [PATCH 027/176] chore: break look if je processed (cherry picked from commit a220dc0c9c694a9213189b96be8eb9431457280c) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index b9507b73e29..e52fd6225fb 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -336,6 +336,8 @@ class JournalEntry(AccountsController): finance_books.db_update() asset.set_status() + + break else: depr_value = d.debit or d.credit From 7692db27bdce071b18c49a3551591eab1f9bd783 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 14 Feb 2023 17:54:51 +0530 Subject: [PATCH 028/176] fix: asset_depreciation_and_balances report doesn't reflect manual depr entries (cherry picked from commit 1535c3d856a50c31ed1e2adaf022ca1a221aab9b) --- .../asset_depreciations_and_balances.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 ad9b1ba58eb..9cea37c4f80 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 @@ -135,6 +135,34 @@ def get_assets(filters): where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != '' group by a.asset_category union + SELECT a.asset_category, + ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then + gle.debit + else + 0 + end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then + gle.debit + else + 0 + end), 0) as depreciation_eliminated_during_the_period, + ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s + and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then + gle.debit + else + 0 + end), 0) as depreciation_amount_during_the_period + from `tabGL Entry` gle + join `tabAsset` a on + gle.against_voucher = a.name + join `tabAsset Category Account` aca on + aca.parent = a.asset_category and aca.company_name = %(company)s + join `tabCompany` company on + company.name = %(company)s + where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) + group by a.asset_category + union SELECT a.asset_category, ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then 0 From e82c101b13f40301d4cc99e5d1874f73528f9977 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Feb 2023 10:33:52 +0000 Subject: [PATCH 029/176] chore(release): Bumped to Version 14.16.1 ## [14.16.1](https://github.com/frappe/erpnext/compare/v14.16.0...v14.16.1) (2023-02-15) ### Bug Fixes * asset_depreciation_and_balances report doesn't reflect manual depr entries ([7692db2](https://github.com/frappe/erpnext/commit/7692db27bdce071b18c49a3551591eab1f9bd783)) * opening_accumulated_depreciation and precision in charts ([4a209fc](https://github.com/frappe/erpnext/commit/4a209fcb7cb7b2c4cee1680643df155491ed294d)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ed5c841843d..a62534530f4 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.16.0" +__version__ = "14.16.1" def get_default_company(user=None): From 0d986a2ac46317a4a26afba2caf68db29a1ae1e9 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 21 Feb 2023 17:17:32 +0000 Subject: [PATCH 030/176] chore(release): Bumped to Version 14.17.0 # [14.17.0](https://github.com/frappe/erpnext/compare/v14.16.1...v14.17.0) (2023-02-21) ### Bug Fixes * asset repair status after deletion and asset status after manual depr entry ([03f07a2](https://github.com/frappe/erpnext/commit/03f07a20e7ae042f840351728f7625525a8b3ef2)) * asset_depreciation_and_balances report doesn't reflect manual depr entries ([1535c3d](https://github.com/frappe/erpnext/commit/1535c3d856a50c31ed1e2adaf022ca1a221aab9b)) * change parameter name for letter head ([4f37ba9](https://github.com/frappe/erpnext/commit/4f37ba9cfedf52973b0f91e7fc27ced7d556e9dc)) * check for duplicate in pos closing and pos merge log entry ([05d6490](https://github.com/frappe/erpnext/commit/05d649087b1aef54c401ab6c18ada95671bed249)) * consider rounded total amount while making payment request ([#34110](https://github.com/frappe/erpnext/issues/34110)) ([7879564](https://github.com/frappe/erpnext/commit/78795643cc13fe544cab03eb3989bd7ee8f31f07)) * create `Delivery Trip` from `Delivery Note` list ([ba5ea88](https://github.com/frappe/erpnext/commit/ba5ea886cf59bf0c490b9401813649cc1107ca93)) * differency entry journal debit/credit missing ([#34104](https://github.com/frappe/erpnext/issues/34104)) ([7556739](https://github.com/frappe/erpnext/commit/75567391a72f91251b721bf1bec2e97833fbd20f)) * Filters in item-wise sales history report ([#34145](https://github.com/frappe/erpnext/issues/34145)) ([44c837f](https://github.com/frappe/erpnext/commit/44c837f86292ce935196d757d088dd15d3f54c4a)) * fiscal year error for existing assets in fixed asset register ([7074c2b](https://github.com/frappe/erpnext/commit/7074c2b161a28de654ea9dee7bbd1bd4bc13b6db)) * forced delete linked desktop_icons (backport [#34107](https://github.com/frappe/erpnext/issues/34107)) ([#34130](https://github.com/frappe/erpnext/issues/34130)) ([53ab4d9](https://github.com/frappe/erpnext/commit/53ab4d92e80a6b8b319887a6555e18da984a5a1e)) * ignore repost payment ledger on basic documents cancellation ([#34054](https://github.com/frappe/erpnext/issues/34054)) ([9890cce](https://github.com/frappe/erpnext/commit/9890cce6808820a3d149bc3ac93543091fecbdb3)) * incorrect consumed qty in subcontracting receipt ([d5f6a5d](https://github.com/frappe/erpnext/commit/d5f6a5d193d538550101f59b6b035de8024e8da7)) * inventory dimension filter not overriding with existing filter for stock ledger report ([6959283](https://github.com/frappe/erpnext/commit/695928389384074764cc902d3fd2ed393210a2d6)) * linters issue ([f65e471](https://github.com/frappe/erpnext/commit/f65e471a75566efbc0ea46bef94b19644ecd767c)) * opening_accumulated_depreciation and precision in charts ([47cc8ab](https://github.com/frappe/erpnext/commit/47cc8ab6c6e6ad3cf13ba750e9410696be827c23)) * purchase invoice performance issue ([8d98599](https://github.com/frappe/erpnext/commit/8d98599a6c634b8ac418bc65196135ab5699829f)) * rename duplicate field name with same type into a DocType to avoid import Error ([#34053](https://github.com/frappe/erpnext/issues/34053)) ([d783168](https://github.com/frappe/erpnext/commit/d7831685afcbb4a4fe15cee7abba113f43a996d1)) * show Purchase Order Portal `Pay` button based on configuration ([84da0c6](https://github.com/frappe/erpnext/commit/84da0c6f1eb8c66d8362ad07079f083aebaf49a9)) * update `reserved_qty` when `Sales Order` marked as `Hold` ([15898cc](https://github.com/frappe/erpnext/commit/15898cc2ec5289ad3852cfe61ec64ff572c262d1)) * Use normal rounding for Tax Withholding Category ([#34114](https://github.com/frappe/erpnext/issues/34114)) ([65aec3e](https://github.com/frappe/erpnext/commit/65aec3e4ff711ebc411fc30bc7db6288150975aa)) * **ux:** `ReferenceError: me is not defined` Delivery Note ([7bd04c2](https://github.com/frappe/erpnext/commit/7bd04c27c8c4b4bf8df61f4782c1c429efc4a4b4)) ### Features * allow to make in transit transfer entry from material request ([a7b682e](https://github.com/frappe/erpnext/commit/a7b682e26b2e1c810891effc3e15606573fb1168)) * Editable Sales Invoice ([#32625](https://github.com/frappe/erpnext/issues/32625)) ([00eb632](https://github.com/frappe/erpnext/commit/00eb6329a7727425d504cc36494f4affa89eb44b)) * provision to convert transaction based reposting to item warehouse based reposting ([72c0b22](https://github.com/frappe/erpnext/commit/72c0b2208fdea3e4660858575c9af17b52338547)) * translate fixtures during runtime, not installation ([#33996](https://github.com/frappe/erpnext/issues/33996)) ([d117de7](https://github.com/frappe/erpnext/commit/d117de7813ea8e773f3b8d5262415ac18fda870f)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a62534530f4..1bc5fa3bf18 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.16.1" +__version__ = "14.17.0" def get_default_company(user=None): From 3c2e21e2aee9bf3ca6bdd167aaa864a13107cff0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 24 Feb 2023 15:28:14 +0530 Subject: [PATCH 031/176] Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202) Revert "fix: Concurrency issues in Sales and Purchase returns (#34019)" This reverts commit a67284e96dabe71b76a373b5f1f3142dccf3952b. (cherry picked from commit e26c6dc76b16707c31e9a0277c2710a4c5cb43f0) (cherry picked from commit 9341d3e60ef528430c1914ddd50c4576d316224a) --- erpnext/controllers/sales_and_purchase_return.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 935796c7a71..8bd09982bf4 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -252,7 +252,6 @@ def get_already_returned_items(doc): child.parent = par.name and par.docstatus = 1 and par.is_return = 1 and par.return_against = %s group by item_code - for update """.format( column, doc.doctype, doc.doctype ), From cb266cd1c65e1b24b52c05952bb18a332de15e36 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Fri, 24 Feb 2023 10:07:56 +0000 Subject: [PATCH 032/176] chore(release): Bumped to Version 14.17.1 ## [14.17.1](https://github.com/frappe/erpnext/compare/v14.17.0...v14.17.1) (2023-02-24) ### Reverts * Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202) ([3c2e21e](https://github.com/frappe/erpnext/commit/3c2e21e2aee9bf3ca6bdd167aaa864a13107cff0)), closes [#34202](https://github.com/frappe/erpnext/issues/34202) [#34019](https://github.com/frappe/erpnext/issues/34019) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 1bc5fa3bf18..ca10f275302 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.17.0" +__version__ = "14.17.1" def get_default_company(user=None): From cc4448b5d5b7c8fc26a31f3288fee333bc474ac3 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 28 Feb 2023 13:29:26 +0000 Subject: [PATCH 033/176] chore(release): Bumped to Version 14.17.2 ## [14.17.2](https://github.com/frappe/erpnext/compare/v14.17.1...v14.17.2) (2023-02-28) ### Bug Fixes * conversion factor not set ([089c7d0](https://github.com/frappe/erpnext/commit/089c7d0a376fa54f7eae825ab4bac565253c952a)) * currency in coa import ([#34174](https://github.com/frappe/erpnext/issues/34174)) ([4d92d46](https://github.com/frappe/erpnext/commit/4d92d469e4c9ff2cfa7f5825dc93a9c53f3e232a)) * default date in Subcontracting reports ([5fce8e2](https://github.com/frappe/erpnext/commit/5fce8e2700a05f77d8f91f6d7278ffa1c7e394ad)) * german translations ([#31732](https://github.com/frappe/erpnext/issues/31732)) ([88a781f](https://github.com/frappe/erpnext/commit/88a781fa431d242e008f1bb7cc2ff072c0e495bc)) * incorrect acc depr amount if multiple FBs with straight line or manual method ([dda6bae](https://github.com/frappe/erpnext/commit/dda6baea3ed18546ce8493ad8ccc519f280d5b29)) * incorrect color in the BOM Stock Report ([001ed9e](https://github.com/frappe/erpnext/commit/001ed9e9ffc89e9f0b94a3d33c79241502f7ed8b)) * manual depr schedule ([971c072](https://github.com/frappe/erpnext/commit/971c0720e52b5120ab3497f3fc5bd5964b0deff7)) * multiple pos conversion issue resolved ([db964e8](https://github.com/frappe/erpnext/commit/db964e82567ead44147711ba9673474a39e700d3)) * not able to repost gl entries ([ae0318e](https://github.com/frappe/erpnext/commit/ae0318ef74f67b515593297744394cfae2d389eb)) * permission error while calling get_work_order_items ([3ea90ee](https://github.com/frappe/erpnext/commit/3ea90ee5cb6dfe83c84804e1d0a062745ce9e667)) * pos return throwing amount greater than grand total ([9cd7b27](https://github.com/frappe/erpnext/commit/9cd7b27ce0ade5bf4ea64c55c0f1f0036b0b7975)) * Remove missing DocField in fetch_from ([dc6ae46](https://github.com/frappe/erpnext/commit/dc6ae46d591d31dda9c44ffadb56650c0cbf01e1)) * set `from_warehouse` and `to_warehouse` while mapping SE ([80e23d0](https://github.com/frappe/erpnext/commit/80e23d035e388d9790ace84a740df948296cf7e1)) * **test:** use standalone method to fetch work orders from SO ([1719884](https://github.com/frappe/erpnext/commit/17198844c0b36aaff667d5cb676e5e95395d3847)) * ui freeze on item selection in sales invoice ([1750ed4](https://github.com/frappe/erpnext/commit/1750ed4fb6648abc8fc0aa1695793ee948fc9303)) * user shouldn't able to make item price for item template ([fb8e45d](https://github.com/frappe/erpnext/commit/fb8e45d3d9520a101ebd7b8f4bd6920edea2b968)) * zero division error while making LCV ([1859be6](https://github.com/frappe/erpnext/commit/1859be6fef648a03cfb10955d7a4b1361505007a)) ### Performance Improvements * fetch SLE's on demand and memoize ([db1f17e](https://github.com/frappe/erpnext/commit/db1f17e5bce13093a97b5cca6da395f0432301e1)) ### Reverts * Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202) ([9341d3e](https://github.com/frappe/erpnext/commit/9341d3e60ef528430c1914ddd50c4576d316224a)), closes [#34202](https://github.com/frappe/erpnext/issues/34202) [#34019](https://github.com/frappe/erpnext/issues/34019) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ca10f275302..3621f47795e 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.17.1" +__version__ = "14.17.2" def get_default_company(user=None): From 6ba97504ed11834603fd788974c0659458bb82da Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 1 Mar 2023 11:06:46 +0530 Subject: [PATCH 034/176] fix: consumed qty validation for subcontracting receipt (cherry picked from commit b38fe240900da4e3ac64c7bb03ef3aecbcd09f0d) (cherry picked from commit 7eccf431fd4caa755a9224a4ea635017022d14fe) --- .../subcontracting_receipt.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index f4fd4de169d..95dbc83bf80 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -191,14 +191,17 @@ class SubcontractingReceipt(SubcontractingController): def validate_available_qty_for_consumption(self): for item in self.get("supplied_items"): + precision = item.precision("consumed_qty") if ( - item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty + item.available_qty_for_consumption + and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0 ): - frappe.throw( - _( - "Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table." - ).format(item.idx) - ) + msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)} + must be less than or equal to Available Qty For Consumption + {flt(item.available_qty_for_consumption, precision)} + in Consumed Items Table.""" + + frappe.throw(_(msg)) def validate_items_qty(self): for item in self.items: From 60a1e10b11206bfd4162e11476c9a03b8a1eae49 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 1 Mar 2023 10:22:45 +0000 Subject: [PATCH 035/176] chore(release): Bumped to Version 14.17.3 ## [14.17.3](https://github.com/frappe/erpnext/compare/v14.17.2...v14.17.3) (2023-03-01) ### Bug Fixes * consumed qty validation for subcontracting receipt ([6ba9750](https://github.com/frappe/erpnext/commit/6ba97504ed11834603fd788974c0659458bb82da)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 3621f47795e..c7da6c14813 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.17.2" +__version__ = "14.17.3" def get_default_company(user=None): From 9930adcd28bfc90d90ecb56449676966e1721840 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 2 Mar 2023 11:15:46 +0530 Subject: [PATCH 036/176] fix: `rejected_serial_no` not getting copied from PR to PR(Return) (cherry picked from commit a9f0a11ce62d8337fcbc1ebdda3be1b818a5f8a2) (cherry picked from commit 3db82587eb9d3e55a672bdbd2118a37ee1e72f33) --- erpnext/controllers/sales_and_purchase_return.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 8bd09982bf4..c5ef28a2eed 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -400,6 +400,16 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): if serial_nos: target_doc.serial_no = "\n".join(serial_nos) + if source_doc.get("rejected_serial_no"): + returned_serial_nos = get_returned_serial_nos( + source_doc, source_parent, serial_no_field="rejected_serial_no" + ) + rejected_serial_nos = list( + set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos) + ) + if rejected_serial_nos: + target_doc.rejected_serial_no = "\n".join(rejected_serial_nos) + if doctype in ["Purchase Receipt", "Subcontracting Receipt"]: returned_qty_map = get_returned_qty_map_for_row( source_parent.name, source_parent.supplier, source_doc.name, doctype @@ -610,7 +620,7 @@ def get_filters( return filters -def get_returned_serial_nos(child_doc, parent_doc): +def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos return_ref_field = frappe.scrub(child_doc.doctype) @@ -619,7 +629,7 @@ def get_returned_serial_nos(child_doc, parent_doc): serial_nos = [] - fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)] + fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"] filters = [ [parent_doc.doctype, "return_against", "=", parent_doc.name], @@ -629,6 +639,6 @@ def get_returned_serial_nos(child_doc, parent_doc): ] for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters): - serial_nos.extend(get_serial_nos(row.serial_no)) + serial_nos.extend(get_serial_nos(row.get(serial_no_field))) return serial_nos From 7629caa647f5d6a33ffafbb8126af95f2720df75 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 2 Mar 2023 11:27:46 +0530 Subject: [PATCH 037/176] fix: `Serial No is mandatory` even if the `qty` is `0` (cherry picked from commit cb0b6de4b97009d0f7d1053f4e065416971a4832) (cherry picked from commit aa6b891ef0285102797f924d75e2332f60008dd8) --- erpnext/controllers/sales_and_purchase_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index c5ef28a2eed..341d51b8a5f 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -131,7 +131,7 @@ def validate_returned_items(doc): ) elif ref.serial_no: - if not d.serial_no: + if d.qty and not d.serial_no: frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx)) else: serial_nos = get_serial_nos(d.serial_no) From a59c580480a1e41a155bf0e301447ae8d7a4fbd7 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 2 Mar 2023 08:08:44 +0000 Subject: [PATCH 038/176] chore(release): Bumped to Version 14.17.4 ## [14.17.4](https://github.com/frappe/erpnext/compare/v14.17.3...v14.17.4) (2023-03-02) ### Bug Fixes * `rejected_serial_no` not getting copied from PR to PR(Return) ([9930adc](https://github.com/frappe/erpnext/commit/9930adcd28bfc90d90ecb56449676966e1721840)) * `Serial No is mandatory` even if the `qty` is `0` ([7629caa](https://github.com/frappe/erpnext/commit/7629caa647f5d6a33ffafbb8126af95f2720df75)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index c7da6c14813..ff1c3e67a2c 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.17.3" +__version__ = "14.17.4" def get_default_company(user=None): From 0696128acc8db5039e77b4b89565b82f29624a75 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 7 Mar 2023 14:26:16 +0000 Subject: [PATCH 039/176] chore(release): Bumped to Version 14.18.0 # [14.18.0](https://github.com/frappe/erpnext/compare/v14.17.4...v14.18.0) (2023-03-07) ### Bug Fixes * `Inventory Dimension` for `Stock Reconciliation` ([b08cdc0](https://github.com/frappe/erpnext/commit/b08cdc00f2022c2622939e6af9db8db51c31912e)) * `rejected_serial_no` not getting copied from PR to PR(Return) ([3db8258](https://github.com/frappe/erpnext/commit/3db82587eb9d3e55a672bdbd2118a37ee1e72f33)) * `Serial No is mandatory` even if the `qty` is `0` ([aa6b891](https://github.com/frappe/erpnext/commit/aa6b891ef0285102797f924d75e2332f60008dd8)) * BOM Update log not completed ([235ecca](https://github.com/frappe/erpnext/commit/235ecca9fa96ce4ce6ff75e05012d9b1adca2148)) * consumed qty validation for subcontracting receipt ([7eccf43](https://github.com/frappe/erpnext/commit/7eccf431fd4caa755a9224a4ea635017022d14fe)) * Default sales team not getting set ([#34284](https://github.com/frappe/erpnext/issues/34284)) ([64c758d](https://github.com/frappe/erpnext/commit/64c758d0c0d2dcc168f5eabb5e3d90f19a0e4c47)) * Do not calculate commission post submit ([#34267](https://github.com/frappe/erpnext/issues/34267)) ([480797e](https://github.com/frappe/erpnext/commit/480797e85699ffe76bac841b30b6b7418451fad7)) * labels name ([5e9f1df](https://github.com/frappe/erpnext/commit/5e9f1dfbb3f136aa70f6bb476d90aae77790d80d)) * **minor:** Dirty the form after clicking on Get advances button in Invoices ([#34323](https://github.com/frappe/erpnext/issues/34323)) ([0e9f9c3](https://github.com/frappe/erpnext/commit/0e9f9c31a0deb7ce02e2eb582ce4c72aaf7964ff)) * Payment Request against sales order with disabled rounded total ([#34281](https://github.com/frappe/erpnext/issues/34281)) ([ca59c69](https://github.com/frappe/erpnext/commit/ca59c699cdfe1eddce7263e1ed586021a2b0590e)) * Performance improvement when adding a new item ([#34195](https://github.com/frappe/erpnext/issues/34195)) ([71a281f](https://github.com/frappe/erpnext/commit/71a281fb11c99e2ab37af6346ee038924de071b4)) * Resolve conflicts ([f6469d8](https://github.com/frappe/erpnext/commit/f6469d83981386a7bb57b41178055eae5cda30bf)) * Stock Reconciliation `actual_qty` ([d97c1bf](https://github.com/frappe/erpnext/commit/d97c1bf0f4dd74bfa64819b901f0ce6f7a798cb9)) * update inventory dimensions before returning sle ([ab73742](https://github.com/frappe/erpnext/commit/ab737424c26fb751693e8003e6d9eade104f08cd)) * Wrap unexpectedly long text in remark ([b13bf1e](https://github.com/frappe/erpnext/commit/b13bf1ebc517ccd9c5b6b079208ca3883e7418fd)) ### Features * adjust purchase receipt valuation rate as per purchase invoice rate ([db033c6](https://github.com/frappe/erpnext/commit/db033c686229c12671062404efb409c17100635f)) ### Reverts * Revert "refactor: use renamed timezone utils (#34301)" ([a2e001a](https://github.com/frappe/erpnext/commit/a2e001a2da81ed074dff7439b74a07a57a0388bc)), closes [#34301](https://github.com/frappe/erpnext/issues/34301) [#34301](https://github.com/frappe/erpnext/issues/34301) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ff1c3e67a2c..46f9074de77 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.17.4" +__version__ = "14.18.0" def get_default_company(user=None): From 59a415eaa920ea3f518632d3983033b02a10fb8f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 5 Mar 2023 21:57:02 +0530 Subject: [PATCH 040/176] perf: Stock Entry (Material Transfer) (cherry picked from commit de18f98c5c3d0ee5cc9d3df5b389917670514e64) (cherry picked from commit 1b514632d2de619108c3b1345ad7d7513a95c0c6) --- .../doctype/job_card/job_card.py | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 3133628cbf2..e82f37977cd 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -561,7 +561,34 @@ class JobCard(Document): ) def set_transferred_qty_in_job_card_item(self, ste_doc): - from frappe.query_builder.functions import Sum + def _get_job_card_items_transferred_qty(ste_doc): + from frappe.query_builder.functions import Sum + + job_card_items_transferred_qty = {} + job_card_items = [ + x.get("job_card_item") for x in ste_doc.get("items") if x.get("job_card_item") + ] + + if job_card_items: + se = frappe.qb.DocType("Stock Entry") + sed = frappe.qb.DocType("Stock Entry Detail") + + query = ( + frappe.qb.from_(sed) + .join(se) + .on(sed.parent == se.name) + .select(sed.job_card_item, Sum(sed.qty)) + .where( + (sed.job_card_item.isin(job_card_items)) + & (se.docstatus == 1) + & (se.purpose == "Material Transfer for Manufacture") + ) + .groupby(sed.job_card_item) + ) + + job_card_items_transferred_qty = frappe._dict(query.run(as_list=True)) + + return job_card_items_transferred_qty def _validate_over_transfer(row, transferred_qty): "Block over transfer of items if not allowed in settings." @@ -578,29 +605,23 @@ class JobCard(Document): exc=JobCardOverTransferError, ) - for row in ste_doc.items: - if not row.job_card_item: - continue - - sed = frappe.qb.DocType("Stock Entry Detail") - se = frappe.qb.DocType("Stock Entry") - transferred_qty = ( - frappe.qb.from_(sed) - .join(se) - .on(sed.parent == se.name) - .select(Sum(sed.qty)) - .where( - (sed.job_card_item == row.job_card_item) - & (se.docstatus == 1) - & (se.purpose == "Material Transfer for Manufacture") - ) - ).run()[0][0] + job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc) + if job_card_items_transferred_qty: allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") - if not allow_excess: - _validate_over_transfer(row, transferred_qty) - frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)) + for row in ste_doc.items: + if not row.job_card_item: + continue + + transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item)) + + if not allow_excess: + _validate_over_transfer(row, transferred_qty) + + frappe.db.set_value( + "Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty) + ) def set_transferred_qty(self, update_status=False): "Set total FG Qty in Job Card for which RM was transferred." From 7a5f7d4920a940aaa36b881ba1d7d9d9e2c45aa0 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 7 Mar 2023 01:11:23 +0530 Subject: [PATCH 041/176] perf: `update_completed_qty()` in `material_request.py` (cherry picked from commit 8ad9e99cea5e3f235d0b3ead812d2a3a11d40081) (cherry picked from commit b37712c038c65e200c5910634b49341199ab2900) --- .../material_request/material_request.py | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index bcf6dd79d8d..3548148145e 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -10,6 +10,7 @@ import json import frappe from frappe import _, msgprint from frappe.model.mapper import get_mapped_doc +from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items @@ -183,6 +184,34 @@ class MaterialRequest(BuyingController): self.update_requested_qty() self.update_requested_qty_in_production_plan() + def get_mr_items_ordered_qty(self, mr_items): + mr_items_ordered_qty = {} + mr_items = [d.name for d in self.get("items") if d.name in mr_items] + + doctype = qty_field = None + if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"): + doctype = frappe.qb.DocType("Stock Entry Detail") + qty_field = doctype.transfer_qty + elif self.material_request_type == "Manufacture": + doctype = frappe.qb.DocType("Work Order") + qty_field = doctype.qty + + if doctype and qty_field: + query = ( + frappe.qb.from_(doctype) + .select(doctype.material_request_item, Sum(qty_field)) + .where( + (doctype.material_request == self.name) + & (doctype.material_request_item.isin(mr_items)) + & (doctype.docstatus == 1) + ) + .groupby(doctype.material_request_item) + ) + + mr_items_ordered_qty = frappe._dict(query.run()) + + return mr_items_ordered_qty + def update_completed_qty(self, mr_items=None, update_modified=True): if self.material_request_type == "Purchase": return @@ -190,18 +219,13 @@ class MaterialRequest(BuyingController): if not mr_items: mr_items = [d.name for d in self.get("items")] + mr_items_ordered_qty = self.get_mr_items_ordered_qty(mr_items) + mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance") + for d in self.get("items"): if d.name in mr_items: if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"): - d.ordered_qty = flt( - frappe.db.sql( - """select sum(transfer_qty) - from `tabStock Entry Detail` where material_request = %s - and material_request_item = %s and docstatus = 1""", - (self.name, d.name), - )[0][0] - ) - mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance") + d.ordered_qty = flt(mr_items_ordered_qty.get(d.name)) if mr_qty_allowance: allowed_qty = d.qty + (d.qty * (mr_qty_allowance / 100)) @@ -220,14 +244,7 @@ class MaterialRequest(BuyingController): ) elif self.material_request_type == "Manufacture": - d.ordered_qty = flt( - frappe.db.sql( - """select sum(qty) - from `tabWork Order` where material_request = %s - and material_request_item = %s and docstatus = 1""", - (self.name, d.name), - )[0][0] - ) + d.ordered_qty = flt(mr_items_ordered_qty.get(d.name)) frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty) From cfe28663bcaad841588340a1f12d8300851dc6d2 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 7 Mar 2023 17:23:00 +0000 Subject: [PATCH 042/176] chore(release): Bumped to Version 14.18.1 ## [14.18.1](https://github.com/frappe/erpnext/compare/v14.18.0...v14.18.1) (2023-03-07) ### Performance Improvements * `update_completed_qty()` in `material_request.py` ([7a5f7d4](https://github.com/frappe/erpnext/commit/7a5f7d4920a940aaa36b881ba1d7d9d9e2c45aa0)) * Stock Entry (Material Transfer) ([59a415e](https://github.com/frappe/erpnext/commit/59a415eaa920ea3f518632d3983033b02a10fb8f)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 46f9074de77..9bf1c0aad22 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.18.0" +__version__ = "14.18.1" def get_default_company(user=None): From f71d85d7c3dcf298b42c224e98386cfc624b3a80 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 15:39:56 +0530 Subject: [PATCH 043/176] Revert "fix: Default sales team not getting set" (#34376) Revert "fix: Default sales team not getting set" (#34376) Revert "fix: Default sales team not getting set (#34284)" This reverts commit 7d0199d743c7861e883cadd582c036cc8d9b0a62. (cherry picked from commit 9a8f8e8b7da532499a8916755a915d2da8081577) Co-authored-by: Deepesh Garg (cherry picked from commit ed338b13952ea65e727b8741d8ad2844646b6ecd) --- erpnext/controllers/selling_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 1a81158b366..590e00fb11e 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -87,9 +87,6 @@ class SellingController(StockController): ) if not self.meta.get_field("sales_team"): party_details.pop("sales_team") - else: - self.set("sales_team", party_details.get("sales_team")) - self.update_if_missing(party_details) elif lead: From fcbcbc0aa7fffbfe732818768ecd5ed32fd4fda8 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 9 Mar 2023 10:26:37 +0000 Subject: [PATCH 044/176] chore(release): Bumped to Version 14.18.2 ## [14.18.2](https://github.com/frappe/erpnext/compare/v14.18.1...v14.18.2) (2023-03-09) ### Reverts * Revert "fix: Default sales team not getting set" (#34376) ([f71d85d](https://github.com/frappe/erpnext/commit/f71d85d7c3dcf298b42c224e98386cfc624b3a80)), closes [#34376](https://github.com/frappe/erpnext/issues/34376) [#34376](https://github.com/frappe/erpnext/issues/34376) [#34284](https://github.com/frappe/erpnext/issues/34284) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 9bf1c0aad22..6976133ba42 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.18.1" +__version__ = "14.18.2" def get_default_company(user=None): From 3e61e763173d14a79912f367bc607a1b6ac1cbfa Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 14 Mar 2023 17:37:03 +0000 Subject: [PATCH 045/176] chore(release): Bumped to Version 14.18.3 ## [14.18.3](https://github.com/frappe/erpnext/compare/v14.18.2...v14.18.3) (2023-03-14) ### Bug Fixes * `BOM Stock Report` ([1c00077](https://github.com/frappe/erpnext/commit/1c0007768b661c6ce031a57f0c937e7a973133df)) * `required_qty` get reset to `1` for Alternative Item in WO ([51bcdb3](https://github.com/frappe/erpnext/commit/51bcdb32f2ab7db1b8a6e5bd0f4b7e59c6d4e641)) * Don't use get_list & get_all interchangeably ([27c524e](https://github.com/frappe/erpnext/commit/27c524e337eecf29dc10cd25a0a8f49edfba52fe)) * Error in consolidated financial statement ([#34330](https://github.com/frappe/erpnext/issues/34330)) ([73866f4](https://github.com/frappe/erpnext/commit/73866f4da7487d7f8fdb815d69fbe6d2cb63764e)) * exchange rate revaluation errors ([#33947](https://github.com/frappe/erpnext/issues/33947)) ([1a629b6](https://github.com/frappe/erpnext/commit/1a629b641845cac7b4b3db32a571d4dd26fbae13)) * filters not getting applied on `Web Form` ([6ef7ddf](https://github.com/frappe/erpnext/commit/6ef7ddfbce271e35123b32bbbe5f605c9b516d1a)) * Linked invoice cancellation issue via timesheet ([#34337](https://github.com/frappe/erpnext/issues/34337)) ([da8cc2b](https://github.com/frappe/erpnext/commit/da8cc2bba9af2e0499975f8649232a7006f5bce0)) * operation time for multi-level BOM in WO ([76e04c8](https://github.com/frappe/erpnext/commit/76e04c8625b92aafdf0467aaa55227789ded214f)) * Set contact filter link in Opportunity ([#34325](https://github.com/frappe/erpnext/issues/34325)) ([c64836d](https://github.com/frappe/erpnext/commit/c64836d3d63b433f6a1df280670fa7b22a8c157f)) * set tax category from address before executing `get_regional_address_details` ([#34372](https://github.com/frappe/erpnext/issues/34372)) ([bf0cbe0](https://github.com/frappe/erpnext/commit/bf0cbe09b9a28f11b4c7d5894b86b7c3f4ef4c99)) * **test:** flaky test case in Payment terms report ([69a5411](https://github.com/frappe/erpnext/commit/69a5411f0ebcc4bbcfb36b3470753e4c556fea1d)) * Total row in trail balance report ([#34395](https://github.com/frappe/erpnext/issues/34395)) ([c353ba7](https://github.com/frappe/erpnext/commit/c353ba741cc04eeb432449731c7ca252929a1fc8)) * Use customer name instead of name(id) in PSOA (backport [#34412](https://github.com/frappe/erpnext/issues/34412)) ([#34425](https://github.com/frappe/erpnext/issues/34425)) ([209adf3](https://github.com/frappe/erpnext/commit/209adf32a5ebf6df70784a1d6567720d6c296343)) ### Performance Improvements * `update_completed_qty()` in `material_request.py` ([b37712c](https://github.com/frappe/erpnext/commit/b37712c038c65e200c5910634b49341199ab2900)) * Stock Entry (Material Transfer) ([1b51463](https://github.com/frappe/erpnext/commit/1b514632d2de619108c3b1345ad7d7513a95c0c6)) ### Reverts * Revert "Update tr.csv (backport #34285)" (#34427) ([b6d059c](https://github.com/frappe/erpnext/commit/b6d059ccb82c7798b6622eb689885dab884cb671)), closes [#34285](https://github.com/frappe/erpnext/issues/34285) [#34427](https://github.com/frappe/erpnext/issues/34427) [#34285](https://github.com/frappe/erpnext/issues/34285) * Revert "fix: Default sales team not getting set" (#34376) ([ed338b1](https://github.com/frappe/erpnext/commit/ed338b13952ea65e727b8741d8ad2844646b6ecd)), closes [#34376](https://github.com/frappe/erpnext/issues/34376) [#34376](https://github.com/frappe/erpnext/issues/34376) [#34284](https://github.com/frappe/erpnext/issues/34284) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6976133ba42..a5f153f5211 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.18.2" +__version__ = "14.18.3" def get_default_company(user=None): From e271935673f1e27d7bd8ab7b7e1731a22b2e0bee Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 21 Mar 2023 12:52:05 +0000 Subject: [PATCH 046/176] chore(release): Bumped to Version 14.19.0 # [14.19.0](https://github.com/frappe/erpnext/compare/v14.18.3...v14.19.0) (2023-03-21) ### Bug Fixes * **client:** Amount calculation for 0 qty debit notes ([#34455](https://github.com/frappe/erpnext/issues/34455)) ([d24f4d2](https://github.com/frappe/erpnext/commit/d24f4d287382939d8a679c1c9444b116a2bf3e18)) * difference amount calculation for company currency accounts ([9ab7bff](https://github.com/frappe/erpnext/commit/9ab7bff0e0ed2ec100307ed7eb29b7c9b0984865)) * don't map item row having `0` qty ([7611a49](https://github.com/frappe/erpnext/commit/7611a49db7a2785bada3f74aeb1bb330cb1e3c67)) * E-commerce issue with Item Variants ([53c3fff](https://github.com/frappe/erpnext/commit/53c3fff2353c8c7363dd55421d7c43350ae3af07)) * german translations ([#34312](https://github.com/frappe/erpnext/issues/34312)) ([dd0c833](https://github.com/frappe/erpnext/commit/dd0c8334cdbe0da2852206c9390004325627c5ab)) * hide `+` button based on `Blanket Order Type` ([daa1bb8](https://github.com/frappe/erpnext/commit/daa1bb86e36f18f8a0d7fd9dcb696bc604ffdbb4)) * incorrect depr schedules after asset repair [v14] ([#34527](https://github.com/frappe/erpnext/issues/34527)) ([560df63](https://github.com/frappe/erpnext/commit/560df6330a1b43648a91e3f1514d8ff5dc382c15)), closes [#30838](https://github.com/frappe/erpnext/issues/30838) * Multiple accounting dimension filtering in AR/AP reports ([#34464](https://github.com/frappe/erpnext/issues/34464)) ([f146479](https://github.com/frappe/erpnext/commit/f146479362d3a2b46d720d635db71c37058e1a41)) * Overallocation of 'qty' from Cr Notes to Parent Invoice ([848e56b](https://github.com/frappe/erpnext/commit/848e56bd4ca07ecbb1ea64dd226680c96a17a29c)) * patch depends on Currency Exchange Settings ([#34494](https://github.com/frappe/erpnext/issues/34494)) ([4acde44](https://github.com/frappe/erpnext/commit/4acde4468fa6574dfeb6ca786fe4b8796a5969eb)) * POS not picking up pos profile company address instead fetch any random company address ([#34521](https://github.com/frappe/erpnext/issues/34521)) ([01f4cc7](https://github.com/frappe/erpnext/commit/01f4cc76fc65ae35ec99e0599818b141a2393f2e)) * Update account number from parent company ([#34474](https://github.com/frappe/erpnext/issues/34474)) ([55d002c](https://github.com/frappe/erpnext/commit/55d002c6364622b40a47d5c8e009d99f097174c7)) * use max function to get default company address (backport [#34116](https://github.com/frappe/erpnext/issues/34116)) ([#34452](https://github.com/frappe/erpnext/issues/34452)) ([ba2fd71](https://github.com/frappe/erpnext/commit/ba2fd71b6522b39d1f17fc00c497d1d59ea00e7e)) ### Features * add field `Over Order Allowance (%)` in `Buying Settings` ([da915f1](https://github.com/frappe/erpnext/commit/da915f15103d969c1c6ae2561ae517c68635cdd8)) * add field `Over Order Allowance (%)` in `Selling Settings` ([46b5ba9](https://github.com/frappe/erpnext/commit/46b5ba9c2aa9093115206cdf79831a86194447de)) * bank reconciliation and plaid changes ([#33986](https://github.com/frappe/erpnext/issues/33986)) ([9b608ea](https://github.com/frappe/erpnext/commit/9b608eaa0fcebcb5a8627ec4c14ccfad1057421e)) * consider `over_order_allowance` while validating order qty ([932639b](https://github.com/frappe/erpnext/commit/932639b4df5747593667849695c01b953d02ef01)) * consider `over_order_allowance` while validating sales order qty ([09b577a](https://github.com/frappe/erpnext/commit/09b577a91fd32fdc3f974e8b7a3a85ad1f37d51d)) * Support for Alternative Items in Quotation ([#33874](https://github.com/frappe/erpnext/issues/33874)) ([9f7da21](https://github.com/frappe/erpnext/commit/9f7da21c9333e61bad2eb561ed3de96c8656ab7e)) ### Performance Improvements * index against_sales_invoice field on DN items (backport [#34509](https://github.com/frappe/erpnext/issues/34509)) ([#34510](https://github.com/frappe/erpnext/issues/34510)) ([baa789b](https://github.com/frappe/erpnext/commit/baa789be347d14420b66498cd978c5a8380d6d2a)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a5f153f5211..63dd69aeca6 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.18.3" +__version__ = "14.19.0" def get_default_company(user=None): From a60c8f0e18c27439d710d61050b2232d21e5d3da Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 28 Mar 2023 18:23:52 +0000 Subject: [PATCH 047/176] chore(release): Bumped to Version 14.20.0 # [14.20.0](https://github.com/frappe/erpnext/compare/v14.19.0...v14.20.0) (2023-03-28) ### Bug Fixes * default pos conversion factor set to 1 ([#34437](https://github.com/frappe/erpnext/issues/34437)) ([18d813a](https://github.com/frappe/erpnext/commit/18d813a6561ddc63f6b880d89d0a3b6b2969e786)) * don't get zero value entries for exchange rate calculation ([#34475](https://github.com/frappe/erpnext/issues/34475)) ([ff24b3e](https://github.com/frappe/erpnext/commit/ff24b3e40c419b91306246dfedfcccd060b6cd37)) * incorrect `Opening Value` in `Stock Balance` report ([76b782a](https://github.com/frappe/erpnext/commit/76b782a03fe28078b992f3ae6f5f25148a8ef4a8)) * Note username overlapping with note content(CRM) ([096e5ef](https://github.com/frappe/erpnext/commit/096e5ef19766e032428afd2566817c90e694a16b)) * Party Name in SOA print when viewed from Customer/Supplier master ([#34597](https://github.com/frappe/erpnext/issues/34597)) ([835edbe](https://github.com/frappe/erpnext/commit/835edbe80e76ca03dfddeeb6d656ad4b5eeee1d6)) * Percentage billing in Sales Order ([#34606](https://github.com/frappe/erpnext/issues/34606)) ([477cb12](https://github.com/frappe/erpnext/commit/477cb12240034da391d6fffea13a589fcec83e76)) * recalculate WDV rate after asset repair [v14] ([#34571](https://github.com/frappe/erpnext/issues/34571)) ([d2ca6f8](https://github.com/frappe/erpnext/commit/d2ca6f8d1f36443635d50c27b3134daca494e33e)) * remove unused translation ([#34519](https://github.com/frappe/erpnext/issues/34519)) ([881e92e](https://github.com/frappe/erpnext/commit/881e92e7b363e6790d3260da11822ccdb5949d2f)) * removing redundant validation ([fd6db41](https://github.com/frappe/erpnext/commit/fd6db41b6e4887fcf898e3615d0f0c75c3c45111)) * Sales person variance report without item group ([#34552](https://github.com/frappe/erpnext/issues/34552)) ([90ddc4a](https://github.com/frappe/erpnext/commit/90ddc4a1e2ff277ef676650c899bc2c172bdbcf5)) * Tax Category not able to set hence it calculating zero tax for item whoes tax template set ([#34525](https://github.com/frappe/erpnext/issues/34525)) ([a8567b0](https://github.com/frappe/erpnext/commit/a8567b09e69e41866cb2c3bdcded9563d06dccf2)) * Time button not working in the job card ([8fed33b](https://github.com/frappe/erpnext/commit/8fed33b03bc1d609526e0506eb17612f21b466b0)) * translations and UX in alternative item mapping ([#34433](https://github.com/frappe/erpnext/issues/34433)) ([702d07e](https://github.com/frappe/erpnext/commit/702d07ea7db1a032ee0f617f642f68e6d63a0f77)) * unset address and contact on trash (backport [#34495](https://github.com/frappe/erpnext/issues/34495)) ([#34560](https://github.com/frappe/erpnext/issues/34560)) ([db01bf5](https://github.com/frappe/erpnext/commit/db01bf5dec74a190452b1c5a95fa59eba4db5c82)) * zero rm-cost for batch rm item in SCR (backport [#34616](https://github.com/frappe/erpnext/issues/34616)) ([#34623](https://github.com/frappe/erpnext/issues/34623)) ([cff35d7](https://github.com/frappe/erpnext/commit/cff35d7286d0831698d9b8c23dcd18343a02f22a)) ### Features * deprecate get_customer_list ([#34563](https://github.com/frappe/erpnext/issues/34563)) ([67576ad](https://github.com/frappe/erpnext/commit/67576ad5bdc78aa8ef79b6bf915df2ba87b9e21f)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 63dd69aeca6..8a50a5d369d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.19.0" +__version__ = "14.20.0" def get_default_company(user=None): From 5ef98fcea1727ccbf4ca8ef3a179555300a7629d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 31 Mar 2023 10:32:49 +0530 Subject: [PATCH 048/176] chore: make `Production Plan Item Reference` table hidden in Production Plan (cherry picked from commit 706be2a4155209abd3065fa1225f441b3c759740) --- .../doctype/production_plan/production_plan.json | 3 ++- .../production_plan_item_reference.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 2624daa41e2..fdaa4a2a1d4 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -344,6 +344,7 @@ { "fieldname": "prod_plan_references", "fieldtype": "Table", + "hidden": 1, "label": "Production Plan Item Reference", "options": "Production Plan Item Reference" }, @@ -397,7 +398,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-26 14:51:08.774372", + "modified": "2023-03-31 10:30:48.118932", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json index 84dee4ad284..15ef20794cb 100644 --- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json @@ -28,7 +28,7 @@ "fieldname": "qty", "fieldtype": "Data", "in_list_view": 1, - "label": "qty" + "label": "Qty" }, { "fieldname": "item_reference", @@ -40,7 +40,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-07 17:03:49.707487", + "modified": "2023-03-31 10:30:14.604051", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item Reference", @@ -48,5 +48,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From b835760b0ba11c30e94582937f930c09e03299d6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 31 Mar 2023 12:09:57 +0530 Subject: [PATCH 049/176] fix: enclose ternary operator in parentheses (cherry picked from commit 986daa65784fde3a2334599b58be222f030d79f6) --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 8e57ebd3677..8efc47d18e5 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -135,7 +135,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } else { // allow for '0' qty on Credit/Debit notes - let qty = item.qty || me.frm.doc.is_debit_note ? 1 : -1; + let qty = item.qty || (me.frm.doc.is_debit_note ? 1 : -1); item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item)); } From 875743589863ae353f2617ed712f453f0e58620b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 30 Mar 2023 13:46:50 +0530 Subject: [PATCH 050/176] fix: BOM Update Cost, when no actual qty (cherry picked from commit a4112c75c5975b53e46ea5bab47daf1c4d8d7e7e) --- erpnext/manufacturing/doctype/bom/bom.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 619a415c8bc..a085af859a4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -943,7 +943,8 @@ def get_valuation_rate(data): 2) If no value, get last valuation rate from SLE 3) If no value, get valuation rate from Item """ - from frappe.query_builder.functions import Sum + from frappe.query_builder.functions import Count, IfNull, Sum + from pypika import Case item_code, company = data.get("item_code"), data.get("company") valuation_rate = 0.0 @@ -954,7 +955,14 @@ def get_valuation_rate(data): frappe.qb.from_(bin_table) .join(wh_table) .on(bin_table.warehouse == wh_table.name) - .select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate")) + .select( + Case() + .when( + Count(bin_table.name) > 0, IfNull(Sum(bin_table.stock_value) / Sum(bin_table.actual_qty), 0.0) + ) + .else_(None) + .as_("valuation_rate") + ) .where((bin_table.item_code == item_code) & (wh_table.company == company)) ).run(as_dict=True)[0] From a00459aec39f0337d8b62e8f2c143f87e6a35daf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:07:28 +0530 Subject: [PATCH 051/176] fix: Supplier RFQ email link (#34338) fix: Supplier RFQ email link (#34338) (cherry picked from commit fc86a8568f41b95f9463d6e29c8628b2b158e67a) Co-authored-by: Deepesh Garg --- .../doctype/request_for_quotation/request_for_quotation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 7927beb8233..4590f8c3d93 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -113,7 +113,10 @@ class RequestforQuotation(BuyingController): def get_link(self): # RFQ link for supplier portal - return get_url("/app/request-for-quotation/" + self.name) + route = frappe.db.get_value( + "Portal Menu Item", {"reference_doctype": "Request for Quotation"}, ["route"] + ) + return get_url("/app/{0}/".format(route) + self.name) def update_supplier_part_no(self, supplier): self.vendor = supplier From f1687cfb14fc204a0b6081577c6c8d683ac1282a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:07:50 +0530 Subject: [PATCH 052/176] fix: Bank clearance for case loan (disburstment/repayment) (#34586) fix: Bank clearance for case loan (disburstment/repayment) (#34586) (cherry picked from commit 74b29eb5e22c15a8055322f4143c9f401cabaa7f) Co-authored-by: Kitti U. @ Ecosoft --- .../doctype/bank_clearance/bank_clearance.py | 16 +++-- .../bank_clearance_summary.py | 64 ++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 80878ac5068..081718726bd 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -81,7 +81,7 @@ class BankClearance(Document): loan_disbursement = frappe.qb.DocType("Loan Disbursement") - loan_disbursements = ( + query = ( frappe.qb.from_(loan_disbursement) .select( ConstantColumn("Loan Disbursement").as_("payment_document"), @@ -90,17 +90,22 @@ class BankClearance(Document): ConstantColumn(0).as_("debit"), loan_disbursement.reference_number.as_("cheque_number"), loan_disbursement.reference_date.as_("cheque_date"), + loan_disbursement.clearance_date.as_("clearance_date"), loan_disbursement.disbursement_date.as_("posting_date"), loan_disbursement.applicant.as_("against_account"), ) .where(loan_disbursement.docstatus == 1) .where(loan_disbursement.disbursement_date >= self.from_date) .where(loan_disbursement.disbursement_date <= self.to_date) - .where(loan_disbursement.clearance_date.isnull()) .where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account])) .orderby(loan_disbursement.disbursement_date) .orderby(loan_disbursement.name, order=frappe.qb.desc) - ).run(as_dict=1) + ) + + if not self.include_reconciled_entries: + query = query.where(loan_disbursement.clearance_date.isnull()) + + loan_disbursements = query.run(as_dict=1) loan_repayment = frappe.qb.DocType("Loan Repayment") @@ -113,16 +118,19 @@ class BankClearance(Document): ConstantColumn(0).as_("credit"), loan_repayment.reference_number.as_("cheque_number"), loan_repayment.reference_date.as_("cheque_date"), + loan_repayment.clearance_date.as_("clearance_date"), loan_repayment.applicant.as_("against_account"), loan_repayment.posting_date, ) .where(loan_repayment.docstatus == 1) - .where(loan_repayment.clearance_date.isnull()) .where(loan_repayment.posting_date >= self.from_date) .where(loan_repayment.posting_date <= self.to_date) .where(loan_repayment.payment_account.isin([self.bank_account, self.account])) ) + if not self.include_reconciled_entries: + query = query.where(loan_repayment.clearance_date.isnull()) + if frappe.db.has_column("Loan Repayment", "repay_from_salary"): query = query.where((loan_repayment.repay_from_salary == 0)) diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 449ebdcd924..306af722ba8 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.custom import ConstantColumn from frappe.utils import getdate, nowdate @@ -91,4 +92,65 @@ def get_entries(filters): as_list=1, ) - return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate())) + # Loan Disbursement + loan_disbursement = frappe.qb.DocType("Loan Disbursement") + + query = ( + frappe.qb.from_(loan_disbursement) + .select( + ConstantColumn("Loan Disbursement").as_("payment_document_type"), + loan_disbursement.name.as_("payment_entry"), + loan_disbursement.disbursement_date.as_("posting_date"), + loan_disbursement.reference_number.as_("cheque_no"), + loan_disbursement.clearance_date.as_("clearance_date"), + loan_disbursement.applicant.as_("against"), + -loan_disbursement.disbursed_amount.as_("amount"), + ) + .where(loan_disbursement.docstatus == 1) + .where(loan_disbursement.disbursement_date >= filters["from_date"]) + .where(loan_disbursement.disbursement_date <= filters["to_date"]) + .where(loan_disbursement.disbursement_account == filters["account"]) + .orderby(loan_disbursement.disbursement_date, order=frappe.qb.desc) + .orderby(loan_disbursement.name, order=frappe.qb.desc) + ) + + if filters.get("from_date"): + query = query.where(loan_disbursement.disbursement_date >= filters["from_date"]) + if filters.get("to_date"): + query = query.where(loan_disbursement.disbursement_date <= filters["to_date"]) + + loan_disbursements = query.run(as_list=1) + + # Loan Repayment + loan_repayment = frappe.qb.DocType("Loan Repayment") + + query = ( + frappe.qb.from_(loan_repayment) + .select( + ConstantColumn("Loan Repayment").as_("payment_document_type"), + loan_repayment.name.as_("payment_entry"), + loan_repayment.posting_date.as_("posting_date"), + loan_repayment.reference_number.as_("cheque_no"), + loan_repayment.clearance_date.as_("clearance_date"), + loan_repayment.applicant.as_("against"), + loan_repayment.amount_paid.as_("amount"), + ) + .where(loan_repayment.docstatus == 1) + .where(loan_repayment.posting_date >= filters["from_date"]) + .where(loan_repayment.posting_date <= filters["to_date"]) + .where(loan_repayment.payment_account == filters["account"]) + .orderby(loan_repayment.posting_date, order=frappe.qb.desc) + .orderby(loan_repayment.name, order=frappe.qb.desc) + ) + + if filters.get("from_date"): + query = query.where(loan_repayment.posting_date >= filters["from_date"]) + if filters.get("to_date"): + query = query.where(loan_repayment.posting_date <= filters["to_date"]) + + loan_repayments = query.run(as_list=1) + + return sorted( + journal_entries + payment_entries + loan_disbursements + loan_repayments, + key=lambda k: k[2] or getdate(nowdate()), + ) From 5677f25215e356f53bd94e57b05540de64e3942e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 12:56:58 +0530 Subject: [PATCH 053/176] fix: Multiple issues in purchase invoice submission (#34600) fix: Multiple issues in purchase invoice submission (#34600) * fix: Multiple issues in purchase invoice submission * fix: Base grand total calculation * chore: Calculate base grand total separately only in multi currency docs * fix: Add gl entry for round off (cherry picked from commit 4c61ee30bbfb56aec18d3cac5770d786f635931b) Co-authored-by: Deepesh Garg --- .../purchase_invoice/purchase_invoice.py | 27 +++++++++- .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 39 ++++++++++++--- erpnext/stock/get_item_details.py | 23 +++++++-- erpnext/utilities/transaction_base.py | 49 ++++++++++++++----- 5 files changed, 114 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index ae707ab1435..0ded4a883bb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -117,7 +117,7 @@ class PurchaseInvoice(BuyingController): self.validate_expense_account() self.set_against_expense_account() self.validate_write_off_account() - self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") + self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount") self.create_remarks() self.set_status() self.validate_purchase_receipt_if_update_stock() @@ -232,7 +232,7 @@ class PurchaseInvoice(BuyingController): ) if ( - cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) + cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate")) and not self.is_return and not self.is_internal_supplier ): @@ -581,6 +581,7 @@ class PurchaseInvoice(BuyingController): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) + self.make_precision_loss_gl_entry(gl_entries) if self.check_asset_cwip_enabled(): self.get_asset_gl_entry(gl_entries) @@ -975,6 +976,28 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount, item.precision("item_tax_amount") ) + def make_precision_loss_gl_entry(self, gl_entries): + round_off_account, round_off_cost_center = get_round_off_account_and_cost_center( + self.company, "Purchase Invoice", self.name + ) + + precision_loss = self.get("base_net_total") - flt( + self.get("net_total") * self.conversion_rate, self.precision("net_total") + ) + + if precision_loss: + gl_entries.append( + self.get_gl_dict( + { + "account": round_off_account, + "against": self.supplier, + "credit": precision_loss, + "cost_center": self.cost_center or round_off_cost_center, + "remarks": _("Net total calculation precision loss"), + } + ) + ) + def get_asset_gl_entry(self, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f5be4c7a3f3..7af98ddf934 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -145,7 +145,7 @@ class SalesInvoice(SellingController): self.set_against_income_account() self.validate_time_sheets_are_submitted() - self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") + self.validate_multiple_billing("Delivery Note", "dn_detail", "amount") if not self.is_return: self.validate_serial_numbers() else: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3705fcf4990..390af0deb21 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -515,6 +515,8 @@ class AccountsController(TransactionBase): parent_dict.update({"customer": parent_dict.get("party_name")}) self.pricing_rules = [] + basic_item_details_map = {} + for item in self.get("items"): if item.get("item_code"): args = parent_dict.copy() @@ -533,7 +535,17 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) + basic_details = basic_item_details_map.get(item.item_code) + ret, basic_item_details = get_item_details( + args, + self, + for_validate=True, + overwrite_warehouse=False, + return_basic_details=True, + basic_details=basic_details, + ) + + basic_item_details_map.setdefault(item.item_code, basic_item_details) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: @@ -1232,7 +1244,7 @@ class AccountsController(TransactionBase): ) ) - def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): + def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on): from erpnext.controllers.status_updater import get_allowance_for item_allowance = {} @@ -1245,17 +1257,20 @@ class AccountsController(TransactionBase): total_overbilled_amt = 0.0 + reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)] + reference_details = self.get_billing_reference_details( + reference_names, ref_dt + " Item", based_on + ) + for item in self.get("items"): if not item.get(item_ref_dn): continue - ref_amt = flt( - frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on), - self.precision(based_on, item), - ) + ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item)) + if not ref_amt: frappe.msgprint( - _("System will not check overbilling since amount for Item {0} in {1} is zero").format( + _("System will not check over billing since amount for Item {0} in {1} is zero").format( item.item_code, ref_dt ), title=_("Warning"), @@ -1302,6 +1317,16 @@ class AccountsController(TransactionBase): alert=True, ) + def get_billing_reference_details(self, reference_names, reference_doctype, based_on): + return frappe._dict( + frappe.get_all( + reference_doctype, + filters={"name": ("in", reference_names)}, + fields=["name", based_on], + as_list=1, + ) + ) + def get_billed_amount_for_item(self, item, item_ref_dn, based_on): """ Returns Sum of Amount of diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 489ec6ebecc..2df39c81832 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -35,7 +35,14 @@ purchase_doctypes = [ @frappe.whitelist() -def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): +def get_item_details( + args, + doc=None, + for_validate=False, + overwrite_warehouse=True, + return_basic_details=False, + basic_details=None, +): """ args = { "item_code": "", @@ -73,7 +80,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if doc.get("doctype") == "Purchase Invoice": args["bill_date"] = doc.get("bill_date") - out = get_basic_details(args, item, overwrite_warehouse) + if not basic_details: + out = get_basic_details(args, item, overwrite_warehouse) + else: + out = basic_details + + basic_details = out.copy() + get_item_tax_template(args, item, out) out["item_tax_rate"] = get_item_tax_map( args.company, @@ -141,7 +154,11 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.amount = flt(args.qty) * flt(out.rate) out = remove_standard_fields(out) - return out + + if return_basic_details: + return out, basic_details + else: + return out def remove_standard_fields(details): diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 21a0a551b62..fc2054533d9 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -58,11 +58,11 @@ class TransactionBase(StatusUpdater): def compare_values(self, ref_doc, fields, doc=None): for reference_doctype, ref_dn_list in ref_doc.items(): + prev_doc_detail_map = self.get_prev_doc_reference_details( + ref_dn_list, reference_doctype, fields + ) for reference_name in ref_dn_list: - prevdoc_values = frappe.db.get_value( - reference_doctype, reference_name, [d[0] for d in fields], as_dict=1 - ) - + prevdoc_values = prev_doc_detail_map.get(reference_name) if not prevdoc_values: frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name)) @@ -70,6 +70,19 @@ class TransactionBase(StatusUpdater): if prevdoc_values[field] is not None and field not in self.exclude_fields: self.validate_value(field, condition, prevdoc_values[field], doc) + def get_prev_doc_reference_details(self, reference_names, reference_doctype, fields): + prev_doc_detail_map = {} + details = frappe.get_all( + reference_doctype, + filters={"name": ("in", reference_names)}, + fields=["name"] + [d[0] for d in fields], + ) + + for d in details: + prev_doc_detail_map.setdefault(d.name, d) + + return prev_doc_detail_map + def validate_rate_with_reference_doc(self, ref_details): if self.get("is_internal_supplier"): return @@ -77,23 +90,23 @@ class TransactionBase(StatusUpdater): buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"] if self.doctype in buying_doctypes: - action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action") - settings_doc = "Buying Settings" + action, role_allowed_to_override = frappe.get_cached_value( + "Buying Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"] + ) else: - action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action") - settings_doc = "Selling Settings" + action, role_allowed_to_override = frappe.get_cached_value( + "Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"] + ) for ref_dt, ref_dn_field, ref_link_field in ref_details: + reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)] + reference_details = self.get_reference_details(reference_names, ref_dt + " Item") for d in self.get("items"): if d.get(ref_link_field): - ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate") + ref_rate = reference_details.get(d.get(ref_link_field)) if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01: if action == "Stop": - role_allowed_to_override = frappe.db.get_single_value( - settings_doc, "role_to_override_stop_action" - ) - if role_allowed_to_override not in frappe.get_roles(): frappe.throw( _("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format( @@ -109,6 +122,16 @@ class TransactionBase(StatusUpdater): indicator="orange", ) + def get_reference_details(self, reference_names, reference_doctype): + return frappe._dict( + frappe.get_all( + reference_doctype, + filters={"name": ("in", reference_names)}, + fields=["name", "rate"], + as_list=1, + ) + ) + def get_link_filters(self, for_doctype): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype): fieldname = self.prev_link_mapper[for_doctype]["fieldname"] From c0f7f7da429dc6e9edd6e6c8da76c3a347d82d24 Mon Sep 17 00:00:00 2001 From: Vishal Date: Mon, 3 Apr 2023 12:46:57 +0530 Subject: [PATCH 054/176] fix: use stock qty to calculate POS reserved stock --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 0af4c0ea480..27e6f0e598f 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -674,7 +674,7 @@ def get_bin_qty(item_code, warehouse): def get_pos_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql( - """select sum(p_item.qty) as qty + """select sum(p_item.stock_qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item where p.name = p_item.parent and ifnull(p.consolidated_invoice, '') = '' From be2990ec88087cc1081d51feb8100e97e9031fd8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:00:22 +0530 Subject: [PATCH 055/176] fix: Allocate tax loss to tax account head on early payment discount (#34287) * fix: Taxes aren't discounted on early payment discount - Deductions in payment entry must be split into income loss and tax loss - Compute total discount in percentage, makes discounting different amounts proportionately easier (cherry picked from commit 768c3a49278e35abc31a04a0b87d2dcd2e8794d8) * fix: Recalculate difference amount after setting deductions (cherry picked from commit 75ec0a0a85a010415765518f5a9e36bb13d08b22) * fix: Set deductions in base currency - Use field precision to get more accurate values (cherry picked from commit dc2998f5442613e3c3624493896686fc75f3c388) * fix: Back update discounted amount in Invoice based on discount type - Discount value was always trated as a percentage on back updation (cherry picked from commit 2ae58342907c0cfb9ae7658176e1549fb51d1cb3) * test: PE from SI with early payment discount amount & PE assertions in discount % test (cherry picked from commit c217bb201878327fb6dfa341fbf65c19761916a5) * fix: Set deduction amount in company currency on Doctype - Even via JS, deductions amount is always in company currency - Since there is nothing dynamic about this field, set it in the doctype spec itself - fixed: Inconsistency between label currency and field currency formatted value (cherry picked from commit 7f2e7badffab44355a4525369eb1a044b2f9e5c1) * fix: Don't add to deductions if amount is 0 - misc: better docstring (cherry picked from commit f02fc8acf0d50fcc178b713a1385595a40cb19f0) * fix: Paid amount must be discounted considering accounting currency - Accounting is in the same currency if party currency and company currency is the same - If accounting is in the same currency, paid and recvd amount is in the base currency - Then, discount amount must also be in the base currency as it is deducted from paid amount - Received amount must be in base currency if not multi currency - cleanup: Deductions setting broken into smaller functions (cherry picked from commit 761f68d7bf0b8539f26a79993245c8ffcbcde5f1) * fix: Multi-currency SI with base currency PE - Return total discount loss in base currency - Allocate payment based on terms: Set allocated amount in references table in base currency if accounting is in that currency - Allocate payment based on terms: While back updating set paid amount (payment schedule) in transaction currency always - minor: discount msgprint in correct currency (cherry picked from commit b09c2381ca144c63098d0fedf79c92fa5f7b929a) * test: Multi currency SI with multi-currency accounting and single currency accounting + Early payment discount (cherry picked from commit 9abf0ef615d38d806e27b0c2fcce48125fd75fa1) * fix: Handle rounding more gracefully - Round off pending discount loss to avoid miniscule losses rounded to 0.0 that are added in deductions - Use base amounts to calculate base losses instead of using conversion factor which increases rounding error - Round of total base loss instead of individual income and tax losses to reduce rounding error - Use default round off account for pending rounding loss in deductions (cherry picked from commit caa1a3dccf66b8ff379a4482841e8309f4a7fa6d) * fix: Provision to apply early payment discount if payment is recorded late - Party could have paid on time but payment is recorded late - Prompt for reference date so that discount is applied while mapping - Prompt only if discount in payment schedule of valid doctypes - test: Reference date and impact on PE - `make_payment_entry` (JS) must be able to access `this` (cherry picked from commit d6d0163514882a9d7ae16a61be54b7776c001e94) * feat: Make Tax loss booking optional - Checkbox in Accounts Settings - Apply checkbox in PE deductions setting logic - Adjust tests (cherry picked from commit 216a46bd6615aab47a30ff79ddf78503080121c1) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json * fix: Merge conflicts --------- Co-authored-by: marination --- .../accounts_settings/accounts_settings.json | 10 +- .../doctype/payment_entry/payment_entry.js | 2 - .../doctype/payment_entry/payment_entry.py | 240 ++++++++++++++++-- .../payment_entry/test_payment_entry.py | 222 +++++++++++++++- .../payment_entry_deduction.json | 29 +-- .../purchase_invoice/purchase_invoice.js | 6 +- .../doctype/sales_invoice/sales_invoice.js | 9 +- .../doctype/purchase_order/purchase_order.js | 6 +- erpnext/public/js/controllers/transaction.js | 52 +++- 9 files changed, 509 insertions(+), 67 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 1e2e2acd79a..1c0d64f065b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -31,6 +31,7 @@ "determine_address_tax_category_from", "column_break_19", "add_taxes_from_item_tax_template", + "book_tax_discount_loss", "print_settings", "show_inclusive_tax_in_print", "column_break_12", @@ -347,6 +348,13 @@ "fieldname": "allow_multi_currency_invoices_against_single_party_account", "fieldtype": "Check", "label": "Allow multi-currency invoices against single party account " + }, + { + "default": "0", + "description": "Split Early Payment Discount Loss into Income and Tax Loss", + "fieldname": "book_tax_discount_loss", + "fieldtype": "Check", + "label": "Book Tax Loss on Early Payment Discount" } ], "icon": "icon-cog", @@ -354,7 +362,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-11-27 21:49:52.538655", + "modified": "2023-03-28 09:50:20.375233", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 91374ae217b..5a56a6b0046 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -245,8 +245,6 @@ frappe.ui.form.on('Payment Entry', { frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"], party_account_currency, "references"); - frm.set_currency_labels(["amount"], company_currency, "deductions"); - cur_frm.set_df_property("source_exchange_rate", "description", ("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency)); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a585924d20f..58ed7d1822c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -416,7 +416,7 @@ class PaymentEntry(AccountsController): for ref in self.get("references"): if ref.payment_term and ref.reference_name: - key = (ref.payment_term, ref.reference_name) + key = (ref.payment_term, ref.reference_name, ref.reference_doctype) invoice_payment_amount_map.setdefault(key, 0.0) invoice_payment_amount_map[key] += ref.allocated_amount @@ -424,20 +424,37 @@ class PaymentEntry(AccountsController): payment_schedule = frappe.get_all( "Payment Schedule", filters={"parent": ref.reference_name}, - fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"], + fields=[ + "paid_amount", + "payment_amount", + "payment_term", + "discount", + "outstanding", + "discount_type", + ], ) for term in payment_schedule: - invoice_key = (term.payment_term, ref.reference_name) + invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype) invoice_paid_amount_map.setdefault(invoice_key, {}) invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding - invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * ( - term.discount / 100 - ) + if not (term.discount_type and term.discount): + continue + + if term.discount_type == "Percentage": + invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * ( + term.discount / 100 + ) + else: + invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1): if not invoice_paid_amount_map.get(key): frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1])) + allocated_amount = self.get_allocated_amount_in_transaction_currency( + allocated_amount, key[2], key[1] + ) + outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding")) discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt")) @@ -472,6 +489,33 @@ class PaymentEntry(AccountsController): (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]), ) + def get_allocated_amount_in_transaction_currency( + self, allocated_amount, reference_doctype, reference_docname + ): + """ + Payment Entry could be in base currency while reference's payment schedule + is always in transaction currency. + E.g. + * SI with base=INR and currency=USD + * SI with payment schedule in USD + * PE in INR (accounting done in base currency) + """ + ref_currency, ref_exchange_rate = frappe.db.get_value( + reference_doctype, reference_docname, ["currency", "conversion_rate"] + ) + is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency + # PE in different currency + reference_is_multi_currency = self.paid_from_account_currency != ref_currency + + if not (is_single_currency and reference_is_multi_currency): + return allocated_amount + + allocated_amount = flt( + allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount") + ) + + return allocated_amount + def set_status(self): if self.docstatus == 2: self.status = "Cancelled" @@ -1642,7 +1686,14 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre @frappe.whitelist() def get_payment_entry( - dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None + dt, + dn, + party_amount=None, + bank_account=None, + bank_amount=None, + party_type=None, + payment_type=None, + reference_date=None, ): reference_doc = None doc = frappe.get_doc(dt, dn) @@ -1669,8 +1720,9 @@ def get_payment_entry( dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc ) - paid_amount, received_amount, discount_amount = apply_early_payment_discount( - paid_amount, received_amount, doc + reference_date = getdate(reference_date) + paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount( + paid_amount, received_amount, doc, party_account_currency, reference_date ) pe = frappe.new_doc("Payment Entry") @@ -1678,6 +1730,7 @@ def get_payment_entry( pe.company = doc.company pe.cost_center = doc.get("cost_center") pe.posting_date = nowdate() + pe.reference_date = reference_date pe.mode_of_payment = doc.get("mode_of_payment") pe.party_type = party_type pe.party = doc.get(scrub(party_type)) @@ -1718,7 +1771,7 @@ def get_payment_entry( ): for reference in get_reference_as_per_payment_terms( - doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount + doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency ): pe.append("references", reference) else: @@ -1769,16 +1822,17 @@ def get_payment_entry( if party_account and bank: pe.set_exchange_rate(ref_doc=reference_doc) pe.set_amounts() + if discount_amount: - pe.set_gain_or_loss( - account_details={ - "account": frappe.get_cached_value("Company", pe.company, "default_discount_account"), - "cost_center": pe.cost_center - or frappe.get_cached_value("Company", pe.company, "cost_center"), - "amount": discount_amount * (-1 if payment_type == "Pay" else 1), - } + base_total_discount_loss = 0 + if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"): + base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts) + + set_pending_discount_loss( + pe, doc, discount_amount, base_total_discount_loss, party_account_currency ) - pe.set_difference_amount() + + pe.set_difference_amount() return pe @@ -1889,20 +1943,28 @@ def set_paid_amount_and_received_amount( return paid_amount, received_amount -def apply_early_payment_discount(paid_amount, received_amount, doc): +def apply_early_payment_discount( + paid_amount, received_amount, doc, party_account_currency, reference_date +): total_discount = 0 + valid_discounts = [] eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"] has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule + is_multi_currency = party_account_currency != doc.company_currency if doc.doctype in eligible_for_payments and has_payment_schedule: for term in doc.payment_schedule: - if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date: + if not term.discounted_amount and term.discount and reference_date <= term.discount_date: + if term.discount_type == "Percentage": - discount_amount = flt(doc.get("grand_total")) * (term.discount / 100) + grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total") + discount_amount = flt(grand_total) * (term.discount / 100) else: discount_amount = term.discount - discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1) + # if accounting is done in the same currency, paid_amount = received_amount + conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1 + discount_amount_in_foreign_currency = discount_amount * conversion_rate if doc.doctype == "Sales Invoice": paid_amount -= discount_amount @@ -1911,23 +1973,151 @@ def apply_early_payment_discount(paid_amount, received_amount, doc): received_amount -= discount_amount paid_amount -= discount_amount_in_foreign_currency + valid_discounts.append({"type": term.discount_type, "discount": term.discount}) total_discount += discount_amount if total_discount: - money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency")) + currency = doc.get("currency") if is_multi_currency else doc.company_currency + money = frappe.utils.fmt_money(total_discount, currency=currency) frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1) - return paid_amount, received_amount, total_discount + return paid_amount, received_amount, total_discount, valid_discounts + + +def set_pending_discount_loss( + pe, doc, discount_amount, base_total_discount_loss, party_account_currency +): + # If multi-currency, get base discount amount to adjust with base currency deductions/losses + if party_account_currency != doc.company_currency: + discount_amount = discount_amount * doc.get("conversion_rate", 1) + + # Avoid considering miniscule losses + discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total")) + + # Set base discount amount (discount loss/pending rounding loss) in deductions + if discount_amount > 0.0: + positive_negative = -1 if pe.payment_type == "Pay" else 1 + + # If tax loss booking is enabled, pending loss will be rounding loss. + # Otherwise it will be the total discount loss. + book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss") + account_type = "round_off_account" if book_tax_loss else "default_discount_account" + + pe.set_gain_or_loss( + account_details={ + "account": frappe.get_cached_value("Company", pe.company, account_type), + "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), + "amount": discount_amount * positive_negative, + } + ) + + +def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float: + """Split early payment discount into Income Loss & Tax Loss.""" + total_discount_percent = get_total_discount_percent(doc, valid_discounts) + + if not total_discount_percent: + return 0.0 + + base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent) + base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent) + + # Round off total loss rather than individual losses to reduce rounding error + return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total")) + + +def get_total_discount_percent(doc, valid_discounts) -> float: + """Get total percentage and amount discount applied as a percentage.""" + total_discount_percent = ( + sum( + discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage" + ) + or 0.0 + ) + + # Operate in percentages only as it makes the income & tax split easier + total_discount_amount = ( + sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount") + or 0.0 + ) + + if total_discount_amount: + discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100 + total_discount_percent += discount_percentage + return total_discount_percent + + return total_discount_percent + + +def add_income_discount_loss(pe, doc, total_discount_percent) -> float: + """Add loss on income discount in base currency.""" + precision = doc.precision("total") + base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100) + + pe.append( + "deductions", + { + "account": frappe.get_cached_value("Company", pe.company, "default_discount_account"), + "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), + "amount": flt(base_loss_on_income, precision), + }, + ) + + return base_loss_on_income # Return loss without rounding + + +def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float: + """Add loss on tax discount in base currency.""" + tax_discount_loss = {} + base_total_tax_loss = 0 + precision = doc.precision("tax_amount_after_discount_amount", "taxes") + + # The same account head could be used more than once + for tax in doc.get("taxes", []): + base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * ( + total_discount_percentage / 100 + ) + + account = tax.get("account_head") + if not tax_discount_loss.get(account): + tax_discount_loss[account] = base_tax_loss + else: + tax_discount_loss[account] += base_tax_loss + + for account, loss in tax_discount_loss.items(): + base_total_tax_loss += loss + if loss == 0.0: + continue + + pe.append( + "deductions", + { + "account": account, + "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), + "amount": flt(loss, precision), + }, + ) + + return base_total_tax_loss # Return loss without rounding def get_reference_as_per_payment_terms( - payment_schedule, dt, dn, doc, grand_total, outstanding_amount + payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency ): references = [] + is_multi_currency_acc = (doc.currency != doc.company_currency) and ( + party_account_currency != doc.company_currency + ) + for payment_term in payment_schedule: payment_term_outstanding = flt( payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount") ) + if not is_multi_currency_acc: + # If accounting is done in company currency for multi-currency transaction + payment_term_outstanding = flt( + payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount") + ) if payment_term_outstanding: references.append( diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 123b5dfd512..67049c47ad0 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -5,7 +5,7 @@ import unittest import frappe from frappe import qb -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import flt, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import ( @@ -256,10 +256,25 @@ class TestPaymentEntry(FrappeTestCase): }, ) si.save() - si.submit() + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1) + pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + + self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount") + self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 236.0) + self.assertEqual(pe_with_tax_loss.paid_amount, 212.4) + self.assertEqual(pe_with_tax_loss.deductions[0].amount, 20.0) # Loss on Income + self.assertEqual(pe_with_tax_loss.deductions[1].amount, 3.6) # Loss on Tax + self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC") + + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0) pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + + self.assertEqual(pe.references[0].allocated_amount, 236.0) + self.assertEqual(pe.paid_amount, 212.4) + self.assertEqual(pe.deductions[0].amount, 23.6) + pe.submit() si.load_from_db() @@ -269,6 +284,190 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(si.payment_schedule[0].outstanding, 0) self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6) + def test_payment_entry_against_payment_terms_with_discount_amount(self): + si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + + si.payment_terms_template = "Test Discount Amount Template" + create_payment_terms_template_with_discount( + name="30 Credit Days with Rs.50 Discount", + discount_type="Amount", + discount=50, + template_name="Test Discount Amount Template", + ) + frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC") + + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 18, + }, + ) + si.save() + si.submit() + + # Set reference date past discount cut off date + pe_1 = get_payment_entry( + "Sales Invoice", + si.name, + bank_account="_Test Cash - _TC", + reference_date=frappe.utils.add_days(si.posting_date, 2), + ) + self.assertEqual(pe_1.paid_amount, 236.0) # discount not applied + + # Test if tax loss is booked on enabling configuration + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1) + pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + self.assertEqual(pe_with_tax_loss.deductions[0].amount, 42.37) # Loss on Income + self.assertEqual(pe_with_tax_loss.deductions[1].amount, 7.63) # Loss on Tax + self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC") + + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0) + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + self.assertEqual(pe.references[0].allocated_amount, 236.0) + self.assertEqual(pe.paid_amount, 186) + self.assertEqual(pe.deductions[0].amount, 50.0) + + pe.submit() + si.load_from_db() + + self.assertEqual(si.payment_schedule[0].payment_amount, 236.0) + self.assertEqual(si.payment_schedule[0].paid_amount, 186) + self.assertEqual(si.payment_schedule[0].outstanding, 0) + self.assertEqual(si.payment_schedule[0].discounted_amount, 50) + + @change_settings( + "Accounts Settings", + { + "allow_multi_currency_invoices_against_single_party_account": 1, + "book_tax_discount_loss": 1, + }, + ) + def test_payment_entry_multicurrency_si_with_base_currency_accounting_early_payment_discount( + self, + ): + """ + 1. Multi-currency SI with single currency accounting (company currency) + 2. PE with early payment discount + 3. Test if Paid Amount is calculated in company currency + 4. Test if deductions are calculated in company currency + + SI is in USD to document agreed amounts that are in USD, but the accounting is in base currency. + """ + si = create_sales_invoice( + customer="_Test Customer", + currency="USD", + conversion_rate=50, + do_not_save=1, + ) + create_payment_terms_template_with_discount() + si.payment_terms_template = "Test Discount Template" + + frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC") + si.save() + si.submit() + + pe = get_payment_entry( + "Sales Invoice", + si.name, + bank_account="_Test Bank - _TC", + ) + pe.reference_no = si.name + pe.reference_date = nowdate() + + # Early payment discount loss on income + self.assertEqual(pe.paid_amount, 4500.0) # Amount in company currency + self.assertEqual(pe.received_amount, 4500.0) + self.assertEqual(pe.deductions[0].amount, 500.0) + self.assertEqual(pe.deductions[0].account, "Write Off - _TC") + self.assertEqual(pe.difference_amount, 0.0) + + pe.insert() + pe.submit() + + expected_gle = dict( + (d[0], d) + for d in [ + ["Debtors - _TC", 0, 5000, si.name], + ["_Test Bank - _TC", 4500, 0, None], + ["Write Off - _TC", 500.0, 0, None], + ] + ) + + self.validate_gl_entries(pe.name, expected_gle) + + outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) + self.assertEqual(outstanding_amount, 0) + + def test_payment_entry_multicurrency_accounting_si_with_early_payment_discount(self): + """ + 1. Multi-currency SI with multi-currency accounting + 2. PE with early payment discount and also exchange loss + 3. Test if Paid Amount is calculated in transaction currency + 4. Test if deductions are calculated in base/company currency + 5. Test if exchange loss is reflected in difference + """ + si = create_sales_invoice( + customer="_Test Customer USD", + debit_to="_Test Receivable USD - _TC", + currency="USD", + conversion_rate=50, + do_not_save=1, + ) + create_payment_terms_template_with_discount() + si.payment_terms_template = "Test Discount Template" + + frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC") + si.save() + si.submit() + + pe = get_payment_entry( + "Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700 + ) + pe.reference_no = si.name + pe.reference_date = nowdate() + + # Early payment discount loss on income + self.assertEqual(pe.paid_amount, 90.0) + self.assertEqual(pe.received_amount, 4200.0) # 5000 - 500 (discount) - 300 (exchange loss) + self.assertEqual(pe.deductions[0].amount, 500.0) + self.assertEqual(pe.deductions[0].account, "Write Off - _TC") + + # Exchange loss + self.assertEqual(pe.difference_amount, 300.0) + + pe.append( + "deductions", + { + "account": "_Test Exchange Gain/Loss - _TC", + "cost_center": "_Test Cost Center - _TC", + "amount": 300.0, + }, + ) + + pe.insert() + pe.submit() + + self.assertEqual(pe.difference_amount, 0.0) + + expected_gle = dict( + (d[0], d) + for d in [ + ["_Test Receivable USD - _TC", 0, 5000, si.name], + ["_Test Bank - _TC", 4200, 0, None], + ["Write Off - _TC", 500.0, 0, None], + ["_Test Exchange Gain/Loss - _TC", 300.0, 0, None], + ] + ) + + self.validate_gl_entries(pe.name, expected_gle) + + outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) + self.assertEqual(outstanding_amount, 0) + def test_payment_against_purchase_invoice_to_check_status(self): pi = make_purchase_invoice( supplier="_Test Supplier USD", @@ -839,24 +1038,27 @@ def create_payment_terms_template(): ).insert() -def create_payment_terms_template_with_discount(): +def create_payment_terms_template_with_discount( + name=None, discount_type=None, discount=None, template_name=None +): + create_payment_term(name or "30 Credit Days with 10% Discount") + template_name = template_name or "Test Discount Template" - create_payment_term("30 Credit Days with 10% Discount") - - if not frappe.db.exists("Payment Terms Template", "Test Discount Template"): - payment_term_template = frappe.get_doc( + if not frappe.db.exists("Payment Terms Template", template_name): + frappe.get_doc( { "doctype": "Payment Terms Template", - "template_name": "Test Discount Template", + "template_name": template_name, "allocate_payment_based_on_payment_terms": 1, "terms": [ { "doctype": "Payment Terms Template Detail", - "payment_term": "30 Credit Days with 10% Discount", + "payment_term": name or "30 Credit Days with 10% Discount", "invoice_portion": 100, "credit_days_based_on": "Day(s) after invoice date", "credit_days": 2, - "discount": 10, + "discount_type": discount_type or "Percentage", + "discount": discount or 10, "discount_validity_based_on": "Day(s) after invoice date", "discount_validity": 1, } diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index 61a1462dd7a..1c31829f0ea 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -3,6 +3,7 @@ "creation": "2016-06-15 15:56:30.815503", "doctype": "DocType", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "account", "cost_center", @@ -17,9 +18,7 @@ "in_list_view": 1, "label": "Account", "options": "Account", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "cost_center", @@ -28,37 +27,30 @@ "label": "Cost Center", "options": "Cost Center", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Amount", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "label": "Amount (Company Currency)", + "options": "Company:company:default_currency", + "reqd": 1 }, { "fieldname": "column_break_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "description", "fieldtype": "Small Text", - "label": "Description", - "show_days": 1, - "show_seconds": 1 + "label": "Description" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-12 20:38:08.110674", + "modified": "2023-03-06 07:11:57.739619", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Deduction", @@ -66,5 +58,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e2b4a1ad5be..5c9168bf9c5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -82,7 +82,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. if(doc.docstatus == 1 && doc.outstanding_amount != 0 && !(doc.is_return && doc.return_against) && !doc.on_hold) { - this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create')); + this.frm.add_custom_button( + __('Payment'), + () => this.make_payment_entry(), + __('Create') + ); cur_frm.page.set_inner_btn_group_as_primary(__('Create')); } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 47e3f9b9354..56e412b297c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -93,9 +93,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e if (doc.docstatus == 1 && doc.outstanding_amount!=0 && !(cint(doc.is_return) && doc.return_against)) { - cur_frm.add_custom_button(__('Payment'), - this.make_payment_entry, __('Create')); - cur_frm.page.set_inner_btn_group_as_primary(__('Create')); + this.frm.add_custom_button( + __('Payment'), + () => this.make_payment_entry(), + __('Create') + ); + this.frm.page.set_inner_btn_group_as_primary(__('Create')); } if(doc.docstatus==1 && !doc.is_return) { diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 47089f7d850..c6c9f1f98a3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -236,7 +236,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e this.make_purchase_invoice, __('Create')); if(flt(doc.per_billed) < 100 && doc.status != "Delivered") { - cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create')); + this.frm.add_custom_button( + __('Payment'), + () => this.make_payment_entry(), + __('Create') + ); } if(flt(doc.per_billed) < 100) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f7620db2f1e..07d1955bfaf 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1897,20 +1897,60 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } make_payment_entry() { + let via_journal_entry = this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry; + if(this.has_discount_in_schedule() && !via_journal_entry) { + // If early payment discount is applied, ask user for reference date + this.prompt_user_for_reference_date(); + } else { + this.make_mapped_payment_entry(); + } + } + + make_mapped_payment_entry(args) { + var me = this; + args = args || { "dt": this.frm.doc.doctype, "dn": this.frm.doc.name }; return frappe.call({ - method: cur_frm.cscript.get_method_for_payment(), - args: { - "dt": cur_frm.doc.doctype, - "dn": cur_frm.doc.name - }, + method: me.get_method_for_payment(), + args: args, callback: function(r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - // cur_frm.refresh_fields() } }); } + prompt_user_for_reference_date(){ + var me = this; + frappe.prompt({ + label: __("Cheque/Reference Date"), + fieldname: "reference_date", + fieldtype: "Date", + reqd: 1, + }, (values) => { + let args = { + "dt": me.frm.doc.doctype, + "dn": me.frm.doc.name, + "reference_date": values.reference_date + } + me.make_mapped_payment_entry(args); + }, + __("Reference Date for Early Payment Discount"), + __("Continue") + ); + } + + has_discount_in_schedule() { + let is_eligible = in_list( + ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"], + this.frm.doctype + ); + let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length; + if(!is_eligible || !has_payment_schedule) return false; + + let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date); + return has_discount; + } + make_quality_inspection() { let data = []; const fields = [ From 6b866e24f63c349181811b533dd712e83516182b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 14:55:32 +0530 Subject: [PATCH 056/176] fix(ui): recalculate difference amount on allocation change (#34694) fix: recalculate difference amount on allocation change (cherry picked from commit 32a4ca6b6c65939e9d2db8b281bbc62cc886b883) Co-authored-by: ruthra kumar --- .../payment_reconciliation.js | 28 +++++++++++++++++++ .../payment_reconciliation.py | 9 ++++++ 2 files changed, 37 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index d986f320669..caffac5354f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -272,4 +272,32 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } }; +frappe.ui.form.on('Payment Reconciliation Allocation', { + allocated_amount: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + // filter invoice + let invoice = frm.doc.invoices.filter((x) => (x.invoice_number == row.invoice_number)); + // filter payment + let payment = frm.doc.payments.filter((x) => (x.reference_name == row.reference_name)); + + frm.call({ + doc: frm.doc, + method: 'calculate_difference_on_allocation_change', + args: { + payment_entry: payment, + invoice: invoice, + allocated_amount: row.allocated_amount + }, + callback: (r) => { + if (r.message) { + row.difference_amount = r.message; + frm.refresh(); + } + } + }); + } +}); + + + extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm})); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index c9e3998ac8a..d8082d058f3 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -233,6 +233,15 @@ class PaymentReconciliation(Document): return difference_amount + @frappe.whitelist() + def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount): + invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry) + invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number")) + new_difference_amount = self.get_difference_amount( + payment_entry[0], invoice[0], allocated_amount + ) + return new_difference_amount + @frappe.whitelist() def allocate_entries(self, args): self.validate_entries() From 2c54e763e48f570d62f0fc7a2bac5f7f3593242c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 3 Apr 2023 14:06:07 +0530 Subject: [PATCH 057/176] fix: consider qty field precision (cherry picked from commit 6ec7590c21d3f97044ed13799ea9c85beeb4cf23) --- erpnext/utilities/transaction_base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index fc2054533d9..7eba35dedd9 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -209,12 +209,15 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None): for f in qty_fields: qty = d.get(f) if qty: - if abs(cint(qty) - flt(qty)) > 0.0000001: + if abs(cint(qty) - flt(qty, d.precision(f))) > 0.0000001: frappe.throw( _( "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}." ).format( - qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field)) + flt(qty, d.precision(f)), + d.idx, + frappe.bold(_("Must be Whole Number")), + frappe.bold(d.get(uom_field)), ), UOMMustBeIntegerError, ) From 551190af305f1dcd6862bbaf298729c79e2a4183 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Apr 2023 14:47:58 +0530 Subject: [PATCH 058/176] fix: bom update log not working for large batch size (cherry picked from commit d56070301cedc3ffbafa8e7c556e775253fddd77) --- .../manufacturing/doctype/bom_update_log/bom_update_log.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py index 51f7b24e745..7477f9528ec 100644 --- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py @@ -164,7 +164,7 @@ def queue_bom_cost_jobs( while current_boms_list: batch_no += 1 - batch_size = 20_000 + batch_size = 7_000 boms_to_process = current_boms_list[:batch_size] # slice out batch of 20k BOMs # update list to exclude 20K (queued) BOMs @@ -212,7 +212,7 @@ def resume_bom_cost_update_jobs(): ["name", "boms_updated", "status"], ) incomplete_level = any(row.get("status") == "Pending" for row in bom_batches) - if not bom_batches or not incomplete_level: + if not bom_batches or incomplete_level: continue # Prep parent BOMs & updated processed BOMs for next level @@ -252,9 +252,6 @@ def get_processed_current_boms( current_boms = [] for row in bom_batches: - if not row.boms_updated: - continue - boms_updated = json.loads(row.boms_updated) current_boms.extend(boms_updated) boms_updated_dict = {bom: True for bom in boms_updated} From da354362bec16d9de42f48b18a0a4b1857070c85 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Apr 2023 14:47:58 +0530 Subject: [PATCH 059/176] fix: bom update log not working for large batch size (cherry picked from commit d56070301cedc3ffbafa8e7c556e775253fddd77) (cherry picked from commit 551190af305f1dcd6862bbaf298729c79e2a4183) --- .../manufacturing/doctype/bom_update_log/bom_update_log.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py index 51f7b24e745..7477f9528ec 100644 --- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py @@ -164,7 +164,7 @@ def queue_bom_cost_jobs( while current_boms_list: batch_no += 1 - batch_size = 20_000 + batch_size = 7_000 boms_to_process = current_boms_list[:batch_size] # slice out batch of 20k BOMs # update list to exclude 20K (queued) BOMs @@ -212,7 +212,7 @@ def resume_bom_cost_update_jobs(): ["name", "boms_updated", "status"], ) incomplete_level = any(row.get("status") == "Pending" for row in bom_batches) - if not bom_batches or not incomplete_level: + if not bom_batches or incomplete_level: continue # Prep parent BOMs & updated processed BOMs for next level @@ -252,9 +252,6 @@ def get_processed_current_boms( current_boms = [] for row in bom_batches: - if not row.boms_updated: - continue - boms_updated = json.loads(row.boms_updated) current_boms.extend(boms_updated) boms_updated_dict = {bom: True for bom in boms_updated} From dfadfdc32c5687ee62bbc44e9a6931e14c40a115 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 3 Apr 2023 17:50:43 +0000 Subject: [PATCH 060/176] chore(release): Bumped to Version 14.20.1 ## [14.20.1](https://github.com/frappe/erpnext/compare/v14.20.0...v14.20.1) (2023-04-03) ### Bug Fixes * bom update log not working for large batch size ([da35436](https://github.com/frappe/erpnext/commit/da354362bec16d9de42f48b18a0a4b1857070c85)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 8a50a5d369d..643815e38fc 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.20.0" +__version__ = "14.20.1" def get_default_company(user=None): From 3896d41e95a2054b4ec76c5f5b2819b3f28ef98e Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 4 Apr 2023 17:49:16 +0530 Subject: [PATCH 061/176] fix: don't include cancelled JVs in assdeprledger report --- .../asset_depreciation_ledger/asset_depreciation_ledger.py | 1 + 1 file changed, 1 insertion(+) 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 57d80492ae0..f21c94b4940 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py @@ -25,6 +25,7 @@ def get_data(filters): ["posting_date", "<=", filters.get("to_date")], ["against_voucher_type", "=", "Asset"], ["account", "in", depreciation_accounts], + ["is_cancelled", "=", 0], ] if filters.get("asset"): From 88c8c3680573232c23429a8123619f9044b7d091 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 5 Apr 2023 11:45:45 +0530 Subject: [PATCH 062/176] fix: asset monthly WDV and DD schedule [v14] (#34644) * fix: monthly wdv and dd schedule * chore: handle case without pro rata * chore: fix DD rate and prev depr amount in case of disposal * chore: minor fix for schedules with just 2 rows * chore: minor bug * refactor: get_depreciation_amount * refactor: another one for get_depreciation_amount --- erpnext/assets/doctype/asset/asset.py | 188 +++++++++++++++++---- erpnext/assets/doctype/asset/test_asset.py | 12 +- 2 files changed, 157 insertions(+), 43 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 47b5f75e668..fe1fd98aa09 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -294,17 +294,42 @@ class Asset(AccountsController): if has_pro_rata: number_of_pending_depreciations += 1 + has_wdv_or_dd_non_yearly_pro_rata = False + if ( + finance_book.depreciation_method in ("Written Down Value", "Double Declining Balance") + and cint(finance_book.frequency_of_depreciation) != 12 + ): + has_wdv_or_dd_non_yearly_pro_rata = self.check_is_pro_rata( + finance_book, wdv_or_dd_non_yearly=True + ) + skip_row = False should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date) + depreciation_amount = 0 + for n in range(start[finance_book.idx - 1], number_of_pending_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) + if n > 0 and len(self.get("schedules")) > n - 1: + prev_depreciation_amount = self.get("schedules")[n - 1].depreciation_amount + else: + prev_depreciation_amount = 0 - if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: + depreciation_amount = get_depreciation_amount( + self, + value_after_depreciation, + finance_book, + n, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + ) + + if not has_pro_rata or ( + n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2 + ): schedule_date = add_months( finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation) ) @@ -320,7 +345,10 @@ class Asset(AccountsController): if date_of_disposal: from_date = self.get_from_date(finance_book.finance_book) depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, from_date, date_of_disposal + finance_book, + depreciation_amount, + from_date, + date_of_disposal, ) if depreciation_amount > 0: @@ -335,12 +363,20 @@ class Asset(AccountsController): break # For first row - if has_pro_rata and not self.opening_accumulated_depreciation and n == 0: + if ( + (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) + and not self.opening_accumulated_depreciation + and n == 0 + ): from_date = add_days( self.available_for_use_date, -1 ) # needed to calc depr amount for available_for_use_date too depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date + finance_book, + depreciation_amount, + from_date, + finance_book.depreciation_start_date, + has_wdv_or_dd_non_yearly_pro_rata, ) # For first depr schedule date will be the start date @@ -359,7 +395,11 @@ class Asset(AccountsController): depreciation_amount_without_pro_rata = depreciation_amount depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, schedule_date, self.to_date + finance_book, + depreciation_amount, + schedule_date, + self.to_date, + has_wdv_or_dd_non_yearly_pro_rata, ) depreciation_amount = self.get_adjusted_depreciation_amount( @@ -479,28 +519,37 @@ class Asset(AccountsController): return add_days(self.available_for_use_date, -1) # if it returns True, depreciation_amount will not be equal for the first and last rows - def check_is_pro_rata(self, row): + def check_is_pro_rata(self, 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 # from_date = 01/01/2022 - from_date = self.get_modified_available_for_use_date(row) + from_date = self.get_modified_available_for_use_date(row, wdv_or_dd_non_yearly) days = date_diff(row.depreciation_start_date, from_date) + 1 - # if frequency_of_depreciation is 12 months, total_days = 365 - total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) + 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) if days < total_days: has_pro_rata = True return has_pro_rata - def get_modified_available_for_use_date(self, row): - return add_months( - self.available_for_use_date, - (self.number_of_depreciations_booked * row.frequency_of_depreciation), - ) + def get_modified_available_for_use_date(self, row, wdv_or_dd_non_yearly=False): + if wdv_or_dd_non_yearly: + return add_months( + self.available_for_use_date, + (self.number_of_depreciations_booked * 12), + ) + else: + return add_months( + self.available_for_use_date, + (self.number_of_depreciations_booked * row.frequency_of_depreciation), + ) def validate_asset_finance_books(self, row): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): @@ -903,7 +952,12 @@ class Asset(AccountsController): float_precision = cint(frappe.db.get_default("float_precision")) or 2 if args.get("depreciation_method") == "Double Declining Balance": - return 200.0 / args.get("total_number_of_depreciations") + return 200.0 / ( + ( + flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation")) + ) + / 12 + ) if args.get("depreciation_method") == "Written Down Value": if ( @@ -920,14 +974,29 @@ class Asset(AccountsController): else: value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount) - depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2)) + depreciation_rate = math.pow( + value, + 1.0 + / ( + ( + flt(args.get("total_number_of_depreciations"), 2) + * flt(args.get("frequency_of_depreciation")) + ) + / 12 + ), + ) return flt((100 * (1 - depreciation_rate)), float_precision) - def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date): + def get_pro_rata_amt( + self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False + ): days = date_diff(to_date, from_date) months = month_diff(to_date, from_date) - total_days = get_total_days(to_date, row.frequency_of_depreciation) + if has_wdv_or_dd_non_yearly_pro_rata: + total_days = get_total_days(to_date, 12) + else: + total_days = get_total_days(to_date, row.frequency_of_depreciation) return (depreciation_amount * flt(days)) / flt(total_days), days, months @@ -1184,27 +1253,72 @@ def get_total_days(date, frequency): @erpnext.allow_regional -def get_depreciation_amount(asset, depreciable_value, row): +def get_depreciation_amount( + asset, + depreciable_value, + row, + schedule_idx=0, + prev_depreciation_amount=0, + has_wdv_or_dd_non_yearly_pro_rata=False, +): if row.depreciation_method in ("Straight Line", "Manual"): - # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value - if asset.flags.increase_in_asset_life: - depreciation_amount = ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / (date_diff(asset.to_date, asset.available_for_use_date) / 365) - # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value - elif asset.flags.increase_in_asset_value_due_to_repair: - depreciation_amount = ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations) - # if the Depreciation Schedule is being prepared for the first time - else: - depreciation_amount = ( - flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations) + return get_straight_line_or_manual_depr_amount(asset, row) else: - depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) + return get_wdv_or_dd_depr_amount( + depreciable_value, + row.rate_of_depreciation, + row.frequency_of_depreciation, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + ) - return depreciation_amount + +def get_straight_line_or_manual_depr_amount(asset, row): + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value + if asset.flags.increase_in_asset_life: + return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( + date_diff(asset.to_date, asset.available_for_use_date) / 365 + ) + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value + elif asset.flags.increase_in_asset_value_due_to_repair: + return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt( + row.total_number_of_depreciations + ) + # if the Depreciation Schedule is being prepared for the first time + else: + return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt( + row.total_number_of_depreciations + ) + + +def get_wdv_or_dd_depr_amount( + depreciable_value, + rate_of_depreciation, + frequency_of_depreciation, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, +): + if cint(frequency_of_depreciation) == 12: + return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) + else: + if has_wdv_or_dd_non_yearly_pro_rata: + if schedule_idx == 0: + return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) + elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1: + return ( + flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) + ) + else: + return prev_depreciation_amount + else: + if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0: + return ( + flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) + ) + else: + return prev_depreciation_amount @frappe.whitelist() diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 71f578c6703..7eaa4bf997a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -818,12 +818,12 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2022-02-28", 647.25, 647.25], - ["2022-03-31", 1210.71, 1857.96], - ["2022-04-30", 1053.99, 2911.95], - ["2022-05-31", 917.55, 3829.5], - ["2022-06-30", 798.77, 4628.27], - ["2022-07-15", 371.73, 5000.0], + ["2022-02-28", 310.89, 310.89], + ["2022-03-31", 654.45, 965.34], + ["2022-04-30", 654.45, 1619.79], + ["2022-05-31", 654.45, 2274.24], + ["2022-06-30", 654.45, 2928.69], + ["2022-07-15", 2071.31, 5000.0], ] schedules = [ From 3c0cc024aa6d172da5da622207372a9bb8bba49c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 12:33:55 +0530 Subject: [PATCH 063/176] fix!: require sender and message for contact us page (#34707) fix!: require sender and message for contact us page (#34707) * fix: require sender and message for contact us page * refactor: dont override frappe.send_message from client side used override_whitelisted_method hook for the same (cherry picked from commit f193393f5713fd274ac419ecc786057415266d38) Co-authored-by: Ritwik Puri --- erpnext/hooks.py | 4 ++++ erpnext/public/js/website_utils.js | 15 --------------- erpnext/templates/utils.py | 9 +++------ 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f1ee370e97e..c4032596f47 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -30,6 +30,10 @@ doctype_js = { override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"} +override_whitelisted_methods = { + "frappe.www.contact.send_message": "erpnext.templates.utils.send_message" +} + welcome_email = "erpnext.setup.utils.welcome_email" # setup wizard diff --git a/erpnext/public/js/website_utils.js b/erpnext/public/js/website_utils.js index b5416065d79..2bb5255eebc 100644 --- a/erpnext/public/js/website_utils.js +++ b/erpnext/public/js/website_utils.js @@ -3,18 +3,6 @@ if(!window.erpnext) window.erpnext = {}; -// Add / update a new Lead / Communication -// subject, sender, description -frappe.send_message = function(opts, btn) { - return frappe.call({ - type: "POST", - method: "erpnext.templates.utils.send_message", - btn: btn, - args: opts, - callback: opts.callback - }); -}; - erpnext.subscribe_to_newsletter = function(opts, btn) { return frappe.call({ type: "POST", @@ -24,6 +12,3 @@ erpnext.subscribe_to_newsletter = function(opts, btn) { callback: opts.callback }); } - -// for backward compatibility -erpnext.send_message = frappe.send_message; diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py index 48b44802a8f..57750a56f6f 100644 --- a/erpnext/templates/utils.py +++ b/erpnext/templates/utils.py @@ -6,13 +6,12 @@ import frappe @frappe.whitelist(allow_guest=True) -def send_message(subject="Website Query", message="", sender="", status="Open"): +def send_message(sender, message, subject="Website Query"): from frappe.www.contact import send_message as website_send_message + website_send_message(sender, message, subject) + lead = customer = None - - website_send_message(subject, message, sender) - customer = frappe.db.sql( """select distinct dl.link_name from `tabDynamic Link` dl left join `tabContact` c on dl.parent=c.name where dl.link_doctype='Customer' @@ -58,5 +57,3 @@ def send_message(subject="Website Query", message="", sender="", status="Open"): } ) comm.insert(ignore_permissions=True) - - return "okay" From 05d24e36650ef07d46668248d7109732926e601b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:00:40 +0530 Subject: [PATCH 064/176] feat: Auto allocate advance payments only against orders (#34727) * feat: Auto allocate advance payments only against orders (#34727) feat: Auto allocate advance payments only againt orders (cherry picked from commit fd3fb64aa344b8a220f217ac6d2ffd09dbcdf0dc) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.json * chore: Resolve conflicts --------- Co-authored-by: Deepesh Garg --- .../doctype/purchase_invoice/purchase_invoice.json | 12 ++++++++++-- .../doctype/sales_invoice/sales_invoice.json | 10 +++++++++- erpnext/controllers/accounts_controller.py | 4 +++- erpnext/public/js/controllers/accounts.js | 8 ++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 629a0ffb582..1c8c4b3193b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -118,6 +118,7 @@ "paid_amount", "advances_section", "allocate_advances_automatically", + "only_include_allocated_payments", "get_advances", "advances", "advance_tax", @@ -1550,17 +1551,24 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "default": "0", + "depends_on": "allocate_advances_automatically", + "description": "Advance payments allocated against orders will only be fetched", + "fieldname": "only_include_allocated_payments", + "fieldtype": "Check", + "label": "Only Include Allocated Payments" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-01-28 19:18:56.586321", + "modified": "2023-04-03 22:57:14.074982", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", - "name_case": "Title Case", "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 9a5b42be4bb..9a0d71a3850 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -120,6 +120,7 @@ "account_for_change_amount", "advances_section", "allocate_advances_automatically", + "only_include_allocated_payments", "get_advances", "advances", "write_off_section", @@ -2126,6 +2127,13 @@ "label": "Repost Required", "no_copy": 1, "read_only": 1 + }, + { + "depends_on": "allocate_advances_automatically", + "description": "Advance payments allocated against orders will only be fetched", + "fieldname": "only_include_allocated_payments", + "fieldtype": "Check", + "label": "Only Include Allocated Payments" } ], "icon": "fa fa-file-text", @@ -2138,7 +2146,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-03-13 11:43:15.883055", + "modified": "2023-04-03 22:55:14.206473", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 390af0deb21..a347323e358 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -845,7 +845,9 @@ class AccountsController(TransactionBase): def set_advances(self): """Returns list of advances against Account, Party, Reference""" - res = self.get_advance_entries() + res = self.get_advance_entries( + include_unallocated=not cint(self.get("only_include_allocated_payments")) + ) self.set("advances", []) advance_allocated = 0 diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index a07f75d1c5d..d943126018a 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -55,6 +55,14 @@ frappe.ui.form.on(cur_frm.doctype, { }, allocate_advances_automatically: function(frm) { + frm.trigger('fetch_advances'); + }, + + only_include_allocated_payments: function(frm) { + frm.trigger('fetch_advances'); + }, + + fetch_advances: function(frm) { if(frm.doc.allocate_advances_automatically) { frappe.call({ doc: frm.doc, From 3ad5d676ab07b3ddd2347842dd23aebf4cd40995 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:09:34 +0530 Subject: [PATCH 065/176] fix: Shop by category fixes (backport #34688) (#34750) fix: Shop by category fixes (#34688) * fix: Shop by category fixes * chore: Update tests (cherry picked from commit 56f50783576c23dff9fcf7aab5c8627abc23eaf8) Co-authored-by: Deepesh Garg --- .../doctype/website_item/test_website_item.py | 8 +++++++- erpnext/setup/doctype/item_group/item_group.py | 9 +++++++-- erpnext/www/shop-by-category/index.py | 12 +++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index bbe04d5514d..019a5f9ee4f 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -199,8 +199,14 @@ class TestWebsiteItem(unittest.TestCase): breadcrumbs = get_parent_item_groups(item.item_group) + settings = frappe.get_cached_doc("E Commerce Settings") + if settings.enable_field_filters: + base_breadcrumb = "Shop by Category" + else: + base_breadcrumb = "All Products" + self.assertEqual(breadcrumbs[0]["name"], "Home") - self.assertEqual(breadcrumbs[1]["name"], "All Products") + self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb) self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1") diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 2fdfcf647d0..2eca5cad8e2 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -148,12 +148,17 @@ def get_item_for_list_in_html(context): def get_parent_item_groups(item_group_name, from_item=False): - base_nav_page = {"name": _("All Products"), "route": "/all-products"} + settings = frappe.get_cached_doc("E Commerce Settings") + + if settings.enable_field_filters: + base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"} + else: + base_nav_page = {"name": _("All Products"), "route": "/all-products"} if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] - if last_page and last_page == "shop-by-category": + if last_page and last_page in ("shop-by-category", "all-products"): base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py index 219747c9f8a..913c1836acd 100644 --- a/erpnext/www/shop-by-category/index.py +++ b/erpnext/www/shop-by-category/index.py @@ -53,6 +53,7 @@ def get_tabs(categories): def get_category_records(categories: list): categorical_data = {} + website_item_meta = frappe.get_meta("Website Item", cached=True) for c in categories: if c == "item_group": @@ -64,7 +65,16 @@ def get_category_records(categories: list): continue - doctype = frappe.unscrub(c) + field_type = website_item_meta.get_field(c).fieldtype + + if field_type == "Table MultiSelect": + child_doc = website_item_meta.get_field(c).options + for field in frappe.get_meta(child_doc, cached=True).fields: + if field.fieldtype == "Link" and field.reqd: + doctype = field.options + else: + doctype = website_item_meta.get_field(c).options + fields = ["name"] try: From 8ba1e0f31e1769bfb4b3111870e4b46cf2c20d16 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:55:13 +0530 Subject: [PATCH 066/176] fix: `payment entry is already created` on posawesome. (backport #34712) (#34752) --- .../doctype/payment_request/payment_request.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index d9b07435fdb..aa8743e1caa 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -497,10 +497,16 @@ def get_amount(ref_doc, payment_account=None): if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) elif dt in ["Sales Invoice", "Purchase Invoice"]: - if ref_doc.party_account_currency == ref_doc.currency: - grand_total = flt(ref_doc.outstanding_amount) - else: - grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate + if not ref_doc.is_pos: + if ref_doc.party_account_currency == ref_doc.currency: + grand_total = flt(ref_doc.outstanding_amount) + else: + grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate + elif dt == "Sales Invoice": + for pay in ref_doc.payments: + if pay.type == "Phone" and pay.account == payment_account: + grand_total = pay.amount + break elif dt == "POS Invoice": for pay in ref_doc.payments: if pay.type == "Phone" and pay.account == payment_account: From 33ee958cfb3881cd3c560e8a4c5404e11478d90b Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 5 Apr 2023 17:41:20 +0530 Subject: [PATCH 067/176] chore: release v14 (#34733) --- .../accounts_settings/accounts_settings.json | 10 +- .../doctype/bank_clearance/bank_clearance.py | 16 +- .../chart_of_accounts_importer.py | 6 +- .../doctype/journal_entry/journal_entry.py | 3 +- .../doctype/payment_entry/payment_entry.js | 2 - .../doctype/payment_entry/payment_entry.py | 240 +++++- .../payment_entry/test_payment_entry.py | 222 ++++- .../payment_entry_deduction.json | 29 +- .../payment_reconciliation.js | 28 + .../payment_reconciliation.py | 9 + .../payment_request/payment_request.py | 14 +- .../doctype/pos_invoice/pos_invoice.py | 2 +- .../purchase_invoice/purchase_invoice.js | 6 +- .../purchase_invoice/purchase_invoice.json | 12 +- .../purchase_invoice/purchase_invoice.py | 27 +- .../doctype/sales_invoice/sales_invoice.js | 9 +- .../doctype/sales_invoice/sales_invoice.json | 10 +- .../doctype/sales_invoice/sales_invoice.py | 2 +- .../asset_depreciation_ledger.py | 1 + .../bank_clearance_summary.py | 64 +- erpnext/assets/doctype/asset/asset.js | 3 + erpnext/assets/doctype/asset/asset.json | 7 +- erpnext/assets/doctype/asset/asset.py | 188 ++++- erpnext/assets/doctype/asset/depreciation.py | 12 +- erpnext/assets/doctype/asset/test_asset.py | 12 +- .../asset_maintenance/asset_maintenance.py | 2 + .../asset_maintenance_task.json | 770 +++--------------- .../asset_value_adjustment.js | 2 +- .../doctype/purchase_order/purchase_order.js | 6 +- .../request_for_quotation.py | 5 +- erpnext/controllers/accounts_controller.py | 43 +- .../crm/report/lead_details/lead_details.py | 2 +- .../lost_opportunity/lost_opportunity.py | 2 +- .../doctype/website_item/test_website_item.py | 8 +- .../doctype/plaid_settings/plaid_settings.py | 2 +- erpnext/hooks.py | 4 + erpnext/manufacturing/doctype/bom/bom.py | 12 +- .../production_plan/production_plan.json | 3 +- .../production_plan_item_reference.json | 5 +- erpnext/public/js/controllers/accounts.js | 8 + .../public/js/controllers/taxes_and_totals.js | 2 +- erpnext/public/js/controllers/transaction.js | 52 +- erpnext/public/js/website_utils.js | 15 - .../setup/doctype/item_group/item_group.py | 9 +- erpnext/stock/doctype/batch/batch.py | 8 +- .../stock_reconciliation.py | 12 +- erpnext/stock/get_item_details.py | 23 +- .../stock/report/stock_ledger/stock_ledger.py | 3 + erpnext/templates/utils.py | 9 +- erpnext/utilities/transaction_base.py | 56 +- erpnext/www/shop-by-category/index.py | 12 +- 51 files changed, 1168 insertions(+), 841 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 1e2e2acd79a..1c0d64f065b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -31,6 +31,7 @@ "determine_address_tax_category_from", "column_break_19", "add_taxes_from_item_tax_template", + "book_tax_discount_loss", "print_settings", "show_inclusive_tax_in_print", "column_break_12", @@ -347,6 +348,13 @@ "fieldname": "allow_multi_currency_invoices_against_single_party_account", "fieldtype": "Check", "label": "Allow multi-currency invoices against single party account " + }, + { + "default": "0", + "description": "Split Early Payment Discount Loss into Income and Tax Loss", + "fieldname": "book_tax_discount_loss", + "fieldtype": "Check", + "label": "Book Tax Loss on Early Payment Discount" } ], "icon": "icon-cog", @@ -354,7 +362,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-11-27 21:49:52.538655", + "modified": "2023-03-28 09:50:20.375233", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 80878ac5068..081718726bd 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -81,7 +81,7 @@ class BankClearance(Document): loan_disbursement = frappe.qb.DocType("Loan Disbursement") - loan_disbursements = ( + query = ( frappe.qb.from_(loan_disbursement) .select( ConstantColumn("Loan Disbursement").as_("payment_document"), @@ -90,17 +90,22 @@ class BankClearance(Document): ConstantColumn(0).as_("debit"), loan_disbursement.reference_number.as_("cheque_number"), loan_disbursement.reference_date.as_("cheque_date"), + loan_disbursement.clearance_date.as_("clearance_date"), loan_disbursement.disbursement_date.as_("posting_date"), loan_disbursement.applicant.as_("against_account"), ) .where(loan_disbursement.docstatus == 1) .where(loan_disbursement.disbursement_date >= self.from_date) .where(loan_disbursement.disbursement_date <= self.to_date) - .where(loan_disbursement.clearance_date.isnull()) .where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account])) .orderby(loan_disbursement.disbursement_date) .orderby(loan_disbursement.name, order=frappe.qb.desc) - ).run(as_dict=1) + ) + + if not self.include_reconciled_entries: + query = query.where(loan_disbursement.clearance_date.isnull()) + + loan_disbursements = query.run(as_dict=1) loan_repayment = frappe.qb.DocType("Loan Repayment") @@ -113,16 +118,19 @@ class BankClearance(Document): ConstantColumn(0).as_("credit"), loan_repayment.reference_number.as_("cheque_number"), loan_repayment.reference_date.as_("cheque_date"), + loan_repayment.clearance_date.as_("clearance_date"), loan_repayment.applicant.as_("against_account"), loan_repayment.posting_date, ) .where(loan_repayment.docstatus == 1) - .where(loan_repayment.clearance_date.isnull()) .where(loan_repayment.posting_date >= self.from_date) .where(loan_repayment.posting_date <= self.to_date) .where(loan_repayment.payment_account.isin([self.bank_account, self.account])) ) + if not self.include_reconciled_entries: + query = query.where(loan_repayment.clearance_date.isnull()) + if frappe.db.has_column("Loan Repayment", "repay_from_salary"): query = query.where((loan_repayment.repay_from_salary == 0)) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index c676c97616c..e78f5f46dbc 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -325,14 +325,14 @@ def get_template(template_type): if template_type == "Blank Template": for root_type in get_root_types(): - writer.writerow(["", "", "", 1, "", root_type]) + writer.writerow(["", "", "", "", 1, "", root_type]) for account in get_mandatory_group_accounts(): - writer.writerow(["", "", "", 1, account, "Asset"]) + writer.writerow(["", "", "", "", 1, account, "Asset"]) for account_type in get_mandatory_account_types(): writer.writerow( - ["", "", "", 0, account_type.get("account_type"), account_type.get("root_type")] + ["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")] ) else: writer = get_sample_template(writer) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 56fe333e047..608267154b6 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -51,7 +51,7 @@ class JournalEntry(AccountsController): self.validate_multi_currency() self.set_amounts_in_company_currency() self.validate_debit_credit_amount() - + self.set_total_debit_credit() # Do not validate while importing via data import if not frappe.flags.in_import: self.validate_total_debit_and_credit() @@ -659,7 +659,6 @@ class JournalEntry(AccountsController): frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx)) def validate_total_debit_and_credit(self): - self.set_total_debit_credit() if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): if self.difference: frappe.throw( diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 91374ae217b..5a56a6b0046 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -245,8 +245,6 @@ frappe.ui.form.on('Payment Entry', { frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"], party_account_currency, "references"); - frm.set_currency_labels(["amount"], company_currency, "deductions"); - cur_frm.set_df_property("source_exchange_rate", "description", ("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency)); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a585924d20f..58ed7d1822c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -416,7 +416,7 @@ class PaymentEntry(AccountsController): for ref in self.get("references"): if ref.payment_term and ref.reference_name: - key = (ref.payment_term, ref.reference_name) + key = (ref.payment_term, ref.reference_name, ref.reference_doctype) invoice_payment_amount_map.setdefault(key, 0.0) invoice_payment_amount_map[key] += ref.allocated_amount @@ -424,20 +424,37 @@ class PaymentEntry(AccountsController): payment_schedule = frappe.get_all( "Payment Schedule", filters={"parent": ref.reference_name}, - fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"], + fields=[ + "paid_amount", + "payment_amount", + "payment_term", + "discount", + "outstanding", + "discount_type", + ], ) for term in payment_schedule: - invoice_key = (term.payment_term, ref.reference_name) + invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype) invoice_paid_amount_map.setdefault(invoice_key, {}) invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding - invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * ( - term.discount / 100 - ) + if not (term.discount_type and term.discount): + continue + + if term.discount_type == "Percentage": + invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * ( + term.discount / 100 + ) + else: + invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1): if not invoice_paid_amount_map.get(key): frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1])) + allocated_amount = self.get_allocated_amount_in_transaction_currency( + allocated_amount, key[2], key[1] + ) + outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding")) discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt")) @@ -472,6 +489,33 @@ class PaymentEntry(AccountsController): (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]), ) + def get_allocated_amount_in_transaction_currency( + self, allocated_amount, reference_doctype, reference_docname + ): + """ + Payment Entry could be in base currency while reference's payment schedule + is always in transaction currency. + E.g. + * SI with base=INR and currency=USD + * SI with payment schedule in USD + * PE in INR (accounting done in base currency) + """ + ref_currency, ref_exchange_rate = frappe.db.get_value( + reference_doctype, reference_docname, ["currency", "conversion_rate"] + ) + is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency + # PE in different currency + reference_is_multi_currency = self.paid_from_account_currency != ref_currency + + if not (is_single_currency and reference_is_multi_currency): + return allocated_amount + + allocated_amount = flt( + allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount") + ) + + return allocated_amount + def set_status(self): if self.docstatus == 2: self.status = "Cancelled" @@ -1642,7 +1686,14 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre @frappe.whitelist() def get_payment_entry( - dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None + dt, + dn, + party_amount=None, + bank_account=None, + bank_amount=None, + party_type=None, + payment_type=None, + reference_date=None, ): reference_doc = None doc = frappe.get_doc(dt, dn) @@ -1669,8 +1720,9 @@ def get_payment_entry( dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc ) - paid_amount, received_amount, discount_amount = apply_early_payment_discount( - paid_amount, received_amount, doc + reference_date = getdate(reference_date) + paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount( + paid_amount, received_amount, doc, party_account_currency, reference_date ) pe = frappe.new_doc("Payment Entry") @@ -1678,6 +1730,7 @@ def get_payment_entry( pe.company = doc.company pe.cost_center = doc.get("cost_center") pe.posting_date = nowdate() + pe.reference_date = reference_date pe.mode_of_payment = doc.get("mode_of_payment") pe.party_type = party_type pe.party = doc.get(scrub(party_type)) @@ -1718,7 +1771,7 @@ def get_payment_entry( ): for reference in get_reference_as_per_payment_terms( - doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount + doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency ): pe.append("references", reference) else: @@ -1769,16 +1822,17 @@ def get_payment_entry( if party_account and bank: pe.set_exchange_rate(ref_doc=reference_doc) pe.set_amounts() + if discount_amount: - pe.set_gain_or_loss( - account_details={ - "account": frappe.get_cached_value("Company", pe.company, "default_discount_account"), - "cost_center": pe.cost_center - or frappe.get_cached_value("Company", pe.company, "cost_center"), - "amount": discount_amount * (-1 if payment_type == "Pay" else 1), - } + base_total_discount_loss = 0 + if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"): + base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts) + + set_pending_discount_loss( + pe, doc, discount_amount, base_total_discount_loss, party_account_currency ) - pe.set_difference_amount() + + pe.set_difference_amount() return pe @@ -1889,20 +1943,28 @@ def set_paid_amount_and_received_amount( return paid_amount, received_amount -def apply_early_payment_discount(paid_amount, received_amount, doc): +def apply_early_payment_discount( + paid_amount, received_amount, doc, party_account_currency, reference_date +): total_discount = 0 + valid_discounts = [] eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"] has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule + is_multi_currency = party_account_currency != doc.company_currency if doc.doctype in eligible_for_payments and has_payment_schedule: for term in doc.payment_schedule: - if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date: + if not term.discounted_amount and term.discount and reference_date <= term.discount_date: + if term.discount_type == "Percentage": - discount_amount = flt(doc.get("grand_total")) * (term.discount / 100) + grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total") + discount_amount = flt(grand_total) * (term.discount / 100) else: discount_amount = term.discount - discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1) + # if accounting is done in the same currency, paid_amount = received_amount + conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1 + discount_amount_in_foreign_currency = discount_amount * conversion_rate if doc.doctype == "Sales Invoice": paid_amount -= discount_amount @@ -1911,23 +1973,151 @@ def apply_early_payment_discount(paid_amount, received_amount, doc): received_amount -= discount_amount paid_amount -= discount_amount_in_foreign_currency + valid_discounts.append({"type": term.discount_type, "discount": term.discount}) total_discount += discount_amount if total_discount: - money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency")) + currency = doc.get("currency") if is_multi_currency else doc.company_currency + money = frappe.utils.fmt_money(total_discount, currency=currency) frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1) - return paid_amount, received_amount, total_discount + return paid_amount, received_amount, total_discount, valid_discounts + + +def set_pending_discount_loss( + pe, doc, discount_amount, base_total_discount_loss, party_account_currency +): + # If multi-currency, get base discount amount to adjust with base currency deductions/losses + if party_account_currency != doc.company_currency: + discount_amount = discount_amount * doc.get("conversion_rate", 1) + + # Avoid considering miniscule losses + discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total")) + + # Set base discount amount (discount loss/pending rounding loss) in deductions + if discount_amount > 0.0: + positive_negative = -1 if pe.payment_type == "Pay" else 1 + + # If tax loss booking is enabled, pending loss will be rounding loss. + # Otherwise it will be the total discount loss. + book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss") + account_type = "round_off_account" if book_tax_loss else "default_discount_account" + + pe.set_gain_or_loss( + account_details={ + "account": frappe.get_cached_value("Company", pe.company, account_type), + "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), + "amount": discount_amount * positive_negative, + } + ) + + +def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float: + """Split early payment discount into Income Loss & Tax Loss.""" + total_discount_percent = get_total_discount_percent(doc, valid_discounts) + + if not total_discount_percent: + return 0.0 + + base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent) + base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent) + + # Round off total loss rather than individual losses to reduce rounding error + return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total")) + + +def get_total_discount_percent(doc, valid_discounts) -> float: + """Get total percentage and amount discount applied as a percentage.""" + total_discount_percent = ( + sum( + discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage" + ) + or 0.0 + ) + + # Operate in percentages only as it makes the income & tax split easier + total_discount_amount = ( + sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount") + or 0.0 + ) + + if total_discount_amount: + discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100 + total_discount_percent += discount_percentage + return total_discount_percent + + return total_discount_percent + + +def add_income_discount_loss(pe, doc, total_discount_percent) -> float: + """Add loss on income discount in base currency.""" + precision = doc.precision("total") + base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100) + + pe.append( + "deductions", + { + "account": frappe.get_cached_value("Company", pe.company, "default_discount_account"), + "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), + "amount": flt(base_loss_on_income, precision), + }, + ) + + return base_loss_on_income # Return loss without rounding + + +def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float: + """Add loss on tax discount in base currency.""" + tax_discount_loss = {} + base_total_tax_loss = 0 + precision = doc.precision("tax_amount_after_discount_amount", "taxes") + + # The same account head could be used more than once + for tax in doc.get("taxes", []): + base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * ( + total_discount_percentage / 100 + ) + + account = tax.get("account_head") + if not tax_discount_loss.get(account): + tax_discount_loss[account] = base_tax_loss + else: + tax_discount_loss[account] += base_tax_loss + + for account, loss in tax_discount_loss.items(): + base_total_tax_loss += loss + if loss == 0.0: + continue + + pe.append( + "deductions", + { + "account": account, + "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), + "amount": flt(loss, precision), + }, + ) + + return base_total_tax_loss # Return loss without rounding def get_reference_as_per_payment_terms( - payment_schedule, dt, dn, doc, grand_total, outstanding_amount + payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency ): references = [] + is_multi_currency_acc = (doc.currency != doc.company_currency) and ( + party_account_currency != doc.company_currency + ) + for payment_term in payment_schedule: payment_term_outstanding = flt( payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount") ) + if not is_multi_currency_acc: + # If accounting is done in company currency for multi-currency transaction + payment_term_outstanding = flt( + payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount") + ) if payment_term_outstanding: references.append( diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 123b5dfd512..67049c47ad0 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -5,7 +5,7 @@ import unittest import frappe from frappe import qb -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import flt, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import ( @@ -256,10 +256,25 @@ class TestPaymentEntry(FrappeTestCase): }, ) si.save() - si.submit() + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1) + pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + + self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount") + self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 236.0) + self.assertEqual(pe_with_tax_loss.paid_amount, 212.4) + self.assertEqual(pe_with_tax_loss.deductions[0].amount, 20.0) # Loss on Income + self.assertEqual(pe_with_tax_loss.deductions[1].amount, 3.6) # Loss on Tax + self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC") + + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0) pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + + self.assertEqual(pe.references[0].allocated_amount, 236.0) + self.assertEqual(pe.paid_amount, 212.4) + self.assertEqual(pe.deductions[0].amount, 23.6) + pe.submit() si.load_from_db() @@ -269,6 +284,190 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(si.payment_schedule[0].outstanding, 0) self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6) + def test_payment_entry_against_payment_terms_with_discount_amount(self): + si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + + si.payment_terms_template = "Test Discount Amount Template" + create_payment_terms_template_with_discount( + name="30 Credit Days with Rs.50 Discount", + discount_type="Amount", + discount=50, + template_name="Test Discount Amount Template", + ) + frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC") + + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 18, + }, + ) + si.save() + si.submit() + + # Set reference date past discount cut off date + pe_1 = get_payment_entry( + "Sales Invoice", + si.name, + bank_account="_Test Cash - _TC", + reference_date=frappe.utils.add_days(si.posting_date, 2), + ) + self.assertEqual(pe_1.paid_amount, 236.0) # discount not applied + + # Test if tax loss is booked on enabling configuration + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1) + pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + self.assertEqual(pe_with_tax_loss.deductions[0].amount, 42.37) # Loss on Income + self.assertEqual(pe_with_tax_loss.deductions[1].amount, 7.63) # Loss on Tax + self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC") + + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0) + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + self.assertEqual(pe.references[0].allocated_amount, 236.0) + self.assertEqual(pe.paid_amount, 186) + self.assertEqual(pe.deductions[0].amount, 50.0) + + pe.submit() + si.load_from_db() + + self.assertEqual(si.payment_schedule[0].payment_amount, 236.0) + self.assertEqual(si.payment_schedule[0].paid_amount, 186) + self.assertEqual(si.payment_schedule[0].outstanding, 0) + self.assertEqual(si.payment_schedule[0].discounted_amount, 50) + + @change_settings( + "Accounts Settings", + { + "allow_multi_currency_invoices_against_single_party_account": 1, + "book_tax_discount_loss": 1, + }, + ) + def test_payment_entry_multicurrency_si_with_base_currency_accounting_early_payment_discount( + self, + ): + """ + 1. Multi-currency SI with single currency accounting (company currency) + 2. PE with early payment discount + 3. Test if Paid Amount is calculated in company currency + 4. Test if deductions are calculated in company currency + + SI is in USD to document agreed amounts that are in USD, but the accounting is in base currency. + """ + si = create_sales_invoice( + customer="_Test Customer", + currency="USD", + conversion_rate=50, + do_not_save=1, + ) + create_payment_terms_template_with_discount() + si.payment_terms_template = "Test Discount Template" + + frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC") + si.save() + si.submit() + + pe = get_payment_entry( + "Sales Invoice", + si.name, + bank_account="_Test Bank - _TC", + ) + pe.reference_no = si.name + pe.reference_date = nowdate() + + # Early payment discount loss on income + self.assertEqual(pe.paid_amount, 4500.0) # Amount in company currency + self.assertEqual(pe.received_amount, 4500.0) + self.assertEqual(pe.deductions[0].amount, 500.0) + self.assertEqual(pe.deductions[0].account, "Write Off - _TC") + self.assertEqual(pe.difference_amount, 0.0) + + pe.insert() + pe.submit() + + expected_gle = dict( + (d[0], d) + for d in [ + ["Debtors - _TC", 0, 5000, si.name], + ["_Test Bank - _TC", 4500, 0, None], + ["Write Off - _TC", 500.0, 0, None], + ] + ) + + self.validate_gl_entries(pe.name, expected_gle) + + outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) + self.assertEqual(outstanding_amount, 0) + + def test_payment_entry_multicurrency_accounting_si_with_early_payment_discount(self): + """ + 1. Multi-currency SI with multi-currency accounting + 2. PE with early payment discount and also exchange loss + 3. Test if Paid Amount is calculated in transaction currency + 4. Test if deductions are calculated in base/company currency + 5. Test if exchange loss is reflected in difference + """ + si = create_sales_invoice( + customer="_Test Customer USD", + debit_to="_Test Receivable USD - _TC", + currency="USD", + conversion_rate=50, + do_not_save=1, + ) + create_payment_terms_template_with_discount() + si.payment_terms_template = "Test Discount Template" + + frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC") + si.save() + si.submit() + + pe = get_payment_entry( + "Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700 + ) + pe.reference_no = si.name + pe.reference_date = nowdate() + + # Early payment discount loss on income + self.assertEqual(pe.paid_amount, 90.0) + self.assertEqual(pe.received_amount, 4200.0) # 5000 - 500 (discount) - 300 (exchange loss) + self.assertEqual(pe.deductions[0].amount, 500.0) + self.assertEqual(pe.deductions[0].account, "Write Off - _TC") + + # Exchange loss + self.assertEqual(pe.difference_amount, 300.0) + + pe.append( + "deductions", + { + "account": "_Test Exchange Gain/Loss - _TC", + "cost_center": "_Test Cost Center - _TC", + "amount": 300.0, + }, + ) + + pe.insert() + pe.submit() + + self.assertEqual(pe.difference_amount, 0.0) + + expected_gle = dict( + (d[0], d) + for d in [ + ["_Test Receivable USD - _TC", 0, 5000, si.name], + ["_Test Bank - _TC", 4200, 0, None], + ["Write Off - _TC", 500.0, 0, None], + ["_Test Exchange Gain/Loss - _TC", 300.0, 0, None], + ] + ) + + self.validate_gl_entries(pe.name, expected_gle) + + outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) + self.assertEqual(outstanding_amount, 0) + def test_payment_against_purchase_invoice_to_check_status(self): pi = make_purchase_invoice( supplier="_Test Supplier USD", @@ -839,24 +1038,27 @@ def create_payment_terms_template(): ).insert() -def create_payment_terms_template_with_discount(): +def create_payment_terms_template_with_discount( + name=None, discount_type=None, discount=None, template_name=None +): + create_payment_term(name or "30 Credit Days with 10% Discount") + template_name = template_name or "Test Discount Template" - create_payment_term("30 Credit Days with 10% Discount") - - if not frappe.db.exists("Payment Terms Template", "Test Discount Template"): - payment_term_template = frappe.get_doc( + if not frappe.db.exists("Payment Terms Template", template_name): + frappe.get_doc( { "doctype": "Payment Terms Template", - "template_name": "Test Discount Template", + "template_name": template_name, "allocate_payment_based_on_payment_terms": 1, "terms": [ { "doctype": "Payment Terms Template Detail", - "payment_term": "30 Credit Days with 10% Discount", + "payment_term": name or "30 Credit Days with 10% Discount", "invoice_portion": 100, "credit_days_based_on": "Day(s) after invoice date", "credit_days": 2, - "discount": 10, + "discount_type": discount_type or "Percentage", + "discount": discount or 10, "discount_validity_based_on": "Day(s) after invoice date", "discount_validity": 1, } diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index 61a1462dd7a..1c31829f0ea 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -3,6 +3,7 @@ "creation": "2016-06-15 15:56:30.815503", "doctype": "DocType", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "account", "cost_center", @@ -17,9 +18,7 @@ "in_list_view": 1, "label": "Account", "options": "Account", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "cost_center", @@ -28,37 +27,30 @@ "label": "Cost Center", "options": "Cost Center", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Amount", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "label": "Amount (Company Currency)", + "options": "Company:company:default_currency", + "reqd": 1 }, { "fieldname": "column_break_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "description", "fieldtype": "Small Text", - "label": "Description", - "show_days": 1, - "show_seconds": 1 + "label": "Description" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-12 20:38:08.110674", + "modified": "2023-03-06 07:11:57.739619", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Deduction", @@ -66,5 +58,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index d986f320669..caffac5354f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -272,4 +272,32 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } }; +frappe.ui.form.on('Payment Reconciliation Allocation', { + allocated_amount: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + // filter invoice + let invoice = frm.doc.invoices.filter((x) => (x.invoice_number == row.invoice_number)); + // filter payment + let payment = frm.doc.payments.filter((x) => (x.reference_name == row.reference_name)); + + frm.call({ + doc: frm.doc, + method: 'calculate_difference_on_allocation_change', + args: { + payment_entry: payment, + invoice: invoice, + allocated_amount: row.allocated_amount + }, + callback: (r) => { + if (r.message) { + row.difference_amount = r.message; + frm.refresh(); + } + } + }); + } +}); + + + extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm})); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index c9e3998ac8a..d8082d058f3 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -233,6 +233,15 @@ class PaymentReconciliation(Document): return difference_amount + @frappe.whitelist() + def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount): + invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry) + invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number")) + new_difference_amount = self.get_difference_amount( + payment_entry[0], invoice[0], allocated_amount + ) + return new_difference_amount + @frappe.whitelist() def allocate_entries(self, args): self.validate_entries() diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index d9b07435fdb..aa8743e1caa 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -497,10 +497,16 @@ def get_amount(ref_doc, payment_account=None): if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) elif dt in ["Sales Invoice", "Purchase Invoice"]: - if ref_doc.party_account_currency == ref_doc.currency: - grand_total = flt(ref_doc.outstanding_amount) - else: - grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate + if not ref_doc.is_pos: + if ref_doc.party_account_currency == ref_doc.currency: + grand_total = flt(ref_doc.outstanding_amount) + else: + grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate + elif dt == "Sales Invoice": + for pay in ref_doc.payments: + if pay.type == "Phone" and pay.account == payment_account: + grand_total = pay.amount + break elif dt == "POS Invoice": for pay in ref_doc.payments: if pay.type == "Phone" and pay.account == payment_account: diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 0af4c0ea480..27e6f0e598f 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -674,7 +674,7 @@ def get_bin_qty(item_code, warehouse): def get_pos_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql( - """select sum(p_item.qty) as qty + """select sum(p_item.stock_qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item where p.name = p_item.parent and ifnull(p.consolidated_invoice, '') = '' diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e2b4a1ad5be..5c9168bf9c5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -82,7 +82,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. if(doc.docstatus == 1 && doc.outstanding_amount != 0 && !(doc.is_return && doc.return_against) && !doc.on_hold) { - this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create')); + this.frm.add_custom_button( + __('Payment'), + () => this.make_payment_entry(), + __('Create') + ); cur_frm.page.set_inner_btn_group_as_primary(__('Create')); } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 629a0ffb582..1c8c4b3193b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -118,6 +118,7 @@ "paid_amount", "advances_section", "allocate_advances_automatically", + "only_include_allocated_payments", "get_advances", "advances", "advance_tax", @@ -1550,17 +1551,24 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "default": "0", + "depends_on": "allocate_advances_automatically", + "description": "Advance payments allocated against orders will only be fetched", + "fieldname": "only_include_allocated_payments", + "fieldtype": "Check", + "label": "Only Include Allocated Payments" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-01-28 19:18:56.586321", + "modified": "2023-04-03 22:57:14.074982", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", - "name_case": "Title Case", "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index ae707ab1435..0ded4a883bb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -117,7 +117,7 @@ class PurchaseInvoice(BuyingController): self.validate_expense_account() self.set_against_expense_account() self.validate_write_off_account() - self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") + self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount") self.create_remarks() self.set_status() self.validate_purchase_receipt_if_update_stock() @@ -232,7 +232,7 @@ class PurchaseInvoice(BuyingController): ) if ( - cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) + cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate")) and not self.is_return and not self.is_internal_supplier ): @@ -581,6 +581,7 @@ class PurchaseInvoice(BuyingController): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) + self.make_precision_loss_gl_entry(gl_entries) if self.check_asset_cwip_enabled(): self.get_asset_gl_entry(gl_entries) @@ -975,6 +976,28 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount, item.precision("item_tax_amount") ) + def make_precision_loss_gl_entry(self, gl_entries): + round_off_account, round_off_cost_center = get_round_off_account_and_cost_center( + self.company, "Purchase Invoice", self.name + ) + + precision_loss = self.get("base_net_total") - flt( + self.get("net_total") * self.conversion_rate, self.precision("net_total") + ) + + if precision_loss: + gl_entries.append( + self.get_gl_dict( + { + "account": round_off_account, + "against": self.supplier, + "credit": precision_loss, + "cost_center": self.cost_center or round_off_cost_center, + "remarks": _("Net total calculation precision loss"), + } + ) + ) + def get_asset_gl_entry(self, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 47e3f9b9354..56e412b297c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -93,9 +93,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e if (doc.docstatus == 1 && doc.outstanding_amount!=0 && !(cint(doc.is_return) && doc.return_against)) { - cur_frm.add_custom_button(__('Payment'), - this.make_payment_entry, __('Create')); - cur_frm.page.set_inner_btn_group_as_primary(__('Create')); + this.frm.add_custom_button( + __('Payment'), + () => this.make_payment_entry(), + __('Create') + ); + this.frm.page.set_inner_btn_group_as_primary(__('Create')); } if(doc.docstatus==1 && !doc.is_return) { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 9a5b42be4bb..9a0d71a3850 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -120,6 +120,7 @@ "account_for_change_amount", "advances_section", "allocate_advances_automatically", + "only_include_allocated_payments", "get_advances", "advances", "write_off_section", @@ -2126,6 +2127,13 @@ "label": "Repost Required", "no_copy": 1, "read_only": 1 + }, + { + "depends_on": "allocate_advances_automatically", + "description": "Advance payments allocated against orders will only be fetched", + "fieldname": "only_include_allocated_payments", + "fieldtype": "Check", + "label": "Only Include Allocated Payments" } ], "icon": "fa fa-file-text", @@ -2138,7 +2146,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-03-13 11:43:15.883055", + "modified": "2023-04-03 22:55:14.206473", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f5be4c7a3f3..7af98ddf934 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -145,7 +145,7 @@ class SalesInvoice(SellingController): self.set_against_income_account() self.validate_time_sheets_are_submitted() - self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") + self.validate_multiple_billing("Delivery Note", "dn_detail", "amount") if not self.is_return: self.validate_serial_numbers() else: 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 57d80492ae0..f21c94b4940 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py @@ -25,6 +25,7 @@ def get_data(filters): ["posting_date", "<=", filters.get("to_date")], ["against_voucher_type", "=", "Asset"], ["account", "in", depreciation_accounts], + ["is_cancelled", "=", 0], ] if filters.get("asset"): diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 449ebdcd924..306af722ba8 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.custom import ConstantColumn from frappe.utils import getdate, nowdate @@ -91,4 +92,65 @@ def get_entries(filters): as_list=1, ) - return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate())) + # Loan Disbursement + loan_disbursement = frappe.qb.DocType("Loan Disbursement") + + query = ( + frappe.qb.from_(loan_disbursement) + .select( + ConstantColumn("Loan Disbursement").as_("payment_document_type"), + loan_disbursement.name.as_("payment_entry"), + loan_disbursement.disbursement_date.as_("posting_date"), + loan_disbursement.reference_number.as_("cheque_no"), + loan_disbursement.clearance_date.as_("clearance_date"), + loan_disbursement.applicant.as_("against"), + -loan_disbursement.disbursed_amount.as_("amount"), + ) + .where(loan_disbursement.docstatus == 1) + .where(loan_disbursement.disbursement_date >= filters["from_date"]) + .where(loan_disbursement.disbursement_date <= filters["to_date"]) + .where(loan_disbursement.disbursement_account == filters["account"]) + .orderby(loan_disbursement.disbursement_date, order=frappe.qb.desc) + .orderby(loan_disbursement.name, order=frappe.qb.desc) + ) + + if filters.get("from_date"): + query = query.where(loan_disbursement.disbursement_date >= filters["from_date"]) + if filters.get("to_date"): + query = query.where(loan_disbursement.disbursement_date <= filters["to_date"]) + + loan_disbursements = query.run(as_list=1) + + # Loan Repayment + loan_repayment = frappe.qb.DocType("Loan Repayment") + + query = ( + frappe.qb.from_(loan_repayment) + .select( + ConstantColumn("Loan Repayment").as_("payment_document_type"), + loan_repayment.name.as_("payment_entry"), + loan_repayment.posting_date.as_("posting_date"), + loan_repayment.reference_number.as_("cheque_no"), + loan_repayment.clearance_date.as_("clearance_date"), + loan_repayment.applicant.as_("against"), + loan_repayment.amount_paid.as_("amount"), + ) + .where(loan_repayment.docstatus == 1) + .where(loan_repayment.posting_date >= filters["from_date"]) + .where(loan_repayment.posting_date <= filters["to_date"]) + .where(loan_repayment.payment_account == filters["account"]) + .orderby(loan_repayment.posting_date, order=frappe.qb.desc) + .orderby(loan_repayment.name, order=frappe.qb.desc) + ) + + if filters.get("from_date"): + query = query.where(loan_repayment.posting_date >= filters["from_date"]) + if filters.get("to_date"): + query = query.where(loan_repayment.posting_date <= filters["to_date"]) + + loan_repayments = query.run(as_list=1) + + return sorted( + journal_entries + payment_entries + loan_disbursements + loan_repayments, + key=lambda k: k[2] or getdate(nowdate()), + ) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 21d846f6806..0923d0093f9 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -469,6 +469,9 @@ frappe.ui.form.on('Asset', { } else { frm.set_value('purchase_date', purchase_doc.posting_date); } + if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) { + frm.set_value('available_for_use_date', frm.doc.purchase_date); + } const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code); if (!item) { doctype_field = frappe.scrub(doctype) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index d581f52153e..3e93f0f03e3 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -81,6 +81,9 @@ "options": "ACC-ASS-.YYYY.-" }, { + "depends_on": "item_code", + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, "fieldname": "asset_name", "fieldtype": "Data", "in_list_view": 1, @@ -527,7 +530,7 @@ "table_fieldname": "accounts" } ], - "modified": "2023-01-25 17:45:48.649543", + "modified": "2023-03-30 15:07:41.542374", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -571,4 +574,4 @@ "states": [], "title_field": "asset_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 47b5f75e668..fe1fd98aa09 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -294,17 +294,42 @@ class Asset(AccountsController): if has_pro_rata: number_of_pending_depreciations += 1 + has_wdv_or_dd_non_yearly_pro_rata = False + if ( + finance_book.depreciation_method in ("Written Down Value", "Double Declining Balance") + and cint(finance_book.frequency_of_depreciation) != 12 + ): + has_wdv_or_dd_non_yearly_pro_rata = self.check_is_pro_rata( + finance_book, wdv_or_dd_non_yearly=True + ) + skip_row = False should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date) + depreciation_amount = 0 + for n in range(start[finance_book.idx - 1], number_of_pending_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) + if n > 0 and len(self.get("schedules")) > n - 1: + prev_depreciation_amount = self.get("schedules")[n - 1].depreciation_amount + else: + prev_depreciation_amount = 0 - if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: + depreciation_amount = get_depreciation_amount( + self, + value_after_depreciation, + finance_book, + n, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + ) + + if not has_pro_rata or ( + n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2 + ): schedule_date = add_months( finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation) ) @@ -320,7 +345,10 @@ class Asset(AccountsController): if date_of_disposal: from_date = self.get_from_date(finance_book.finance_book) depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, from_date, date_of_disposal + finance_book, + depreciation_amount, + from_date, + date_of_disposal, ) if depreciation_amount > 0: @@ -335,12 +363,20 @@ class Asset(AccountsController): break # For first row - if has_pro_rata and not self.opening_accumulated_depreciation and n == 0: + if ( + (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) + and not self.opening_accumulated_depreciation + and n == 0 + ): from_date = add_days( self.available_for_use_date, -1 ) # needed to calc depr amount for available_for_use_date too depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date + finance_book, + depreciation_amount, + from_date, + finance_book.depreciation_start_date, + has_wdv_or_dd_non_yearly_pro_rata, ) # For first depr schedule date will be the start date @@ -359,7 +395,11 @@ class Asset(AccountsController): depreciation_amount_without_pro_rata = depreciation_amount depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, schedule_date, self.to_date + finance_book, + depreciation_amount, + schedule_date, + self.to_date, + has_wdv_or_dd_non_yearly_pro_rata, ) depreciation_amount = self.get_adjusted_depreciation_amount( @@ -479,28 +519,37 @@ class Asset(AccountsController): return add_days(self.available_for_use_date, -1) # if it returns True, depreciation_amount will not be equal for the first and last rows - def check_is_pro_rata(self, row): + def check_is_pro_rata(self, 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 # from_date = 01/01/2022 - from_date = self.get_modified_available_for_use_date(row) + from_date = self.get_modified_available_for_use_date(row, wdv_or_dd_non_yearly) days = date_diff(row.depreciation_start_date, from_date) + 1 - # if frequency_of_depreciation is 12 months, total_days = 365 - total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) + 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) if days < total_days: has_pro_rata = True return has_pro_rata - def get_modified_available_for_use_date(self, row): - return add_months( - self.available_for_use_date, - (self.number_of_depreciations_booked * row.frequency_of_depreciation), - ) + def get_modified_available_for_use_date(self, row, wdv_or_dd_non_yearly=False): + if wdv_or_dd_non_yearly: + return add_months( + self.available_for_use_date, + (self.number_of_depreciations_booked * 12), + ) + else: + return add_months( + self.available_for_use_date, + (self.number_of_depreciations_booked * row.frequency_of_depreciation), + ) def validate_asset_finance_books(self, row): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): @@ -903,7 +952,12 @@ class Asset(AccountsController): float_precision = cint(frappe.db.get_default("float_precision")) or 2 if args.get("depreciation_method") == "Double Declining Balance": - return 200.0 / args.get("total_number_of_depreciations") + return 200.0 / ( + ( + flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation")) + ) + / 12 + ) if args.get("depreciation_method") == "Written Down Value": if ( @@ -920,14 +974,29 @@ class Asset(AccountsController): else: value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount) - depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2)) + depreciation_rate = math.pow( + value, + 1.0 + / ( + ( + flt(args.get("total_number_of_depreciations"), 2) + * flt(args.get("frequency_of_depreciation")) + ) + / 12 + ), + ) return flt((100 * (1 - depreciation_rate)), float_precision) - def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date): + def get_pro_rata_amt( + self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False + ): days = date_diff(to_date, from_date) months = month_diff(to_date, from_date) - total_days = get_total_days(to_date, row.frequency_of_depreciation) + if has_wdv_or_dd_non_yearly_pro_rata: + total_days = get_total_days(to_date, 12) + else: + total_days = get_total_days(to_date, row.frequency_of_depreciation) return (depreciation_amount * flt(days)) / flt(total_days), days, months @@ -1184,27 +1253,72 @@ def get_total_days(date, frequency): @erpnext.allow_regional -def get_depreciation_amount(asset, depreciable_value, row): +def get_depreciation_amount( + asset, + depreciable_value, + row, + schedule_idx=0, + prev_depreciation_amount=0, + has_wdv_or_dd_non_yearly_pro_rata=False, +): if row.depreciation_method in ("Straight Line", "Manual"): - # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value - if asset.flags.increase_in_asset_life: - depreciation_amount = ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / (date_diff(asset.to_date, asset.available_for_use_date) / 365) - # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value - elif asset.flags.increase_in_asset_value_due_to_repair: - depreciation_amount = ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations) - # if the Depreciation Schedule is being prepared for the first time - else: - depreciation_amount = ( - flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations) + return get_straight_line_or_manual_depr_amount(asset, row) else: - depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) + return get_wdv_or_dd_depr_amount( + depreciable_value, + row.rate_of_depreciation, + row.frequency_of_depreciation, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + ) - return depreciation_amount + +def get_straight_line_or_manual_depr_amount(asset, row): + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value + if asset.flags.increase_in_asset_life: + return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( + date_diff(asset.to_date, asset.available_for_use_date) / 365 + ) + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value + elif asset.flags.increase_in_asset_value_due_to_repair: + return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt( + row.total_number_of_depreciations + ) + # if the Depreciation Schedule is being prepared for the first time + else: + return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt( + row.total_number_of_depreciations + ) + + +def get_wdv_or_dd_depr_amount( + depreciable_value, + rate_of_depreciation, + frequency_of_depreciation, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, +): + if cint(frequency_of_depreciation) == 12: + return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) + else: + if has_wdv_or_dd_non_yearly_pro_rata: + if schedule_idx == 0: + return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) + elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1: + return ( + flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) + ) + else: + return prev_depreciation_amount + else: + if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0: + return ( + flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) + ) + else: + return prev_depreciation_amount @frappe.whitelist() diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index e72e0afb9ce..74625890a69 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -218,10 +218,16 @@ def notify_depr_entry_posting_error(failed_asset_names): asset_links = get_comma_separated_asset_links(failed_asset_names) message = ( - _("Hi,") - + "
" - + _("The following assets have failed to post depreciation entries: {0}").format(asset_links) + _("Hello,") + + "

" + + _("The following assets have failed to automatically post depreciation entries: {0}").format( + asset_links + ) + "." + + "

" + + _( + "Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table." + ) ) frappe.sendmail(recipients=recipients, subject=subject, message=message) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 71f578c6703..7eaa4bf997a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -818,12 +818,12 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2022-02-28", 647.25, 647.25], - ["2022-03-31", 1210.71, 1857.96], - ["2022-04-30", 1053.99, 2911.95], - ["2022-05-31", 917.55, 3829.5], - ["2022-06-30", 798.77, 4628.27], - ["2022-07-15", 371.73, 5000.0], + ["2022-02-28", 310.89, 310.89], + ["2022-03-31", 654.45, 965.34], + ["2022-04-30", 654.45, 1619.79], + ["2022-05-31", 654.45, 2274.24], + ["2022-06-30", 654.45, 2928.69], + ["2022-07-15", 2071.31, 5000.0], ] schedules = [ diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 0028d84508d..83031415ec3 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -84,6 +84,8 @@ def calculate_next_due_date( next_due_date = add_years(start_date, 1) if periodicity == "2 Yearly": next_due_date = add_years(start_date, 2) + if periodicity == "3 Yearly": + next_due_date = add_years(start_date, 3) if periodicity == "Quarterly": next_due_date = add_months(start_date, 3) if end_date and ( diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json index 20963e3fdc7..b7cb23e6687 100644 --- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json +++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json @@ -1,664 +1,156 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2017-10-20 07:10:55.903571", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-10-20 07:10:55.903571", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "maintenance_task", + "maintenance_type", + "column_break_2", + "maintenance_status", + "section_break_2", + "start_date", + "periodicity", + "column_break_4", + "end_date", + "certificate_required", + "section_break_9", + "assign_to", + "column_break_10", + "assign_to_name", + "section_break_10", + "next_due_date", + "column_break_14", + "last_completion_date", + "section_break_7", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_task", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Maintenance Task", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "maintenance_task", + "fieldtype": "Data", + "in_filter": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Maintenance Task", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Maintenance Type", - "length": 0, - "no_copy": 0, - "options": "Preventive Maintenance\nCalibration", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "maintenance_type", + "fieldtype": "Select", + "label": "Maintenance Type", + "options": "Preventive Maintenance\nCalibration" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "maintenance_status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maintenance Status", - "length": 0, - "no_copy": 0, - "options": "Planned\nOverdue\nCancelled", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "maintenance_status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Maintenance Status", + "options": "Planned\nOverdue\nCancelled", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "periodicity", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Periodicity", - "length": 0, - "no_copy": 0, - "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "periodicity", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Periodicity", + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "certificate_required", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Certificate Required", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "certificate_required", + "fieldtype": "Check", + "label": "Certificate Required", + "search_index": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "assign_to", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Assign To", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "assign_to", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assign To", + "options": "User" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assign_to.full_name", - "fieldname": "assign_to_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assign to Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "assign_to_name", + "fieldtype": "Read Only", + "label": "Assign to Name" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_10", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "next_due_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Next Due Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "next_due_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Next Due Date" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "last_completion_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Last Completion Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "last_completion_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Last Completion Date" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-06-18 16:12:04.330021", - "modified_by": "Administrator", - "module": "Assets", - "name": "Asset Maintenance Task", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2023-03-23 07:03:07.113452", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance Task", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js index ae0e1bda020..d07f40cdf42 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js @@ -49,7 +49,7 @@ frappe.ui.form.on('Asset Value Adjustment', { frm.call({ method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation", args: { - asset: frm.doc.asset, + asset_name: frm.doc.asset, finance_book: frm.doc.finance_book }, callback: function(r) { diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 47089f7d850..c6c9f1f98a3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -236,7 +236,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e this.make_purchase_invoice, __('Create')); if(flt(doc.per_billed) < 100 && doc.status != "Delivered") { - cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create')); + this.frm.add_custom_button( + __('Payment'), + () => this.make_payment_entry(), + __('Create') + ); } if(flt(doc.per_billed) < 100) { diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 7927beb8233..4590f8c3d93 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -113,7 +113,10 @@ class RequestforQuotation(BuyingController): def get_link(self): # RFQ link for supplier portal - return get_url("/app/request-for-quotation/" + self.name) + route = frappe.db.get_value( + "Portal Menu Item", {"reference_doctype": "Request for Quotation"}, ["route"] + ) + return get_url("/app/{0}/".format(route) + self.name) def update_supplier_part_no(self, supplier): self.vendor = supplier diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3705fcf4990..a347323e358 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -515,6 +515,8 @@ class AccountsController(TransactionBase): parent_dict.update({"customer": parent_dict.get("party_name")}) self.pricing_rules = [] + basic_item_details_map = {} + for item in self.get("items"): if item.get("item_code"): args = parent_dict.copy() @@ -533,7 +535,17 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) + basic_details = basic_item_details_map.get(item.item_code) + ret, basic_item_details = get_item_details( + args, + self, + for_validate=True, + overwrite_warehouse=False, + return_basic_details=True, + basic_details=basic_details, + ) + + basic_item_details_map.setdefault(item.item_code, basic_item_details) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: @@ -833,7 +845,9 @@ class AccountsController(TransactionBase): def set_advances(self): """Returns list of advances against Account, Party, Reference""" - res = self.get_advance_entries() + res = self.get_advance_entries( + include_unallocated=not cint(self.get("only_include_allocated_payments")) + ) self.set("advances", []) advance_allocated = 0 @@ -1232,7 +1246,7 @@ class AccountsController(TransactionBase): ) ) - def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): + def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on): from erpnext.controllers.status_updater import get_allowance_for item_allowance = {} @@ -1245,17 +1259,20 @@ class AccountsController(TransactionBase): total_overbilled_amt = 0.0 + reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)] + reference_details = self.get_billing_reference_details( + reference_names, ref_dt + " Item", based_on + ) + for item in self.get("items"): if not item.get(item_ref_dn): continue - ref_amt = flt( - frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on), - self.precision(based_on, item), - ) + ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item)) + if not ref_amt: frappe.msgprint( - _("System will not check overbilling since amount for Item {0} in {1} is zero").format( + _("System will not check over billing since amount for Item {0} in {1} is zero").format( item.item_code, ref_dt ), title=_("Warning"), @@ -1302,6 +1319,16 @@ class AccountsController(TransactionBase): alert=True, ) + def get_billing_reference_details(self, reference_names, reference_doctype, based_on): + return frappe._dict( + frappe.get_all( + reference_doctype, + filters={"name": ("in", reference_names)}, + fields=["name", based_on], + as_list=1, + ) + ) + def get_billed_amount_for_item(self, item, item_ref_dn, based_on): """ Returns Sum of Amount of diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py index 8660c733103..7b8c43b2d65 100644 --- a/erpnext/crm/report/lead_details/lead_details.py +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -98,7 +98,7 @@ def get_data(filters): `tabAddress`.name=`tabDynamic Link`.parent) WHERE company = %(company)s - AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s + AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s {conditions} ORDER BY `tabLead`.creation asc """.format( diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py index 254511c92fa..b37cfa449fe 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py @@ -82,7 +82,7 @@ def get_data(filters): {join} WHERE `tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s - AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s + AND DATE(`tabOpportunity`.modified) BETWEEN %(from_date)s AND %(to_date)s {conditions} GROUP BY `tabOpportunity`.name diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index bbe04d5514d..019a5f9ee4f 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -199,8 +199,14 @@ class TestWebsiteItem(unittest.TestCase): breadcrumbs = get_parent_item_groups(item.item_group) + settings = frappe.get_cached_doc("E Commerce Settings") + if settings.enable_field_filters: + base_breadcrumb = "Shop by Category" + else: + base_breadcrumb = "All Products" + self.assertEqual(breadcrumbs[0]["name"], "Home") - self.assertEqual(breadcrumbs[1]["name"], "All Products") + self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb) self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1") diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index f3aa6a37935..e57a30a88e1 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -220,7 +220,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): if e.code == "ITEM_LOGIN_REQUIRED": msg = _("There was an error syncing transactions.") + " " msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " - frappe.log_error(msg, title=_("Plaid Link Refresh Required")) + frappe.log_error(message=msg, title=_("Plaid Link Refresh Required")) return transactions diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f1ee370e97e..c4032596f47 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -30,6 +30,10 @@ doctype_js = { override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"} +override_whitelisted_methods = { + "frappe.www.contact.send_message": "erpnext.templates.utils.send_message" +} + welcome_email = "erpnext.setup.utils.welcome_email" # setup wizard diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 619a415c8bc..a085af859a4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -943,7 +943,8 @@ def get_valuation_rate(data): 2) If no value, get last valuation rate from SLE 3) If no value, get valuation rate from Item """ - from frappe.query_builder.functions import Sum + from frappe.query_builder.functions import Count, IfNull, Sum + from pypika import Case item_code, company = data.get("item_code"), data.get("company") valuation_rate = 0.0 @@ -954,7 +955,14 @@ def get_valuation_rate(data): frappe.qb.from_(bin_table) .join(wh_table) .on(bin_table.warehouse == wh_table.name) - .select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate")) + .select( + Case() + .when( + Count(bin_table.name) > 0, IfNull(Sum(bin_table.stock_value) / Sum(bin_table.actual_qty), 0.0) + ) + .else_(None) + .as_("valuation_rate") + ) .where((bin_table.item_code == item_code) & (wh_table.company == company)) ).run(as_dict=True)[0] diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 2624daa41e2..fdaa4a2a1d4 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -344,6 +344,7 @@ { "fieldname": "prod_plan_references", "fieldtype": "Table", + "hidden": 1, "label": "Production Plan Item Reference", "options": "Production Plan Item Reference" }, @@ -397,7 +398,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-26 14:51:08.774372", + "modified": "2023-03-31 10:30:48.118932", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json index 84dee4ad284..15ef20794cb 100644 --- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json @@ -28,7 +28,7 @@ "fieldname": "qty", "fieldtype": "Data", "in_list_view": 1, - "label": "qty" + "label": "Qty" }, { "fieldname": "item_reference", @@ -40,7 +40,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-07 17:03:49.707487", + "modified": "2023-03-31 10:30:14.604051", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item Reference", @@ -48,5 +48,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index a07f75d1c5d..d943126018a 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -55,6 +55,14 @@ frappe.ui.form.on(cur_frm.doctype, { }, allocate_advances_automatically: function(frm) { + frm.trigger('fetch_advances'); + }, + + only_include_allocated_payments: function(frm) { + frm.trigger('fetch_advances'); + }, + + fetch_advances: function(frm) { if(frm.doc.allocate_advances_automatically) { frappe.call({ doc: frm.doc, diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 8e57ebd3677..8efc47d18e5 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -135,7 +135,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } else { // allow for '0' qty on Credit/Debit notes - let qty = item.qty || me.frm.doc.is_debit_note ? 1 : -1; + let qty = item.qty || (me.frm.doc.is_debit_note ? 1 : -1); item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item)); } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f7620db2f1e..07d1955bfaf 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1897,20 +1897,60 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } make_payment_entry() { + let via_journal_entry = this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry; + if(this.has_discount_in_schedule() && !via_journal_entry) { + // If early payment discount is applied, ask user for reference date + this.prompt_user_for_reference_date(); + } else { + this.make_mapped_payment_entry(); + } + } + + make_mapped_payment_entry(args) { + var me = this; + args = args || { "dt": this.frm.doc.doctype, "dn": this.frm.doc.name }; return frappe.call({ - method: cur_frm.cscript.get_method_for_payment(), - args: { - "dt": cur_frm.doc.doctype, - "dn": cur_frm.doc.name - }, + method: me.get_method_for_payment(), + args: args, callback: function(r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - // cur_frm.refresh_fields() } }); } + prompt_user_for_reference_date(){ + var me = this; + frappe.prompt({ + label: __("Cheque/Reference Date"), + fieldname: "reference_date", + fieldtype: "Date", + reqd: 1, + }, (values) => { + let args = { + "dt": me.frm.doc.doctype, + "dn": me.frm.doc.name, + "reference_date": values.reference_date + } + me.make_mapped_payment_entry(args); + }, + __("Reference Date for Early Payment Discount"), + __("Continue") + ); + } + + has_discount_in_schedule() { + let is_eligible = in_list( + ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"], + this.frm.doctype + ); + let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length; + if(!is_eligible || !has_payment_schedule) return false; + + let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date); + return has_discount; + } + make_quality_inspection() { let data = []; const fields = [ diff --git a/erpnext/public/js/website_utils.js b/erpnext/public/js/website_utils.js index b5416065d79..2bb5255eebc 100644 --- a/erpnext/public/js/website_utils.js +++ b/erpnext/public/js/website_utils.js @@ -3,18 +3,6 @@ if(!window.erpnext) window.erpnext = {}; -// Add / update a new Lead / Communication -// subject, sender, description -frappe.send_message = function(opts, btn) { - return frappe.call({ - type: "POST", - method: "erpnext.templates.utils.send_message", - btn: btn, - args: opts, - callback: opts.callback - }); -}; - erpnext.subscribe_to_newsletter = function(opts, btn) { return frappe.call({ type: "POST", @@ -24,6 +12,3 @@ erpnext.subscribe_to_newsletter = function(opts, btn) { callback: opts.callback }); } - -// for backward compatibility -erpnext.send_message = frappe.send_message; diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 2fdfcf647d0..2eca5cad8e2 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -148,12 +148,17 @@ def get_item_for_list_in_html(context): def get_parent_item_groups(item_group_name, from_item=False): - base_nav_page = {"name": _("All Products"), "route": "/all-products"} + settings = frappe.get_cached_doc("E Commerce Settings") + + if settings.enable_field_filters: + base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"} + else: + base_nav_page = {"name": _("All Products"), "route": "/all-products"} if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] - if last_page and last_page == "shop-by-category": + if last_page and last_page in ("shop-by-category", "all-products"): base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index f14288beb20..4a165212dce 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last -from frappe.utils import cint, flt, get_link_to_form +from frappe.utils import cint, flt, get_link_to_form, nowtime from frappe.utils.data import add_days from frappe.utils.jinja import render_template @@ -179,7 +179,11 @@ def get_batch_qty( out = 0 if batch_no and warehouse: cond = "" - if posting_date and posting_time: + + if posting_date: + if posting_time is None: + posting_time = nowtime() + cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format( posting_date, posting_time ) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 3f6a2c881b8..04d1a3a5e22 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -4,7 +4,7 @@ from typing import Optional import frappe -from frappe import _, msgprint +from frappe import _, bold, msgprint from frappe.utils import cint, cstr, flt import erpnext @@ -89,7 +89,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation" and not item.serial_no: + if self.purpose == "Stock Reconciliation" and not item.serial_no and item.qty: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") @@ -140,6 +140,14 @@ class StockReconciliation(StockController): self.validate_item(row.item_code, row) + if row.serial_no and not row.qty: + self.validation_messages.append( + _get_msg( + row_num, + f"Quantity should not be zero for the {bold(row.item_code)} since serial nos are specified", + ) + ) + # validate warehouse if not frappe.db.get_value("Warehouse", row.warehouse): self.validation_messages.append(_get_msg(row_num, _("Warehouse not found in the system"))) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 489ec6ebecc..2df39c81832 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -35,7 +35,14 @@ purchase_doctypes = [ @frappe.whitelist() -def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): +def get_item_details( + args, + doc=None, + for_validate=False, + overwrite_warehouse=True, + return_basic_details=False, + basic_details=None, +): """ args = { "item_code": "", @@ -73,7 +80,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if doc.get("doctype") == "Purchase Invoice": args["bill_date"] = doc.get("bill_date") - out = get_basic_details(args, item, overwrite_warehouse) + if not basic_details: + out = get_basic_details(args, item, overwrite_warehouse) + else: + out = basic_details + + basic_details = out.copy() + get_item_tax_template(args, item, out) out["item_tax_rate"] = get_item_tax_map( args.company, @@ -141,7 +154,11 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.amount = flt(args.qty) * flt(out.rate) out = remove_standard_fields(out) - return out + + if return_basic_details: + return out, basic_details + else: + return out def remove_standard_fields(details): diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index da17cdeb5ae..77bc4e004de 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -34,6 +34,9 @@ def execute(filters=None): conversion_factors.append(0) actual_qty = stock_value = 0 + if opening_row: + actual_qty = opening_row.get("qty_after_transaction") + stock_value = opening_row.get("stock_value") available_serial_nos = {} inventory_dimension_filters_applied = check_inventory_dimension_filters_applied(filters) diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py index 48b44802a8f..57750a56f6f 100644 --- a/erpnext/templates/utils.py +++ b/erpnext/templates/utils.py @@ -6,13 +6,12 @@ import frappe @frappe.whitelist(allow_guest=True) -def send_message(subject="Website Query", message="", sender="", status="Open"): +def send_message(sender, message, subject="Website Query"): from frappe.www.contact import send_message as website_send_message + website_send_message(sender, message, subject) + lead = customer = None - - website_send_message(subject, message, sender) - customer = frappe.db.sql( """select distinct dl.link_name from `tabDynamic Link` dl left join `tabContact` c on dl.parent=c.name where dl.link_doctype='Customer' @@ -58,5 +57,3 @@ def send_message(subject="Website Query", message="", sender="", status="Open"): } ) comm.insert(ignore_permissions=True) - - return "okay" diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 21a0a551b62..7eba35dedd9 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -58,11 +58,11 @@ class TransactionBase(StatusUpdater): def compare_values(self, ref_doc, fields, doc=None): for reference_doctype, ref_dn_list in ref_doc.items(): + prev_doc_detail_map = self.get_prev_doc_reference_details( + ref_dn_list, reference_doctype, fields + ) for reference_name in ref_dn_list: - prevdoc_values = frappe.db.get_value( - reference_doctype, reference_name, [d[0] for d in fields], as_dict=1 - ) - + prevdoc_values = prev_doc_detail_map.get(reference_name) if not prevdoc_values: frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name)) @@ -70,6 +70,19 @@ class TransactionBase(StatusUpdater): if prevdoc_values[field] is not None and field not in self.exclude_fields: self.validate_value(field, condition, prevdoc_values[field], doc) + def get_prev_doc_reference_details(self, reference_names, reference_doctype, fields): + prev_doc_detail_map = {} + details = frappe.get_all( + reference_doctype, + filters={"name": ("in", reference_names)}, + fields=["name"] + [d[0] for d in fields], + ) + + for d in details: + prev_doc_detail_map.setdefault(d.name, d) + + return prev_doc_detail_map + def validate_rate_with_reference_doc(self, ref_details): if self.get("is_internal_supplier"): return @@ -77,23 +90,23 @@ class TransactionBase(StatusUpdater): buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"] if self.doctype in buying_doctypes: - action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action") - settings_doc = "Buying Settings" + action, role_allowed_to_override = frappe.get_cached_value( + "Buying Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"] + ) else: - action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action") - settings_doc = "Selling Settings" + action, role_allowed_to_override = frappe.get_cached_value( + "Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"] + ) for ref_dt, ref_dn_field, ref_link_field in ref_details: + reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)] + reference_details = self.get_reference_details(reference_names, ref_dt + " Item") for d in self.get("items"): if d.get(ref_link_field): - ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate") + ref_rate = reference_details.get(d.get(ref_link_field)) if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01: if action == "Stop": - role_allowed_to_override = frappe.db.get_single_value( - settings_doc, "role_to_override_stop_action" - ) - if role_allowed_to_override not in frappe.get_roles(): frappe.throw( _("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format( @@ -109,6 +122,16 @@ class TransactionBase(StatusUpdater): indicator="orange", ) + def get_reference_details(self, reference_names, reference_doctype): + return frappe._dict( + frappe.get_all( + reference_doctype, + filters={"name": ("in", reference_names)}, + fields=["name", "rate"], + as_list=1, + ) + ) + def get_link_filters(self, for_doctype): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype): fieldname = self.prev_link_mapper[for_doctype]["fieldname"] @@ -186,12 +209,15 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None): for f in qty_fields: qty = d.get(f) if qty: - if abs(cint(qty) - flt(qty)) > 0.0000001: + if abs(cint(qty) - flt(qty, d.precision(f))) > 0.0000001: frappe.throw( _( "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}." ).format( - qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field)) + flt(qty, d.precision(f)), + d.idx, + frappe.bold(_("Must be Whole Number")), + frappe.bold(d.get(uom_field)), ), UOMMustBeIntegerError, ) diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py index 219747c9f8a..913c1836acd 100644 --- a/erpnext/www/shop-by-category/index.py +++ b/erpnext/www/shop-by-category/index.py @@ -53,6 +53,7 @@ def get_tabs(categories): def get_category_records(categories: list): categorical_data = {} + website_item_meta = frappe.get_meta("Website Item", cached=True) for c in categories: if c == "item_group": @@ -64,7 +65,16 @@ def get_category_records(categories: list): continue - doctype = frappe.unscrub(c) + field_type = website_item_meta.get_field(c).fieldtype + + if field_type == "Table MultiSelect": + child_doc = website_item_meta.get_field(c).options + for field in frappe.get_meta(child_doc, cached=True).fields: + if field.fieldtype == "Link" and field.reqd: + doctype = field.options + else: + doctype = website_item_meta.get_field(c).options + fields = ["name"] try: From d817c50581fedd46b5ee93f3cfd55247d1fd9823 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 Apr 2023 23:56:57 +0530 Subject: [PATCH 068/176] fix: incorrect stock balance quantity for batch item (cherry picked from commit ef4bd771968274d73ec5df865159d251c91ebb3e) --- .../stock_reconciliation.py | 49 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 15 +++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 04d1a3a5e22..482b103d1e4 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -5,6 +5,7 @@ from typing import Optional import frappe from frappe import _, bold, msgprint +from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt import erpnext @@ -569,6 +570,54 @@ class StockReconciliation(StockController): else: self._cancel() + def recalculate_current_qty(self, item_code, batch_no): + for row in self.items: + if not (row.item_code == item_code and row.batch_no == batch_no): + continue + + row.current_qty = get_batch_qty_for_stock_reco(item_code, row.warehouse, batch_no) + + qty, val_rate = get_stock_balance( + item_code, + row.warehouse, + self.posting_date, + self.posting_time, + with_valuation_rate=True, + ) + + row.current_valuation_rate = val_rate + + row.db_set( + { + "current_qty": row.current_qty, + "current_valuation_rate": row.current_valuation_rate, + "current_amount": flt(row.current_qty * row.current_valuation_rate), + } + ) + + +def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no): + ledger = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(ledger) + .select( + Sum(ledger.actual_qty).as_("batch_qty"), + ) + .where( + (ledger.item_code == item_code) + & (ledger.warehouse == warehouse) + & (ledger.docstatus == 1) + & (ledger.is_cancelled == 0) + & (ledger.batch_no == batch_no) + ) + .groupby(ledger.batch_no) + ) + + sle = query.run(as_dict=True) + + return flt(sle[0].batch_qty) if sle else 0 + @frappe.whitelist() def get_items( diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 08fc6fbd42f..c954befdc29 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1337,6 +1337,9 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): next_stock_reco_detail = get_next_stock_reco(args) if next_stock_reco_detail: detail = next_stock_reco_detail[0] + if detail.batch_no: + regenerate_sle_for_batch_stock_reco(detail) + # add condition to update SLEs before this date & time datetime_limit_condition = get_datetime_limit_condition(detail) @@ -1364,6 +1367,16 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): validate_negative_qty_in_future_sle(args, allow_negative_stock) +def regenerate_sle_for_batch_stock_reco(detail): + doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) + doc.docstatus = 2 + doc.update_stock_ledger() + + doc.recalculate_current_qty(detail.item_code, detail.batch_no) + doc.docstatus = 1 + doc.update_stock_ledger() + + def get_stock_reco_qty_shift(args): stock_reco_qty_shift = 0 if args.get("is_cancelled"): @@ -1393,7 +1406,7 @@ def get_next_stock_reco(args): return frappe.db.sql( """ select - name, posting_date, posting_time, creation, voucher_no + name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty from `tabStock Ledger Entry` where From c7cee866850b8dab3a97dde1225a12cda15cb532 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 Apr 2023 23:56:57 +0530 Subject: [PATCH 069/176] fix: incorrect stock balance quantity for batch item (cherry picked from commit ef4bd771968274d73ec5df865159d251c91ebb3e) (cherry picked from commit d817c50581fedd46b5ee93f3cfd55247d1fd9823) --- .../stock_reconciliation.py | 49 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 15 +++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 04d1a3a5e22..482b103d1e4 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -5,6 +5,7 @@ from typing import Optional import frappe from frappe import _, bold, msgprint +from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt import erpnext @@ -569,6 +570,54 @@ class StockReconciliation(StockController): else: self._cancel() + def recalculate_current_qty(self, item_code, batch_no): + for row in self.items: + if not (row.item_code == item_code and row.batch_no == batch_no): + continue + + row.current_qty = get_batch_qty_for_stock_reco(item_code, row.warehouse, batch_no) + + qty, val_rate = get_stock_balance( + item_code, + row.warehouse, + self.posting_date, + self.posting_time, + with_valuation_rate=True, + ) + + row.current_valuation_rate = val_rate + + row.db_set( + { + "current_qty": row.current_qty, + "current_valuation_rate": row.current_valuation_rate, + "current_amount": flt(row.current_qty * row.current_valuation_rate), + } + ) + + +def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no): + ledger = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(ledger) + .select( + Sum(ledger.actual_qty).as_("batch_qty"), + ) + .where( + (ledger.item_code == item_code) + & (ledger.warehouse == warehouse) + & (ledger.docstatus == 1) + & (ledger.is_cancelled == 0) + & (ledger.batch_no == batch_no) + ) + .groupby(ledger.batch_no) + ) + + sle = query.run(as_dict=True) + + return flt(sle[0].batch_qty) if sle else 0 + @frappe.whitelist() def get_items( diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 08fc6fbd42f..c954befdc29 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1337,6 +1337,9 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): next_stock_reco_detail = get_next_stock_reco(args) if next_stock_reco_detail: detail = next_stock_reco_detail[0] + if detail.batch_no: + regenerate_sle_for_batch_stock_reco(detail) + # add condition to update SLEs before this date & time datetime_limit_condition = get_datetime_limit_condition(detail) @@ -1364,6 +1367,16 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): validate_negative_qty_in_future_sle(args, allow_negative_stock) +def regenerate_sle_for_batch_stock_reco(detail): + doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) + doc.docstatus = 2 + doc.update_stock_ledger() + + doc.recalculate_current_qty(detail.item_code, detail.batch_no) + doc.docstatus = 1 + doc.update_stock_ledger() + + def get_stock_reco_qty_shift(args): stock_reco_qty_shift = 0 if args.get("is_cancelled"): @@ -1393,7 +1406,7 @@ def get_next_stock_reco(args): return frappe.db.sql( """ select - name, posting_date, posting_time, creation, voucher_no + name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty from `tabStock Ledger Entry` where From b6ae9a4a72fcfc83e0ff73102eef4736abd5c789 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 5 Apr 2023 18:57:55 +0000 Subject: [PATCH 070/176] chore(release): Bumped to Version 14.20.2 ## [14.20.2](https://github.com/frappe/erpnext/compare/v14.20.1...v14.20.2) (2023-04-05) ### Bug Fixes * incorrect stock balance quantity for batch item ([c7cee86](https://github.com/frappe/erpnext/commit/c7cee866850b8dab3a97dde1225a12cda15cb532)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 643815e38fc..a782703e760 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.20.1" +__version__ = "14.20.2" def get_default_company(user=None): From a1f7e359142132a3e0bbcd49f37bab84071ef4dc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 12:49:15 +0530 Subject: [PATCH 071/176] fix: Unable to create payment request against purchase invoice (#34762) fix: Unable to create payment request against purchase invoice (#34762) (cherry picked from commit 91a26608ee6a8cb09547e2d5059a36ae4daaa0d9) Co-authored-by: Deepesh Garg --- .../payment_request/payment_request.py | 2 +- .../payment_request/test_payment_request.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index aa8743e1caa..0955664d98b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -497,7 +497,7 @@ def get_amount(ref_doc, payment_account=None): if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) elif dt in ["Sales Invoice", "Purchase Invoice"]: - if not ref_doc.is_pos: + if not ref_doc.get("is_pos"): if ref_doc.party_account_currency == ref_doc.currency: grand_total = flt(ref_doc.outstanding_amount) else: diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 4279aa4f85c..e17a846dd81 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -6,6 +6,7 @@ import unittest import frappe from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request +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.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.setup.utils import get_exchange_rate @@ -74,6 +75,29 @@ class TestPaymentRequest(unittest.TestCase): self.assertEqual(pr.reference_name, si_usd.name) self.assertEqual(pr.currency, "USD") + def test_payment_entry_against_purchase_invoice(self): + si_usd = make_purchase_invoice( + customer="_Test Supplier USD", + debit_to="_Test Payable USD - _TC", + currency="USD", + conversion_rate=50, + ) + + pr = make_payment_request( + dt="Purchase Invoice", + dn=si_usd.name, + recipient_id="user@example.com", + mute_email=1, + payment_gateway_account="_Test Gateway - USD", + submit_doc=1, + return_doc=1, + ) + + pe = pr.create_payment_entry() + pr.load_from_db() + + self.assertEqual(pr.status, "Paid") + def test_payment_entry(self): frappe.db.set_value( "Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC" From f4473b36a5ab5892117e7e2c69f6ef98a7d6c8b6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:22:09 +0530 Subject: [PATCH 072/176] fix: Unable to create payment request against purchase invoice (#34762) fix: Unable to create payment request against purchase invoice (#34762) fix: Unable to create payment request against purchase invoice (#34762) (cherry picked from commit 91a26608ee6a8cb09547e2d5059a36ae4daaa0d9) Co-authored-by: Deepesh Garg (cherry picked from commit a1f7e359142132a3e0bbcd49f37bab84071ef4dc) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../payment_request/payment_request.py | 2 +- .../payment_request/test_payment_request.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index aa8743e1caa..0955664d98b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -497,7 +497,7 @@ def get_amount(ref_doc, payment_account=None): if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) elif dt in ["Sales Invoice", "Purchase Invoice"]: - if not ref_doc.is_pos: + if not ref_doc.get("is_pos"): if ref_doc.party_account_currency == ref_doc.currency: grand_total = flt(ref_doc.outstanding_amount) else: diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 4279aa4f85c..e17a846dd81 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -6,6 +6,7 @@ import unittest import frappe from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request +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.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.setup.utils import get_exchange_rate @@ -74,6 +75,29 @@ class TestPaymentRequest(unittest.TestCase): self.assertEqual(pr.reference_name, si_usd.name) self.assertEqual(pr.currency, "USD") + def test_payment_entry_against_purchase_invoice(self): + si_usd = make_purchase_invoice( + customer="_Test Supplier USD", + debit_to="_Test Payable USD - _TC", + currency="USD", + conversion_rate=50, + ) + + pr = make_payment_request( + dt="Purchase Invoice", + dn=si_usd.name, + recipient_id="user@example.com", + mute_email=1, + payment_gateway_account="_Test Gateway - USD", + submit_doc=1, + return_doc=1, + ) + + pe = pr.create_payment_entry() + pr.load_from_db() + + self.assertEqual(pr.status, "Paid") + def test_payment_entry(self): frappe.db.set_value( "Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC" From 2a8c9f8e69ebb568dd7430de3324203a128cf3b5 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 6 Apr 2023 07:54:09 +0000 Subject: [PATCH 073/176] chore(release): Bumped to Version 14.20.3 ## [14.20.3](https://github.com/frappe/erpnext/compare/v14.20.2...v14.20.3) (2023-04-06) ### Bug Fixes * Unable to create payment request against purchase invoice ([#34762](https://github.com/frappe/erpnext/issues/34762)) ([f4473b3](https://github.com/frappe/erpnext/commit/f4473b36a5ab5892117e7e2c69f6ef98a7d6c8b6)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a782703e760..456ca52020b 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import inspect import frappe -__version__ = "14.20.2" +__version__ = "14.20.3" def get_default_company(user=None): From 99226d3811db504a747ccafcaef4f2ed2022c924 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 5 Apr 2023 13:20:32 +0530 Subject: [PATCH 074/176] fix: Subcontracting Receipt incorrect `status` (cherry picked from commit a55b8181192c89df2a19dc815950e3e7dab1bd34) --- .../subcontracting_receipt.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 95dbc83bf80..4f8e045d706 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -245,17 +245,17 @@ class SubcontractingReceipt(SubcontractingController): item.expense_account = expense_account def update_status(self, status=None, update_modified=False): - if self.docstatus >= 1 and not status: - if self.docstatus == 1: + if not status: + if self.docstatus == 0: + status = "Draft" + elif self.docstatus == 1: + status = "Completed" if self.is_return: status = "Return" return_against = frappe.get_doc("Subcontracting Receipt", self.return_against) return_against.run_method("update_status") - else: - if self.per_returned == 100: - status = "Return Issued" - elif self.status == "Draft": - status = "Completed" + elif self.per_returned == 100: + status = "Return Issued" elif self.docstatus == 2: status = "Cancelled" From d4a6035c83b766a4dde740ffb9249e16f437cd57 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 6 Apr 2023 12:51:32 +0530 Subject: [PATCH 075/176] fix: UX for stock entry, bom and work order (cherry picked from commit 82a136f991d801051c7a071017e6bb9ed58eac06) --- erpnext/manufacturing/doctype/bom/bom.json | 15 +++++------ .../doctype/work_order/work_order.json | 25 +++++++++++-------- .../doctype/stock_entry/stock_entry.json | 15 +++++------ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index db699b94d8f..d02402299e3 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -9,15 +9,14 @@ "production_item_tab", "item", "company", - "item_name", "uom", + "quantity", "cb0", "is_active", "is_default", "allow_alternative_item", "set_rate_of_sub_assembly_item_based_on_bom", "project", - "quantity", "image", "currency_detail", "rm_cost_as_per", @@ -27,6 +26,8 @@ "column_break_ivyw", "currency", "conversion_rate", + "materials_section", + "items", "section_break_21", "operations_section_section", "with_operations", @@ -38,8 +39,6 @@ "operating_cost_per_bom_quantity", "operations_section", "operations", - "materials_section", - "items", "scrap_section", "scrap_items_section", "scrap_items", @@ -59,6 +58,7 @@ "total_cost", "base_total_cost", "more_info_tab", + "item_name", "description", "column_break_27", "has_variants", @@ -192,6 +192,7 @@ "options": "Quality Inspection Template" }, { + "collapsible": 1, "fieldname": "currency_detail", "fieldtype": "Section Break", "label": "Cost Configuration" @@ -417,7 +418,7 @@ { "collapsible": 1, "fieldname": "website_section", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Website" }, { @@ -482,7 +483,7 @@ { "fieldname": "section_break_21", "fieldtype": "Tab Break", - "label": "Operations & Materials" + "label": "Operations" }, { "fieldname": "column_break_23", @@ -605,7 +606,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-02-13 17:31:37.504565", + "modified": "2023-04-06 12:47:58.514795", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 25e16d63376..aa9049801cc 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -22,17 +22,13 @@ "produced_qty", "process_loss_qty", "project", - "serial_no_and_batch_for_finished_good_section", - "has_serial_no", - "has_batch_no", - "column_break_17", - "serial_no", - "batch_size", + "section_break_ndpq", + "required_items", "work_order_configuration", "settings_section", "allow_alternative_item", "use_multi_level_bom", - "column_break_18", + "column_break_17", "skip_transfer", "from_wip_warehouse", "update_consumed_material_cost_in_project", @@ -42,9 +38,14 @@ "column_break_12", "fg_warehouse", "scrap_warehouse", + "serial_no_and_batch_for_finished_good_section", + "has_serial_no", + "has_batch_no", + "column_break_18", + "serial_no", + "batch_size", "required_items_section", "materials_and_operations_tab", - "required_items", "operations_section", "operations", "transfer_material_against", @@ -586,7 +587,11 @@ { "fieldname": "materials_and_operations_tab", "fieldtype": "Tab Break", - "label": "Materials & Operations" + "label": "Operations" + }, + { + "fieldname": "section_break_ndpq", + "fieldtype": "Section Break" } ], "icon": "fa fa-cogs", @@ -594,7 +599,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-01-03 14:16:35.427731", + "modified": "2023-04-06 12:35:12.149827", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 9c0f1fc03f4..bc5533fd2de 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -27,7 +27,6 @@ "set_posting_time", "inspection_required", "apply_putaway_rule", - "items_tab", "bom_info_section", "from_bom", "use_multi_level_bom", @@ -256,7 +255,7 @@ "description": "As per Stock UOM", "fieldname": "fg_completed_qty", "fieldtype": "Float", - "label": "For Quantity", + "label": "Finished Good Quantity ", "oldfieldname": "fg_completed_qty", "oldfieldtype": "Currency", "print_hide": 1 @@ -612,11 +611,7 @@ "read_only": 1 }, { - "fieldname": "items_tab", - "fieldtype": "Tab Break", - "label": "Items" - }, - { + "collapsible": 1, "fieldname": "bom_info_section", "fieldtype": "Section Break", "label": "BOM Info" @@ -644,8 +639,10 @@ "oldfieldtype": "Section Break" }, { + "collapsible": 1, "fieldname": "section_break_7qsm", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Process Loss" }, { "depends_on": "process_loss_percentage", @@ -677,7 +674,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-01-03 16:02:50.741816", + "modified": "2023-04-06 12:42:56.673180", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", From 1c5e36c7b6791482cb42c673bd0ecc116bce8b41 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 6 Apr 2023 09:29:44 +0530 Subject: [PATCH 076/176] feat: add `Received Qty` field in `Delivery Note Item` (cherry picked from commit bc39dfab5d91c67d4ea6fe628eae7914937c38fa) --- .../delivery_note_item/delivery_note_item.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 1763269193a..180adee0cb0 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -83,6 +83,8 @@ "actual_qty", "installed_qty", "item_tax_rate", + "column_break_atna", + "received_qty", "accounting_details_section", "expense_account", "allow_zero_valuation_rate", @@ -832,13 +834,27 @@ "fieldname": "material_request_item", "fieldtype": "Data", "label": "Material Request Item" + }, + { + "fieldname": "column_break_atna", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: parent.is_internal_customer", + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-03-20 14:24:10.406746", + "modified": "2023-04-06 09:28:29.182053", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", From b79ddbbf60620ed5eb41ac1ea66389916a5696d0 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 6 Apr 2023 09:31:46 +0530 Subject: [PATCH 077/176] chore: add `Delivery Note Item` in Purchase Receipt `Status Updater` (cherry picked from commit 0d1df26b88ad81018bb1e2f3c8415e2e002c9172) --- .../stock/doctype/purchase_receipt/purchase_receipt.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c1abd31bcc1..d268cc11963 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -65,6 +65,16 @@ class PurchaseReceipt(BuyingController): "percent_join_field": "purchase_invoice", "overflow_type": "receipt", }, + { + "source_dt": "Purchase Receipt Item", + "target_dt": "Delivery Note Item", + "join_field": "delivery_note_item", + "source_field": "received_qty", + "target_field": "received_qty", + "target_parent_dt": "Delivery Note", + "target_ref_field": "qty", + "overflow_type": "receipt", + }, ] if cint(self.is_return): From 769736ffea941debe841c574d569ac400cc3f953 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 6 Apr 2023 14:59:14 +0530 Subject: [PATCH 078/176] test: add test cases for internal PR received qty (cherry picked from commit a575bd50efbc93d151b76cf6d0d08ce87101d957) --- .../purchase_receipt/test_purchase_receipt.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b6341466f87..7567cfe98c5 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1544,6 +1544,72 @@ class TestPurchaseReceipt(FrappeTestCase): res = get_item_details(args) self.assertEqual(res.get("last_purchase_rate"), 100) + def test_validate_received_qty_for_internal_pr(self): + prepare_data_for_internal_transfer() + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + + # Step 1: Create Item + item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) + + # Step 2: Create Stock Entry (Material Receipt) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + make_stock_entry( + purpose="Material Receipt", + item_code=item.name, + qty=15, + company=company, + to_warehouse=from_warehouse, + ) + + # Step 3: Create Delivery Note with Internal Customer + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + dn = create_delivery_note( + item_code=item.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=100, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + # Step 4: Create Internal Purchase Receipt + from erpnext.controllers.status_updater import OverAllowanceError + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + + pr = make_inter_company_purchase_receipt(dn.name) + pr.items[0].qty = 15 + pr.items[0].from_warehouse = target_warehouse + pr.items[0].warehouse = to_warehouse + pr.items[0].rejected_warehouse = from_warehouse + pr.save() + + self.assertRaises(OverAllowanceError, pr.submit) + + # Step 5: Test Over Receipt Allowance + frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50) + + make_stock_entry( + purpose="Material Transfer", + item_code=item.name, + qty=5, + company=company, + from_warehouse=from_warehouse, + to_warehouse=target_warehouse, + ) + + pr.submit() + + frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 50abbded34b77c63498b837b3a2fc35e23525189 Mon Sep 17 00:00:00 2001 From: Hossein Yousefian <86075967+ihosseinu@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:49:43 +0330 Subject: [PATCH 079/176] 'Make Asset Movement' button translation fix (cherry picked from commit b70615ef18eb08a0ddba7fa14e6cb043219aa8ee) --- erpnext/assets/doctype/asset/asset_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js index 3d00eb74aa0..5f53b005aaf 100644 --- a/erpnext/assets/doctype/asset/asset_list.js +++ b/erpnext/assets/doctype/asset/asset_list.js @@ -36,7 +36,7 @@ frappe.listview_settings['Asset'] = { } }, onload: function(me) { - me.page.add_action_item('Make Asset Movement', function() { + me.page.add_action_item(__("Make Asset Movement"), function() { const assets = me.get_checked_items(); frappe.call({ method: "erpnext.assets.doctype.asset.asset.make_asset_movement", From 35c9493336a6a10802c8889dab9f2098636de203 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 29 Mar 2023 14:02:09 +0530 Subject: [PATCH 080/176] refactor: rewrite `batch.py` queries in `QB` (cherry picked from commit 517b5f856728748461096c568e2f17633f068d75) # Conflicts: # erpnext/stock/doctype/batch/batch.py --- erpnext/stock/doctype/batch/batch.py | 107 +++++++++++++++------------ 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 4a165212dce..023827c4347 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -6,7 +6,12 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last +<<<<<<< HEAD from frappe.utils import cint, flt, get_link_to_form, nowtime +======= +from frappe.query_builder.functions import CurDate, Sum, Timestamp +from frappe.utils import cint, flt, get_link_to_form +>>>>>>> 517b5f8567 (refactor: rewrite `batch.py` queries in `QB`) from frappe.utils.data import add_days from frappe.utils.jinja import render_template @@ -176,8 +181,11 @@ def get_batch_qty( :param warehouse: Optional - give qty for this warehouse :param item_code: Optional - give qty for this item""" + sle = frappe.qb.DocType("Stock Ledger Entry") + out = 0 if batch_no and warehouse: +<<<<<<< HEAD cond = "" if posting_date: @@ -186,39 +194,36 @@ def get_batch_qty( cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format( posting_date, posting_time +======= + query = ( + frappe.qb.from_(sle) + .select(Sum(sle.actual_qty)) + .where((sle.is_cancelled == 0) & (sle.warehouse == warehouse) & (sle.batch_no == batch_no)) + ) + + if posting_date and posting_time: + query = query.where( + Timestamp(sle.posting_date, sle.posting_time) <= Timestamp(posting_date, posting_time) +>>>>>>> 517b5f8567 (refactor: rewrite `batch.py` queries in `QB`) ) - out = float( - frappe.db.sql( - """select sum(actual_qty) - from `tabStock Ledger Entry` - where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format( - cond - ), - (warehouse, batch_no), - )[0][0] - or 0 - ) + out = query.run(as_list=True)[0][0] or 0 if batch_no and not warehouse: - out = frappe.db.sql( - """select warehouse, sum(actual_qty) as qty - from `tabStock Ledger Entry` - where is_cancelled = 0 and batch_no=%s - group by warehouse""", - batch_no, - as_dict=1, - ) + out = ( + frappe.qb.from_(sle) + .select(sle.warehouse, Sum(sle.actual_qty).as_("qty")) + .where((sle.is_cancelled == 0) & (sle.batch_no == batch_no)) + .groupby(sle.warehouse) + ).run(as_dict=True) if not batch_no and item_code and warehouse: - out = frappe.db.sql( - """select batch_no, sum(actual_qty) as qty - from `tabStock Ledger Entry` - where is_cancelled = 0 and item_code = %s and warehouse=%s - group by batch_no""", - (item_code, warehouse), - as_dict=1, - ) + out = ( + frappe.qb.from_(sle) + .select(sle.batch_no, Sum(sle.actual_qty).as_("qty")) + .where((sle.is_cancelled == 0) & (sle.item_code == item_code) & (sle.warehouse == warehouse)) + .groupby(sle.batch_no) + ).run(as_dict=True) return out @@ -314,40 +319,44 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None): def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - cond = "" + batch = frappe.qb.DocType("Batch") + sle = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(batch) + .join(sle) + .on(batch.batch_id == sle.batch_no) + .select( + batch.batch_id, + Sum(sle.actual_qty).as_("qty"), + ) + .where( + (sle.item_code == item_code) + & (sle.warehouse == warehouse) + & (sle.is_cancelled == 0) + & ((batch.expiry_date >= CurDate()) | (batch.expiry_date.isnull())) + ) + .groupby(batch.batch_id) + .orderby(batch.expiry_date, batch.creation) + ) + if serial_no and frappe.get_cached_value("Item", item_code, "has_batch_no"): serial_nos = get_serial_nos(serial_no) - batch = frappe.get_all( + batches = frappe.get_all( "Serial No", fields=["distinct batch_no"], filters={"item_code": item_code, "warehouse": warehouse, "name": ("in", serial_nos)}, ) - if not batch: + if not batches: validate_serial_no_with_batch(serial_nos, item_code) - if batch and len(batch) > 1: + if batches and len(batches) > 1: return [] - cond = " and `tabBatch`.name = %s" % (frappe.db.escape(batch[0].batch_no)) + query = query.where(batch.name == batches[0].batch_no) - return frappe.db.sql( - """ - select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty - from `tabBatch` - join `tabStock Ledger Entry` ignore index (item_code, warehouse) - on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no ) - where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s - and `tabStock Ledger Entry`.is_cancelled = 0 - and (`tabBatch`.expiry_date >= CURRENT_DATE or `tabBatch`.expiry_date IS NULL) {0} - group by batch_id - order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC - """.format( - cond - ), - (item_code, warehouse), - as_dict=True, - ) + return query.run(as_dict=True) def validate_serial_no_with_batch(serial_nos, item_code): From 5723a200c52db4aecef017cde5c0cd479c85e4af Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 8 Apr 2023 19:10:39 +0530 Subject: [PATCH 081/176] chore: `conflicts` --- erpnext/stock/doctype/batch/batch.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 023827c4347..3b9fe7b97cd 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -6,12 +6,8 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last -<<<<<<< HEAD -from frappe.utils import cint, flt, get_link_to_form, nowtime -======= from frappe.query_builder.functions import CurDate, Sum, Timestamp -from frappe.utils import cint, flt, get_link_to_form ->>>>>>> 517b5f8567 (refactor: rewrite `batch.py` queries in `QB`) +from frappe.utils import cint, flt, get_link_to_form, nowtime from frappe.utils.data import add_days from frappe.utils.jinja import render_template @@ -185,26 +181,18 @@ def get_batch_qty( out = 0 if batch_no and warehouse: -<<<<<<< HEAD - cond = "" - - if posting_date: - if posting_time is None: - posting_time = nowtime() - - cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format( - posting_date, posting_time -======= query = ( frappe.qb.from_(sle) .select(Sum(sle.actual_qty)) .where((sle.is_cancelled == 0) & (sle.warehouse == warehouse) & (sle.batch_no == batch_no)) ) - if posting_date and posting_time: + if posting_date: + if posting_time is None: + posting_time = nowtime() + query = query.where( Timestamp(sle.posting_date, sle.posting_time) <= Timestamp(posting_date, posting_time) ->>>>>>> 517b5f8567 (refactor: rewrite `batch.py` queries in `QB`) ) out = query.run(as_list=True)[0][0] or 0 From cc21241887621283c7dd0f79281fd24d625cf3b4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 9 Apr 2023 18:55:11 +0530 Subject: [PATCH 082/176] fix: Item tax validity comparison fixes (#34784) fix: Item tax validity comparison fixes (#34784) fix: Item tax validity comparsion fixes (cherry picked from commit 6f6928fa7bb20959b34d82e283dd80b1956c9a26) Co-authored-by: Deepesh Garg --- erpnext/setup/doctype/item_group/item_group.py | 16 ++++++++++++++++ erpnext/stock/doctype/item/item.py | 11 ++++++++--- erpnext/stock/get_item_details.py | 4 +++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 2eca5cad8e2..f5432c18258 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -36,8 +36,24 @@ class ItemGroup(NestedSet, WebsiteGenerator): self.make_route() self.validate_item_group_defaults() + self.check_item_tax() ECommerceSettings.validate_field_filters(self.filter_fields, enable_field_filters=True) + def check_item_tax(self): + """Check whether Tax Rate is not entered twice for same Tax Type""" + check_list = [] + for d in self.get("taxes"): + if d.item_tax_template: + if (d.item_tax_template, d.tax_category) in check_list: + frappe.throw( + _("{0} entered twice {1} in Item Taxes").format( + frappe.bold(d.item_tax_template), + "for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "", + ) + ) + else: + check_list.append((d.item_tax_template, d.tax_category)) + def on_update(self): NestedSet.on_update(self) invalidate_cache_for(self) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 423b9defc19..8a25be58618 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -350,10 +350,15 @@ class Item(Document): check_list = [] for d in self.get("taxes"): if d.item_tax_template: - if d.item_tax_template in check_list: - frappe.throw(_("{0} entered twice in Item Tax").format(d.item_tax_template)) + if (d.item_tax_template, d.tax_category) in check_list: + frappe.throw( + _("{0} entered twice {1} in Item Taxes").format( + frappe.bold(d.item_tax_template), + "for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "", + ) + ) else: - check_list.append(d.item_tax_template) + check_list.append((d.item_tax_template, d.tax_category)) def validate_barcode(self): from stdnum import ean diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2df39c81832..2cf3797a36b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -637,7 +637,9 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): taxes_with_no_validity.append(tax) if taxes_with_validity: - taxes = sorted(taxes_with_validity, key=lambda i: i.valid_from, reverse=True) + taxes = sorted( + taxes_with_validity, key=lambda i: i.valid_from or tax.maximum_net_rate, reverse=True + ) else: taxes = taxes_with_no_validity From 3023dbbe9500e540a75dfde8d3da96dba609014f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 9 Apr 2023 20:13:36 +0530 Subject: [PATCH 083/176] fix: add german translation of "Partly Paid" (#34776) fix: add german translation of "Partly Paid" (#34776) (cherry picked from commit 934e1b4e6a7a95544cb3226535cf31a22d6552bd) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/translations/de.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 8472d04f1ca..ac6ccf99c6e 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1875,6 +1875,7 @@ Parents Teacher Meeting Attendance,Eltern Lehrer Treffen Teilnahme, Part-time,Teilzeit, Partially Depreciated,Teilweise abgeschrieben, Partially Received,Teilweise erhalten, +Partly Paid,Teilweise bezahlt, Party,Partei, Party Name,Name der Partei, Party Type,Partei-Typ, From fee4cd5f40c462a76b22414d2c6f7952f8c09cdb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 13:50:04 +0530 Subject: [PATCH 084/176] fix: provide filter by depreciable assets in fixed asset register (#34803) fix: provide filter by depreciable assets in fixed asset register (#34803) (cherry picked from commit c957a5cd2eda6d2ab589919c20dd56245589e140) Co-authored-by: Anand Baburajan --- .../fixed_asset_register.js | 24 +++++++++----- .../fixed_asset_register.py | 33 ++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 06989a95da7..65a4226ebdf 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -24,7 +24,7 @@ frappe.query_reports["Fixed Asset Register"] = { "label": __("Period Based On"), "fieldtype": "Select", "options": ["Fiscal Year", "Date Range"], - "default": ["Fiscal Year"], + "default": "Fiscal Year", "reqd": 1 }, { @@ -75,12 +75,6 @@ frappe.query_reports["Fixed Asset Register"] = { fieldtype: "Link", options: "Asset Category" }, - { - fieldname:"finance_book", - label: __("Finance Book"), - fieldtype: "Link", - options: "Finance Book" - }, { fieldname:"cost_center", label: __("Cost Center"), @@ -96,8 +90,20 @@ frappe.query_reports["Fixed Asset Register"] = { reqd: 1 }, { - fieldname:"is_existing_asset", - label: __("Is Existing Asset"), + fieldname:"finance_book", + label: __("Finance Book"), + fieldtype: "Link", + options: "Finance Book", + depends_on: "eval: doc.only_depreciable_assets == 1", + }, + { + fieldname:"only_depreciable_assets", + label: __("Only depreciable assets"), + fieldtype: "Check" + }, + { + fieldname:"only_existing_assets", + label: __("Only existing assets"), fieldtype: "Check" }, ] 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 ae99c491a93..63f9889f054 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -45,8 +45,10 @@ def get_conditions(filters): filters.year_end_date = getdate(fiscal_year.year_end_date) conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]] - if filters.get("is_existing_asset"): - conditions["is_existing_asset"] = filters.get("is_existing_asset") + if filters.get("only_depreciable_assets"): + conditions["calculate_depreciation"] = filters.get("only_depreciable_assets") + if filters.get("only_existing_assets"): + conditions["is_existing_asset"] = filters.get("only_existing_assets") if filters.get("asset_category"): conditions["asset_category"] = filters.get("asset_category") if filters.get("cost_center"): @@ -102,19 +104,18 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - assets_linked_to_fb = frappe.db.get_all( - doctype="Asset Finance Book", - filters={"finance_book": filters.finance_book or ("is", "not set")}, - pluck="parent", - ) + assets_linked_to_fb = None + + if filters.only_depreciable_assets: + assets_linked_to_fb = frappe.db.get_all( + doctype="Asset Finance Book", + filters={"finance_book": filters.finance_book or ("is", "not set")}, + pluck="parent", + ) for asset in assets_record: - if filters.finance_book: - if asset.asset_id not in assets_linked_to_fb: - continue - else: - if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb: - continue + if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb: + continue asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book) row = { @@ -172,11 +173,11 @@ def prepare_chart_data(data, filters): "datasets": [ { "name": _("Asset Value"), - "values": [d.get("asset_value") for d in labels_values_map.values()], + "values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()], }, { "name": _("Depreciatied Amount"), - "values": [d.get("depreciated_amount") for d in labels_values_map.values()], + "values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()], }, ], }, @@ -310,7 +311,7 @@ def get_columns(filters): return [ { - "label": _("Asset Id"), + "label": _("Asset ID"), "fieldtype": "Link", "fieldname": "asset_id", "options": "Asset", From 994272b9664200a1fceea77eba05f2b739bd3c0b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Apr 2023 14:17:27 +0530 Subject: [PATCH 085/176] fix: customer selection not mandatory in purchase invoice to fetch item details (#34810) --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 07d1955bfaf..e58dd98efd4 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1696,7 +1696,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } $.each(["company", "customer"], function(i, fieldname) { - if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && me.frm.doc.doctype != "Purchase Order") { + if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && !["Purchase Order","Purchase Invoice"].includes(me.frm.doc.doctype)) { if (!me.frm.doc[fieldname]) { frappe.msgprint(__("Please specify") + ": " + frappe.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name) + From 9b90323d539df3043293954b27943c05a0eee791 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 11 Apr 2023 13:24:43 +0530 Subject: [PATCH 086/176] fix: reposting record not created for backdated stock reco (cherry picked from commit 6851b5ba974a3460fca7af2e66440f64f6361223) --- .../stock_reconciliation.py | 16 +++- .../test_stock_reconciliation.py | 73 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 1 + 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 482b103d1e4..e304bd18193 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -5,7 +5,7 @@ from typing import Optional import frappe from frappe import _, bold, msgprint -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import cint, cstr, flt import erpnext @@ -575,7 +575,9 @@ class StockReconciliation(StockController): if not (row.item_code == item_code and row.batch_no == batch_no): continue - row.current_qty = get_batch_qty_for_stock_reco(item_code, row.warehouse, batch_no) + row.current_qty = get_batch_qty_for_stock_reco( + item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name + ) qty, val_rate = get_stock_balance( item_code, @@ -596,7 +598,9 @@ class StockReconciliation(StockController): ) -def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no): +def get_batch_qty_for_stock_reco( + item_code, warehouse, batch_no, posting_date, posting_time, voucher_no +): ledger = frappe.qb.DocType("Stock Ledger Entry") query = ( @@ -610,6 +614,12 @@ def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no): & (ledger.docstatus == 1) & (ledger.is_cancelled == 0) & (ledger.batch_no == batch_no) + & (ledger.posting_date <= posting_date) + & ( + CombineDatetime(ledger.posting_date, ledger.posting_time) + <= CombineDatetime(posting_date, posting_time) + ) + & (ledger.voucher_no != voucher_no) ) .groupby(ledger.batch_no) ) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index eaea301432e..7d59441d8b7 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -676,6 +676,79 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(flt(sl_entry.actual_qty), 1.0) self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0) + def test_backdated_stock_reco_entry(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item_code = self.make_item( + "Test New Batch Item ABCV", + { + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "BNS9.####", + "create_new_batch": 1, + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + # Added 100 Qty, Balace Qty 100 + se1 = make_stock_entry( + item_code=item_code, posting_time="09:00:00", target=warehouse, qty=100, basic_rate=700 + ) + + # Removed 50 Qty, Balace Qty 50 + se2 = make_stock_entry( + item_code=item_code, + batch_no=se1.items[0].batch_no, + posting_time="10:00:00", + source=warehouse, + qty=50, + basic_rate=700, + ) + + # Stock Reco for 100, Balace Qty 100 + stock_reco = create_stock_reconciliation( + item_code=item_code, + posting_time="11:00:00", + warehouse=warehouse, + batch_no=se1.items[0].batch_no, + qty=100, + rate=100, + ) + + # Removed 50 Qty, Balace Qty 50 + make_stock_entry( + item_code=item_code, + batch_no=se1.items[0].batch_no, + posting_time="12:00:00", + source=warehouse, + qty=50, + basic_rate=700, + ) + + self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name})) + + # Cancel the backdated Stock Entry se2, + # Since Stock Reco entry in the future the Balace Qty should remain as it's (50) + + se2.cancel() + + self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name})) + + self.assertEqual( + frappe.db.get_value("Repost Item Valuation", {"voucher_no": stock_reco.name}, "status"), + "Completed", + ) + + sle = frappe.get_all( + "Stock Ledger Entry", + filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0}, + fields=["qty_after_transaction"], + order_by="posting_time desc, creation desc", + ) + + self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0)) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c954befdc29..b0a093def49 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1375,6 +1375,7 @@ def regenerate_sle_for_batch_stock_reco(detail): doc.recalculate_current_qty(detail.item_code, detail.batch_no) doc.docstatus = 1 doc.update_stock_ledger() + doc.repost_future_sle_and_gle() def get_stock_reco_qty_shift(args): From 8a331e0f261e668cee04f4e66f281ed7cd850ad7 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Tue, 11 Apr 2023 16:10:32 +0530 Subject: [PATCH 087/176] revert: remove frappe.send_message (v14) (#34816) revert: remove frappe.send_message --- erpnext/hooks.py | 4 ---- erpnext/public/js/website_utils.js | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c4032596f47..f1ee370e97e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -30,10 +30,6 @@ doctype_js = { override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"} -override_whitelisted_methods = { - "frappe.www.contact.send_message": "erpnext.templates.utils.send_message" -} - welcome_email = "erpnext.setup.utils.welcome_email" # setup wizard diff --git a/erpnext/public/js/website_utils.js b/erpnext/public/js/website_utils.js index 2bb5255eebc..b5416065d79 100644 --- a/erpnext/public/js/website_utils.js +++ b/erpnext/public/js/website_utils.js @@ -3,6 +3,18 @@ if(!window.erpnext) window.erpnext = {}; +// Add / update a new Lead / Communication +// subject, sender, description +frappe.send_message = function(opts, btn) { + return frappe.call({ + type: "POST", + method: "erpnext.templates.utils.send_message", + btn: btn, + args: opts, + callback: opts.callback + }); +}; + erpnext.subscribe_to_newsletter = function(opts, btn) { return frappe.call({ type: "POST", @@ -12,3 +24,6 @@ erpnext.subscribe_to_newsletter = function(opts, btn) { callback: opts.callback }); } + +// for backward compatibility +erpnext.send_message = frappe.send_message; From 41a7d3fd605c99d96195783d51e1bc2c3fff3b4d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:39:07 +0530 Subject: [PATCH 088/176] chore: update CODEOWNERS (#34817) * chore: update CODEOWNERS [skip ci] (cherry picked from commit aa8b241d5a7767911c585bd244c60b6fafbf0f30) # Conflicts: # CODEOWNERS * fix: conflicts --------- Co-authored-by: Saqib Ansari --- CODEOWNERS | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 465337a0c5c..7f8c4d1ac87 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,13 +3,13 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar +erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/assets/ @anandbaburajan @deepeshgarg007 -erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 -erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar -erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar -erpnext/support/ @nextchamp-saqib @deepeshgarg007 -pos* @nextchamp-saqib +erpnext/loan_management/ @deepeshgarg007 +erpnext/regional @deepeshgarg007 @ruthra-kumar +erpnext/selling @deepeshgarg007 @ruthra-kumar +erpnext/support/ @deepeshgarg007 +pos* erpnext/buying/ @rohitwaghchaure @s-aga-r erpnext/maintenance/ @rohitwaghchaure @s-aga-r @@ -18,12 +18,8 @@ erpnext/quality_management/ @rohitwaghchaure @s-aga-r erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/subcontracting @rohitwaghchaure @s-aga-r -erpnext/crm/ @NagariaHussain -erpnext/education/ @rutwikhdev -erpnext/projects/ @ruchamahabal +erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure +erpnext/patches/ @deepeshgarg007 -erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure -erpnext/patches/ @deepeshgarg007 @nextchamp-saqib - -.github/ @ankush +.github/ @deepeshgarg007 pyproject.toml @ankush From 0cf6144b3f6e66ad160e29759aed666225f79af0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:26:14 +0530 Subject: [PATCH 089/176] fix: Remove unnecessary checkbox from Accounts doctype (#34821) fix: Remove unnecessary checkbox from Accounts doctype (#34821) (cherry picked from commit 66130493ebb7bc5d4fa69f5fd96cd3cebcbacb21) Co-authored-by: Deepesh Garg --- erpnext/accounts/doctype/account/account.json | 88 +++++-------------- .../journal_entry/test_journal_entry.py | 4 - .../general_ledger/test_general_ledger.py | 1 - 3 files changed, 20 insertions(+), 73 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index d2659d429b0..e79fb660625 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -18,7 +18,6 @@ "root_type", "report_type", "account_currency", - "inter_company_account", "column_break1", "parent_account", "account_type", @@ -34,15 +33,11 @@ { "fieldname": "properties", "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "fieldname": "column_break0", "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -53,9 +48,7 @@ "no_copy": 1, "oldfieldname": "account_name", "oldfieldtype": "Data", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "account_number", @@ -63,17 +56,13 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Account Number", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "fieldname": "is_group", "fieldtype": "Check", - "label": "Is Group", - "show_days": 1, - "show_seconds": 1 + "label": "Is Group" }, { "fieldname": "company", @@ -85,9 +74,7 @@ "options": "Company", "read_only": 1, "remember_last_selected_value": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "root_type", @@ -95,9 +82,7 @@ "in_standard_filter": 1, "label": "Root Type", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "report_type", @@ -105,32 +90,18 @@ "in_standard_filter": 1, "label": "Report Type", "options": "\nBalance Sheet\nProfit and Loss", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:doc.is_group==0", "fieldname": "account_currency", "fieldtype": "Link", "label": "Currency", - "options": "Currency", - "show_days": 1, - "show_seconds": 1 - }, - { - "default": "0", - "fieldname": "inter_company_account", - "fieldtype": "Check", - "label": "Inter Company Account", - "show_days": 1, - "show_seconds": 1 + "options": "Currency" }, { "fieldname": "column_break1", "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -142,9 +113,7 @@ "oldfieldtype": "Link", "options": "Account", "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "description": "Setting Account Type helps in selecting this Account in transactions.", @@ -154,9 +123,7 @@ "label": "Account Type", "oldfieldname": "account_type", "oldfieldtype": "Select", - "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary", - "show_days": 1, - "show_seconds": 1 + "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary" }, { "description": "Rate at which this tax is applied", @@ -164,9 +131,7 @@ "fieldtype": "Float", "label": "Rate", "oldfieldname": "tax_rate", - "oldfieldtype": "Currency", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Currency" }, { "description": "If the account is frozen, entries are allowed to restricted users.", @@ -175,17 +140,13 @@ "label": "Frozen", "oldfieldname": "freeze_account", "oldfieldtype": "Select", - "options": "No\nYes", - "show_days": 1, - "show_seconds": 1 + "options": "No\nYes" }, { "fieldname": "balance_must_be", "fieldtype": "Select", "label": "Balance must be", - "options": "\nDebit\nCredit", - "show_days": 1, - "show_seconds": 1 + "options": "\nDebit\nCredit" }, { "fieldname": "lft", @@ -194,9 +155,7 @@ "label": "Lft", "print_hide": 1, "read_only": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "rgt", @@ -205,9 +164,7 @@ "label": "Rgt", "print_hide": 1, "read_only": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "old_parent", @@ -215,33 +172,27 @@ "hidden": 1, "label": "Old Parent", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "fieldname": "include_in_gross", "fieldtype": "Check", - "label": "Include in gross", - "show_days": 1, - "show_seconds": 1 + "label": "Include in gross" }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disable", - "show_days": 1, - "show_seconds": 1 + "label": "Disable" } ], "icon": "fa fa-money", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-06-11 15:15:54.338622", + "modified": "2023-04-11 16:08:46.983677", "modified_by": "Administrator", "module": "Accounts", "name": "Account", @@ -301,5 +252,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 2cc5378e927..f7297d19e0f 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -287,10 +287,6 @@ class TestJournalEntry(unittest.TestCase): jv.submit() def test_inter_company_jv(self): - frappe.db.set_value("Account", "Sales Expenses - _TC", "inter_company_account", 1) - frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1) - frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1) - frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1) jv = make_journal_entry( "Sales Expenses - _TC", "Buildings - _TC", diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index c5637857636..a8c362e78c1 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -24,7 +24,6 @@ class TestGeneralLedger(FrappeTestCase): "root_type": "Asset", "report_type": "Balance Sheet", "account_currency": "USD", - "inter_company_account": 0, "parent_account": "Bank Accounts - _TC", "account_type": "Bank", "doctype": "Account", From c11aebaaae7edb910d29309cb7d1a9bbc63842ee Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:26:33 +0530 Subject: [PATCH 090/176] fix: Don't use stale item details (#34847) fix: Don't use stale item details (#34847) (cherry picked from commit a7051cb9b59d3f86670a2cb5968af9585a083ab9) Co-authored-by: Deepesh Garg --- erpnext/controllers/accounts_controller.py | 13 +------------ erpnext/stock/get_item_details.py | 22 +++------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a347323e358..7fcc28bac36 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -515,7 +515,6 @@ class AccountsController(TransactionBase): parent_dict.update({"customer": parent_dict.get("party_name")}) self.pricing_rules = [] - basic_item_details_map = {} for item in self.get("items"): if item.get("item_code"): @@ -535,17 +534,7 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - basic_details = basic_item_details_map.get(item.item_code) - ret, basic_item_details = get_item_details( - args, - self, - for_validate=True, - overwrite_warehouse=False, - return_basic_details=True, - basic_details=basic_details, - ) - - basic_item_details_map.setdefault(item.item_code, basic_item_details) + ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2cf3797a36b..ce85702f486 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -35,14 +35,7 @@ purchase_doctypes = [ @frappe.whitelist() -def get_item_details( - args, - doc=None, - for_validate=False, - overwrite_warehouse=True, - return_basic_details=False, - basic_details=None, -): +def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): """ args = { "item_code": "", @@ -80,12 +73,7 @@ def get_item_details( if doc.get("doctype") == "Purchase Invoice": args["bill_date"] = doc.get("bill_date") - if not basic_details: - out = get_basic_details(args, item, overwrite_warehouse) - else: - out = basic_details - - basic_details = out.copy() + out = get_basic_details(args, item, overwrite_warehouse) get_item_tax_template(args, item, out) out["item_tax_rate"] = get_item_tax_map( @@ -154,11 +142,7 @@ def get_item_details( out.amount = flt(args.qty) * flt(out.rate) out = remove_standard_fields(out) - - if return_basic_details: - return out, basic_details - else: - return out + return out def remove_standard_fields(details): From 3738ea5794dd5e56bc18237dc4b4afeb482dcf85 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 14:52:02 +0530 Subject: [PATCH 091/176] feat: add german sales tax template (#34823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: add german sales tax template (#34823) Nullsteuersatz nach § 12 Abs. 3 UStG (cherry picked from commit 59f6b773cdb0eb0964c7b27123bb2e03cf447d93) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../setup_wizard/data/country_wise_tax.json | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 45e39c5bd0b..c6efbb78125 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -581,6 +581,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ @@ -1339,6 +1344,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ @@ -2097,6 +2107,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ @@ -2849,6 +2864,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ From 15f5e8d4ffba5ea76470c1c3c371ddf1c569a711 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:42:12 +0530 Subject: [PATCH 092/176] fix(ux): don't throw error when company defaults aren't set (#34825) fix(ux): don't throw error when company defaults aren't set (#34825) * fix(ux): don't throw error when company defaults aren't set; instead prompt account input. * fix: translate label and title (cherry picked from commit 51c4338661817267e383b094ad802da4cfa87940) Co-authored-by: Devin Slauenwhite --- .../doctype/payment_entry/payment_entry.js | 52 +++++++++++++------ .../doctype/payment_entry/payment_entry.py | 12 +---- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 5a56a6b0046..00a948a8387 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -971,29 +971,47 @@ frappe.ui.form.on('Payment Entry', { }, callback: function(r, rt) { if(r.message) { - var write_off_row = $.map(frm.doc["deductions"] || [], function(t) { + const write_off_row = $.map(frm.doc["deductions"] || [], function(t) { return t.account==r.message[account] ? t : null; }); - var row = []; - - var difference_amount = flt(frm.doc.difference_amount, + const difference_amount = flt(frm.doc.difference_amount, precision("difference_amount")); - if (!write_off_row.length && difference_amount) { - row = frm.add_child("deductions"); - row.account = r.message[account]; - row.cost_center = r.message["cost_center"]; - } else { - row = write_off_row[0]; - } + const add_deductions = (details) => { + if (!write_off_row.length && difference_amount) { + row = frm.add_child("deductions"); + row.account = details[account]; + row.cost_center = details["cost_center"]; + } else { + row = write_off_row[0]; + } - if (row) { - row.amount = flt(row.amount) + difference_amount; - } else { - frappe.msgprint(__("No gain or loss in the exchange rate")) - } + if (row) { + row.amount = flt(row.amount) + difference_amount; + } else { + frappe.msgprint(__("No gain or loss in the exchange rate")) + } + refresh_field("deductions"); + }; - refresh_field("deductions"); + if (!r.message[account]) { + frappe.prompt({ + label: __("Please Specify Account"), + fieldname: account, + fieldtype: "Link", + options: "Account", + get_query: () => ({ + filters: { + company: frm.doc.company, + } + }) + }, (values) => { + const details = Object.assign({}, r.message, values); + add_deductions(details); + }, __(frappe.unscrub(account))); + } else { + add_deductions(r.message); + } frm.events.set_unallocated_amount(frm); } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 58ed7d1822c..e3eb1010016 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1594,17 +1594,7 @@ def get_account_details(account, date, cost_center=None): @frappe.whitelist() def get_company_defaults(company): fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"] - ret = frappe.get_cached_value("Company", company, fields, as_dict=1) - - for fieldname in fields: - if not ret[fieldname]: - frappe.throw( - _("Please set default {0} in Company {1}").format( - frappe.get_meta("Company").get_label(fieldname), company - ) - ) - - return ret + return frappe.get_cached_value("Company", company, fields, as_dict=1) def get_outstanding_on_journal_entry(name): From 7c8194a1a86283e52b60923143ac883f880043d1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:42:37 +0530 Subject: [PATCH 093/176] fix: for Tree Type item and item group show net amount (#31776) fix: for Tree Type item and item group show net amout (cherry picked from commit 91762097a54bfcbb5d693a3feb4238b0ae28cf78) Co-authored-by: hrzzz --- erpnext/selling/report/sales_analytics/sales_analytics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index b08f1deefe3..605d2fac44f 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -168,7 +168,7 @@ class Analytics(object): def get_sales_transactions_based_on_items(self): if self.filters["value_quantity"] == "Value": - value_field = "base_amount" + value_field = "base_net_amount" else: value_field = "stock_qty" @@ -216,7 +216,7 @@ class Analytics(object): def get_sales_transactions_based_on_item_group(self): if self.filters["value_quantity"] == "Value": - value_field = "base_amount" + value_field = "base_net_amount" else: value_field = "qty" From 2e7043ca9087dc386303c00f1064508afe28f0d6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 14 Apr 2023 15:59:35 +0530 Subject: [PATCH 094/176] fix: unable to change `company` for manual `Serial No` entry (cherry picked from commit fb3271c6249d647c2add2a83c06cbbb879762b81) --- erpnext/stock/doctype/serial_no/serial_no.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 6e1e0d461ab..7989b1ac75b 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -410,10 +410,10 @@ "fieldtype": "Link", "label": "Company", "options": "Company", - "read_only": 1, "remember_last_selected_value": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "set_only_once": 1 }, { "fieldname": "status", @@ -433,7 +433,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2021-12-23 10:44:30.299450", + "modified": "2023-04-14 15:58:46.139887", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", @@ -461,7 +461,6 @@ "read": 1, "report": 1, "role": "Stock Manager", - "set_user_permissions": 1, "write": 1 }, { From 79fd38cf3fbd5dd91c6b0080b3e3420407df7987 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 16 Apr 2023 13:10:42 +0530 Subject: [PATCH 095/176] fix: selling workspace is not migrating properly (cherry picked from commit 5a4dd354c1662db7a7c2ed7312a04833a4822223) --- erpnext/selling/workspace/selling/selling.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index 45e160d143e..180a3d783e0 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -704,7 +704,7 @@ "type": "Link" } ], - "modified": "2022-04-26 13:29:55.087240", + "modified": "2023-04-16 13:29:55.087240", "modified_by": "Administrator", "module": "Selling", "name": "Selling", From 461780da222ce2bf9c22a8279fab2da8df7b59fe Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Apr 2023 11:05:32 +0530 Subject: [PATCH 096/176] fix: don't show disabled warehouses in the Warehouse Wise Stock Balance report (cherry picked from commit 9ceb1f6bda093061c7f8d70d6ab169258d4815a9) --- .../warehouse_wise_stock_balance.js | 7 +++++ .../warehouse_wise_stock_balance.py | 27 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js index 58a043ec20d..752e464e27c 100644 --- a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js @@ -11,6 +11,13 @@ frappe.query_reports["Warehouse Wise Stock Balance"] = { "options": "Company", "reqd": 1, "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname":"show_disabled_warehouses", + "label": __("Show Disabled Warehouses"), + "fieldtype": "Check", + "default": 0 + } ], "initial_depth": 3, diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py index d364b577a26..a0e994482f8 100644 --- a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py @@ -11,6 +11,7 @@ from frappe.query_builder.functions import Sum class StockBalanceFilter(TypedDict): company: Optional[str] warehouse: Optional[str] + show_disabled_warehouses: Optional[int] SLEntry = Dict[str, Any] @@ -18,7 +19,7 @@ SLEntry = Dict[str, Any] def execute(filters=None): columns, data = [], [] - columns = get_columns() + columns = get_columns(filters) data = get_data(filters) return columns, data @@ -42,10 +43,14 @@ def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]: def get_warehouses(report_filters: StockBalanceFilter): + filters = {"company": report_filters.company, "disabled": 0} + if report_filters.get("show_disabled_warehouses"): + filters["disabled"] = ("in", [0, report_filters.show_disabled_warehouses]) + return frappe.get_all( "Warehouse", - fields=["name", "parent_warehouse", "is_group"], - filters={"company": report_filters.company}, + fields=["name", "parent_warehouse", "is_group", "disabled"], + filters=filters, order_by="lft", ) @@ -90,8 +95,8 @@ def set_balance_in_parent(warehouses): update_balance(warehouse, warehouse.stock_balance) -def get_columns(): - return [ +def get_columns(filters: StockBalanceFilter) -> List[Dict]: + columns = [ { "label": _("Warehouse"), "fieldname": "name", @@ -101,3 +106,15 @@ def get_columns(): }, {"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150}, ] + + if filters.get("show_disabled_warehouses"): + columns.append( + { + "label": _("Warehouse Disabled?"), + "fieldname": "disabled", + "fieldtype": "Check", + "width": 200, + } + ) + + return columns From a981b79865c6ccd0938692a6a133e00cd0bc52e7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Apr 2023 12:22:19 +0530 Subject: [PATCH 097/176] fix: too many writes error while making backdated stock reconciliation (cherry picked from commit 7bfc8f12367c14245682b32bb0853bbf044114c4) --- .../stock_reconciliation.py | 26 ++++++++++++++----- erpnext/stock/stock_ledger.py | 24 +++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e304bd18193..3fd4cec5d88 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -571,24 +571,33 @@ class StockReconciliation(StockController): self._cancel() def recalculate_current_qty(self, item_code, batch_no): + from erpnext.stock.stock_ledger import get_valuation_rate + + sl_entries = [] for row in self.items: if not (row.item_code == item_code and row.batch_no == batch_no): continue - row.current_qty = get_batch_qty_for_stock_reco( + current_qty = get_batch_qty_for_stock_reco( item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name ) - qty, val_rate = get_stock_balance( - item_code, - row.warehouse, - self.posting_date, - self.posting_time, - with_valuation_rate=True, + precesion = row.precision("current_qty") + if flt(current_qty, precesion) == flt(row.current_qty, precesion): + continue + + val_rate = get_valuation_rate( + item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no ) row.current_valuation_rate = val_rate + if not row.current_qty and current_qty: + sle = self.get_sle_for_items(row) + sle.actual_qty = current_qty * -1 + sle.valuation_rate = val_rate + sl_entries.append(sle) + row.current_qty = current_qty row.db_set( { "current_qty": row.current_qty, @@ -597,6 +606,9 @@ class StockReconciliation(StockController): } ) + if sl_entries: + self.make_sl_entries(sl_entries) + def get_batch_qty_for_stock_reco( item_code, warehouse, batch_no, posting_date, posting_time, voucher_no diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b0a093def49..a605b0c24a9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -544,6 +544,14 @@ class update_entries_after(object): if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) + if ( + sle.voucher_type == "Stock Reconciliation" + and sle.batch_no + and sle.voucher_detail_no + and sle.actual_qty < 0 + ): + self.reset_actual_qty_for_stock_reco(sle) + if ( sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and sle.voucher_detail_no @@ -605,6 +613,16 @@ class update_entries_after(object): if not self.args.get("sle_id"): self.update_outgoing_rate_on_transaction(sle) + def reset_actual_qty_for_stock_reco(self, sle): + current_qty = frappe.get_cached_value( + "Stock Reconciliation Item", sle.voucher_detail_no, "current_qty" + ) + + if current_qty: + sle.actual_qty = current_qty * -1 + elif current_qty == 0: + sle.is_cancelled = 1 + def validate_negative_stock(self, sle): """ validate negative stock for entries current datetime onwards @@ -1369,12 +1387,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): def regenerate_sle_for_batch_stock_reco(detail): doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) - doc.docstatus = 2 - doc.update_stock_ledger() - doc.recalculate_current_qty(detail.item_code, detail.batch_no) - doc.docstatus = 1 - doc.update_stock_ledger() doc.repost_future_sle_and_gle() @@ -1416,6 +1429,7 @@ def get_next_stock_reco(args): and voucher_type = 'Stock Reconciliation' and voucher_no != %(voucher_no)s and is_cancelled = 0 + and batch_no = %(batch_no)s and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) or ( timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) From c53dc06f80cd86fcaa7c92dc8de42526f6b6c6c4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Apr 2023 13:00:12 +0530 Subject: [PATCH 098/176] fix: linters issues (cherry picked from commit d9dd64b4d2fcce9ffb6d2f21b5a9f56c39580b02) --- erpnext/controllers/stock_controller.py | 13 ++++-- erpnext/stock/stock_ledger.py | 61 +++++++++++++++---------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1e4fabe0d26..479fef72c66 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -859,6 +859,8 @@ def is_reposting_pending(): def future_sle_exists(args, sl_entries=None): key = (args.voucher_type, args.voucher_no) + if not hasattr(frappe.local, "future_sle"): + frappe.local.future_sle = {} if validate_future_sle_not_exists(args, key, sl_entries): return False @@ -892,6 +894,9 @@ def future_sle_exists(args, sl_entries=None): ) for d in data: + if key not in frappe.local.future_sle: + frappe.local.future_sle[key] = frappe._dict({}) + frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row return len(data) @@ -903,6 +908,9 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): item_key = (args.get("item_code"), args.get("warehouse")) if not sl_entries and hasattr(frappe.local, "future_sle"): + if key not in frappe.local.future_sle: + return False + if not frappe.local.future_sle.get(key) or ( item_key and item_key not in frappe.local.future_sle.get(key) ): @@ -910,11 +918,8 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): def get_cached_data(args, key): - if not hasattr(frappe.local, "future_sle"): - frappe.local.future_sle = {} - if key not in frappe.local.future_sle: - frappe.local.future_sle[key] = frappe._dict({}) + return False if args.get("item_code"): item_key = (args.get("item_code"), args.get("warehouse")) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index a605b0c24a9..03c04a54adc 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1414,35 +1414,50 @@ def get_stock_reco_qty_shift(args): return stock_reco_qty_shift -def get_next_stock_reco(args): +def get_next_stock_reco(kwargs): """Returns next nearest stock reconciliaton's details.""" - return frappe.db.sql( - """ - select - name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty - from - `tabStock Ledger Entry` - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and voucher_type = 'Stock Reconciliation' - and voucher_no != %(voucher_no)s - and is_cancelled = 0 - and batch_no = %(batch_no)s - and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) - or ( - timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) - and creation > %(creation)s + sle = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.creation, + sle.voucher_no, + sle.item_code, + sle.batch_no, + sle.actual_qty, + ) + .where( + (sle.item_code == kwargs.get("item_code")) + & (sle.warehouse == kwargs.get("warehouse")) + & (sle.voucher_type == "Stock Reconciliation") + & (sle.voucher_no != kwargs.get("voucher_no")) + & (sle.is_cancelled == 0) + & ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + > CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + | ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + == CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + ) + & (sle.creation > kwargs.get("creation")) + ) ) ) - order by timestamp(posting_date, posting_time) asc, creation asc - limit 1 - """, - args, - as_dict=1, + ) ) + if kwargs.get("batch_no"): + query.where(sle.batch_no == kwargs.get("batch_no")) + + return query.run(as_dict=True) + def get_datetime_limit_condition(detail): return f""" From 2f356dcc6cbb74660f95efdd40d3d1ba401e30b5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Apr 2023 14:22:27 +0530 Subject: [PATCH 099/176] fix: stock reco test case (cherry picked from commit 6bccd8644e99b3750d643a7d98ee75e2f79cef9a) --- erpnext/stock/stock_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 03c04a54adc..b638f08ed9b 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1451,6 +1451,8 @@ def get_next_stock_reco(kwargs): ) ) ) + .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) + .orderby(sle.creation) ) if kwargs.get("batch_no"): From 74612392183623a9c234e49066f8d8b7873d6c62 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:54:54 +0530 Subject: [PATCH 100/176] fix: Advance payment against payment terms (#34872) * fix: Advance payment against payment terms (#34872) (cherry picked from commit 5c75894065cd0d20be33eeed43a8f0d6e9ed3656) # Conflicts: # erpnext/accounts/doctype/payment_entry/payment_entry.py * chore: resolve conflicts --------- Co-authored-by: Deepesh Garg --- .../accounts_settings/accounts_settings.json | 3 +- .../doctype/payment_entry/payment_entry.py | 7 +++- erpnext/controllers/accounts_controller.py | 34 +++++++++++-------- erpnext/public/js/controllers/transaction.js | 4 +-- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 1c0d64f065b..eafdb3c7467 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -182,6 +182,7 @@ }, { "default": "0", + "description": "Payment Terms from orders will be fetched into the invoices as is", "fieldname": "automatically_fetch_payment_terms", "fieldtype": "Check", "label": "Automatically Fetch Payment Terms from Order" @@ -362,7 +363,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-03-28 09:50:20.375233", + "modified": "2023-04-14 17:22:03.680886", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e3eb1010016..a4319521e36 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1754,7 +1754,12 @@ def get_payment_entry( if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date)) else: - if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value( + if doc.doctype in ( + "Sales Invoice", + "Purchase Invoice", + "Purchase Order", + "Sales Order", + ) and frappe.get_cached_value( "Payment Terms Template", {"name": doc.payment_terms_template}, "allocate_payment_based_on_payment_terms", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7fcc28bac36..c7416228eed 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -273,8 +273,8 @@ class AccountsController(TransactionBase): self.validate_payment_schedule_dates() self.set_due_date() self.set_payment_schedule() - self.validate_payment_schedule_amount() if not self.get("ignore_default_payment_terms_template"): + self.validate_payment_schedule_amount() self.validate_due_date() self.validate_advance_entries() @@ -1607,6 +1607,7 @@ class AccountsController(TransactionBase): base_grand_total = self.get("base_rounded_total") or self.base_grand_total grand_total = self.get("rounded_total") or self.grand_total + automatically_fetch_payment_terms = 0 if self.doctype in ("Sales Invoice", "Purchase Invoice"): base_grand_total = base_grand_total - flt(self.base_write_off_amount) @@ -1652,19 +1653,20 @@ class AccountsController(TransactionBase): ) self.append("payment_schedule", data) - for d in self.get("payment_schedule"): - if d.invoice_portion: - d.payment_amount = flt( - grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount") - ) - d.base_payment_amount = flt( - base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount") - ) - d.outstanding = d.payment_amount - elif not d.invoice_portion: - d.base_payment_amount = flt( - d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") - ) + if not automatically_fetch_payment_terms: + for d in self.get("payment_schedule"): + if d.invoice_portion: + d.payment_amount = flt( + grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount") + ) + d.base_payment_amount = flt( + base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount") + ) + d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt( + d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") + ) def get_order_details(self): if self.doctype == "Sales Invoice": @@ -1717,6 +1719,10 @@ class AccountsController(TransactionBase): "invoice_portion": schedule.invoice_portion, "mode_of_payment": schedule.mode_of_payment, "description": schedule.description, + "payment_amount": schedule.payment_amount, + "base_payment_amount": schedule.base_payment_amount, + "outstanding": schedule.outstanding, + "paid_amount": schedule.paid_amount, } if schedule.discount_type == "Percentage": diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e58dd98efd4..29437beab10 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1920,7 +1920,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } prompt_user_for_reference_date(){ - var me = this; + let me = this; frappe.prompt({ label: __("Cheque/Reference Date"), fieldname: "reference_date", @@ -1947,7 +1947,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length; if(!is_eligible || !has_payment_schedule) return false; - let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date); + let has_discount = this.frm.doc.payment_schedule.some(row => row.discount); return has_discount; } From 8f26d62b35a8c1f65cb6bc810b2386e043c710f1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Apr 2023 07:42:45 +0530 Subject: [PATCH 101/176] fix: change discuss forum url (#34891) fix: change discuss forum url (#34891) [skip ci] (cherry picked from commit dd93ea067e1a147462fe1bc5aabb0ca63f3c89fc) Co-authored-by: MohsinAli --- erpnext/setup/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 088958d1b26..3e1e39410ed 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -161,7 +161,7 @@ def add_standard_navbar_items(): { "item_label": "User Forum", "item_type": "Route", - "route": "https://discuss.erpnext.com", + "route": "https://discuss.frappe.io", "is_standard": 1, }, { From f1a1fc6c5bb6fde45167d490efee36bca4d3856c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Apr 2023 07:43:17 +0530 Subject: [PATCH 102/176] fix: Add offers info to website item (#34873) fix: Add offers info to website item (#34873) * fix: Add offers info to website item * Revert "fix: Add offers info to website item" This reverts commit 88b598edb61f0ab97b751581cc2719964e169055. * fix: Add offer properties to website item (cherry picked from commit 534ea5ad2170bfd95a620c82fd700287404cda65) Co-authored-by: Deepesh Garg --- erpnext/templates/generators/item/item_add_to_cart.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index 8000a2446be..1381dfe3b74 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -11,7 +11,10 @@
- {{ price_info.formatted_price_sales_uom }} + + {{ price_info.formatted_price_sales_uom }} + {{ price_info.currency }} + {% if price_info.formatted_mrp %} From 09b92fd78c3b321ae848f0c5c0b4f2c3b8552570 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 Apr 2023 08:24:22 +0530 Subject: [PATCH 103/176] fix: whitelist doc method This should've been whitelisted, looks like it was missed out closes https://github.com/frappe/erpnext/issues/34898 (cherry picked from commit e4f152a41638ed91e505c3e48156cad6493d681f) --- erpnext/e_commerce/doctype/website_item/website_item.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 3e5d5f768fa..81b8ecab48e 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -315,6 +315,7 @@ class WebsiteItem(WebsiteGenerator): self.item_code, skip_quotation_creation=True ) + @frappe.whitelist() def copy_specification_from_item_group(self): self.set("website_specifications", []) if self.item_group: From b0b00dc869701775e2bbf9c1360f54cb09be1291 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 4 Apr 2023 16:57:14 +0530 Subject: [PATCH 104/176] chore: add items field label (cherry picked from commit c9418aab45a147b2654de60f3817006a25e36d62) --- erpnext/buying/doctype/purchase_order/purchase_order.json | 6 +++--- .../doctype/supplier_quotation/supplier_quotation.json | 4 ++-- erpnext/selling/doctype/quotation/quotation.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 29afc8476e4..ff08ddd33dc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -495,6 +495,7 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", + "label": "Items", "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Purchase Order Item", @@ -1100,8 +1101,7 @@ { "fieldname": "before_items_section", "fieldtype": "Section Break", - "hide_border": 1, - "label": "Items" + "hide_border": 1 }, { "fieldname": "items_col_break", @@ -1271,7 +1271,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-01-28 18:59:16.322824", + "modified": "2023-04-14 16:42:29.448464", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index c5b369bedd5..11ff91af94d 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -310,7 +310,6 @@ "fieldname": "items_section", "fieldtype": "Section Break", "hide_border": 1, - "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -318,6 +317,7 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", + "label": "Items", "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Supplier Quotation Item", @@ -844,7 +844,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:35:39.740974", + "modified": "2023-04-14 16:43:41.714832", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index eb2c0a48ac5..2ffa6a5c120 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -416,7 +416,6 @@ "fieldname": "items_section", "fieldtype": "Section Break", "hide_border": 1, - "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -424,6 +423,7 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", + "label": "Items", "oldfieldname": "quotation_details", "oldfieldtype": "Table", "options": "Quotation Item", @@ -1072,7 +1072,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:32:28.671332", + "modified": "2023-04-14 16:50:44.550098", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", From 93bc1c53825c066d3366a8ce740d525c7d612a5c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 13 Apr 2023 09:59:35 +0530 Subject: [PATCH 105/176] refactor: checkbox to toggle merging of JE account heads (cherry picked from commit a3e3fe149d910a7613d6be54a5528c76a5e38e14) --- .../accounts_settings/accounts_settings.json | 16 +++++++++++++++- .../doctype/journal_entry/journal_entry.py | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index eafdb3c7467..379e470fc94 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -19,6 +19,8 @@ "column_break_17", "enable_common_party_accounting", "allow_multi_currency_invoices_against_single_party_account", + "journals_section", + "merge_similar_account_heads", "report_setting_section", "use_custom_cash_flow", "deferred_accounting_settings_section", @@ -356,6 +358,18 @@ "fieldname": "book_tax_discount_loss", "fieldtype": "Check", "label": "Book Tax Loss on Early Payment Discount" + }, + { + "fieldname": "journals_section", + "fieldtype": "Section Break", + "label": "Journals" + }, + { + "default": "0", + "description": "Rows with Same Account heads will be merged on Ledger", + "fieldname": "merge_similar_account_heads", + "fieldtype": "Check", + "label": "Merge Similar Account Heads" } ], "icon": "icon-cog", @@ -363,7 +377,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-04-14 17:22:03.680886", + "modified": "2023-04-17 11:45:42.049247", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 608267154b6..40018a1f49f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -878,6 +878,8 @@ class JournalEntry(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): from erpnext.accounts.general_ledger import make_gl_entries + merge_entries = frappe.db.get_single_value("Accounts Settings", "merge_similar_account_heads") + gl_map = self.build_gl_map() if self.voucher_type in ("Deferred Revenue", "Deferred Expense"): update_outstanding = "No" @@ -885,7 +887,13 @@ class JournalEntry(AccountsController): update_outstanding = "Yes" if gl_map: - make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding) + make_gl_entries( + gl_map, + cancel=cancel, + adv_adj=adv_adj, + merge_entries=merge_entries, + update_outstanding=update_outstanding, + ) @frappe.whitelist() def get_balance(self, difference_account=None): From 9f090d28618595e9133e4e33cb19f9edb3a28c09 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 17 Apr 2023 17:18:07 +0530 Subject: [PATCH 106/176] chore(patch): by default ledger entries of JE's will not be merged (cherry picked from commit 3f537d30bd484eb44c2ed8efb99267cd943555de) --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5803f46dea3..0dc6a28657c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -326,5 +326,6 @@ erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers erpnext.patches.v14_0.update_asset_value_for_manual_depr_entries erpnext.patches.v14_0.set_pick_list_status erpnext.patches.v13_0.update_docs_link +execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) # below migration patches should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger From 55da91cb34972a9bd19569d267d171fcd4584a1a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 12:55:16 +0530 Subject: [PATCH 107/176] fix: use CombineDatetime instead of Timestamp in QB queries (cherry picked from commit 91a398a191a62ea9f7d5583c9fda6e7997337303) --- erpnext/stock/doctype/batch/batch.py | 5 +++-- .../incorrect_stock_value_report.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 3b9fe7b97cd..e6f55275437 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last -from frappe.query_builder.functions import CurDate, Sum, Timestamp +from frappe.query_builder.functions import CombineDatetime, CurDate, Sum from frappe.utils import cint, flt, get_link_to_form, nowtime from frappe.utils.data import add_days from frappe.utils.jinja import render_template @@ -192,7 +192,8 @@ def get_batch_qty( posting_time = nowtime() query = query.where( - Timestamp(sle.posting_date, sle.posting_time) <= Timestamp(posting_date, posting_time) + CombineDatetime(sle.posting_date, sle.posting_time) + <= CombineDatetime(posting_date, posting_time) ) out = query.run(as_list=True)[0][0] or 0 diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index df01b14d11a..e9c96084d99 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.query_builder import Field -from frappe.query_builder.functions import Min, Timestamp +from frappe.query_builder.functions import CombineDatetime, Min from frappe.utils import add_days, getdate, today import erpnext @@ -75,7 +75,7 @@ def get_data(report_filters): & (sle.company == report_filters.company) & (sle.is_cancelled == 0) ) - .orderby(Timestamp(sle.posting_date, sle.posting_time), sle.creation) + .orderby(CombineDatetime(sle.posting_date, sle.posting_time), sle.creation) ).run(as_dict=True) for d in data: From 7c4f83ed60bea28375ec92b8a551b790c452c8b7 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 12:04:34 +0530 Subject: [PATCH 108/176] fix: add item-code filter for SCR supplied-items batch-no (cherry picked from commit e91abbfbe30234a7c577bab13cacea9ab54b4a56) --- .../subcontracting_receipt/subcontracting_receipt.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 3a2c53f4e44..45289b1dab5 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -67,6 +67,15 @@ frappe.ui.form.on('Subcontracting Receipt', { } }); + frm.set_query('batch_no', 'supplied_items', function(doc, cdt, cdn) { + var row = locals[cdt][cdn]; + return { + filters: { + item: row.rm_item_code + } + } + }); + let batch_no_field = frm.get_docfield("items", "batch_no"); if (batch_no_field) { batch_no_field.get_route_options_for_new_doc = function(row) { From e7ca83392976dae6578391614e7f51e288111d5d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Apr 2023 20:35:14 +0530 Subject: [PATCH 109/176] fix: Incorrect difference value in Stock and Account Value Comparison report (cherry picked from commit a77182645f734db83b728f90d4cfc55acf6e28cd) --- .../stock_and_account_value_comparison.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 106e877c4cd..5fb456502ee 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -41,7 +41,7 @@ def get_data(report_filters): key = (d.voucher_type, d.voucher_no) gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) - d.difference_value = d.stock_value - d.account_value + d.difference_value = abs(d.stock_value) - abs(d.account_value) if abs(d.difference_value) > 0.1: data.append(d) From 7131ff28fdd207872b1c60790b9b1487160a8b7f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:37:58 +0530 Subject: [PATCH 110/176] fix: add limit for get_next_stock_reco (#34937) fix: limit stock reco issue (cherry picked from commit fcfa8842a7d0413a865741be4f7904034b365411) Co-authored-by: Rohit Waghchaure --- 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 b638f08ed9b..c197769d0a3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1453,6 +1453,7 @@ def get_next_stock_reco(kwargs): ) .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) .orderby(sle.creation) + .limit(1) ) if kwargs.get("batch_no"): From f7e436fe717cd2718d70ddb20dd88f5a7633ab99 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 18:38:28 +0530 Subject: [PATCH 111/176] fix: internal Purchase Receipt GL Entries (cherry picked from commit 6fca9adcd4a881b09630f5e0ad875df136e1d6e0) --- .../doctype/purchase_receipt/purchase_receipt.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d268cc11963..530427328a8 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -380,7 +380,19 @@ class PurchaseReceipt(BuyingController): outgoing_amount = d.base_net_amount if self.is_internal_supplier and d.valuation_rate: - outgoing_amount = d.valuation_rate * d.stock_qty + outgoing_amount = abs( + frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": self.name, + "voucher_detail_no": d.name, + "warehouse": d.from_warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + ) credit_amount = outgoing_amount if credit_amount: From 7cc012930218e0abfa485e16088796d70129d89b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 21:03:23 +0530 Subject: [PATCH 112/176] test: add test case for internal PR GL Entries (cherry picked from commit c86c543fbfaa46c45f06a745a0841e821e14e712) --- .../purchase_receipt/test_purchase_receipt.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7567cfe98c5..8af279a9c83 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1610,6 +1610,89 @@ 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 + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, + ) + + prepare_data_for_internal_transfer() + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + + item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) + make_stock_entry( + purpose="Material Receipt", + item_code=item.name, + qty=10, + company=company, + to_warehouse=from_warehouse, + posting_date=add_days(today(), -3), + ) + + # Step - 1: Create Delivery Note with Internal Customer + dn = create_delivery_note( + item_code=item.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=100, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + posting_date=add_days(today(), -2), + ) + + # Step - 2: Create Internal Purchase Receipt + pr = make_inter_company_purchase_receipt(dn.name) + pr.items[0].qty = 10 + pr.items[0].from_warehouse = target_warehouse + pr.items[0].warehouse = to_warehouse + pr.items[0].rejected_warehouse = from_warehouse + pr.save() + pr.submit() + + # Step - 3: Create back-date Stock Reconciliation [After DN and Before PR] + create_stock_reconciliation( + item_code=item, + warehouse=target_warehouse, + qty=10, + rate=50, + company=company, + posting_date=add_days(today(), -1), + ) + + warehouse_account = get_warehouse_account_map(company) + stock_account_value = frappe.db.get_value( + "GL Entry", + { + "account": warehouse_account[target_warehouse]["account"], + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "is_cancelled": 0, + }, + fieldname=["credit"], + ) + stock_diff = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "is_cancelled": 0, + }, + fieldname=["sum(stock_value_difference)"], + ) + + # Value of Stock Account should be equal to the sum of Stock Value Difference + self.assertEqual(stock_account_value, stock_diff) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 2ad157bd77f2ade86d777c3f871ea8f1a86a0401 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 19 Apr 2023 16:01:45 +0530 Subject: [PATCH 113/176] fix(test): `test_backdated_stock_reco_cancellation_future_negative_stock` (cherry picked from commit 11c8503180cda70b1aebce3711def05eec840133) --- .../doctype/stock_reconciliation/test_stock_reconciliation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 7d59441d8b7..2e5d2c3aaff 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -530,7 +530,9 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): # check if cancellation of stock reco is blocked self.assertRaises(NegativeStockError, sr.cancel) - repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) + repost_exists = bool( + frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name, "status": "Queued"}) + ) self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") def test_intermediate_sr_bin_update(self): From 3b23fc1ebaea09dd1ab51b446575eca3f4f652b0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:50:40 +0530 Subject: [PATCH 114/176] fix: filtering via batch no (#34951) fix: filtering via batch no(#34950) * fix: filtering via batch no (cherry picked from commit ea6eeace8028ded8d19ae4795887ec9750298d89) Co-authored-by: Deepesh Garg --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c197769d0a3..0f12987fbb6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1457,7 +1457,7 @@ def get_next_stock_reco(kwargs): ) if kwargs.get("batch_no"): - query.where(sle.batch_no == kwargs.get("batch_no")) + query = query.where(sle.batch_no == kwargs.get("batch_no")) return query.run(as_dict=True) From 8c5d64467188b3891645d2b29a3179e9b3369b3c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Apr 2023 11:51:08 +0530 Subject: [PATCH 115/176] refactor: move set_missing_ref_detials out of set_missing_values (cherry picked from commit 11cb2db3fee91877fc6c05ae692b8163ca61d84e) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- erpnext/accounts/utils.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a4319521e36..55b314c98fd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -60,6 +60,7 @@ class PaymentEntry(AccountsController): def validate(self): self.setup_party_account_field() self.set_missing_values() + self.set_missing_ref_details() self.validate_payment_type() self.validate_party_details() self.set_exchange_rate() @@ -219,8 +220,6 @@ class PaymentEntry(AccountsController): else self.paid_to_account_currency ) - self.set_missing_ref_details() - def set_missing_ref_details(self, force=False): for d in self.get("references"): if d.allocated_amount: @@ -1811,6 +1810,7 @@ def get_payment_entry( pe.setup_party_account_field() pe.set_missing_values() + pe.set_missing_ref_details() update_accounting_dimensions(pe, doc) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 4ab0d56f681..7fd6242b9c6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -632,6 +632,12 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): payment_entry.set_gain_or_loss(account_details=account_details) + payment_entry.flags.ignore_validate_update_after_submit = True + payment_entry.setup_party_account_field() + payment_entry.set_missing_values() + payment_entry.set_missing_ref_details() + payment_entry.set_amounts() + if not do_not_save: payment_entry.save(ignore_permissions=True) From 3d3da757269a7cb2b7e474ee42dad260cab468e9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Apr 2023 12:11:05 +0530 Subject: [PATCH 116/176] refactor: update ref details for selected references set_missing_ref_details can update only for selected references (cherry picked from commit b7d6e30f63c70408fa29993e5cd958c26fb2e33c) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 55b314c98fd..fb211d993a4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -220,9 +220,16 @@ class PaymentEntry(AccountsController): else self.paid_to_account_currency ) - def set_missing_ref_details(self, force=False): + def set_missing_ref_details( + self, force: bool = False, update_ref_details_only_for: list | None = None + ) -> None: for d in self.get("references"): if d.allocated_amount: + if update_ref_details_only_for and ( + not (d.reference_doctype, d.reference_name) in update_ref_details_only_for + ): + continue + ref_details = get_reference_details( d.reference_doctype, d.reference_name, self.party_account_currency ) From 7740ceb27e8aee0f9e67467ef883b6c4b2be5a2c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 20 Apr 2023 14:05:08 +0530 Subject: [PATCH 117/176] fix(test): `test_internal_pr_gl_entries` --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 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 8af279a9c83..63f418d8eff 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1661,7 +1661,7 @@ class TestPurchaseReceipt(FrappeTestCase): # Step - 3: Create back-date Stock Reconciliation [After DN and Before PR] create_stock_reconciliation( - item_code=item, + item_code=item.name, warehouse=target_warehouse, qty=10, rate=50, From 44188629655a6f86935f86adc9863d23ee6ce8d2 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 20 Apr 2023 14:32:32 +0530 Subject: [PATCH 118/176] fix: process_loss_percentage in BOM (cherry picked from commit b572bef71d88e604d730b70e59d0b219fac54c32) --- erpnext/manufacturing/doctype/bom/bom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 4304193afae..7cdcef9c7ae 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -411,7 +411,6 @@ frappe.ui.form.on("BOM", { } frm.set_value("process_loss_qty", qty); - frm.set_value("add_process_loss_cost_in_fg", qty ? 1: 0); } }); From a3568c1b277a3e2da98e04622de4759f236d66c5 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 20 Apr 2023 12:39:30 +0530 Subject: [PATCH 119/176] fix: `PermissionError` in Work Order (cherry picked from commit 8108b2de0aa86b743c767808e38349644c2f9be9) --- .../doctype/work_order/work_order.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 97480b29454..d0c9966f8ba 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -625,20 +625,18 @@ erpnext.work_order = { // all materials transferred for manufacturing, make this primary finish_btn.addClass('btn-primary'); } - } else { - frappe.db.get_doc("Manufacturing Settings").then((doc) => { - let allowance_percentage = doc.overproduction_percentage_for_work_order; + } else if (frm.doc.__onload && frm.doc.__onload.overproduction_percentage) { + let allowance_percentage = frm.doc.__onload.overproduction_percentage; - if (allowance_percentage > 0) { - let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); - if ((flt(doc.produced_qty) < allowed_qty)) { - frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); - } + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); } - }); + } } } } else { From 56ef0baa9d615feee37e5d9ad82bea55ce7c3685 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:23:23 +0530 Subject: [PATCH 120/176] fix: batch qty conversion factor issue fixed in pos transaction (#34917) fix: batch qty conversion factor issue fixed in pos transaction (#34917) (cherry picked from commit 59f3fedbf7b92161f842479d05563f723d61bd78) Co-authored-by: Vishal Dhayagude Co-authored-by: Sagar Sharma --- erpnext/selling/page/point_of_sale/pos_controller.js | 6 ++++-- erpnext/stock/doctype/batch/batch.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index da798ab6d2d..07b1c2eb7ee 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -559,8 +559,10 @@ erpnext.PointOfSale.Controller = class { item_row = this.frm.add_child('items', new_item); - if (field === 'qty' && value !== 0 && !this.allow_negative_stock) - await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); + if (field === 'qty' && value !== 0 && !this.allow_negative_stock) { + const qty_needed = value * item_row.conversion_factor; + await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse); + } await this.trigger_new_item_events(item_row); diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index e6f55275437..1843c6e7975 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -377,7 +377,7 @@ def get_pos_reserved_batch_qty(filters): p = frappe.qb.DocType("POS Invoice").as_("p") item = frappe.qb.DocType("POS Invoice Item").as_("item") - sum_qty = frappe.query_builder.functions.Sum(item.qty).as_("qty") + sum_qty = frappe.query_builder.functions.Sum(item.stock_qty).as_("qty") reserved_batch_qty = ( frappe.qb.from_(p) From 789dfd6774b46e1b835842d723d9192d87e67ad5 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 4 Apr 2023 17:49:50 +0530 Subject: [PATCH 121/176] fix: set `frappe.flags.company` to call regional code accurately (cherry picked from commit 17ef3c964f194816c60d49fa8ec471b184869d3e) --- erpnext/accounts/party.py | 2 ++ erpnext/controllers/taxes_and_totals.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 22cff133089..30d949b73b6 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -259,6 +259,8 @@ def set_address_details( ) if doctype in TRANSACTION_TYPES: + # required to set correct region + frappe.flags.company = company get_regional_address_details(party_details, doctype, company) return party_address, shipping_address diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 1edd7bf85e1..4661c5ca7e8 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -976,6 +976,8 @@ def get_itemised_tax_breakup_html(doc): @frappe.whitelist() def get_round_off_applicable_accounts(company, account_list): + # required to set correct region + frappe.flags.company = company account_list = get_regional_round_off_accounts(company, account_list) return account_list From 87595bdb7e6193dcfe689929614d9343fc160294 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 4 Apr 2023 17:50:31 +0530 Subject: [PATCH 122/176] fix: simplify `erpnext.get_region` (cherry picked from commit 2fa641f86de8ad0be5f39575726d3672bfd885d4) --- erpnext/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 456ca52020b..7e5c60c609f 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -120,12 +120,14 @@ def get_region(company=None): You can also set global company flag in `frappe.flags.company` """ - if company or frappe.flags.company: - return frappe.get_cached_value("Company", company or frappe.flags.company, "country") - elif frappe.flags.country: - return frappe.flags.country - else: - return frappe.get_system_settings("country") + + if not company: + company = frappe.local.flags.company + + if company: + return frappe.get_cached_value("Company", company, "country") + + return frappe.flags.country or frappe.get_system_settings("country") def allow_regional(fn): From 2ec18eb4cf17a56014071161c1b2fc81113d86a0 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 4 Apr 2023 19:24:07 +0530 Subject: [PATCH 123/176] fix: use `functools.wraps` to preserve doc signature (cherry picked from commit 776b56ccd13c45e9eda0a67e8c9f42e65acb3135) --- erpnext/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 7e5c60c609f..61180579d2b 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -1,3 +1,4 @@ +import functools import inspect import frappe @@ -138,6 +139,7 @@ def allow_regional(fn): def myfunction(): pass""" + @functools.wraps(fn) def caller(*args, **kwargs): overrides = frappe.get_hooks("regional_overrides", {}).get(get_region()) function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}" From 33a16086efa498fbabe7d819c5fc4a535bf81cbe Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Apr 2023 16:36:10 +0530 Subject: [PATCH 124/176] fix: stock entry type issue (cherry picked from commit c3b5dcb7675b07979b2095e885ed12037ceac940) --- erpnext/stock/doctype/material_request/material_request.py | 2 +- .../stock/doctype/material_request/test_material_request.py | 4 ++++ erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 3548148145e..ebd9a17c3bc 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -619,7 +619,7 @@ def make_stock_entry(source_name, target_doc=None): target.set_transfer_qty() target.set_actual_qty() target.calculate_rate_and_amount(raise_error_if_no_rate=False) - target.set_stock_entry_type() + target.stock_entry_type = target.purpose target.set_job_card_data() doclist = get_mapped_doc( diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index a707c74c7db..03f58c664d3 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -54,6 +54,8 @@ class TestMaterialRequest(FrappeTestCase): mr.submit() se = make_stock_entry(mr.name) + self.assertEqual(se.stock_entry_type, "Material Transfer") + self.assertEqual(se.purpose, "Material Transfer") self.assertEqual(se.doctype, "Stock Entry") self.assertEqual(len(se.get("items")), len(mr.get("items"))) @@ -69,6 +71,8 @@ class TestMaterialRequest(FrappeTestCase): in_transit_warehouse = get_in_transit_warehouse(mr.company) se = make_in_transit_stock_entry(mr.name, in_transit_warehouse) + self.assertEqual(se.stock_entry_type, "Material Transfer") + self.assertEqual(se.purpose, "Material Transfer") self.assertEqual(se.doctype, "Stock Entry") for row in se.get("items"): self.assertEqual(row.t_warehouse, in_transit_warehouse) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7e39cb92f70..5d6e45013d8 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2349,7 +2349,7 @@ def move_sample_to_retention_warehouse(company, items): @frappe.whitelist() def make_stock_in_entry(source_name, target_doc=None): def set_missing_values(source, target): - target.set_stock_entry_type() + target.stock_entry_type = "Material Transfer" target.set_missing_values() def update_item(source_doc, target_doc, source_parent): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index cc06bd709ad..c43a1b1b81e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -202,6 +202,9 @@ class TestStockEntry(FrappeTestCase): ) end_transit_entry = make_stock_in_entry(transit_entry.name) + + self.assertEqual(end_transit_entry.stock_entry_type, "Material Transfer") + self.assertEqual(end_transit_entry.purpose, "Material Transfer") self.assertEqual(transit_entry.name, end_transit_entry.outgoing_stock_entry) self.assertEqual(transit_entry.name, end_transit_entry.items[0].against_stock_entry) self.assertEqual(transit_entry.items[0].name, end_transit_entry.items[0].ste_detail) From 9d17d3ff065f21bf9575312dfff766d1f7bcd791 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Apr 2023 16:01:05 +0530 Subject: [PATCH 125/176] fix: removed depends on for the Employee Detail section (cherry picked from commit a90a5b4aa4c8a363f66496d29f23d183928453ae) --- erpnext/projects/doctype/timesheet/timesheet.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 468300661a0..ba6262dc3de 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -96,7 +96,6 @@ "read_only": 1 }, { - "depends_on": "eval:!doc.work_order || doc.docstatus == 1", "fieldname": "employee_detail", "fieldtype": "Section Break", "label": "Employee Detail" @@ -311,7 +310,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2023-02-14 04:55:41.735991", + "modified": "2023-04-20 15:59:11.107831", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", From de86e8fb9546b805756af910791c47145b34df29 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:22:42 +0530 Subject: [PATCH 126/176] chore: Move source and campaign to more info section (#34946) chore: Move source and campaign to more info section (#34946) (cherry picked from commit a02705ded7dfd8f2fbd1b4ad0ace5a8b8f3da45c) Co-authored-by: Deepesh Garg --- .../doctype/sales_order/sales_order.json | 18 +++--------------- .../doctype/delivery_note/delivery_note.json | 13 ++++--------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index ccea8407ab8..4f498fb20d5 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -30,10 +30,6 @@ "cost_center", "dimension_col_break", "project", - "column_break_77", - "source", - "campaign", - "custom_dimensions_section", "currency_and_price_list", "currency", "conversion_rate", @@ -162,7 +158,9 @@ "is_internal_customer", "represents_company", "column_break_152", + "source", "inter_company_order_reference", + "campaign", "party_account_currency", "connections_tab" ], @@ -1164,12 +1162,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "column_break_77", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, { "fieldname": "source", "fieldtype": "Link", @@ -1612,10 +1604,6 @@ "fieldname": "column_break_92", "fieldtype": "Column Break" }, - { - "fieldname": "custom_dimensions_section", - "fieldtype": "Section Break" - }, { "collapsible": 1, "fieldname": "additional_info_section", @@ -1643,7 +1631,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:34:00.681780", + "modified": "2023-04-20 11:14:01.036202", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 0c1f82029e6..2adf9c310f9 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -28,8 +28,6 @@ "column_break_18", "project", "dimension_col_break", - "campaign", - "source", "custom_dimensions_section", "currency_and_price_list", "currency", @@ -161,11 +159,12 @@ "inter_company_reference", "customer_group", "territory", + "source", + "campaign", "column_break5", "excise_page", "instructions", - "connections_tab", - "column_break_25" + "connections_tab" ], "fields": [ { @@ -1339,10 +1338,6 @@ "fieldname": "column_break_10", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_25", - "fieldtype": "Column Break" - }, { "fieldname": "section_break_30", "fieldtype": "Section Break", @@ -1403,7 +1398,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-02-14 04:45:44.179670", + "modified": "2023-04-21 11:15:23.931084", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 6aabab26d8c796c16e52bbeed5800d6f14d09401 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:23:18 +0530 Subject: [PATCH 127/176] fix: FEC report for France accountancy (#34781) fix: FEC report for France accountancy (#34781) * fix: FEC report for France Accountancy legal requirement * fix: FEC report for France Accountancy legal requirement * fix: change to query standard * fix: change to query standard * fix: columns to standard dict * fix: columns to standard dict * fix: columns to data * refactor: french report FEC * refactor: french report FEC (2) --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> (cherry picked from commit af8da53cf4920688e8b756d25685964e0e57b9f3) Co-authored-by: HENRY Florian --- .../fichier_des_ecritures_comptables_[fec].py | 305 ++++++++++++------ 1 file changed, 198 insertions(+), 107 deletions(-) diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py index c75179ee5d1..67179890088 100644 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py +++ b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py @@ -1,31 +1,135 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - import re import frappe from frappe import _ from frappe.utils import format_datetime +COLUMNS = [ + { + "label": "JournalCode", + "fieldname": "JournalCode", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "JournalLib", + "fieldname": "JournalLib", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureNum", + "fieldname": "EcritureNum", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureDate", + "fieldname": "EcritureDate", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "CompteNum", + "fieldname": "CompteNum", + "fieldtype": "Link", + "options": "Account", + "width": 100, + }, + { + "label": "CompteLib", + "fieldname": "CompteLib", + "fieldtype": "Link", + "options": "Account", + "width": 200, + }, + { + "label": "CompAuxNum", + "fieldname": "CompAuxNum", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "CompAuxLib", + "fieldname": "CompAuxLib", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "PieceRef", + "fieldname": "PieceRef", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "PieceDate", + "fieldname": "PieceDate", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureLib", + "fieldname": "EcritureLib", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Debit", + "fieldname": "Debit", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Credit", + "fieldname": "Credit", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureLet", + "fieldname": "EcritureLet", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "DateLet", + "fieldname": "DateLet", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "ValidDate", + "fieldname": "ValidDate", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Montantdevise", + "fieldname": "Montantdevise", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Idevise", + "fieldname": "Idevise", + "fieldtype": "Data", + "width": 90, + }, +] + def execute(filters=None): - account_details = {} - for acc in frappe.db.sql("""select name, is_group from tabAccount""", as_dict=1): - account_details.setdefault(acc.name, acc) - - validate_filters(filters, account_details) - - filters = set_account_currency(filters) - - columns = get_columns(filters) - - res = get_result(filters) - - return columns, res + validate_filters(filters) + return COLUMNS, get_result( + company=filters["company"], + fiscal_year=filters["fiscal_year"], + ) -def validate_filters(filters, account_details): +def validate_filters(filters): if not filters.get("company"): frappe.throw(_("{0} is mandatory").format(_("Company"))) @@ -33,107 +137,96 @@ def validate_filters(filters, account_details): frappe.throw(_("{0} is mandatory").format(_("Fiscal Year"))) -def set_account_currency(filters): +def get_gl_entries(company, fiscal_year): + gle = frappe.qb.DocType("GL Entry") + sales_invoice = frappe.qb.DocType("Sales Invoice") + purchase_invoice = frappe.qb.DocType("Purchase Invoice") + journal_entry = frappe.qb.DocType("Journal Entry") + payment_entry = frappe.qb.DocType("Payment Entry") + customer = frappe.qb.DocType("Customer") + supplier = frappe.qb.DocType("Supplier") + employee = frappe.qb.DocType("Employee") - filters["company_currency"] = frappe.get_cached_value( - "Company", filters.company, "default_currency" + debit = frappe.query_builder.functions.Sum(gle.debit).as_("debit") + credit = frappe.query_builder.functions.Sum(gle.credit).as_("credit") + debit_currency = frappe.query_builder.functions.Sum(gle.debit_in_account_currency).as_( + "debitCurr" + ) + credit_currency = frappe.query_builder.functions.Sum(gle.credit_in_account_currency).as_( + "creditCurr" ) - return filters - - -def get_columns(filters): - columns = [ - "JournalCode" + "::90", - "JournalLib" + "::90", - "EcritureNum" + ":Dynamic Link:90", - "EcritureDate" + "::90", - "CompteNum" + ":Link/Account:100", - "CompteLib" + ":Link/Account:200", - "CompAuxNum" + "::90", - "CompAuxLib" + "::90", - "PieceRef" + "::90", - "PieceDate" + "::90", - "EcritureLib" + "::90", - "Debit" + "::90", - "Credit" + "::90", - "EcritureLet" + "::90", - "DateLet" + "::90", - "ValidDate" + "::90", - "Montantdevise" + "::90", - "Idevise" + "::90", - ] - - return columns - - -def get_result(filters): - gl_entries = get_gl_entries(filters) - - result = get_result_as_list(gl_entries, filters) - - return result - - -def get_gl_entries(filters): - - group_by_condition = ( - "group by voucher_type, voucher_no, account" - if filters.get("group_by_voucher") - else "group by gl.name" + query = ( + frappe.qb.from_(gle) + .left_join(sales_invoice) + .on(gle.voucher_no == sales_invoice.name) + .left_join(purchase_invoice) + .on(gle.voucher_no == purchase_invoice.name) + .left_join(journal_entry) + .on(gle.voucher_no == journal_entry.name) + .left_join(payment_entry) + .on(gle.voucher_no == payment_entry.name) + .left_join(customer) + .on(gle.party == customer.name) + .left_join(supplier) + .on(gle.party == supplier.name) + .left_join(employee) + .on(gle.party == employee.name) + .select( + gle.posting_date.as_("GlPostDate"), + gle.name.as_("GlName"), + gle.account, + gle.transaction_date, + debit, + credit, + debit_currency, + credit_currency, + gle.voucher_type, + gle.voucher_no, + gle.against_voucher_type, + gle.against_voucher, + gle.account_currency, + gle.against, + gle.party_type, + gle.party, + sales_invoice.name.as_("InvName"), + sales_invoice.title.as_("InvTitle"), + sales_invoice.posting_date.as_("InvPostDate"), + purchase_invoice.name.as_("PurName"), + purchase_invoice.title.as_("PurTitle"), + purchase_invoice.posting_date.as_("PurPostDate"), + journal_entry.cheque_no.as_("JnlRef"), + journal_entry.posting_date.as_("JnlPostDate"), + journal_entry.title.as_("JnlTitle"), + payment_entry.name.as_("PayName"), + payment_entry.posting_date.as_("PayPostDate"), + payment_entry.title.as_("PayTitle"), + customer.customer_name, + customer.name.as_("cusName"), + supplier.supplier_name, + supplier.name.as_("supName"), + employee.employee_name, + employee.name.as_("empName"), + ) + .where((gle.company == company) & (gle.fiscal_year == fiscal_year)) + .groupby(gle.voucher_type, gle.voucher_no, gle.account) + .orderby(gle.posting_date, gle.voucher_no) ) - gl_entries = frappe.db.sql( - """ - select - gl.posting_date as GlPostDate, gl.name as GlName, gl.account, gl.transaction_date, - sum(gl.debit) as debit, sum(gl.credit) as credit, - sum(gl.debit_in_account_currency) as debitCurr, sum(gl.credit_in_account_currency) as creditCurr, - gl.voucher_type, gl.voucher_no, gl.against_voucher_type, - gl.against_voucher, gl.account_currency, gl.against, - gl.party_type, gl.party, - inv.name as InvName, inv.title as InvTitle, inv.posting_date as InvPostDate, - pur.name as PurName, pur.title as PurTitle, pur.posting_date as PurPostDate, - jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle, - pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle, - cus.customer_name, cus.name as cusName, - sup.supplier_name, sup.name as supName, - emp.employee_name, emp.name as empName, - stu.title as student_name, stu.name as stuName, - member_name, mem.name as memName - - from `tabGL Entry` gl - left join `tabSales Invoice` inv on gl.voucher_no = inv.name - left join `tabPurchase Invoice` pur on gl.voucher_no = pur.name - left join `tabJournal Entry` jnl on gl.voucher_no = jnl.name - left join `tabPayment Entry` pay on gl.voucher_no = pay.name - left join `tabCustomer` cus on gl.party = cus.name - left join `tabSupplier` sup on gl.party = sup.name - left join `tabEmployee` emp on gl.party = emp.name - left join `tabStudent` stu on gl.party = stu.name - left join `tabMember` mem on gl.party = mem.name - where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s - {group_by_condition} - order by GlPostDate, voucher_no""".format( - group_by_condition=group_by_condition - ), - filters, - as_dict=1, - ) - - return gl_entries + return query.run(as_dict=True) -def get_result_as_list(data, filters): +def get_result(company, fiscal_year): + data = get_gl_entries(company, fiscal_year) + result = [] - company_currency = frappe.get_cached_value("Company", filters.company, "default_currency") + company_currency = frappe.get_cached_value("Company", company, "default_currency") accounts = frappe.get_all( - "Account", filters={"Company": filters.company}, fields=["name", "account_number"] + "Account", filters={"Company": company}, fields=["name", "account_number"] ) for d in data: - JournalCode = re.split("-|/|[0-9]", d.get("voucher_no"))[0] if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith( @@ -141,9 +234,7 @@ def get_result_as_list(data, filters): ): EcritureNum = re.split("-|/", d.get("voucher_no"))[1] else: - EcritureNum = re.search( - r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE - ).group(1) + EcritureNum = re.search(r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE)[1] EcritureDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") @@ -185,7 +276,7 @@ def get_result_as_list(data, filters): ValidDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") - PieceRef = d.get("voucher_no") if d.get("voucher_no") else "Sans Reference" + PieceRef = d.get("voucher_no") or "Sans Reference" # EcritureLib is the reference title unless it is an opening entry if d.get("is_opening") == "Yes": From 00968badf5bf2680408ef010e2497bfcab29c5a9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:26:10 +0530 Subject: [PATCH 128/176] fix: broken 'set exchange gain/loss' btn in payment entry (#34940) fix: broken set exchagne gain/loss btn broken in payment entry (cherry picked from commit df0682fa8c85221e214fda0e05a1699377868fae) Co-authored-by: ruthra kumar --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 00a948a8387..969027dd97c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -978,6 +978,7 @@ frappe.ui.form.on('Payment Entry', { precision("difference_amount")); const add_deductions = (details) => { + let row = null; if (!write_off_row.length && difference_amount) { row = frm.add_child("deductions"); row.account = details[account]; From a5823547d3998bfeec2a5d9c62301ddbbad2a757 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 Apr 2023 16:49:48 +0530 Subject: [PATCH 129/176] fix: validation for internal transfer entry (cherry picked from commit 19911b48fdb89ca943ce1e5131eb465ebe12547c) --- .../doctype/sales_invoice/sales_invoice.js | 1 + erpnext/controllers/accounts_controller.py | 11 +++- .../purchase_receipt/test_purchase_receipt.py | 58 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 56e412b297c..8cb29505eb2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -334,6 +334,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e } make_inter_company_invoice() { + let me = this; frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_inter_company_purchase_invoice", frm: me.frm diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c7416228eed..642d51c3254 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -5,7 +5,7 @@ import json import frappe -from frappe import _, throw +from frappe import _, bold, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( @@ -405,6 +405,15 @@ class AccountsController(TransactionBase): msg += _("Please create purchase from internal sale or delivery document itself") frappe.throw(msg, title=_("Internal Sales Reference Missing")) + label = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item" + + field = frappe.scrub(label) + + for row in self.get("items"): + if not row.get(field): + msg = f"At Row {row.idx}: The field {bold(label)} is mandatory for internal transfer" + frappe.throw(_(msg), title=_("Internal Transfer Reference Missing")) + def disable_pricing_rule_on_internal_transfer(self): if not self.get("ignore_pricing_rule") and self.is_internal_transfer(): self.ignore_pricing_rule = 1 diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 63f418d8eff..b5740ca575b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1693,6 +1693,64 @@ class TestPurchaseReceipt(FrappeTestCase): # Value of Stock Account should be equal to the sum of Stock Value Difference self.assertEqual(stock_account_value, stock_diff) + def test_internal_pr_reference(self): + item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + from_warehouse = create_warehouse("_Test Internal From Warehouse New 1", company=company) + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New 1", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New 1", company=company) + + # Step 2: Create Stock Entry (Material Receipt) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + make_stock_entry( + purpose="Material Receipt", + item_code=item.name, + qty=15, + company=company, + to_warehouse=from_warehouse, + ) + + # Step 3: Create Delivery Note with Internal Customer + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + dn = create_delivery_note( + item_code=item.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=100, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + # Step 4: Create Internal Purchase Receipt + from erpnext.controllers.status_updater import OverAllowanceError + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + + pr = make_inter_company_purchase_receipt(dn.name) + pr.inter_company_reference = "" + self.assertRaises(frappe.ValidationError, pr.save) + + pr.inter_company_reference = dn.name + pr.items[0].qty = 10 + pr.items[0].from_warehouse = target_warehouse + pr.items[0].warehouse = to_warehouse + pr.items[0].rejected_warehouse = from_warehouse + pr.save() + + delivery_note_item = pr.items[0].delivery_note_item + pr.items[0].delivery_note_item = "" + + self.assertRaises(frappe.ValidationError, pr.save) + + pr.load_from_db() + pr.items[0].delivery_note_item = delivery_note_item + pr.save() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From a5fde5d9331425495bfaa92ddede9eda0a45474f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 Apr 2023 17:34:22 +0530 Subject: [PATCH 130/176] fix: added validation for extra job card (cherry picked from commit 6a0b7c9e8cc84ba0196440a5a0486323c54c720d) --- .../doctype/job_card/job_card.py | 31 +++++++++++ .../doctype/work_order/test_work_order.py | 51 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e82f37977cd..f89951619e0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -74,6 +74,37 @@ class JobCard(Document): self.update_sub_operation_status() self.validate_work_order() + def on_update(self): + self.validate_job_card_qty() + + def validate_job_card_qty(self): + if not (self.operation_id and self.work_order): + return + + wo_qty = flt(frappe.get_cached_value("Work Order", self.work_order, "qty")) + + completed_qty = flt( + frappe.db.get_value("Work Order Operation", self.operation_id, "completed_qty") + ) + + job_card_qty = frappe.get_all( + "Job Card", + fields=["sum(for_quantity)"], + filters={ + "work_order": self.work_order, + "operation_id": self.operation_id, + "docstatus": ["!=", 2], + }, + as_list=1, + ) + + job_card_qty = flt(job_card_qty[0][0]) if job_card_qty else 0 + + if job_card_qty and ((job_card_qty - completed_qty) > wo_qty): + msg = f"""Job Card quantity cannot be greater than + Work Order quantity for the operation {self.operation}""" + frappe.throw(_(msg), title=_("Extra Job Card Quantity")) + def set_sub_operations(self): if not self.sub_operations and self.operation: self.sub_operations = [] diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 729ed42f51a..540b7dc9ea6 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1598,6 +1598,57 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(row.to_time, add_to_date(planned_start_date, minutes=30)) self.assertEqual(row.workstation, workstations_to_check[index]) + def test_job_card_extra_qty(self): + items = [ + "Test FG Item for Scrap Item Test 1", + "Test RM Item 1 for Scrap Item Test 1", + "Test RM Item 2 for Scrap Item Test 1", + ] + + company = "_Test Company with perpetual inventory" + for item_code in items: + create_item( + item_code=item_code, + is_stock_item=1, + is_purchase_item=1, + opening_stock=100, + valuation_rate=10, + company=company, + warehouse="Stores - TCP1", + ) + + item = "Test FG Item for Scrap Item Test 1" + raw_materials = ["Test RM Item 1 for Scrap Item Test 1", "Test RM Item 2 for Scrap Item Test 1"] + if not frappe.db.get_value("BOM", {"item": item}): + bom = make_bom( + item=item, source_warehouse="Stores - TCP1", raw_materials=raw_materials, do_not_save=True + ) + bom.with_operations = 1 + bom.append( + "operations", + { + "operation": "_Test Operation 1", + "workstation": "_Test Workstation 1", + "hour_rate": 20, + "time_in_mins": 60, + }, + ) + + bom.submit() + + wo_order = make_wo_order_test_record( + item=item, + company=company, + planned_start_date=now(), + qty=20, + ) + job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name") + job_card_doc = frappe.get_doc("Job Card", job_card) + + # Make another Job Card for the same Work Order + job_card2 = frappe.copy_doc(job_card_doc) + self.assertRaises(frappe.ValidationError, job_card2.save) + def prepare_data_for_workstation_type_check(): from erpnext.manufacturing.doctype.operation.test_operation import make_operation From 83a1b836f964f6823780805d5967a7325c7409c6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 21:13:38 +0530 Subject: [PATCH 131/176] fix: SLA permissions (backport #34981) (#34986) fix: SLA permissions (#34981) (cherry picked from commit ac871797b28d87a09c5ca26e618fdf01b087f9de) Co-authored-by: Ankush Menat --- .../service_level_agreement.json | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json index 1698e2380f7..1c6f24b23ce 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json @@ -192,7 +192,7 @@ } ], "links": [], - "modified": "2021-11-26 15:45:33.289911", + "modified": "2023-04-21 17:16:56.192560", "modified_by": "Administrator", "module": "Support", "name": "Service Level Agreement", @@ -212,19 +212,12 @@ "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 + "role": "All" } ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From aeac43ccf9059042be27d16b2862e845c353d1d0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 22 Apr 2023 11:16:12 +0530 Subject: [PATCH 132/176] fix: duplicate reposting entries of same voucher (cherry picked from commit f2253dd6452d2ef421fb449182b98a5618a22021) --- erpnext/stock/stock_ledger.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 0f12987fbb6..861ea84de37 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1388,7 +1388,11 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): def regenerate_sle_for_batch_stock_reco(detail): doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) doc.recalculate_current_qty(detail.item_code, detail.batch_no) - doc.repost_future_sle_and_gle() + + if not frappe.db.exists( + "Repost Item Valuation", {"voucher_no": doc.name, "status": "Queued", "docstatus": "1"} + ): + doc.repost_future_sle_and_gle() def get_stock_reco_qty_shift(args): From 70014028e90b2ef4ae9e36e4c13829751d421ba3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:28:41 +0530 Subject: [PATCH 133/176] chore: ERPNext setup wizard cleanup (#33675) chore: ERPNext setup wizard cleanup (#33675) * chore: ERPNext setup wizard cleanup * chore: Remove default website * chore: Remove flaky tests * chore: remove unwanted tests (cherry picked from commit 3598bcc9a85b79bf1db33f79982023cb658324ca) Co-authored-by: Deepesh Garg --- .../homepage_section/test_homepage_section.py | 56 -------- erpnext/public/js/setup_wizard.js | 124 +++++------------- .../setup_wizard/operations/company_setup.py | 78 ----------- .../operations/default_website.py | 89 ------------- erpnext/setup/setup_wizard/setup_wizard.py | 9 -- 5 files changed, 34 insertions(+), 322 deletions(-) delete mode 100644 erpnext/setup/setup_wizard/operations/default_website.py diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py index 27c8fe4c95a..3df56e67f60 100644 --- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py @@ -10,62 +10,6 @@ from frappe.website.serve import get_response class TestHomepageSection(unittest.TestCase): - def test_homepage_section_card(self): - try: - frappe.get_doc( - { - "doctype": "Homepage Section", - "name": "Card Section", - "section_based_on": "Cards", - "section_cards": [ - { - "title": "Card 1", - "subtitle": "Subtitle 1", - "content": "This is test card 1", - "route": "/card-1", - }, - { - "title": "Card 2", - "subtitle": "Subtitle 2", - "content": "This is test card 2", - "image": "test.jpg", - }, - ], - "no_of_columns": 3, - } - ).insert(ignore_if_duplicate=True) - except frappe.DuplicateEntryError: - pass - - set_request(method="GET", path="home") - response = get_response() - - self.assertEqual(response.status_code, 200) - - html = frappe.safe_decode(response.get_data()) - - soup = BeautifulSoup(html, "html.parser") - sections = soup.find("main").find_all("section") - self.assertEqual(len(sections), 3) - - homepage_section = sections[2] - self.assertEqual(homepage_section.h3.text, "Card Section") - - cards = homepage_section.find_all(class_="card") - - self.assertEqual(len(cards), 2) - self.assertEqual(cards[0].h5.text, "Card 1") - self.assertEqual(cards[0].a["href"], "/card-1") - self.assertEqual(cards[1].p.text, "Subtitle 2") - - img = cards[1].find(class_="card-img-top") - - self.assertEqual(img["src"], "test.jpg") - self.assertEqual(img["loading"], "lazy") - - # cleanup - frappe.db.rollback() - def test_homepage_section_custom_html(self): frappe.get_doc( { diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 9288f515cd4..a913844e186 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -13,19 +13,11 @@ frappe.setup.on("before_load", function () { erpnext.setup.slides_settings = [ { - // Brand - name: 'brand', - icon: "fa fa-bookmark", - title: __("The Brand"), - // help: __('Upload your letter head and logo. (you can edit them later).'), + // Organization + name: 'organization', + title: __("Setup your organization"), + icon: "fa fa-building", fields: [ - { - fieldtype: "Attach Image", fieldname: "attach_logo", - label: __("Attach Logo"), - description: __("100px by 100px"), - is_private: 0, - align: 'center' - }, { fieldname: 'company_name', label: __('Company Name'), @@ -35,54 +27,9 @@ erpnext.setup.slides_settings = [ { fieldname: 'company_abbr', label: __('Company Abbreviation'), - fieldtype: 'Data' - } - ], - onload: function(slide) { - this.bind_events(slide); - }, - bind_events: function (slide) { - slide.get_input("company_name").on("change", function () { - var parts = slide.get_input("company_name").val().split(" "); - var abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join(""); - slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase()); - }).val(frappe.boot.sysdefaults.company_name || "").trigger("change"); - - slide.get_input("company_abbr").on("change", function () { - if (slide.get_input("company_abbr").val().length > 10) { - frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters")); - slide.get_field("company_abbr").set_value(""); - } - }); - }, - validate: function() { - if ((this.values.company_name || "").toLowerCase() == "company") { - frappe.msgprint(__("Company Name cannot be Company")); - return false; - } - if (!this.values.company_abbr) { - return false; - } - if (this.values.company_abbr.length > 10) { - return false; - } - return true; - } - }, - { - // Organisation - name: 'organisation', - title: __("Your Organization"), - icon: "fa fa-building", - fields: [ - { - fieldname: 'company_tagline', - label: __('What does it do?'), fieldtype: 'Data', - placeholder: __('e.g. "Build tools for builders"'), - reqd: 1 + hidden: 1 }, - { fieldname: 'bank_account', label: __('Bank Name'), fieldtype: 'Data', reqd: 1 }, { fieldname: 'chart_of_accounts', label: __('Chart of Accounts'), options: "", fieldtype: 'Select' @@ -94,40 +41,24 @@ erpnext.setup.slides_settings = [ ], onload: function (slide) { - this.load_chart_of_accounts(slide); this.bind_events(slide); + this.load_chart_of_accounts(slide); this.set_fy_dates(slide); }, - validate: function () { - let me = this; - let exist; - if (!this.validate_fy_dates()) { return false; } - // Validate bank name - if(me.values.bank_account) { - frappe.call({ - async: false, - method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account", - args: { - "coa": me.values.chart_of_accounts, - "bank_account": me.values.bank_account - }, - callback: function (r) { - if(r.message){ - exist = r.message; - me.get_field("bank_account").set_value(""); - let message = __('Account {0} already exists. Please enter a different name for your bank account.', - [me.values.bank_account] - ); - frappe.msgprint(message); - } - } - }); - return !exist; // Return False if exist = true + if ((this.values.company_name || "").toLowerCase() == "company") { + frappe.msgprint(__("Company Name cannot be Company")); + return false; + } + if (!this.values.company_abbr) { + return false; + } + if (this.values.company_abbr.length > 10) { + return false; } return true; @@ -151,15 +82,15 @@ erpnext.setup.slides_settings = [ var country = frappe.wizard.values.country; if (country) { - var fy = erpnext.setup.fiscal_years[country]; - var current_year = moment(new Date()).year(); - var next_year = current_year + 1; + let fy = erpnext.setup.fiscal_years[country]; + let current_year = moment(new Date()).year(); + let next_year = current_year + 1; if (!fy) { fy = ["01-01", "12-31"]; next_year = current_year; } - var year_start_date = current_year + "-" + fy[0]; + let year_start_date = current_year + "-" + fy[0]; if (year_start_date > frappe.datetime.get_today()) { next_year = current_year; current_year -= 1; @@ -171,7 +102,7 @@ erpnext.setup.slides_settings = [ load_chart_of_accounts: function (slide) { - var country = frappe.wizard.values.country; + let country = frappe.wizard.values.country; if (country) { frappe.call({ @@ -202,12 +133,25 @@ erpnext.setup.slides_settings = [ me.charts_modal(slide, chart_template); }); + + slide.get_input("company_name").on("change", function () { + let parts = slide.get_input("company_name").val().split(" "); + let abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join(""); + slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase()); + }).val(frappe.boot.sysdefaults.company_name || "").trigger("change"); + + slide.get_input("company_abbr").on("change", function () { + if (slide.get_input("company_abbr").val().length > 10) { + frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters")); + slide.get_field("company_abbr").set_value(""); + } + }); }, charts_modal: function(slide, chart_template) { let parent = __('All Accounts'); - var dialog = new frappe.ui.Dialog({ + let dialog = new frappe.ui.Dialog({ title: chart_template, fields: [ {'fieldname': 'expand_all', 'label': __('Expand All'), 'fieldtype': 'Button', diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index aadc98989fa..ace5cca0b02 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -4,7 +4,6 @@ import frappe from frappe import _ from frappe.utils import cstr, getdate -from .default_website import website_maker def create_fiscal_year_and_company(args): @@ -48,83 +47,6 @@ def enable_shopping_cart(args): # nosemgrep ).insert() -def create_email_digest(): - from frappe.utils.user import get_system_managers - - system_managers = get_system_managers(only_name=True) - - if not system_managers: - return - - recipients = [] - for d in system_managers: - recipients.append({"recipient": d}) - - companies = frappe.db.sql_list("select name FROM `tabCompany`") - for company in companies: - if not frappe.db.exists("Email Digest", "Default Weekly Digest - " + company): - edigest = frappe.get_doc( - { - "doctype": "Email Digest", - "name": "Default Weekly Digest - " + company, - "company": company, - "frequency": "Weekly", - "recipients": recipients, - } - ) - - for df in edigest.meta.get("fields", {"fieldtype": "Check"}): - if df.fieldname != "scheduler_errors": - edigest.set(df.fieldname, 1) - - edigest.insert() - - # scheduler errors digest - if companies: - edigest = frappe.new_doc("Email Digest") - edigest.update( - { - "name": "Scheduler Errors", - "company": companies[0], - "frequency": "Daily", - "recipients": recipients, - "scheduler_errors": 1, - "enabled": 1, - } - ) - edigest.insert() - - -def create_logo(args): - if args.get("attach_logo"): - attach_logo = args.get("attach_logo").split(",") - if len(attach_logo) == 3: - filename, filetype, content = attach_logo - _file = frappe.get_doc( - { - "doctype": "File", - "file_name": filename, - "attached_to_doctype": "Website Settings", - "attached_to_name": "Website Settings", - "decode": True, - } - ) - _file.save() - fileurl = _file.file_url - frappe.db.set_value( - "Website Settings", - "Website Settings", - "brand_html", - " {1}".format( - fileurl, args.get("company_name") - ), - ) - - -def create_website(args): - website_maker(args) - - def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year if start_year == getdate(fy_end_date).year: diff --git a/erpnext/setup/setup_wizard/operations/default_website.py b/erpnext/setup/setup_wizard/operations/default_website.py deleted file mode 100644 index 40b02b35dfd..00000000000 --- a/erpnext/setup/setup_wizard/operations/default_website.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe import _ -from frappe.utils import nowdate - - -class website_maker(object): - def __init__(self, args): - self.args = args - self.company = args.company_name - self.tagline = args.company_tagline - self.user = args.get("email") - self.make_web_page() - self.make_website_settings() - self.make_blog() - - def make_web_page(self): - # home page - homepage = frappe.get_doc("Homepage", "Homepage") - homepage.company = self.company - homepage.tag_line = self.tagline - homepage.setup_items() - homepage.save() - - def make_website_settings(self): - # update in home page in settings - website_settings = frappe.get_doc("Website Settings", "Website Settings") - website_settings.home_page = "home" - website_settings.brand_html = self.company - website_settings.copyright = self.company - website_settings.top_bar_items = [] - website_settings.append( - "top_bar_items", {"doctype": "Top Bar Item", "label": "Contact", "url": "/contact"} - ) - website_settings.append( - "top_bar_items", {"doctype": "Top Bar Item", "label": "Blog", "url": "/blog"} - ) - website_settings.append( - "top_bar_items", {"doctype": "Top Bar Item", "label": _("Products"), "url": "/all-products"} - ) - website_settings.save() - - def make_blog(self): - blog_category = frappe.get_doc( - {"doctype": "Blog Category", "category_name": "general", "published": 1, "title": _("General")} - ).insert() - - if not self.user: - # Admin setup - return - - blogger = frappe.new_doc("Blogger") - user = frappe.get_doc("User", self.user) - blogger.user = self.user - blogger.full_name = user.first_name + (" " + user.last_name if user.last_name else "") - blogger.short_name = user.first_name.lower() - blogger.avatar = user.user_image - blogger.insert() - - frappe.get_doc( - { - "doctype": "Blog Post", - "title": "Welcome", - "published": 1, - "published_on": nowdate(), - "blogger": blogger.name, - "blog_category": blog_category.name, - "blog_intro": "My First Blog", - "content": frappe.get_template("setup/setup_wizard/data/sample_blog_post.html").render(), - } - ).insert() - - -def test(): - frappe.delete_doc("Web Page", "test-company") - frappe.delete_doc("Blog Post", "welcome") - frappe.delete_doc("Blogger", "administrator") - frappe.delete_doc("Blog Category", "general") - website_maker( - { - "company": "Test Company", - "company_tagline": "Better Tools for Everyone", - "name": "Administrator", - } - ) - frappe.db.commit() diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index bd86a5b9693..65b268e385c 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -5,7 +5,6 @@ import frappe from frappe import _ -from .operations import company_setup from .operations import install_fixtures as fixtures @@ -35,7 +34,6 @@ def get_setup_stages(args=None): "fail_msg": "Failed to set defaults", "tasks": [ {"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")}, - {"fn": stage_four, "args": args, "fail_msg": _("Failed to create website")}, ], }, { @@ -60,12 +58,6 @@ def setup_defaults(args): fixtures.install_defaults(frappe._dict(args)) -def stage_four(args): - company_setup.create_website(args) - company_setup.create_email_digest() - company_setup.create_logo(args) - - def fin(args): frappe.local.message_log = [] login_as_first_user(args) @@ -81,5 +73,4 @@ def setup_complete(args=None): stage_fixtures(args) setup_company(args) setup_defaults(args) - stage_four(args) fin(args) From 28cd79a0403aa256cb6abd5cf9896348ca6a57e4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Apr 2023 14:50:27 +0530 Subject: [PATCH 134/176] fix: item not showing in the BOM (cherry picked from commit 02c3b41dc27f4826b63b5694862af7c0ce9dc7e1) --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index a085af859a4..b53149affd3 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1317,7 +1317,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if not field in searchfields ] - query_filters = {"disabled": 0, "end_of_life": (">", today())} + query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} or_cond_filters = {} if txt: From c020789bfc6731760e151563da3a7e7a97e6e03a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Apr 2023 17:32:32 +0530 Subject: [PATCH 135/176] fix: incorrect OR condition causing timeout error (cherry picked from commit 379b215aeafe0d6fe01952cd31ab536ef4959974) --- erpnext/stock/stock_ledger.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 861ea84de37..82fc0df8def 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1445,13 +1445,13 @@ def get_next_stock_reco(kwargs): ( CombineDatetime(sle.posting_date, sle.posting_time) > CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) - | ( - ( - CombineDatetime(sle.posting_date, sle.posting_time) - == CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) - ) - & (sle.creation > kwargs.get("creation")) + ) + | ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + == CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) ) + & (sle.creation > kwargs.get("creation")) ) ) ) From f44a79fa73e9558ba1d77ff9ec98bd7eb00bc7ca Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 21 Apr 2023 12:59:52 +0530 Subject: [PATCH 136/176] Revert "fix: Rate from LDC in TDS reports (#33699)" This reverts commit db9beb3cddc78376ccd30b57efafa35381b482d6. (cherry picked from commit cb7a99cbaa2caa9746a49dd09c2c2bdea5ba1540) --- .../report/tds_payable_monthly/tds_payable_monthly.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index bfe2a0fd2be..98838907be1 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -4,7 +4,6 @@ import frappe from frappe import _ -from frappe.utils import flt def execute(filters=None): @@ -66,12 +65,6 @@ def get_result( else: total_amount_credited += entry.credit - ## Check if ldc is applied and show rate as per ldc - actual_rate = (tds_deducted / total_amount_credited) * 100 - - if flt(actual_rate) < flt(rate): - rate = actual_rate - if tds_deducted: row = { "pan" From 7d9c9884dc6c518ccab4e4d25266273e221ab9f4 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 25 Apr 2023 12:01:26 +0530 Subject: [PATCH 137/176] =?UTF-8?q?Revert=20"fix:=20Incorrect=20difference?= =?UTF-8?q?=20value=20in=20Stock=20and=20Account=20Value=20Comparison?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 7a63fbef4fcb0f57e5ec38f7900c6905cfe3954b) --- .../stock_and_account_value_comparison.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 5fb456502ee..106e877c4cd 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -41,7 +41,7 @@ def get_data(report_filters): key = (d.voucher_type, d.voucher_no) gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) - d.difference_value = abs(d.stock_value) - abs(d.account_value) + d.difference_value = d.stock_value - d.account_value if abs(d.difference_value) > 0.1: data.append(d) From 3c75e55cb91f2c78356054b9c1ac9ea280032959 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 25 Apr 2023 12:44:58 +0530 Subject: [PATCH 138/176] fix: value of depreciable assets not updating after manual depr entry [v14] (#35010) * fix: update value of asset with calc_depr on after manual depr entry * fix: value of asset with calc_depr on after manual depr entry not reflecting in asset_depr_and_bal report * chore: add validation for depr journal entry * test: manual_depr_for_depreciable_asset and manual_depr_w_incorrect_jv_voucher_type * chore: unlink depreciable asset from manual depr entry --- .../accounts/doctype/account/test_account.py | 2 +- .../doctype/journal_entry/journal_entry.js | 2 +- .../doctype/journal_entry/journal_entry.py | 71 +++++++++++-------- .../asset_depreciations_and_balances.py | 24 +------ erpnext/assets/doctype/asset/depreciation.py | 1 + erpnext/assets/doctype/asset/test_asset.py | 62 +++++++++++++++- 6 files changed, 105 insertions(+), 57 deletions(-) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index f9c9173af08..3a360c48c43 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -297,7 +297,7 @@ def _make_test_records(verbose=None): # fixed asset depreciation ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None], - ["_Test Depreciations", "Expenses", 0, None, None], + ["_Test Depreciations", "Expenses", 0, "Depreciation", None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], # Receivable / Payable Account ["_Test Receivable", "Current Assets", 0, "Receivable", None], diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 089f20b467d..302acc4f1f7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry"); frappe.ui.form.on("Journal Entry", { setup: function(frm) { frm.add_fetch("bank_account", "account", "account"); - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger"]; + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement']; }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 40018a1f49f..92c37bbe5e5 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -69,6 +69,7 @@ class JournalEntry(AccountsController): self.validate_empty_accounts_table() self.set_account_and_party_balance() self.validate_inter_company_accounts() + self.validate_depr_entry_voucher_type() if self.docstatus == 0: self.apply_tax_withholding() @@ -130,6 +131,13 @@ class JournalEntry(AccountsController): if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit: frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry")) + def validate_depr_entry_voucher_type(self): + if ( + any(d.account_type == "Depreciation" for d in self.get("accounts")) + and self.voucher_type != "Depreciation Entry" + ): + frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation")) + def validate_stock_accounts(self): stock_accounts = get_stock_accounts(self.company, self.doctype, self.name) for account in stock_accounts: @@ -233,25 +241,30 @@ class JournalEntry(AccountsController): self.remove(d) def update_asset_value(self): - if self.voucher_type != "Depreciation Entry": + if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry": return - processed_assets = [] - for d in self.get("accounts"): if ( - d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets + d.reference_type == "Asset" + and d.reference_name + and d.account_type == "Depreciation" + and d.debit ): - processed_assets.append(d.reference_name) - asset = frappe.get_doc("Asset", d.reference_name) if asset.calculate_depreciation: - continue - - depr_value = d.debit or d.credit - - asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value) + fb_idx = 1 + if self.finance_book: + for fb_row in asset.get("finance_books"): + if fb_row.finance_book == self.finance_book: + fb_idx = fb_row.idx + break + fb_row = asset.get("finance_books")[fb_idx - 1] + fb_row.value_after_depreciation -= d.debit + fb_row.db_update() + else: + asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit) asset.set_status() @@ -316,35 +329,35 @@ class JournalEntry(AccountsController): if self.voucher_type != "Depreciation Entry": return - processed_assets = [] - for d in self.get("accounts"): if ( - d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets + d.reference_type == "Asset" + and d.reference_name + and d.account_type == "Depreciation" + and d.debit ): - processed_assets.append(d.reference_name) - asset = frappe.get_doc("Asset", d.reference_name) if asset.calculate_depreciation: + fb_idx = None for s in asset.get("schedules"): if s.journal_entry == self.name: s.db_set("journal_entry", None) - - idx = cint(s.finance_book_id) or 1 - finance_books = asset.get("finance_books")[idx - 1] - finance_books.value_after_depreciation += s.depreciation_amount - finance_books.db_update() - - asset.set_status() - + fb_idx = cint(s.finance_book_id) or 1 break + if not fb_idx: + fb_idx = 1 + if self.finance_book: + for fb_row in asset.get("finance_books"): + if fb_row.finance_book == self.finance_book: + fb_idx = fb_row.idx + break + fb_row = asset.get("finance_books")[fb_idx - 1] + fb_row.value_after_depreciation += d.debit + fb_row.db_update() else: - depr_value = d.debit or d.credit - - asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value) - - asset.set_status() + asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit) + asset.set_status() def unlink_inter_company_jv(self): if ( 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 9cea37c4f80..d67eee3552d 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 @@ -114,28 +114,6 @@ def get_assets(filters): sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period from (SELECT a.asset_category, - ifnull(sum(case when ds.schedule_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then - ds.depreciation_amount - else - 0 - end), 0) as accumulated_depreciation_as_on_from_date, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then - ds.depreciation_amount - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s - and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then - ds.depreciation_amount - else - 0 - end), 0) as depreciation_amount_during_the_period - from `tabAsset` a, `tabDepreciation Schedule` ds - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != '' - group by a.asset_category - union - SELECT a.asset_category, ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then gle.debit else @@ -160,7 +138,7 @@ def get_assets(filters): aca.parent = a.asset_category and aca.company_name = %(company)s join `tabCompany` company on company.name = %(company)s - where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) group by a.asset_category union SELECT a.asset_category, diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 74625890a69..a1d29f88cb1 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -126,6 +126,7 @@ def make_depreciation_entry(asset_name, date=None): je.append("accounts", debit_entry) je.flags.ignore_permissions = True + je.flags.planned_depr_entry = True je.save() if not je.meta.get_workflow(): je.submit() diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7eaa4bf997a..8a1df6f71ce 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1453,7 +1453,7 @@ class TestDepreciationBasics(AssetSetup): ) self.assertEqual(asset.status, "Submitted") - self.assertEqual(asset.get("value_after_depreciation"), 100000) + self.assertEqual(asset.get_value_after_depreciation(), 100000) jv = make_journal_entry( "_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False @@ -1466,12 +1466,68 @@ class TestDepreciationBasics(AssetSetup): jv.submit() asset.reload() - self.assertEqual(asset.get("value_after_depreciation"), 99900) + self.assertEqual(asset.get_value_after_depreciation(), 99900) jv.cancel() asset.reload() - self.assertEqual(asset.get("value_after_depreciation"), 100000) + self.assertEqual(asset.get_value_after_depreciation(), 100000) + + def test_manual_depreciation_for_depreciable_asset(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + purchase_date="2020-01-30", + available_for_use_date="2020-01-30", + expected_value_after_useful_life=10000, + total_number_of_depreciations=10, + frequency_of_depreciation=1, + submit=1, + ) + + self.assertEqual(asset.status, "Submitted") + self.assertEqual(asset.get_value_after_depreciation(), 100000) + + jv = make_journal_entry( + "_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False + ) + for d in jv.accounts: + d.reference_type = "Asset" + d.reference_name = asset.name + jv.voucher_type = "Depreciation Entry" + jv.insert() + jv.submit() + + asset.reload() + self.assertEqual(asset.get_value_after_depreciation(), 99900) + + jv.cancel() + + asset.reload() + self.assertEqual(asset.get_value_after_depreciation(), 100000) + + def test_manual_depreciation_with_incorrect_jv_voucher_type(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + purchase_date="2020-01-30", + available_for_use_date="2020-01-30", + expected_value_after_useful_life=10000, + total_number_of_depreciations=10, + frequency_of_depreciation=1, + submit=1, + ) + + jv = make_journal_entry( + "_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False + ) + for d in jv.accounts: + d.reference_type = "Asset" + d.reference_name = asset.name + d.account_type = "Depreciation" + jv.voucher_type = "Journal Entry" + + self.assertRaises(frappe.ValidationError, jv.insert) def create_asset_data(): From 5630e8189bd3c6bbbd35fafc01a6d7326b7ef1f5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:53:10 +0530 Subject: [PATCH 139/176] fix: use filter_by_finance_book instead of only_depreciable_assets in fixed asset register (backport #35031) (#35035) fix: use filter_by_finance_book instead of only_depreciable_assets in fixed asset register (#35031) fix: use filter_by_finance_book instead of only_depreciable_assets (cherry picked from commit e08d636bf7979356f60301260177e57213e84fd7) Co-authored-by: Anand Baburajan --- .../report/fixed_asset_register/fixed_asset_register.js | 6 +++--- .../report/fixed_asset_register/fixed_asset_register.py | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 65a4226ebdf..4f7b8361077 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -94,11 +94,11 @@ frappe.query_reports["Fixed Asset Register"] = { label: __("Finance Book"), fieldtype: "Link", options: "Finance Book", - depends_on: "eval: doc.only_depreciable_assets == 1", + depends_on: "eval: doc.filter_by_finance_book == 1", }, { - fieldname:"only_depreciable_assets", - label: __("Only depreciable assets"), + fieldname:"filter_by_finance_book", + label: __("Filter by Finance Book"), fieldtype: "Check" }, { 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 63f9889f054..c6fbc6c58e8 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -45,8 +45,6 @@ def get_conditions(filters): filters.year_end_date = getdate(fiscal_year.year_end_date) conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]] - if filters.get("only_depreciable_assets"): - conditions["calculate_depreciation"] = filters.get("only_depreciable_assets") if filters.get("only_existing_assets"): conditions["is_existing_asset"] = filters.get("only_existing_assets") if filters.get("asset_category"): @@ -106,7 +104,7 @@ def get_data(filters): assets_linked_to_fb = None - if filters.only_depreciable_assets: + if filters.filter_by_finance_book: assets_linked_to_fb = frappe.db.get_all( doctype="Asset Finance Book", filters={"finance_book": filters.finance_book or ("is", "not set")}, From 5cc3978c165497c4a4a3e52df30df3648df41046 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:42:32 +0530 Subject: [PATCH 140/176] fix: pass reference_doctype in link queries (backport #35038) (#35039) fix: pass reference_doctype in link queries (#35038) (cherry picked from commit 6de71eb15857291295921df984c59d4d871eb9f0) Co-authored-by: Ankush Menat --- erpnext/controllers/queries.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index b0cf7241669..799fed99cc7 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -576,7 +576,9 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters): +def get_filtered_dimensions( + doctype, txt, searchfield, start, page_len, filters, reference_doctype=None +): from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import ( get_dimension_filter_map, ) @@ -617,7 +619,12 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) query_filters.append(["name", query_selector, dimensions]) output = frappe.get_list( - doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1 + doctype, + fields=fields, + filters=query_filters, + or_filters=or_filters, + as_list=1, + reference_doctype=reference_doctype, ) return [tuple(d) for d in set(output)] From 5923a80a0fc4e66e3cbfcba0012ddccebff9f8fe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 19:20:06 +0530 Subject: [PATCH 141/176] feat: Reconcile Payments in background (#34596) feat: Reconcile Payments in background (#34596) * feat: auto reconcile in background * chore: Option to enable auto reconciliation in settings * refactor: validate if feature is enabled in settings * refactor: check for running job while using reconciliation tool * chore: using doc to get filter values * chore: use frappe.db.get_value in validations * chore: cleanup commented out code * chore: replace get_list with get_all * chore: use block scope variable * chore: type information for functions * refactor: flag to ignore job validation check * refactor: update parent doc status if all reconciled * chore: create test_records file * test: create a bunch of vouchers for testing auto reconcile * chore: renamed auto_reconcile to process_payment_reconciliation * chore: another child doctype to hold payments * chore: remove duplicate field * chore: add fetched payments to log * chore: Popup comment message update * chore: replace get_all with get_value * chore: replace label in settings page * chore: remove unit test and records * refactor: status in reconciliation log * refactor: set status in log as well * chore: fix field name * chore: change triggered job name * chore: use status field in list view of log * chore: status while there are no allocations * refactor: split trigger function into two * chore: adding cancelled status * refactor: function trigger queued docs * chore: cron job scheduled * chore: fixing accouts settings json file * chore: typos and variable scope * chore: use 'pluck' in db call * chore: remove redundant whitelist decorator * chore: use single DB call to fetch values * chore: replace get_all with get_value * refactor: use raw db calls to fetch reconciliation log records Using get_doc on `Process Payment Reconciliation Log` is costly when handling large volumes of invoices. Use raw frappe.db.get_all to selectively pull status and reconciled count * chore: update status on successful batch operation * chore: make payment table readonly * chore: ability to pause the background job * chore: remove isolate_each_allocation * chore: more description in progress bar * refactor: partially working state * refactor: update reconcile flag and setting hard limits for fetching * chore: make allocation editable -- NEED TO REVERT * chore: pause button * refactor: skip setter function in Payment Entry for better performan * refactor: split reconcile function and skip a setter function 1. Split reconcile function into 2 2. While reconciling against payment entry, skip a set_missing_ref_details setter method * chore: increase payment limit * refactor: replace frappe.db.get_all with frappe.db.get_value * chore: remove unwanted doctypes * refactor: make allocation table readonly * perf: update ref_details only for newly linked invoices * chore: rename skip flag * refactor(UI): receivable_payable field should auto populate * refactor: no control statements in finally block * chore: cleanup section and rename checkbox * chore: update new fieldname in code * chore: update error msg * refactor: start and pause integrated into status pause checkbox has been removed * refactor: added cancelled status to the log doctype 1. Moved the status section to the bottom in parent doc 2. Using alerts to indicate Job trigger status (cherry picked from commit ed14d1ce443e87ba69b1960eb08854af4d62e39e) Co-authored-by: ruthra kumar --- .../accounts_settings/accounts_settings.json | 20 +- .../payment_reconciliation.js | 26 + .../payment_reconciliation.py | 34 +- .../__init__.py | 0 .../process_payment_reconciliation.js | 130 +++++ .../process_payment_reconciliation.json | 173 ++++++ .../process_payment_reconciliation.py | 503 ++++++++++++++++++ ...rocess_payment_reconciliation_dashboard.py | 15 + .../process_payment_reconciliation_list.js | 15 + .../test_process_payment_reconciliation.py | 9 + .../__init__.py | 0 .../process_payment_reconciliation_log.js | 17 + .../process_payment_reconciliation_log.json | 137 +++++ .../process_payment_reconciliation_log.py | 9 + ...process_payment_reconciliation_log_list.js | 15 + ...test_process_payment_reconciliation_log.py | 9 + .../__init__.py | 0 ...ayment_reconciliation_log_allocations.json | 170 ++++++ ..._payment_reconciliation_log_allocations.py | 9 + erpnext/accounts/utils.py | 13 +- erpnext/hooks.py | 1 + 21 files changed, 1290 insertions(+), 15 deletions(-) create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/__init__.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_dashboard.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_list.js create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log/__init__.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.js create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log_list.js create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/__init__.py create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json create mode 100644 erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 379e470fc94..d5ed0676fd1 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -40,6 +40,8 @@ "show_payment_schedule_in_print", "currency_exchange_section", "allow_stale", + "section_break_jpd0", + "auto_reconcile_payments", "stale_days", "invoicing_settings_tab", "accounts_transactions_settings_section", @@ -170,11 +172,6 @@ "fieldtype": "Int", "label": "Stale Days" }, - { - "fieldname": "report_settings_sb", - "fieldtype": "Section Break", - "label": "Report Settings" - }, { "default": "0", "description": "Only select this if you have set up the Cash Flow Mapper documents", @@ -370,6 +367,17 @@ "fieldname": "merge_similar_account_heads", "fieldtype": "Check", "label": "Merge Similar Account Heads" + }, + { + "fieldname": "section_break_jpd0", + "fieldtype": "Section Break", + "label": "Payment Reconciliations" + }, + { + "default": "0", + "fieldname": "auto_reconcile_payments", + "fieldtype": "Check", + "label": "Auto Reconcile Payments" } ], "icon": "icon-cog", @@ -377,7 +385,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-04-17 11:45:42.049247", + "modified": "2023-04-21 13:11:37.130743", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index caffac5354f..08d38dde474 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -82,6 +82,32 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default'); this.frm.change_custom_button_type('Allocate', null, 'default'); } + + // check for any running reconciliation jobs + if (this.frm.doc.receivable_payable_account) { + frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments").then((enabled) => { + if(enabled) { + this.frm.call({ + 'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running", + "args": { + for_filter: { + company: this.frm.doc.company, + party_type: this.frm.doc.party_type, + party: this.frm.doc.party, + receivable_payable_account: this.frm.doc.receivable_payable_account + } + } + }).then(r => { + if (r.message) { + let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true); + let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]); + this.frm.dashboard.add_comment(msg, "yellow"); + } + }); + } + }); + } + } company() { diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index d8082d058f3..cc2b9420cc2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -7,9 +7,12 @@ from frappe import _, msgprint, qb from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import IfNull -from frappe.utils import flt, getdate, nowdate, today +from frappe.utils import flt, get_link_to_form, getdate, nowdate, today import erpnext +from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import ( + is_any_doc_running, +) from erpnext.accounts.utils import ( QueryPaymentLedger, get_outstanding_invoices, @@ -304,9 +307,7 @@ class PaymentReconciliation(Document): } ) - @frappe.whitelist() - def reconcile(self): - self.validate_allocation() + def reconcile_allocations(self, skip_ref_details_update_for_pe=False): dr_or_cr = ( "credit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == "Receivable" @@ -330,12 +331,35 @@ class PaymentReconciliation(Document): self.make_difference_entry(payment_details) if entry_list: - reconcile_against_document(entry_list) + reconcile_against_document(entry_list, skip_ref_details_update_for_pe) if dr_or_cr_notes: reconcile_dr_cr_note(dr_or_cr_notes, self.company) + @frappe.whitelist() + def reconcile(self): + if frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"): + running_doc = is_any_doc_running( + dict( + company=self.company, + party_type=self.party_type, + party=self.party, + receivable_payable_account=self.receivable_payable_account, + ) + ) + + if running_doc: + frappe.throw( + _("A Reconciliation Job {0} is running for the same filters. Cannot reconcile now").format( + get_link_to_form("Auto Reconcile", running_doc) + ) + ) + return + + self.validate_allocation() + self.reconcile_allocations() msgprint(_("Successfully Reconciled")) + self.get_unreconciled_entries() def make_difference_entry(self, row): diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/__init__.py b/erpnext/accounts/doctype/process_payment_reconciliation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js new file mode 100644 index 00000000000..dd601bfc451 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js @@ -0,0 +1,130 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Process Payment Reconciliation", { + onload: function(frm) { + // set queries + frm.set_query("party_type", function() { + return { + "filters": { + "name": ["in", Object.keys(frappe.boot.party_account_types)], + } + } + }); + frm.set_query('receivable_payable_account', function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0, + "account_type": frappe.boot.party_account_types[doc.party_type] + } + }; + }); + frm.set_query('cost_center', function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0, + } + }; + }); + frm.set_query('bank_cash_account', function(doc) { + return { + filters:[ + ['Account', 'company', '=', doc.company], + ['Account', 'is_group', '=', 0], + ['Account', 'account_type', 'in', ['Bank', 'Cash']] + ] + }; + }); + + }, + refresh: function(frm) { + if (frm.doc.docstatus==1 && ['Queued', 'Paused'].find(x => x == frm.doc.status)) { + let execute_btn = __("Start / Resume") + + frm.add_custom_button(execute_btn, () => { + frm.call({ + method: 'erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_job_for_doc', + args: { + docname: frm.doc.name + } + }).then(r => { + if(!r.exc) { + frappe.show_alert(__("Job Started")); + frm.reload_doc(); + } + }); + }); + } + if (frm.doc.docstatus==1 && ['Completed', 'Running', 'Paused', 'Partially Reconciled'].find(x => x == frm.doc.status)) { + frm.call({ + 'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.get_reconciled_count", + args: { + "docname": frm.docname, + } + }).then(r => { + if (r.message) { + let progress = 0; + let description = ""; + + if (r.message.processed) { + progress = (r.message.processed/r.message.total) * 100; + description = r.message.processed + "/" + r.message.total + " processed"; + } else if (r.message.total == 0 && frm.doc.status == "Completed") { + progress = 100; + } + + + frm.dashboard.add_progress('Reconciliation Progress', progress, description); + } + }) + } + if (frm.doc.docstatus==1 && frm.doc.status == 'Running') { + let execute_btn = __("Pause") + + frm.add_custom_button(execute_btn, () => { + frm.call({ + 'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.pause_job_for_doc", + args: { + "docname": frm.docname, + } + }).then(r => { + if (!r.exc) { + frappe.show_alert(__("Job Paused")); + frm.reload_doc() + } + }); + + }); + } + }, + company(frm) { + frm.set_value('party', ''); + frm.set_value('receivable_payable_account', ''); + }, + party_type(frm) { + frm.set_value('party', ''); + }, + + party(frm) { + frm.set_value('receivable_payable_account', ''); + if (!frm.doc.receivable_payable_account && frm.doc.party_type && frm.doc.party) { + return frappe.call({ + method: "erpnext.accounts.party.get_party_account", + args: { + company: frm.doc.company, + party_type: frm.doc.party_type, + party: frm.doc.party + }, + callback: (r) => { + if (!r.exc && r.message) { + frm.set_value("receivable_payable_account", r.message); + } + frm.refresh(); + + } + }); + } + } +}); diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json new file mode 100644 index 00000000000..8bb7092dc50 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json @@ -0,0 +1,173 @@ +{ + "actions": [], + "autoname": "format:ACC-PPR-{#####}", + "beta": 1, + "creation": "2023-03-30 21:28:39.793927", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "party_type", + "column_break_io6c", + "party", + "receivable_payable_account", + "filter_section", + "from_invoice_date", + "to_invoice_date", + "column_break_kegk", + "from_payment_date", + "to_payment_date", + "column_break_uj04", + "cost_center", + "bank_cash_account", + "section_break_2n02", + "status", + "error_log", + "section_break_a8yx", + "amended_from" + ], + "fields": [ + { + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nQueued\nRunning\nPaused\nCompleted\nPartially Reconciled\nFailed\nCancelled", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "party_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Party Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "column_break_io6c", + "fieldtype": "Column Break" + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Party", + "options": "party_type", + "reqd": 1 + }, + { + "fieldname": "receivable_payable_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Receivable/Payable Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "filter_section", + "fieldtype": "Section Break", + "label": "Filters" + }, + { + "fieldname": "from_invoice_date", + "fieldtype": "Date", + "label": "From Invoice Date" + }, + { + "fieldname": "to_invoice_date", + "fieldtype": "Date", + "label": "To Invoice Date" + }, + { + "fieldname": "column_break_kegk", + "fieldtype": "Column Break" + }, + { + "fieldname": "from_payment_date", + "fieldtype": "Date", + "label": "From Payment Date" + }, + { + "fieldname": "to_payment_date", + "fieldtype": "Date", + "label": "To Payment Date" + }, + { + "fieldname": "column_break_uj04", + "fieldtype": "Column Break" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "bank_cash_account", + "fieldtype": "Link", + "label": "Bank/Cash Account", + "options": "Account" + }, + { + "fieldname": "section_break_2n02", + "fieldtype": "Section Break", + "label": "Status" + }, + { + "depends_on": "eval:doc.error_log", + "fieldname": "error_log", + "fieldtype": "Long Text", + "label": "Error Log" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Process Payment Reconciliation", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_a8yx", + "fieldtype": "Section Break" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2023-04-21 17:19:30.912953", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Payment Reconciliation", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "company" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py new file mode 100644 index 00000000000..ecb51ce1445 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -0,0 +1,503 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _, qb +from frappe.model.document import Document +from frappe.utils import get_link_to_form +from frappe.utils.scheduler import is_scheduler_inactive + + +class ProcessPaymentReconciliation(Document): + def validate(self): + self.validate_receivable_payable_account() + self.validate_bank_cash_account() + + def validate_receivable_payable_account(self): + if self.receivable_payable_account: + if self.company != frappe.db.get_value("Account", self.receivable_payable_account, "company"): + frappe.throw( + _("Receivable/Payable Account: {0} doesn't belong to company {1}").format( + frappe.bold(self.receivable_payable_account), frappe.bold(self.company) + ) + ) + + def validate_bank_cash_account(self): + if self.bank_cash_account: + if self.company != frappe.db.get_value("Account", self.bank_cash_account, "company"): + frappe.throw( + _("Bank/Cash Account {0} doesn't belong to company {1}").format( + frappe.bold(self.bank_cash_account), frappe.bold(self.company) + ) + ) + + def before_save(self): + self.status = "" + self.error_log = "" + + def on_submit(self): + self.db_set("status", "Queued") + self.db_set("error_log", None) + + def on_cancel(self): + self.db_set("status", "Cancelled") + log = frappe.db.get_value( + "Process Payment Reconciliation Log", filters={"process_pr": self.name} + ) + if log: + frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Cancelled") + + +@frappe.whitelist() +def get_reconciled_count(docname: str | None = None) -> float: + current_status = {} + if docname: + reconcile_log = frappe.db.get_value( + "Process Payment Reconciliation Log", filters={"process_pr": docname}, fieldname="name" + ) + if reconcile_log: + res = frappe.get_all( + "Process Payment Reconciliation Log", + filters={"name": reconcile_log}, + fields=["reconciled_entries", "total_allocations"], + as_list=1, + ) + current_status["processed"], current_status["total"] = res[0] + + return current_status + + +def get_pr_instance(doc: str): + process_payment_reconciliation = frappe.get_doc("Process Payment Reconciliation", doc) + + pr = frappe.get_doc("Payment Reconciliation") + fields = [ + "company", + "party_type", + "party", + "receivable_payable_account", + "from_invoice_date", + "to_invoice_date", + "from_payment_date", + "to_payment_date", + ] + d = {} + for field in fields: + d[field] = process_payment_reconciliation.get(field) + pr.update(d) + pr.invoice_limit = 1000 + pr.payment_limit = 1000 + return pr + + +def is_job_running(job_name: str) -> bool: + jobs = frappe.db.get_all("RQ Job", filters={"status": ["in", ["started", "queued"]]}) + for x in jobs: + if x.job_name == job_name: + return True + return False + + +@frappe.whitelist() +def pause_job_for_doc(docname: str | None = None): + if docname: + frappe.db.set_value("Process Payment Reconciliation", docname, "status", "Paused") + log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": docname}) + if log: + frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Paused") + + +@frappe.whitelist() +def trigger_job_for_doc(docname: str | None = None): + """ + Trigger background job + """ + if not docname: + return + + if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"): + frappe.throw( + _("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format( + get_link_to_form("Accounts Settings", "Accounts Settings") + ) + ) + + return + + if not is_scheduler_inactive(): + if frappe.db.get_value("Process Payment Reconciliation", docname, "status") == "Queued": + frappe.db.set_value("Process Payment Reconciliation", docname, "status", "Running") + job_name = f"start_processing_{docname}" + if not is_job_running(job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile_based_on_filters", + queue="long", + is_async=True, + job_name=job_name, + enqueue_after_commit=True, + doc=docname, + ) + + elif frappe.db.get_value("Process Payment Reconciliation", docname, "status") == "Paused": + frappe.db.set_value("Process Payment Reconciliation", docname, "status", "Running") + log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": docname}) + if log: + frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Running") + + # Resume tasks for running doc + job_name = f"start_processing_{docname}" + if not is_job_running(job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile_based_on_filters", + queue="long", + is_async=True, + job_name=job_name, + doc=docname, + ) + else: + frappe.msgprint(_("Scheduler is Inactive. Can't trigger job now.")) + + +def trigger_reconciliation_for_queued_docs(): + """ + Will be called from Cron Job + Fetch queued docs and start reconciliation process for each one + """ + if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"): + frappe.throw( + _("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format( + get_link_to_form("Accounts Settings", "Accounts Settings") + ) + ) + + return + + if not is_scheduler_inactive(): + # Get all queued documents + all_queued = frappe.db.get_all( + "Process Payment Reconciliation", + filters={"docstatus": 1, "status": "Queued"}, + order_by="creation desc", + as_list=1, + ) + + docs_to_trigger = [] + unique_filters = set() + queue_size = 5 + + fields = ["company", "party_type", "party", "receivable_payable_account"] + + def get_filters_as_tuple(fields, doc): + filters = () + for x in fields: + filters += tuple(doc.get(x)) + return filters + + for x in all_queued: + doc = frappe.get_doc("Process Payment Reconciliation", x) + filters = get_filters_as_tuple(fields, doc) + if filters not in unique_filters: + unique_filters.add(filters) + docs_to_trigger.append(doc.name) + if len(docs_to_trigger) == queue_size: + break + + # trigger reconcilation process for queue_size unique filters + for doc in docs_to_trigger: + trigger_job_for_doc(doc) + + else: + frappe.msgprint(_("Scheduler is Inactive. Can't trigger jobs now.")) + + +def reconcile_based_on_filters(doc: None | str = None) -> None: + """ + Identify current state of document and execute next tasks in background + """ + if doc: + log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": doc}) + if not log: + log = frappe.new_doc("Process Payment Reconciliation Log") + log.process_pr = doc + log.status = "Running" + log = log.save() + + job_name = f"process_{doc}_fetch_and_allocate" + if not is_job_running(job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.fetch_and_allocate", + queue="long", + timeout="3600", + is_async=True, + job_name=job_name, + enqueue_after_commit=True, + doc=doc, + ) + else: + res = frappe.get_all( + "Process Payment Reconciliation Log", + filters={"name": log}, + fields=["allocated", "reconciled"], + as_list=1, + ) + allocated, reconciled = res[0] + + if not allocated: + job_name = f"process__{doc}_fetch_and_allocate" + if not is_job_running(job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.fetch_and_allocate", + queue="long", + timeout="3600", + is_async=True, + job_name=job_name, + enqueue_after_commit=True, + doc=doc, + ) + elif not reconciled: + allocation = get_next_allocation(log) + if allocation: + reconcile_job_name = ( + f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}" + ) + else: + reconcile_job_name = f"process_{doc}_reconcile" + if not is_job_running(reconcile_job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile", + queue="long", + timeout="3600", + is_async=True, + job_name=reconcile_job_name, + enqueue_after_commit=True, + doc=doc, + ) + elif reconciled: + frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") + + +def get_next_allocation(log: str) -> list: + if log: + allocations = [] + next = frappe.db.get_all( + "Process Payment Reconciliation Log Allocations", + filters={"parent": log, "reconciled": 0}, + fields=["reference_type", "reference_name"], + order_by="idx", + limit=1, + ) + + if next: + allocations = frappe.db.get_all( + "Process Payment Reconciliation Log Allocations", + filters={ + "parent": log, + "reconciled": 0, + "reference_type": next[0].reference_type, + "reference_name": next[0].reference_name, + }, + fields=["*"], + order_by="idx", + ) + + return allocations + return [] + + +def fetch_and_allocate(doc: str) -> None: + """ + Fetch Invoices and Payments based on filters applied. FIFO ordering is used for allocation. + """ + + if doc: + log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": doc}) + if log: + if not frappe.db.get_value("Process Payment Reconciliation Log", log, "allocated"): + reconcile_log = frappe.get_doc("Process Payment Reconciliation Log", log) + + pr = get_pr_instance(doc) + pr.get_unreconciled_entries() + + if len(pr.invoices) > 0 and len(pr.payments) > 0: + 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})) + + for x in pr.get("allocation"): + reconcile_log.append( + "allocations", + x.as_dict().update( + { + "parenttype": "Process Payment Reconciliation Log", + "parent": reconcile_log.name, + "name": None, + "reconciled": False, + } + ), + ) + reconcile_log.allocated = True + reconcile_log.total_allocations = len(reconcile_log.get("allocations")) + reconcile_log.reconciled_entries = 0 + reconcile_log.save() + + # generate reconcile job name + allocation = get_next_allocation(log) + if allocation: + reconcile_job_name = ( + f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}" + ) + else: + reconcile_job_name = f"process_{doc}_reconcile" + + if not is_job_running(reconcile_job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile", + queue="long", + timeout="3600", + is_async=True, + job_name=reconcile_job_name, + enqueue_after_commit=True, + doc=doc, + ) + + +def reconcile(doc: None | str = None) -> None: + if doc: + log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": doc}) + if log: + res = frappe.get_all( + "Process Payment Reconciliation Log", + filters={"name": log}, + fields=["reconciled_entries", "total_allocations"], + as_list=1, + limit=1, + ) + + reconciled_entries, total_allocations = res[0] + if reconciled_entries != total_allocations: + try: + # Fetch next allocation + allocations = get_next_allocation(log) + + pr = get_pr_instance(doc) + + # pass allocation to PR instance + for x in allocations: + pr.append("allocation", x) + + # reconcile + pr.reconcile_allocations(skip_ref_details_update_for_pe=True) + + # If Payment Entry, update details only for newly linked references + # This is for performance + if allocations[0].reference_type == "Payment Entry": + + references = [(x.invoice_type, x.invoice_number) for x in allocations] + pe = frappe.get_doc(allocations[0].reference_type, allocations[0].reference_name) + pe.flags.ignore_validate_update_after_submit = True + pe.set_missing_ref_details(update_ref_details_only_for=references) + pe.save() + + # Update reconciled flag + allocation_names = [x.name for x in allocations] + ppa = qb.DocType("Process Payment Reconciliation Log Allocations") + qb.update(ppa).set(ppa.reconciled, True).where(ppa.name.isin(allocation_names)).run() + + # Update reconciled count + reconciled_count = frappe.db.count( + "Process Payment Reconciliation Log Allocations", filters={"parent": log, "reconciled": True} + ) + frappe.db.set_value( + "Process Payment Reconciliation Log", log, "reconciled_entries", reconciled_count + ) + + except Exception as err: + # Update the parent doc about the exception + frappe.db.rollback() + + traceback = frappe.get_traceback() + if traceback: + message = "Traceback:
" + traceback + frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message) + frappe.db.set_value( + "Process Payment Reconciliation", + doc, + "error_log", + message, + ) + if reconciled_entries and total_allocations and reconciled_entries < total_allocations: + frappe.db.set_value( + "Process Payment Reconciliation Log", log, "status", "Partially Reconciled" + ) + frappe.db.set_value( + "Process Payment Reconciliation", + doc, + "status", + "Partially Reconciled", + ) + else: + frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Failed") + frappe.db.set_value( + "Process Payment Reconciliation", + doc, + "status", + "Failed", + ) + finally: + if reconciled_entries == total_allocations: + frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Reconciled") + frappe.db.set_value("Process Payment Reconciliation Log", log, "reconciled", True) + frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") + else: + + if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"): + # trigger next batch in job + # generate reconcile job name + allocation = get_next_allocation(log) + if allocation: + reconcile_job_name = ( + f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}" + ) + else: + reconcile_job_name = f"process_{doc}_reconcile" + + if not is_job_running(reconcile_job_name): + job = frappe.enqueue( + method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile", + queue="long", + timeout="3600", + is_async=True, + job_name=reconcile_job_name, + enqueue_after_commit=True, + doc=doc, + ) + else: + frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Reconciled") + frappe.db.set_value("Process Payment Reconciliation Log", log, "reconciled", True) + frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") + + +@frappe.whitelist() +def is_any_doc_running(for_filter: str | dict | None = None) -> str | None: + running_doc = None + if for_filter: + if type(for_filter) == str: + for_filter = frappe.json.loads(for_filter) + + running_doc = frappe.db.get_value( + "Process Payment Reconciliation", + filters={ + "docstatus": 1, + "status": ["in", ["Running", "Paused"]], + "company": for_filter.get("company"), + "party_type": for_filter.get("party_type"), + "party": for_filter.get("party"), + "receivable_payable_account": for_filter.get("receivable_payable_account"), + }, + fieldname="name", + ) + else: + running_doc = frappe.db.get_value( + "Process Payment Reconciliation", filters={"docstatus": 1, "status": "Running"} + ) + return running_doc diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_dashboard.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_dashboard.py new file mode 100644 index 00000000000..784f4548bd4 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_dashboard.py @@ -0,0 +1,15 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "process_pr", + "transactions": [ + { + "label": _("Reconciliation Logs"), + "items": [ + "Process Payment Reconciliation Log", + ], + }, + ], + } diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_list.js b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_list.js new file mode 100644 index 00000000000..8012d6e0374 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation_list.js @@ -0,0 +1,15 @@ +frappe.listview_settings['Process Payment Reconciliation'] = { + add_fields: ["status"], + get_indicator: function(doc) { + let colors = { + 'Queued': 'orange', + 'Paused': 'orange', + 'Completed': 'green', + 'Partially Reconciled': 'orange', + 'Running': 'blue', + 'Failed': 'red', + }; + let status = doc.status; + return [__(status), colors[status], 'status,=,'+status]; + }, +}; diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py new file mode 100644 index 00000000000..ad1e952579d --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestProcessPaymentReconciliation(FrappeTestCase): + pass diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/__init__.py b/erpnext/accounts/doctype/process_payment_reconciliation_log/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.js b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.js new file mode 100644 index 00000000000..2468f10bccf --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.js @@ -0,0 +1,17 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Process Payment Reconciliation Log", { + refresh(frm) { + if (['Completed', 'Running', 'Paused', 'Partially Reconciled'].find(x => x == frm.doc.status)) { + let progress = 0; + if (frm.doc.reconciled_entries != 0) { + progress = frm.doc.reconciled_entries / frm.doc.total_allocations * 100; + } else if(frm.doc.total_allocations == 0 && frm.doc.status == "Completed"){ + progress = 100; + } + frm.dashboard.add_progress(__('Reconciliation Progress'), progress); + } + + }, +}); diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json new file mode 100644 index 00000000000..1131a0fca69 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json @@ -0,0 +1,137 @@ +{ + "actions": [], + "autoname": "format:PPR-LOG-{##}", + "beta": 1, + "creation": "2023-03-13 15:00:09.149681", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "process_pr", + "section_break_fvdw", + "status", + "tasks_section", + "allocated", + "reconciled", + "column_break_yhin", + "total_allocations", + "reconciled_entries", + "section_break_4ywv", + "error_log", + "allocations_section", + "allocations" + ], + "fields": [ + { + "fieldname": "allocations", + "fieldtype": "Table", + "label": "Allocations", + "options": "Process Payment Reconciliation Log Allocations", + "read_only": 1 + }, + { + "default": "0", + "description": "All allocations have been successfully reconciled", + "fieldname": "reconciled", + "fieldtype": "Check", + "label": "Reconciled", + "read_only": 1 + }, + { + "fieldname": "total_allocations", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Total Allocations", + "read_only": 1 + }, + { + "default": "0", + "description": "Invoices and Payments have been Fetched and Allocated", + "fieldname": "allocated", + "fieldtype": "Check", + "label": "Allocated", + "read_only": 1 + }, + { + "fieldname": "reconciled_entries", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Reconciled Entries", + "read_only": 1 + }, + { + "fieldname": "tasks_section", + "fieldtype": "Section Break", + "label": "Tasks" + }, + { + "fieldname": "allocations_section", + "fieldtype": "Section Break", + "label": "Allocations" + }, + { + "fieldname": "column_break_yhin", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4ywv", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:doc.error_log", + "fieldname": "error_log", + "fieldtype": "Long Text", + "label": "Reconciliation Error Log", + "read_only": 1 + }, + { + "fieldname": "process_pr", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parent Document", + "options": "Process Payment Reconciliation", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "section_break_fvdw", + "fieldtype": "Section Break", + "label": "Status" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Running\nPaused\nReconciled\nPartially Reconciled\nFailed\nCancelled", + "read_only": 1 + } + ], + "in_create": 1, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-04-21 17:36:26.642617", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Payment Reconciliation Log", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "search_fields": "allocated, reconciled, total_allocations, reconciled_entries", + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.py b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.py new file mode 100644 index 00000000000..85d70a48327 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ProcessPaymentReconciliationLog(Document): + pass diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log_list.js b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log_list.js new file mode 100644 index 00000000000..5a652048a23 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log_list.js @@ -0,0 +1,15 @@ +frappe.listview_settings['Process Payment Reconciliation Log'] = { + add_fields: ["status"], + get_indicator: function(doc) { + var colors = { + 'Partially Reconciled': 'orange', + 'Paused': 'orange', + 'Reconciled': 'green', + 'Failed': 'red', + 'Cancelled': 'red', + 'Running': 'blue', + }; + let status = doc.status; + return [__(status), colors[status], "status,=,"+status]; + }, +}; diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py b/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py new file mode 100644 index 00000000000..c2da62e2de4 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestProcessPaymentReconciliationLog(FrappeTestCase): + pass diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/__init__.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json new file mode 100644 index 00000000000..b97d73886a9 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -0,0 +1,170 @@ +{ + "actions": [], + "creation": "2023-03-13 13:51:27.351463", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "reference_row", + "column_break_3", + "invoice_type", + "invoice_number", + "section_break_6", + "allocated_amount", + "unreconciled_amount", + "column_break_8", + "amount", + "is_advance", + "section_break_5", + "difference_amount", + "column_break_7", + "difference_account", + "exchange_rate", + "currency", + "reconciled" + ], + "fields": [ + { + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "options": "DocType", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "options": "reference_type", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "invoice_type", + "fieldtype": "Link", + "label": "Invoice Type", + "options": "DocType", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "invoice_number", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Invoice Number", + "options": "invoice_type", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated Amount", + "options": "currency", + "reqd": 1 + }, + { + "fieldname": "unreconciled_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Unreconciled Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "is_advance", + "fieldtype": "Data", + "hidden": 1, + "label": "Is Advance", + "read_only": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "difference_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Difference Amount", + "options": "Currency", + "read_only": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "difference_account", + "fieldtype": "Link", + "label": "Difference Account", + "options": "Account", + "read_only": 1 + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "read_only": 1 + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency" + }, + { + "default": "0", + "fieldname": "reconciled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Reconciled" + } + ], + "istable": 1, + "links": [], + "modified": "2023-03-20 21:05:43.121945", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Payment Reconciliation Log Allocations", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py new file mode 100644 index 00000000000..c3e43297d08 --- /dev/null +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ProcessPaymentReconciliationLogAllocations(Document): + pass diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7fd6242b9c6..c410856a5db 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -420,7 +420,7 @@ def add_cc(args=None): return cc.name -def reconcile_against_document(args): # nosemgrep +def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # nosemgrep """ Cancel PE or JV, Update against document, split if required and resubmit """ @@ -449,7 +449,9 @@ def reconcile_against_document(args): # nosemgrep if voucher_type == "Journal Entry": update_reference_in_journal_entry(entry, doc, do_not_save=True) else: - update_reference_in_payment_entry(entry, doc, do_not_save=True) + update_reference_in_payment_entry( + entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe + ) doc.save(ignore_permissions=True) # re-submit advance entry @@ -586,7 +588,9 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): journal_entry.save(ignore_permissions=True) -def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): +def update_reference_in_payment_entry( + d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False +): reference_details = { "reference_doctype": d.against_voucher_type, "reference_name": d.against_voucher, @@ -635,7 +639,8 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): payment_entry.flags.ignore_validate_update_after_submit = True payment_entry.setup_party_account_field() payment_entry.set_missing_values() - payment_entry.set_missing_ref_details() + if not skip_ref_details_update_for_pe: + payment_entry.set_missing_ref_details() payment_entry.set_amounts() if not do_not_save: diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f1ee370e97e..0ba8a89bea3 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -370,6 +370,7 @@ scheduler_events = { "cron": { "0/15 * * * *": [ "erpnext.manufacturing.doctype.bom_update_log.bom_update_log.resume_bom_cost_update_jobs", + "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_reconciliation_for_queued_docs", ], "0/30 * * * *": [ "erpnext.utilities.doctype.video.video.update_youtube_data", From 665021270f337fd4f17418a914d53fb7ec83531f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:04:28 +0530 Subject: [PATCH 142/176] fix: Use set instead of db_set as it is called from validate (#34967) fix: Use set instead of db_set as it is called from validate (#34967) (cherry picked from commit 72b5c1f70a51f18cda48c8e71602ee2d620e582b) Co-authored-by: Nabin Hait --- erpnext/crm/doctype/opportunity/opportunity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index f4b6e910ed1..cc4ee06072d 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -60,7 +60,7 @@ class Opportunity(TransactionBase, CRMNote): if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field): try: value = frappe.db.get_value(self.opportunity_from, self.party_name, field) - self.db_set(field, value) + self.set(field, value) except Exception: continue From 5045ad6be6c728492de64b9e2137f1f9b10fe270 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:04:47 +0530 Subject: [PATCH 143/176] fix: Unable to allocate advance against invoice (#35007) fix: Unable to allocate advance against invoice (#35007) (cherry picked from commit f7b50f2adef11b9f2bd843d6bca3f2b6cf7be19b) Co-authored-by: Deepesh Garg --- erpnext/controllers/accounts_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 642d51c3254..6982f716bf9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1662,7 +1662,10 @@ class AccountsController(TransactionBase): ) self.append("payment_schedule", data) - if not automatically_fetch_payment_terms: + if not ( + automatically_fetch_payment_terms + and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype) + ): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt( From 29aa4a0222e70d4685fdf96bdd6b38a0b545ed67 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:05:13 +0530 Subject: [PATCH 144/176] fix: respect title_field from doctype to bulk transactions (#34928) fix: respect title_field from doctype to bulk transactions (#34928) (cherry picked from commit 22290c2694e626132de692a04e0fce2dba4c29df) Co-authored-by: HarryPaulo --- erpnext/utilities/bulk_transaction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index c1579b3cbcc..7fc1d9734ca 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -104,6 +104,7 @@ def task(doc_name, from_doctype, to_doctype): obj = mapper[from_doctype][to_doctype](doc_name) obj.flags.ignore_validate = True + obj.set_title_field() obj.insert(ignore_mandatory=True) From 878d7477bcb8f37283e2c7fbd00b50e4b193a871 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:05:34 +0530 Subject: [PATCH 145/176] fix: Bulk Payment Entry from PO/SO (#34942) fix: Bulk Payment Entry from PO/SO (#34942) Co-authored-by: Nihantra Patel (cherry picked from commit f1acc5fabb27999dbae2f56885c8922a4b683139) Co-authored-by: Solufy Solution <34390782+Solufyin@users.noreply.github.com> --- .../buying/doctype/purchase_order/purchase_order_list.js | 2 +- erpnext/selling/doctype/sales_order/sales_order_list.js | 2 +- erpnext/utilities/bulk_transaction.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js index d7907e4274b..6594746cfc5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js @@ -43,7 +43,7 @@ frappe.listview_settings['Purchase Order'] = { }); listview.page.add_action_item(__("Advance Payment"), ()=>{ - erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Advance Payment"); + erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Payment Entry"); }); } diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js index 4691190d2a5..64c58ef5d7b 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_list.js +++ b/erpnext/selling/doctype/sales_order/sales_order_list.js @@ -57,7 +57,7 @@ frappe.listview_settings['Sales Order'] = { }); listview.page.add_action_item(__("Advance Payment"), ()=>{ - erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Advance Payment"); + erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Payment Entry"); }); } diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 7fc1d9734ca..5e57b317937 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -69,7 +69,7 @@ def task(doc_name, from_doctype, to_doctype): "Sales Order": { "Sales Invoice": sales_order.make_sales_invoice, "Delivery Note": sales_order.make_delivery_note, - "Advance Payment": payment_entry.get_payment_entry, + "Payment Entry": payment_entry.get_payment_entry, }, "Sales Invoice": { "Delivery Note": sales_invoice.make_delivery_note, @@ -86,11 +86,11 @@ def task(doc_name, from_doctype, to_doctype): "Supplier Quotation": { "Purchase Order": supplier_quotation.make_purchase_order, "Purchase Invoice": supplier_quotation.make_purchase_invoice, - "Advance Payment": payment_entry.get_payment_entry, }, "Purchase Order": { "Purchase Invoice": purchase_order.make_purchase_invoice, "Purchase Receipt": purchase_order.make_purchase_receipt, + "Payment Entry": payment_entry.get_payment_entry, }, "Purchase Invoice": { "Purchase Receipt": purchase_invoice.make_purchase_receipt, @@ -98,7 +98,7 @@ def task(doc_name, from_doctype, to_doctype): }, "Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice}, } - if to_doctype in ["Advance Payment", "Payment Entry"]: + if to_doctype in ["Payment Entry"]: obj = mapper[from_doctype][to_doctype](from_doctype, doc_name) else: obj = mapper[from_doctype][to_doctype](doc_name) From 47df41fdbde0ed7efad8690b3f6848b0db673995 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:08:01 +0530 Subject: [PATCH 146/176] fix: wrong qty of remaining work orders to be created when using "Create" > "Work Order" (#34726) fix: wrong qty of remaining work orders to be created when using "Create" > "Work Order" (#34726) * fix: convert asynchronous field update to synchronous * fix: wrong qty of remaining work orders to be created when using "Create" > "Work Order" (cherry picked from commit 189b020d228bdb1c0c589697162cf91718b2fa27) Co-authored-by: danjeremynavarro <46537526+danjeremynavarro@users.noreply.github.com> --- erpnext/selling/doctype/sales_order/sales_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index ee9161bee48..d995517af82 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1340,8 +1340,9 @@ def get_work_order_items(sales_order, for_raw_material_request=0): .select(Sum(wo.qty)) .where( (wo.production_item == i.item_code) - & (wo.sales_order == so.name) * (wo.sales_order_item == i.name) - & (wo.docstatus.lte(2)) + & (wo.sales_order == so.name) + & (wo.sales_order_item == i.name) + & (wo.docstatus.lt(2)) ) .run()[0][0] ) From 06f204a8d646739f644d91f82cb5dbad03641856 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:11:08 +0530 Subject: [PATCH 147/176] fix: click handler should not attempt indexed access of empty array (#35013) fix: click handler should not attempt indexed access of empty array (#35013) fix: click handler should not attempt indexed access of empty array (cherry picked from commit 3d90b970d19ee0f41582dada6dbcdc4999fde10a) Co-authored-by: tundebabzy --- erpnext/public/js/projects/timer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/projects/timer.js b/erpnext/public/js/projects/timer.js index 9dae7118d9a..0209f4c2322 100644 --- a/erpnext/public/js/projects/timer.js +++ b/erpnext/public/js/projects/timer.js @@ -68,7 +68,7 @@ erpnext.timesheet.control_timer = function(frm, dialog, row, timestamp=0) { // New activity if no activities found var args = dialog.get_values(); if(!args) return; - if (frm.doc.time_logs.length <= 1 && !frm.doc.time_logs[0].activity_type && !frm.doc.time_logs[0].from_time) { + if (frm.doc.time_logs.length == 1 && !frm.doc.time_logs[0].activity_type && !frm.doc.time_logs[0].from_time) { frm.doc.time_logs = []; } row = frappe.model.add_child(frm.doc, "Timesheet Detail", "time_logs"); From f9f42c7e988426bc688ad2db3e3a804f13b88edc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:12:58 +0530 Subject: [PATCH 148/176] fix: per_billed condition for Payment Entry (#34969) fix: per_billed condition for Payment Entry (#34969) (cherry picked from commit d6bc8bba8b7ed748483bf61b03c8c87eb54f8ab0) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index fb211d993a4..e1536fac537 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1693,7 +1693,10 @@ def get_payment_entry( ): reference_doc = None doc = frappe.get_doc(dt, dn) - if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= 99.99: + over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance") + if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= ( + 100.0 + over_billing_allowance + ): frappe.throw(_("Can only make payment against unbilled {0}").format(dt)) if not party_type: From f43ea0d6ff2a3cbd1ef152b119a1c4114b958046 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:13:20 +0530 Subject: [PATCH 149/176] fix: Payment entry with TDS in bank reco statement (#34961) fix: Payment entry with TDS in bank reco statement (#34961) (cherry picked from commit ecea9b44a339fcfb261696118add5873a4d625fb) Co-authored-by: Deepesh Garg --- erpnext/accounts/doctype/bank_clearance/bank_clearance.py | 2 +- .../report/bank_clearance_summary/bank_clearance_summary.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 081718726bd..8ad0bd17b48 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -56,7 +56,7 @@ class BankClearance(Document): select "Payment Entry" as payment_document, name as payment_entry, reference_no as cheque_number, reference_date as cheque_date, - if(paid_from=%(account)s, paid_amount, 0) as credit, + if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit, if(paid_from=%(account)s, 0, received_amount) as debit, posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 306af722ba8..2d68bb70b83 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -80,7 +80,7 @@ def get_entries(filters): payment_entries = frappe.db.sql( """SELECT "Payment Entry", name, posting_date, reference_no, clearance_date, party, - if(paid_from=%(account)s, paid_amount * -1, received_amount) + if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount) FROM `tabPayment Entry` WHERE From 693007adfe6f8d9abf7375de38a2ad64405dc784 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:16:59 +0530 Subject: [PATCH 150/176] fix: Common party JV cost center (#35008) fix: Common party JV cost center (#35008) (cherry picked from commit f88431a79a6bda662352ec38b8fe650c7f07fdd3) Co-authored-by: Deepesh Garg --- erpnext/controllers/accounts_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6982f716bf9..d0ec6541623 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1904,12 +1904,14 @@ class AccountsController(TransactionBase): reconcilation_entry.party = secondary_party reconcilation_entry.reference_type = self.doctype reconcilation_entry.reference_name = self.name - reconcilation_entry.cost_center = self.cost_center + reconcilation_entry.cost_center = self.cost_center or erpnext.get_default_cost_center( + self.company + ) advance_entry.account = primary_account advance_entry.party_type = primary_party_type advance_entry.party = primary_party - advance_entry.cost_center = self.cost_center + advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company) advance_entry.is_advance = "Yes" if self.doctype == "Sales Invoice": From d7320831669019a3d7a28d336ba2fe75f3a74933 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:33:43 +0530 Subject: [PATCH 151/176] fix: Add company field to lower deduction certificate (#34914) * fix: Add company field to lower deduction certificate (#34914) (cherry picked from commit b545e3def01fb9b9dd6b964478efcb7d764ce386) # Conflicts: # erpnext/patches.txt * chore: resolve conflicts --------- Co-authored-by: Deepesh Garg --- .../tax_withholding_category.py | 5 +++-- erpnext/patches.txt | 1 + erpnext/patches/v14_0/update_company_in_ldc.py | 14 ++++++++++++++ .../lower_deduction_certificate.json | 11 ++++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v14_0/update_company_in_ldc.py diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index f0146ea70eb..ad3477ef3d3 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -215,7 +215,7 @@ def get_tax_row_for_tds(tax_details, tax_amount): } -def get_lower_deduction_certificate(tax_details, pan_no): +def get_lower_deduction_certificate(company, tax_details, pan_no): ldc_name = frappe.db.get_value( "Lower Deduction Certificate", { @@ -223,6 +223,7 @@ def get_lower_deduction_certificate(tax_details, pan_no): "tax_withholding_category": tax_details.tax_withholding_category, "valid_from": (">=", tax_details.from_date), "valid_upto": ("<=", tax_details.to_date), + "company": company, }, "name", ) @@ -255,7 +256,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_amount = 0 if party_type == "Supplier": - ldc = get_lower_deduction_certificate(tax_details, pan_no) + ldc = get_lower_deduction_certificate(inv.company, tax_details, pan_no) if tax_deducted: net_total = inv.tax_withholding_net_total if ldc: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0dc6a28657c..e11ee0385b0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -329,3 +329,4 @@ erpnext.patches.v13_0.update_docs_link execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) # below migration patches should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger +erpnext.patches.v14_0.update_company_in_ldc diff --git a/erpnext/patches/v14_0/update_company_in_ldc.py b/erpnext/patches/v14_0/update_company_in_ldc.py new file mode 100644 index 00000000000..ca95cf2fd7a --- /dev/null +++ b/erpnext/patches/v14_0/update_company_in_ldc.py @@ -0,0 +1,14 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE + + +import frappe + +from erpnext import get_default_company + + +def execute(): + company = get_default_company() + if company: + for d in frappe.get_all("Lower Deduction Certificate", pluck="name"): + frappe.db.set_value("Lower Deduction Certificate", d, "company", company, update_modified=False) diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index c32ab6bec24..d332b4e76bd 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -10,6 +10,7 @@ "tax_withholding_category", "fiscal_year", "column_break_3", + "company", "certificate_no", "section_break_3", "supplier", @@ -123,11 +124,18 @@ "label": "Tax Withholding Category", "options": "Tax Withholding Category", "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-10-23 18:33:38.962622", + "modified": "2023-04-18 08:25:35.302081", "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", @@ -136,5 +144,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 39b51477682608c0c99530b6c63a33aa913050aa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:53:07 +0530 Subject: [PATCH 152/176] perf: Journal Entries (backport #34918) (#35054) * refactor: rewrite `get_stock_value_on()` queries in `QB` (cherry picked from commit e43bc38e05349a780e0a4a97812cebea4a90a109) * refactor: sum up SLE value in query (cherry picked from commit 9a37ac6c2563f8f6459c5c47a95ef349a9fc10bf) * refactor: `get_stock_value_on()` to get stock value of multiple warehouses at once (cherry picked from commit e782a054c80656f378da6108bdd91fae99de685e) --------- Co-authored-by: s-aga-r --- erpnext/accounts/utils.py | 5 +- .../incorrect_stock_value_report.py | 2 +- erpnext/stock/utils.py | 65 ++++++++----------- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c410856a5db..320cf9de68c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1363,10 +1363,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None) if wh_details.account == account and not wh_details.is_group ] - total_stock_value = 0.0 - for warehouse in related_warehouses: - value = get_stock_value_on(warehouse, posting_date) - total_stock_value += value + total_stock_value = get_stock_value_on(related_warehouses, posting_date) precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index e9c96084d99..e4f657ca707 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -84,7 +84,7 @@ def get_data(report_filters): closing_date = add_days(from_date, -1) for key, stock_data in voucher_wise_dict.items(): prev_stock_value = get_stock_value_on( - posting_date=closing_date, item_code=key[0], warehouse=key[1] + posting_date=closing_date, item_code=key[0], warehouses=key[1] ) for data in stock_data: expected_stock_value = prev_stock_value + data.stock_value_difference diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b8c5187b2c3..fb526971ede 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -7,10 +7,11 @@ from typing import Dict, Optional import frappe from frappe import _ -from frappe.query_builder.functions import CombineDatetime +from frappe.query_builder.functions import CombineDatetime, IfNull, Sum from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime import erpnext +from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.valuation import FIFOValuation, LIFOValuation BarcodeScanResult = Dict[str, Optional[str]] @@ -53,50 +54,36 @@ def get_stock_value_from_bin(warehouse=None, item_code=None): return stock_value -def get_stock_value_on(warehouse=None, posting_date=None, item_code=None): +def get_stock_value_on( + warehouses: list | str = None, posting_date: str = None, item_code: str = None +) -> float: if not posting_date: posting_date = nowdate() - values, condition = [posting_date], "" - - if warehouse: - - lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"]) - - if is_group: - values.extend([lft, rgt]) - condition += "and exists (\ - select name from `tabWarehouse` wh where wh.name = sle.warehouse\ - and wh.lft >= %s and wh.rgt <= %s)" - - else: - values.append(warehouse) - condition += " AND warehouse = %s" - - if item_code: - values.append(item_code) - condition += " AND item_code = %s" - - stock_ledger_entries = frappe.db.sql( - """ - SELECT item_code, stock_value, name, warehouse - FROM `tabStock Ledger Entry` sle - WHERE posting_date <= %s {0} - and is_cancelled = 0 - ORDER BY timestamp(posting_date, posting_time) DESC, creation DESC - """.format( - condition - ), - values, - as_dict=1, + sle = frappe.qb.DocType("Stock Ledger Entry") + query = ( + 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) ) - sle_map = {} - for sle in stock_ledger_entries: - if not (sle.item_code, sle.warehouse) in sle_map: - sle_map[(sle.item_code, sle.warehouse)] = flt(sle.stock_value) + if warehouses: + if isinstance(warehouses, str): + warehouses = [warehouses] - return sum(sle_map.values()) + warehouses = set(warehouses) + for wh in list(warehouses): + if frappe.db.get_value("Warehouse", wh, "is_group"): + warehouses.update(get_child_warehouses(wh)) + + query = query.where(sle.warehouse.isin(warehouses)) + + if item_code: + query = query.where(sle.item_code == item_code) + + return query.run(as_list=True)[0][0] @frappe.whitelist() From 82d83791889ed8a87b27b5c9698df5c6103970ba Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:58:27 +0530 Subject: [PATCH 153/176] fix: v14, Bank Reconcile Tools not cover case JV debit bank (#35000) fix: v14, Bank Reconcile Tools not cover case JV debit bank (#35000) (cherry picked from commit c36dc3dc57629c4f77e492508beb09b705a2f25b) Co-authored-by: Kitti U. @ Ecosoft --- .../doctype/bank_transaction/bank_transaction.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 15162376c15..d2d961f7c2a 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -281,10 +281,13 @@ def get_paid_amount(payment_entry, currency, gl_bank_account): ) elif payment_entry.payment_document == "Journal Entry": - return frappe.db.get_value( - "Journal Entry Account", - {"parent": payment_entry.payment_entry, "account": gl_bank_account}, - "sum(credit_in_account_currency)", + return abs( + frappe.db.get_value( + "Journal Entry Account", + {"parent": payment_entry.payment_entry, "account": gl_bank_account}, + "sum(debit_in_account_currency-credit_in_account_currency)", + ) + or 0 ) elif payment_entry.payment_document == "Expense Claim": From a5489ee2ac915aeba3afa1d070ff9495bf0db603 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 26 Apr 2023 20:30:53 +0530 Subject: [PATCH 154/176] fix: don't create material request from sales order against the delivered items (cherry picked from commit 1e2deee57933792e612c4f2c63cae147d8242194) --- .../doctype/sales_order/sales_order.py | 4 +-- .../doctype/sales_order/test_sales_order.py | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d995517af82..de63f6dec58 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -547,7 +547,7 @@ def make_material_request(source_name, target_doc=None): # qty is for packed items, because packed items don't have stock_qty field qty = source.get("qty") target.project = source_parent.project - target.qty = qty - requested_item_qty.get(source.name, 0) + target.qty = qty - requested_item_qty.get(source.name, 0) - source.delivered_qty target.stock_qty = flt(target.qty) * flt(target.conversion_factor) args = target.as_dict().copy() @@ -581,7 +581,7 @@ def make_material_request(source_name, target_doc=None): "doctype": "Material Request Item", "field_map": {"name": "sales_order_item", "parent": "sales_order"}, "condition": lambda doc: not frappe.db.exists("Product Bundle", doc.item_code) - and doc.stock_qty > requested_item_qty.get(doc.name, 0), + and (doc.stock_qty - doc.delivered_qty) > requested_item_qty.get(doc.name, 0), "postprocess": update_item, }, }, diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 627914f0c7e..ba8bbc21857 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1878,6 +1878,37 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(pe.references[1].reference_name, so.name) self.assertEqual(pe.references[1].allocated_amount, 300) + def test_delivered_item_material_request(self): + "SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO." + from erpnext.manufacturing.doctype.work_order.work_order import ( + make_stock_entry as make_se_from_wo, + ) + from erpnext.stock.doctype.material_request.material_request import raise_work_orders + + so = make_sales_order( + item_list=[ + {"item_code": "_Test FG Item", "qty": 10, "rate": 100, "warehouse": "Work In Progress - _TC"} + ] + ) + + make_stock_entry( + item_code="_Test FG Item", target="Work In Progress - _TC", qty=4, basic_rate=100 + ) + + dn = make_delivery_note(so.name) + dn.items[0].qty = 4 + dn.submit() + + so.load_from_db() + self.assertEqual(so.items[0].delivered_qty, 4) + + mr = make_material_request(so.name) + mr.material_request_type = "Purchase" + mr.schedule_date = today() + mr.save() + + self.assertEqual(mr.items[0].qty, 6) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") From fc611cf86bbc98e9cb654c599a2208ad84434949 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 27 Apr 2023 15:04:50 +0530 Subject: [PATCH 155/176] fix: Report link, option, and added a link for Sales Person in GP (cherry picked from commit 6dfca79af31e4aa39a48a323f23c7b326b229504) --- erpnext/accounts/report/gross_profit/gross_profit.js | 2 +- erpnext/accounts/report/gross_profit/gross_profit.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 615804ef623..cf4ccef2433 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -59,7 +59,7 @@ frappe.query_reports["Gross Profit"] = { if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) { column._options = "Sales Invoice"; } else { - column._options = "Item"; + column._options = ""; } value = default_formatter(value, row, column, data); diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 41ba11adc6f..224f79babad 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -250,7 +250,7 @@ def get_columns(group_wise_columns, filters): "label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", - "options": "warehouse", + "options": "Warehouse", "width": 100, }, "qty": {"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 80}, @@ -305,7 +305,8 @@ def get_columns(group_wise_columns, filters): "sales_person": { "label": _("Sales Person"), "fieldname": "sales_person", - "fieldtype": "Data", + "fieldtype": "Link", + "options": "Sales Person", "width": 100, }, "allocated_amount": { @@ -326,14 +327,14 @@ def get_columns(group_wise_columns, filters): "label": _("Customer Group"), "fieldname": "customer_group", "fieldtype": "Link", - "options": "customer", + "options": "Customer Group", "width": 100, }, "territory": { "label": _("Territory"), "fieldname": "territory", "fieldtype": "Link", - "options": "territory", + "options": "Territory", "width": 100, }, "monthly": { From e4ce6fa195a68c6f0b18fb1f5b928d48574161ec Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 27 Apr 2023 17:04:39 +0530 Subject: [PATCH 156/176] fix: Hyperlink in Quality Inspection Summary (cherry picked from commit 72dd7884a89b7dcce342410c8bc3f3da56bf99e8) --- .../quality_inspection_summary/quality_inspection_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py index de96a6c0323..38e05852ee8 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -69,7 +69,7 @@ def get_columns(filters): "label": _("Id"), "fieldname": "name", "fieldtype": "Link", - "options": "Work Order", + "options": "Quality Inspection", "width": 100, }, {"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150}, From 28dfc13dc6a676fab9f0544d83c282a043a1264a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 28 Apr 2023 15:16:02 +0530 Subject: [PATCH 157/176] fix: not able to create delivery note from sales order (cherry picked from commit bdf2f7416a012576561e4911e89088c80bc6af6e) --- erpnext/stock/get_item_details.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ce85702f486..f3adefb3e74 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1508,11 +1508,15 @@ def get_so_reservation_for_item(args): elif args.get("against_sales_invoice"): sales_order = frappe.db.get_all( "Sales Invoice Item", - filters={"parent": args.get("against_sales_invoice"), "item_code": args.get("item_code")}, + filters={ + "parent": args.get("against_sales_invoice"), + "item_code": args.get("item_code"), + "docstatus": 1, + }, fields="sales_order", ) if sales_order and sales_order[0]: - if get_reserved_qty_for_so(sales_order[0][0], args.get("item_code")): + if get_reserved_qty_for_so(sales_order[0].sales_order, args.get("item_code")): reserved_so = sales_order[0] elif args.get("sales_order"): if get_reserved_qty_for_so(args.get("sales_order"), args.get("item_code")): From 7021e3adb17aa6b386a98216550de66447a38aa0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 13:02:11 +0530 Subject: [PATCH 158/176] fix: Naming series error in Journal Entry template (#35084) fix: Naming series error in Journal Entry template (#35084) (cherry picked from commit f3b3dabb9a6e5f8cd34c4a07ed54738b28f2b69a) Co-authored-by: Deepesh Garg --- .../journal_entry_template.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js index 88f1c9069c8..5ebdf61db2c 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -2,6 +2,21 @@ // For license information, please see license.txt frappe.ui.form.on("Journal Entry Template", { + onload: function(frm) { + if(frm.is_new()) { + frappe.call({ + type: "GET", + method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series", + callback: function(r){ + if(r.message) { + frm.set_df_property("naming_series", "options", r.message.split("\n")); + frm.set_value("naming_series", r.message.split("\n")[0]); + frm.refresh_field("naming_series"); + } + } + }); + } + }, refresh: function(frm) { frappe.model.set_default_values(frm.doc); @@ -19,18 +34,6 @@ frappe.ui.form.on("Journal Entry Template", { return { filters: filters }; }); - - frappe.call({ - type: "GET", - method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series", - callback: function(r){ - if(r.message){ - frm.set_df_property("naming_series", "options", r.message.split("\n")); - frm.set_value("naming_series", r.message.split("\n")[0]); - frm.refresh_field("naming_series"); - } - } - }); }, voucher_type: function(frm) { var add_accounts = function(doc, r) { From 80230fec3ef079b75825447a61836f8a9ce64f57 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 1 May 2023 19:53:39 +0530 Subject: [PATCH 159/176] fix: handle expected_value_after_useful_life properly in asset value adjustment (#35117) --- .../doctype/asset_value_adjustment/asset_value_adjustment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index e718472e164..ee75b83af39 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -116,7 +116,9 @@ class AssetValueAdjustment(Document): if d.depreciation_method in ("Straight Line", "Manual"): end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx) total_days = date_diff(end_date, self.date) - rate_per_day = flt(d.value_after_depreciation) / flt(total_days) + rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt( + total_days + ) from_date = self.date else: no_of_depreciations = len( From cca2fcec54bfbd730565858a1c5c8e96219a884f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 May 2023 18:47:25 +0530 Subject: [PATCH 160/176] fix: don't allow to make reposting for the closed period (cherry picked from commit f751727149392cfa304e20b2d50a7cbeef17d388) # Conflicts: # erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py --- .../test_period_closing_voucher.py | 138 ++++++++++++++++++ .../repost_item_valuation.py | 29 +++- 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index e9ed2e46940..e9e9e21cfca 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -177,7 +177,145 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertSequenceEqual(pcv_gle, expected_gle) +<<<<<<< HEAD def make_period_closing_voucher(self, submit=True): +======= + def test_gl_entries_restrictions(self): + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") + frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") + + company = create_company() + cost_center = create_cost_center("Test Cost Center 1") + + self.make_period_closing_voucher(posting_date="2021-03-31") + + jv1 = make_journal_entry( + posting_date="2021-03-15", + amount=400, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center, + save=False, + ) + jv1.company = company + jv1.save() + + self.assertRaises(frappe.ValidationError, jv1.submit) + + def test_closing_balance_with_dimensions_and_test_reposting_entry(self): + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") + frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") + frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'") + + company = create_company() + cost_center1 = create_cost_center("Test Cost Center 1") + cost_center2 = create_cost_center("Test Cost Center 2") + + jv1 = make_journal_entry( + posting_date="2021-03-15", + amount=400, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center1, + save=False, + ) + jv1.company = company + jv1.save() + jv1.submit() + + jv2 = make_journal_entry( + posting_date="2021-03-15", + amount=200, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center2, + save=False, + ) + jv2.company = company + jv2.save() + jv2.submit() + + pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31") + + closing_balance = frappe.db.get_value( + "Account Closing Balance", + { + "account": "Sales - TPC", + "cost_center": cost_center1, + "period_closing_voucher": pcv1.name, + "is_period_closing_voucher_entry": 0, + }, + ["credit", "credit_in_account_currency"], + as_dict=1, + ) + + self.assertEqual(closing_balance.credit, 400) + self.assertEqual(closing_balance.credit_in_account_currency, 400) + + jv3 = make_journal_entry( + posting_date="2022-03-15", + amount=300, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center2, + save=False, + ) + + jv3.company = company + jv3.save() + jv3.submit() + + pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31") + + cc1_closing_balance = frappe.db.get_value( + "Account Closing Balance", + { + "account": "Sales - TPC", + "cost_center": cost_center1, + "period_closing_voucher": pcv2.name, + "is_period_closing_voucher_entry": 0, + }, + ["credit", "credit_in_account_currency"], + as_dict=1, + ) + + cc2_closing_balance = frappe.db.get_value( + "Account Closing Balance", + { + "account": "Sales - TPC", + "cost_center": cost_center2, + "period_closing_voucher": pcv2.name, + "is_period_closing_voucher_entry": 0, + }, + ["credit", "credit_in_account_currency"], + as_dict=1, + ) + + self.assertEqual(cc1_closing_balance.credit, 400) + self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400) + self.assertEqual(cc2_closing_balance.credit, 500) + self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500) + + warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name") + + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "company": company, + "posting_date": "2020-03-15", + "based_on": "Item and Warehouse", + "item_code": "Test Item 1", + "warehouse": warehouse, + } + ) + + self.assertRaises(frappe.ValidationError, repost_doc.save) + + repost_doc.posting_date = today() + repost_doc.save() + + def make_period_closing_voucher(self, posting_date=None, submit=True): +>>>>>>> f751727149 (fix: don't allow to make reposting for the closed period) surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc( 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 bbed099da96..aabc6fcfe30 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -6,7 +6,7 @@ from frappe import _ from frappe.exceptions import QueryDeadlockError, QueryTimeoutError from frappe.model.document import Document from frappe.query_builder import DocType, Interval -from frappe.query_builder.functions import Now +from frappe.query_builder.functions import Max, Now from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -36,11 +36,38 @@ class RepostItemValuation(Document): ) def validate(self): + self.validate_period_closing_voucher() self.set_status(write=False) self.reset_field_values() self.set_company() self.validate_accounts_freeze() + def validate_period_closing_voucher(self): + year_end_date = self.get_max_year_end_date(self.company) + if year_end_date and getdate(self.posting_date) <= getdate(year_end_date): + msg = f"Due to period closing, you cannot repost item valuation before {year_end_date}" + frappe.throw(_(msg)) + + @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") + + query = ( + frappe.qb.from_(table) + .select(Max(table.year_end_date)) + .where((table.name.isin(fiscal_years)) & (table.disabled == 0)) + ).run() + + return query[0][0] if query else None + def validate_accounts_freeze(self): acc_settings = frappe.db.get_value( "Accounts Settings", From 3ba2b9ed2e17860c1346284c4a9cd64a6ee9caf9 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 20:47:12 +0530 Subject: [PATCH 161/176] fix: conflicts --- .../test_period_closing_voucher.py | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index e9e9e21cfca..fe163a3cf86 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -177,31 +177,6 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertSequenceEqual(pcv_gle, expected_gle) -<<<<<<< HEAD - def make_period_closing_voucher(self, submit=True): -======= - def test_gl_entries_restrictions(self): - frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") - frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") - - company = create_company() - cost_center = create_cost_center("Test Cost Center 1") - - self.make_period_closing_voucher(posting_date="2021-03-31") - - jv1 = make_journal_entry( - posting_date="2021-03-15", - amount=400, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center, - save=False, - ) - jv1.company = company - jv1.save() - - self.assertRaises(frappe.ValidationError, jv1.submit) - def test_closing_balance_with_dimensions_and_test_reposting_entry(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") @@ -314,8 +289,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): repost_doc.posting_date = today() repost_doc.save() - def make_period_closing_voucher(self, posting_date=None, submit=True): ->>>>>>> f751727149 (fix: don't allow to make reposting for the closed period) + def make_period_closing_voucher(self, submit=True): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc( From d844a2b990c2561157332c7181b2ec74e3b8986c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 20:49:17 +0530 Subject: [PATCH 162/176] fix: test case --- .../test_period_closing_voucher.py | 96 +------------------ 1 file changed, 1 insertion(+), 95 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index fe163a3cf86..6cb4f291f84 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -176,101 +176,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): ) self.assertSequenceEqual(pcv_gle, expected_gle) - - def test_closing_balance_with_dimensions_and_test_reposting_entry(self): - frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") - frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") - frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'") - - company = create_company() - cost_center1 = create_cost_center("Test Cost Center 1") - cost_center2 = create_cost_center("Test Cost Center 2") - - jv1 = make_journal_entry( - posting_date="2021-03-15", - amount=400, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center1, - save=False, - ) - jv1.company = company - jv1.save() - jv1.submit() - - jv2 = make_journal_entry( - posting_date="2021-03-15", - amount=200, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center2, - save=False, - ) - jv2.company = company - jv2.save() - jv2.submit() - - pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31") - - closing_balance = frappe.db.get_value( - "Account Closing Balance", - { - "account": "Sales - TPC", - "cost_center": cost_center1, - "period_closing_voucher": pcv1.name, - "is_period_closing_voucher_entry": 0, - }, - ["credit", "credit_in_account_currency"], - as_dict=1, - ) - - self.assertEqual(closing_balance.credit, 400) - self.assertEqual(closing_balance.credit_in_account_currency, 400) - - jv3 = make_journal_entry( - posting_date="2022-03-15", - amount=300, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center2, - save=False, - ) - - jv3.company = company - jv3.save() - jv3.submit() - - pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31") - - cc1_closing_balance = frappe.db.get_value( - "Account Closing Balance", - { - "account": "Sales - TPC", - "cost_center": cost_center1, - "period_closing_voucher": pcv2.name, - "is_period_closing_voucher_entry": 0, - }, - ["credit", "credit_in_account_currency"], - as_dict=1, - ) - - cc2_closing_balance = frappe.db.get_value( - "Account Closing Balance", - { - "account": "Sales - TPC", - "cost_center": cost_center2, - "period_closing_voucher": pcv2.name, - "is_period_closing_voucher_entry": 0, - }, - ["credit", "credit_in_account_currency"], - as_dict=1, - ) - - self.assertEqual(cc1_closing_balance.credit, 400) - self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400) - self.assertEqual(cc2_closing_balance.credit, 500) - self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500) - + warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name") repost_doc = frappe.get_doc( From e33fb3b2429e18c88483cfd7ab13d83b32f987d1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 May 2023 21:17:18 +0530 Subject: [PATCH 163/176] fix: timeout error while submitting delivery note (cherry picked from commit 2d5ccc07b1f277ebeddc9033af19ba1878fdb734) --- erpnext/controllers/stock_controller.py | 16 +++++++--------- .../stock/doctype/delivery_note/delivery_note.py | 4 ++-- .../delivery_note_item/delivery_note_item.json | 8 +++++--- erpnext/stock/stock_ledger.py | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 479fef72c66..a27e34819d4 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -329,9 +329,10 @@ class StockController(AccountsController): """Create batches if required. Called before submit""" for d in self.items: if d.get(warehouse_field) and not d.batch_no: - has_batch_no, create_new_batch = frappe.db.get_value( + has_batch_no, create_new_batch = frappe.get_cached_value( "Item", d.item_code, ["has_batch_no", "create_new_batch"] ) + if has_batch_no and create_new_batch: d.batch_no = ( frappe.get_doc( @@ -414,7 +415,7 @@ class StockController(AccountsController): "voucher_no": self.name, "voucher_detail_no": d.name, "actual_qty": (self.docstatus == 1 and 1 or -1) * flt(d.get("stock_qty")), - "stock_uom": frappe.db.get_value( + "stock_uom": frappe.get_cached_value( "Item", args.get("item_code") or d.get("item_code"), "stock_uom" ), "incoming_rate": 0, @@ -609,7 +610,7 @@ class StockController(AccountsController): def validate_customer_provided_item(self): for d in self.get("items"): # Customer Provided parts will have zero valuation rate - if frappe.db.get_value("Item", d.item_code, "is_customer_provided_item"): + if frappe.get_cached_value("Item", d.item_code, "is_customer_provided_item"): d.allow_zero_valuation_rate = 1 def set_rate_of_stock_uom(self): @@ -722,7 +723,7 @@ class StockController(AccountsController): message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link) return message - def repost_future_sle_and_gle(self): + def repost_future_sle_and_gle(self, force=False): args = frappe._dict( { "posting_date": self.posting_date, @@ -733,7 +734,7 @@ class StockController(AccountsController): } ) - if future_sle_exists(args) or repost_required_for_queue(self): + if force or future_sle_exists(args) or repost_required_for_queue(self): item_based_reposting = cint( frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting") ) @@ -894,9 +895,6 @@ def future_sle_exists(args, sl_entries=None): ) for d in data: - if key not in frappe.local.future_sle: - frappe.local.future_sle[key] = frappe._dict({}) - frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row return len(data) @@ -919,7 +917,7 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): def get_cached_data(args, key): if key not in frappe.local.future_sle: - return False + frappe.local.future_sle[key] = frappe._dict({}) if args.get("item_code"): item_key = (args.get("item_code"), args.get("warehouse")) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 9f9f5cbe2a4..16caceba09f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -118,7 +118,7 @@ class DeliveryNote(SellingController): def so_required(self): """check in manage account if sales order required or not""" - if frappe.db.get_value("Selling Settings", None, "so_required") == "Yes": + if frappe.db.get_single_value("Selling Settings", "so_required") == "Yes": for d in self.get("items"): if not d.against_sales_order: frappe.throw(_("Sales Order required for Item {0}").format(d.item_code)) @@ -205,7 +205,7 @@ class DeliveryNote(SellingController): super(DeliveryNote, self).validate_warehouse() for d in self.get_item_list(): - if not d["warehouse"] and frappe.db.get_value("Item", d["item_code"], "is_stock_item") == 1: + if not d["warehouse"] and frappe.get_cached_value("Item", d["item_code"], "is_stock_item") == 1: frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"])) def update_current_stock(self): diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 180adee0cb0..e46cab05762 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -629,7 +629,8 @@ "no_copy": 1, "options": "Sales Order", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "against_sales_invoice", @@ -662,7 +663,8 @@ "label": "Against Sales Invoice Item", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "installed_qty", @@ -854,7 +856,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-04-06 09:28:29.182053", + "modified": "2023-05-01 21:05:14.175640", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 82fc0df8def..8b517bf1e0f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1392,7 +1392,7 @@ def regenerate_sle_for_batch_stock_reco(detail): if not frappe.db.exists( "Repost Item Valuation", {"voucher_no": doc.name, "status": "Queued", "docstatus": "1"} ): - doc.repost_future_sle_and_gle() + doc.repost_future_sle_and_gle(force=True) def get_stock_reco_qty_shift(args): From 6597c74d6cad1f7200f1fcb111350f31495ac619 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 23:32:54 +0530 Subject: [PATCH 164/176] fix: test case --- .../period_closing_voucher/test_period_closing_voucher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 6cb4f291f84..c628cee2f67 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -5,7 +5,7 @@ import unittest import frappe -from frappe.utils import today +from frappe.utils import today, add_days from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry @@ -192,7 +192,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertRaises(frappe.ValidationError, repost_doc.save) - repost_doc.posting_date = today() + repost_doc.posting_date = add_days(today(), 1) repost_doc.save() def make_period_closing_voucher(self, submit=True): From 35ec125b3492743dea76f7e4aba602267003fbd8 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 2 May 2023 11:00:12 +0530 Subject: [PATCH 165/176] fix: test case --- .../period_closing_voucher/test_period_closing_voucher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index c628cee2f67..1deb733f074 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -5,7 +5,7 @@ import unittest import frappe -from frappe.utils import today, add_days +from frappe.utils import today, add_months from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry @@ -192,7 +192,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertRaises(frappe.ValidationError, repost_doc.save) - repost_doc.posting_date = add_days(today(), 1) + repost_doc.posting_date = add_months(today(), 13) repost_doc.save() def make_period_closing_voucher(self, submit=True): From 453249d868f82b015c7f963b3cf3da15ec099b2c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 2 May 2023 11:08:06 +0530 Subject: [PATCH 166/176] fix: linter issue --- .../period_closing_voucher/test_period_closing_voucher.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 1deb733f074..fe7108a97d5 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -176,7 +176,6 @@ class TestPeriodClosingVoucher(unittest.TestCase): ) self.assertSequenceEqual(pcv_gle, expected_gle) - warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name") repost_doc = frappe.get_doc( From 78c34d71e2d50ea4f845dd5717fb50f011cf12fe Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 2 May 2023 14:01:26 +0530 Subject: [PATCH 167/176] fix: linter issue --- .../period_closing_voucher/test_period_closing_voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index fe7108a97d5..57458c619a1 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -5,7 +5,7 @@ import unittest import frappe -from frappe.utils import today, add_months +from frappe.utils import add_months, today from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry From 551c96e1e50976e3bab72c14e597d4dcfa11cb6e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 25 Apr 2023 20:26:50 +0530 Subject: [PATCH 168/176] refactor: don't book exch gain/loss for sales/purchase orders (cherry picked from commit effb34bbfa4144590ab4075cfcb603c50ca5c561) --- .../doctype/payment_entry/payment_entry.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e1536fac537..d1eb4dffa2a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -654,6 +654,28 @@ class PaymentEntry(AccountsController): self.precision("base_received_amount"), ) + def calculate_base_allocated_amount_for_reference(self, d) -> float: + base_allocated_amount = 0 + if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"): + # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. + # This is so there are no Exchange Gain/Loss generated for such doctypes + + exchange_rate = 1 + if self.payment_type == "Receive": + exchange_rate = self.source_exchange_rate + elif self.payment_type == "Pay": + exchange_rate = self.target_exchange_rate + + base_allocated_amount += flt( + flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") + ) + else: + base_allocated_amount += flt( + flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount") + ) + + return base_allocated_amount + def set_total_allocated_amount(self): if self.payment_type == "Internal Transfer": return @@ -662,9 +684,7 @@ class PaymentEntry(AccountsController): for d in self.get("references"): if d.allocated_amount: total_allocated_amount += flt(d.allocated_amount) - base_total_allocated_amount += flt( - flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount") - ) + base_total_allocated_amount += self.calculate_base_allocated_amount_for_reference(d) self.total_allocated_amount = abs(total_allocated_amount) self.base_total_allocated_amount = abs(base_total_allocated_amount) @@ -881,9 +901,7 @@ class PaymentEntry(AccountsController): } ) - allocated_amount_in_company_currency = flt( - flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("paid_amount") - ) + allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) gle.update( { From fedde7fe3b68efd0f73466e6291387cb28778c2b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 May 2023 13:54:15 +0530 Subject: [PATCH 169/176] test: Sales/Purchase Orders will not book Exchange gain/loss (cherry picked from commit ce4e18c8d232bbf19fea74e6e6133b773d21f3d5) --- .../payment_entry/test_payment_entry.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 67049c47ad0..68f333dc29f 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -51,6 +51,38 @@ class TestPaymentEntry(FrappeTestCase): so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") self.assertEqual(so_advance_paid, 0) + def test_payment_against_sales_order_usd_to_inr(self): + so = make_sales_order( + customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True + ) + so.conversion_rate = 50 + so.submit() + pe = get_payment_entry("Sales Order", so.name) + pe.source_exchange_rate = 55 + pe.received_amount = 5500 + pe.insert() + pe.submit() + + # there should be no difference amount + pe.reload() + self.assertEqual(pe.difference_amount, 0) + self.assertEqual(pe.deductions, []) + + expected_gle = dict( + (d[0], d) + for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], ["Cash - _TC", 5500.0, 0, None]] + ) + + self.validate_gl_entries(pe.name, expected_gle) + + so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") + self.assertEqual(so_advance_paid, 100) + + pe.cancel() + + so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") + self.assertEqual(so_advance_paid, 0) + def test_payment_entry_for_blocked_supplier_invoice(self): supplier = frappe.get_doc("Supplier", "_Test Supplier") supplier.on_hold = 1 From 0e39e5e86852cad9a995df03a69c68e6828beecf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Apr 2023 14:06:04 +0530 Subject: [PATCH 170/176] refactor: checkbox in purchase invoice (cherry picked from commit b44331c981974cae72175fa10c8d10f0378129b9) --- .../doctype/purchase_invoice/purchase_invoice.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 1c8c4b3193b..719ef7d4c44 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -89,6 +89,7 @@ "column_break8", "grand_total", "rounding_adjustment", + "use_company_roundoff_cost_center", "rounded_total", "in_words", "total_advance", @@ -1559,13 +1560,19 @@ "fieldname": "only_include_allocated_payments", "fieldtype": "Check", "label": "Only Include Allocated Payments" + }, + { + "default": "0", + "fieldname": "use_company_roundoff_cost_center", + "fieldtype": "Check", + "label": "Use Company Default Round Off Cost Center" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-04-03 22:57:14.074982", + "modified": "2023-04-28 12:57:50.832598", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From 25b37737a2a7cdc8486ed7b663420663e1640cdd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Apr 2023 14:07:28 +0530 Subject: [PATCH 171/176] refactor: checkbox to toggle parent doc cost center preference (cherry picked from commit ebe67875103e4b2aecad8cc5cd20a896cfe5ebd9) --- .../doctype/purchase_invoice/purchase_invoice.py | 12 ++++++++---- erpnext/accounts/general_ledger.py | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 0ded4a883bb..6d905a37cde 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -978,7 +978,7 @@ class PurchaseInvoice(BuyingController): def make_precision_loss_gl_entry(self, gl_entries): round_off_account, round_off_cost_center = get_round_off_account_and_cost_center( - self.company, "Purchase Invoice", self.name + self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center ) precision_loss = self.get("base_net_total") - flt( @@ -992,7 +992,9 @@ class PurchaseInvoice(BuyingController): "account": round_off_account, "against": self.supplier, "credit": precision_loss, - "cost_center": self.cost_center or round_off_cost_center, + "cost_center": round_off_cost_center + if self.use_company_roundoff_cost_center + else self.cost_center or round_off_cost_center, "remarks": _("Net total calculation precision loss"), } ) @@ -1386,7 +1388,7 @@ class PurchaseInvoice(BuyingController): not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment ): round_off_account, round_off_cost_center = get_round_off_account_and_cost_center( - self.company, "Purchase Invoice", self.name + self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center ) gl_entries.append( @@ -1396,7 +1398,9 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "debit_in_account_currency": self.rounding_adjustment, "debit": self.base_rounding_adjustment, - "cost_center": self.cost_center or round_off_cost_center, + "cost_center": round_off_cost_center + if self.use_company_roundoff_cost_center + else (self.cost_center or round_off_cost_center), }, item=self, ) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 41fdb6a97f8..7beb7cc3b19 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -472,7 +472,9 @@ def update_accounting_dimensions(round_off_gle): round_off_gle[dimension] = dimension_values.get(dimension) -def get_round_off_account_and_cost_center(company, voucher_type, voucher_no): +def get_round_off_account_and_cost_center( + company, voucher_type, voucher_no, use_company_default=False +): round_off_account, round_off_cost_center = frappe.get_cached_value( "Company", company, ["round_off_account", "round_off_cost_center"] ) or [None, None] @@ -480,7 +482,7 @@ def get_round_off_account_and_cost_center(company, voucher_type, voucher_no): meta = frappe.get_meta(voucher_type) # Give first preference to parent cost center for round off GLE - if meta.has_field("cost_center"): + if not use_company_default and meta.has_field("cost_center"): parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center") if parent_cost_center: round_off_cost_center = parent_cost_center From 3810b02023eb8e61509587122d60e574119c3b44 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Apr 2023 14:18:08 +0530 Subject: [PATCH 172/176] refactor: checkbox in Sales Invoice (cherry picked from commit 0f3b06cc8aa36e8f3695d32b3f4a0b915744814b) --- .../accounts/doctype/sales_invoice/sales_invoice.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 9a0d71a3850..0e6118abe6b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -79,6 +79,7 @@ "column_break5", "grand_total", "rounding_adjustment", + "use_company_roundoff_cost_center", "rounded_total", "in_words", "total_advance", @@ -2134,6 +2135,12 @@ "fieldname": "only_include_allocated_payments", "fieldtype": "Check", "label": "Only Include Allocated Payments" + }, + { + "default": "0", + "fieldname": "use_company_roundoff_cost_center", + "fieldtype": "Check", + "label": "Use Company default Cost Center for Round off" } ], "icon": "fa fa-file-text", @@ -2146,7 +2153,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-04-03 22:55:14.206473", + "modified": "2023-04-28 14:15:59.901154", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 8ac718d98cdcc9c4c64cd197b28f23b5220f7306 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Apr 2023 14:20:06 +0530 Subject: [PATCH 173/176] refactor: checkbox to toggle parent doc cost center preference (cherry picked from commit 4ccce933941d9caab29ac1c551f55777b7da8e0b) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7af98ddf934..bb85fedab05 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1450,7 +1450,7 @@ class SalesInvoice(SellingController): and not self.is_internal_transfer() ): round_off_account, round_off_cost_center = get_round_off_account_and_cost_center( - self.company, "Sales Invoice", self.name + self.company, "Sales Invoice", self.name, self.use_company_roundoff_cost_center ) gl_entries.append( @@ -1462,7 +1462,9 @@ class SalesInvoice(SellingController): self.rounding_adjustment, self.precision("rounding_adjustment") ), "credit": flt(self.base_rounding_adjustment, self.precision("base_rounding_adjustment")), - "cost_center": self.cost_center or round_off_cost_center, + "cost_center": round_off_cost_center + if self.use_company_roundoff_cost_center + else (self.cost_center or round_off_cost_center), }, item=self, ) From f50b4d80f182ae94be5025a580c08633419e5238 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 27 Apr 2023 12:56:27 +0530 Subject: [PATCH 174/176] fix: incorrect paid_amount and exchange rate in PE If Company master has no default cash or bank account set but Party has default company bank account set. In this case, paid_amount and conversion rate are not calculated correctly (cherry picked from commit 123355392b58f9a383632a995ba17e47d4281283) --- .../doctype/payment_entry/payment_entry.py | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index d1eb4dffa2a..49b75dc0570 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1733,6 +1733,13 @@ def get_payment_entry( # bank or cash bank = get_bank_cash_account(doc, bank_account) + # if default bank or cash account is not set in company master and party has default company bank account, fetch it + if party_type in ["Customer", "Supplier"] and not bank: + party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type))) + if party_bank_account: + account = frappe.db.get_value("Bank Account", party_bank_account, "account") + bank = get_bank_cash_account(doc, account) + paid_amount, received_amount = set_paid_amount_and_received_amount( dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc ) @@ -1949,19 +1956,27 @@ def set_paid_amount_and_received_amount( paid_amount = received_amount = 0 if party_account_currency == bank.account_currency: paid_amount = received_amount = abs(outstanding_amount) - elif payment_type == "Receive": - paid_amount = abs(outstanding_amount) - if bank_amount: - received_amount = bank_amount - else: - received_amount = paid_amount * doc.get("conversion_rate", 1) else: - received_amount = abs(outstanding_amount) - if bank_amount: - paid_amount = bank_amount + company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency") + if payment_type == "Receive": + paid_amount = abs(outstanding_amount) + if bank_amount: + received_amount = bank_amount + else: + if company_currency != bank.account_currency: + received_amount = paid_amount / doc.get("conversion_rate", 1) + else: + received_amount = paid_amount * doc.get("conversion_rate", 1) else: - # if party account currency and bank currency is different then populate paid amount as well - paid_amount = received_amount * doc.get("conversion_rate", 1) + received_amount = abs(outstanding_amount) + if bank_amount: + paid_amount = bank_amount + else: + if company_currency != bank.account_currency: + paid_amount = received_amount / doc.get("conversion_rate", 1) + else: + # if party account currency and bank currency is different then populate paid amount as well + paid_amount = received_amount * doc.get("conversion_rate", 1) return paid_amount, received_amount From bef9dd79e7e1ec92a3e393f4f24a340da7c83d8a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 23:06:52 +0530 Subject: [PATCH 175/176] fix: Updates in process statement of Accounts (#35064) * fix: Updates in process statement of Accounts (#35064) (cherry picked from commit ea0b03ae9e481dc1048fb5d3fbffc3a948c07573) # Conflicts: # erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json * chore: resolve conflicts --------- Co-authored-by: Deepesh Garg --- .../process_statement_of_accounts.html | 7 ++++- .../process_statement_of_accounts.json | 26 ++++++++++++++++++- .../process_statement_of_accounts.py | 2 +- ...rocess_statement_of_accounts_customer.json | 4 +-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index b9680dfb3bf..03abc93e0b8 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -15,7 +15,12 @@

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

-
{{ _("Customer: ") }} {{filters.party_name[0] }}
+ {% if filters.party[0] == filters.party_name[0] %} +
{{ _("Customer: ") }} {{ filters.party_name[0] }}
+ {% else %} +
{{ _("Customer: ") }} {{ filters.party[0] }}
+
{{ _("Customer Name: ") }} {{filters.party_name[0] }}
+ {% endif %}
{{ _("Date: ") }} {{ frappe.format(filters.from_date, 'Date')}} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index a26267ba5e8..0620e566b74 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -34,6 +34,8 @@ "terms_and_conditions", "section_break_1", "enable_auto_email", + "column_break_ocfq", + "sender", "section_break_18", "frequency", "filter_duration", @@ -284,10 +286,32 @@ "fieldtype": "Link", "label": "Terms and Conditions", "options": "Terms and Conditions" + }, + { + "default": "1", + "fieldname": "include_break", + "fieldtype": "Check", + "label": "Page Break After Each SoA" + }, + { + "default": "0", + "fieldname": "show_net_values_in_party_account", + "fieldtype": "Check", + "label": "Show Net Values in Party Account" + }, + { + "fieldname": "sender", + "fieldtype": "Link", + "label": "Sender", + "options": "Email Account" + }, + { + "fieldname": "column_break_ocfq", + "fieldtype": "Column Break" } ], "links": [], - "modified": "2021-09-06 21:00:45.732505", + "modified": "2023-04-26 12:46:43.645455", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 39b80143208..18d2f3b5a13 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -327,7 +327,7 @@ def send_emails(document_name, from_scheduler=False): queue="short", method=frappe.sendmail, recipients=recipients, - sender=frappe.session.user, + sender=doc.sender or frappe.session.user, cc=cc, subject=subject, message=message, diff --git a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json index 8bffd6a93b9..1749d72e167 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json @@ -27,7 +27,7 @@ }, { "fieldname": "billing_email", - "fieldtype": "Read Only", + "fieldtype": "Data", "in_list_view": 1, "label": "Billing Email" }, @@ -41,7 +41,7 @@ ], "istable": 1, "links": [], - "modified": "2023-03-13 00:12:34.508086", + "modified": "2023-04-26 13:02:41.964499", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts Customer", From 344c33948400ad51ed769309ed52c7cf1c72e937 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 3 May 2023 07:11:01 +0530 Subject: [PATCH 176/176] fix: handle finance book properly in trial balance and general ledger [v14] (#35136) fix: handle FBs properly in general ledger and trial balance --- .../accounts/report/financial_statements.py | 19 ++++++++++++---- .../report/general_ledger/general_ledger.js | 3 ++- .../report/general_ledger/general_ledger.py | 22 ++++++++++++++----- .../report/trial_balance/trial_balance.py | 21 +++++++++++++----- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 36825771f8a..86fdaaa924a 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -492,11 +492,22 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions.append("cost_center in %(cost_center)s") if filters.get("include_default_book_entries"): - additional_conditions.append( - "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" - ) + if filters.get("finance_book"): + if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr( + filters.get("company_fb") + ): + frappe.throw( + _("To use a different finance book, please uncheck 'Include Default Book Entries'") + ) + else: + additional_conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") + else: + additional_conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)") else: - additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)") + if filters.get("finance_book"): + additional_conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") + else: + additional_conditions.append("(finance_book IS NULL)") if accounting_dimensions: for dimension in accounting_dimensions: diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 2100f26c1ec..57a9091cf9b 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -176,7 +176,8 @@ frappe.query_reports["General Ledger"] = { { "fieldname": "include_default_book_entries", "label": __("Include Default Book Entries"), - "fieldtype": "Check" + "fieldtype": "Check", + "default": 1 }, { "fieldname": "show_cancelled_entries", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 745b6dfbdce..6a4f394ebc8 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -244,13 +244,23 @@ def get_conditions(filters): if filters.get("project"): conditions.append("project in %(project)s") - if filters.get("finance_book"): - if filters.get("include_default_book_entries"): - conditions.append( - "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" - ) + if filters.get("include_default_book_entries"): + if filters.get("finance_book"): + if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr( + filters.get("company_fb") + ): + frappe.throw( + _("To use a different finance book, please uncheck 'Include Default Book Entries'") + ) + else: + conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") else: - conditions.append("finance_book in (%(finance_book)s)") + conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)") + else: + if filters.get("finance_book"): + conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") + else: + conditions.append("(finance_book IS NULL)") if not filters.get("show_cancelled_entries"): conditions.append("is_cancelled = 0") diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 61bc58009a6..d2300cd1238 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -157,12 +157,23 @@ def get_rootwise_opening_balances(filters, report_type): if filters.project: additional_conditions += " and project = %(project)s" + company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book") + if filters.get("include_default_book_entries"): - additional_conditions += ( - " AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" - ) + if filters.get("finance_book"): + if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb): + frappe.throw( + _("To use a different finance book, please uncheck 'Include Default Book Entries'") + ) + else: + additional_conditions += " AND (finance_book in (%(finance_book)s) OR finance_book IS NULL)" + else: + additional_conditions += " AND (finance_book in (%(company_fb)s) OR finance_book IS NULL)" else: - additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)" + if filters.get("finance_book"): + additional_conditions += " AND (finance_book in (%(finance_book)s) OR finance_book IS NULL)" + else: + additional_conditions += " AND (finance_book IS NULL)" accounting_dimensions = get_accounting_dimensions(as_list=False) @@ -174,7 +185,7 @@ def get_rootwise_opening_balances(filters, report_type): "year_start_date": filters.year_start_date, "project": filters.project, "finance_book": filters.finance_book, - "company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"), + "company_fb": company_fb, } if accounting_dimensions: