Compare commits

..

84 Commits

Author SHA1 Message Date
Frappe PR Bot
22ace5cb5a chore(release): Bumped to Version 14.62.4
## [14.62.4](https://github.com/frappe/erpnext/compare/v14.62.3...v14.62.4) (2024-02-14)

### Bug Fixes

*  production plan issue with sales order (backport [#39901](https://github.com/frappe/erpnext/issues/39901)) ([#39903](https://github.com/frappe/erpnext/issues/39903)) ([ab7e323](ab7e323648))
* accommodate for default rounding method in v14 ([d6a758d](d6a758d1f4))
* broken route option in Profitability report ([f79e0d1](f79e0d1e37))
* calculate `stock_value_diff` ([8f58b61](8f58b613e4))
* cancel asset capitalization ([2438600](24386006d6))
* do not throw validation for canceled SLE (backport [#39769](https://github.com/frappe/erpnext/issues/39769)) ([#39810](https://github.com/frappe/erpnext/issues/39810)) ([2885b8f](2885b8fa44))
* Handling circular linking while cancelling asset capitalization ([f2d094d](f2d094d1ab))
* incorrect planned qty in PP (backport [#39785](https://github.com/frappe/erpnext/issues/39785)) ([#39792](https://github.com/frappe/erpnext/issues/39792)) ([df9d52d](df9d52d3ce))
* landed cost voucher not submitting because of incorrect reference (backport [#39898](https://github.com/frappe/erpnext/issues/39898)) ([#39899](https://github.com/frappe/erpnext/issues/39899)) ([d0b9c56](d0b9c568d3))
* remove duplicates from tax category map ([c56f3a5](c56f3a58ab))
* set rate for PO created against BO (backport [#39765](https://github.com/frappe/erpnext/issues/39765)) ([#39766](https://github.com/frappe/erpnext/issues/39766)) ([de47e67](de47e67dfa))
* set route options to cost center ([#37235](https://github.com/frappe/erpnext/issues/37235)) ([79a16ba](79a16bad15))
* **ux:** set rate as price list rate on uom change in MR (backport [#39816](https://github.com/frappe/erpnext/issues/39816)) ([#39817](https://github.com/frappe/erpnext/issues/39817)) ([de6e8c7](de6e8c74c5))

### Performance Improvements

* cached get_last_purchase_details to fix performance issue (backport [#39854](https://github.com/frappe/erpnext/issues/39854)) ([#39855](https://github.com/frappe/erpnext/issues/39855)) ([08e0271](08e02710cd))
* production plan submission (backport [#39846](https://github.com/frappe/erpnext/issues/39846)) ([#39859](https://github.com/frappe/erpnext/issues/39859)) ([a61cffd](a61cffd7c2))
2024-02-14 12:06:19 +00:00
rohitwaghchaure
4fd7b01beb Merge pull request #39881 from frappe/version-14-hotfix
chore: release v14
2024-02-14 17:34:41 +05:30
mergify[bot]
ab7e323648 fix: production plan issue with sales order (backport #39901) (#39903)
fix:  production plan issue with sales order (#39901)

(cherry picked from commit d0df5df4a6)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-02-14 17:28:50 +05:30
mergify[bot]
d0b9c568d3 fix: landed cost voucher not submitting because of incorrect reference (backport #39898) (#39899)
fix: landed cost voucher not submitting because of incorrect reference (#39898)

(cherry picked from commit 6239fd704b)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-02-14 15:57:48 +05:30
Raffael Meyer
6b53288975 Merge pull request #39893 from frappe/mergify/bp/version-14-hotfix/pr-39868
fix(Purchase Receipt): calculate `stock_value_diff` (backport #39868)
2024-02-14 01:22:09 +01:00
barredterra
8f58b613e4 fix: calculate stock_value_diff
`d.item_tax_amount` is already in base currency.

(cherry picked from commit 5df5851798)
2024-02-13 23:52:34 +00:00
Nabin Hait
6460e649a5 Merge pull request #39814 from nabinhait/asset-capitalization-cancellation
fix: Handling circular linking while cancelling asset capitalization
2024-02-13 09:46:31 +05:30
mergify[bot]
a61cffd7c2 perf: production plan submission (backport #39846) (#39859)
perf: production plan submission

(cherry picked from commit aa1c69dd7a)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-12 18:17:49 +05:30
mergify[bot]
08e02710cd perf: cached get_last_purchase_details to fix performance issue (backport #39854) (#39855)
perf: cached get_last_purchase_details to fix performance issue (#39854)

(cherry picked from commit b966c06a4f)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-02-12 15:54:03 +05:30
Nabin Hait
24386006d6 fix: cancel asset capitalization 2024-02-12 11:44:41 +05:30
mergify[bot]
de6e8c74c5 fix(ux): set rate as price list rate on uom change in MR (backport #39816) (#39817)
* fix: add price list rate field in MR Item

(cherry picked from commit 61a29eb5fb)

# Conflicts:
#	erpnext/stock/doctype/material_request_item/material_request_item.py

* fix: set rate as price list rate on uom change

(cherry picked from commit 5cf0759b0c)

* chore: linter

(cherry picked from commit 1745371cd6)

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-11 17:38:37 +05:30
mergify[bot]
79a16bad15 fix: set route options to cost center (#37235)
fix: set route to cost center

cost center mapping is not correct

(cherry picked from commit 5e4b73918d)

Co-authored-by: NIYAZ RAZAK <76736615+niyazrazak@users.noreply.github.com>
2024-02-11 17:19:59 +05:30
ruthra kumar
157d17d0f3 Merge pull request #39808 from ruthra-kumar/dimension_filter_issue_in_profitability_report
fix: incorrect route options on Profitability to Profit and Loss report
2024-02-08 15:53:26 +05:30
Nabin Hait
f2d094d1ab fix: Handling circular linking while cancelling asset capitalization 2024-02-08 15:51:29 +05:30
Frappe PR Bot
8caf655529 chore(release): Bumped to Version 14.62.3
## [14.62.3](https://github.com/frappe/erpnext/compare/v14.62.2...v14.62.3) (2024-02-08)

### Bug Fixes

* do not throw validation for canceled SLE (backport [#39769](https://github.com/frappe/erpnext/issues/39769)) ([#39810](https://github.com/frappe/erpnext/issues/39810)) ([d6054db](d6054dbdbd))
2024-02-08 09:12:55 +00:00
rohitwaghchaure
408d026465 Merge pull request #39812 from frappe/mergify/bp/version-14/pr-39810
fix: do not throw validation for canceled SLE (backport #39769) (backport #39810)
2024-02-08 14:41:43 +05:30
mergify[bot]
d6054dbdbd fix: do not throw validation for canceled SLE (backport #39769) (#39810)
fix: do not throw validation for cancelled sle

(cherry picked from commit 32ccf3524a)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
(cherry picked from commit 2885b8fa44)
2024-02-08 08:50:17 +00:00
mergify[bot]
2885b8fa44 fix: do not throw validation for canceled SLE (backport #39769) (#39810)
fix: do not throw validation for cancelled sle

(cherry picked from commit 32ccf3524a)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-02-08 14:18:50 +05:30
ruthra kumar
f79e0d1e37 fix: broken route option in Profitability report 2024-02-08 13:22:55 +05:30
Frappe PR Bot
0822a2b40a chore(release): Bumped to Version 14.62.2
## [14.62.2](https://github.com/frappe/erpnext/compare/v14.62.1...v14.62.2) (2024-02-08)

### Bug Fixes

* accommodate for default rounding method in v14 ([79479b6](79479b633f))
* remove duplicates from tax category map ([a4e6c38](a4e6c388cb))
2024-02-08 07:28:09 +00:00
ruthra kumar
cb64c5f579 Merge pull request #39797 from frappe/mergify/bp/version-14/pr-39789
fix: remove duplicates from tax category map (backport #39789)
2024-02-08 12:56:59 +05:30
rohitwaghchaure
a9c95567cb Merge pull request #39790 from aynugek/patch-2
refactor: Clean up code used to fetch website item stock details
2024-02-08 11:06:20 +05:30
ruthra kumar
e452e42145 Merge pull request #39802 from frappe/mergify/bp/version-14/pr-39783
fix: cancelling cr/dr notes should update the linked Invoice status (backport #39783)
2024-02-08 10:26:26 +05:30
ruthra kumar
8888ce196d refactor(test): Forex Credit Note cancellation against Invoice
(cherry picked from commit 2f676ced5c)
2024-02-08 04:36:03 +00:00
ruthra kumar
129ab38fba refactor(test): assert Invoice status as well
(cherry picked from commit 33efe0d12d)
2024-02-08 04:36:02 +00:00
ruthra kumar
c419c1de06 test: Invoice status on Cr/Dr note cancellation
(cherry picked from commit 31a8c3bdc4)
2024-02-08 04:36:02 +00:00
ruthra kumar
5c59ab5975 refactor: cancel Cr/Dr JE's on Sales/Purchase return cancel
(cherry picked from commit 0549535603)
2024-02-08 04:36:02 +00:00
ruthra kumar
28b0e988db Merge pull request #39798 from frappe/mergify/bp/version-14-hotfix/pr-39783
fix: cancelling cr/dr notes should update the linked Invoice status (backport #39783)
2024-02-08 10:00:54 +05:30
Frappe PR Bot
7413c8b0a9 chore(release): Bumped to Version 14.62.1
## [14.62.1](https://github.com/frappe/erpnext/compare/v14.62.0...v14.62.1) (2024-02-07)

### Bug Fixes

* incorrect planned qty in PP (backport [#39785](https://github.com/frappe/erpnext/issues/39785)) (backport [#39792](https://github.com/frappe/erpnext/issues/39792)) ([#39795](https://github.com/frappe/erpnext/issues/39795)) ([596c9fd](596c9fd507))
2024-02-07 15:31:34 +00:00
mergify[bot]
596c9fd507 fix: incorrect planned qty in PP (backport #39785) (backport #39792) (#39795)
fix: incorrect planned qty in PP (backport #39785) (#39792)

fix: incorrect planned qty in PP

(cherry picked from commit a8ebc94a36)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
(cherry picked from commit df9d52d3ce)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-02-07 21:00:18 +05:30
ruthra kumar
8d9b5764dd refactor(test): Forex Credit Note cancellation against Invoice
(cherry picked from commit 2f676ced5c)
2024-02-07 15:00:47 +00:00
ruthra kumar
dd74728568 refactor(test): assert Invoice status as well
(cherry picked from commit 33efe0d12d)
2024-02-07 15:00:47 +00:00
ruthra kumar
e97f30d8e2 test: Invoice status on Cr/Dr note cancellation
(cherry picked from commit 31a8c3bdc4)
2024-02-07 15:00:46 +00:00
ruthra kumar
33777a4d4c refactor: cancel Cr/Dr JE's on Sales/Purchase return cancel
(cherry picked from commit 0549535603)
2024-02-07 15:00:44 +00:00
Gursheen Anand
79479b633f fix: accommodate for default rounding method in v14
(cherry picked from commit d6a758d1f4)
2024-02-07 14:23:32 +00:00
Gursheen Anand
a4e6c388cb fix: remove duplicates from tax category map
(cherry picked from commit c56f3a58ab)
2024-02-07 14:23:32 +00:00
Gursheen Kaur Anand
d62db9bb22 Merge pull request #39789 from GursheenK/duplicates-in-tax-category-bpv14
fix: remove duplicates from tax category map
2024-02-07 19:51:51 +05:30
mergify[bot]
df9d52d3ce fix: incorrect planned qty in PP (backport #39785) (#39792)
fix: incorrect planned qty in PP

(cherry picked from commit a8ebc94a36)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-07 19:26:14 +05:30
Gursheen Anand
d6a758d1f4 fix: accommodate for default rounding method in v14 2024-02-07 19:12:32 +05:30
mergify[bot]
de47e67dfa fix: set rate for PO created against BO (backport #39765) (#39766)
* fix: set rate for PO created against BO

(cherry picked from commit 0e5b4e5f07)

# Conflicts:
#	erpnext/manufacturing/doctype/blanket_order/blanket_order.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-07 19:03:38 +05:30
Gursheen Anand
c56f3a58ab fix: remove duplicates from tax category map 2024-02-07 18:46:07 +05:30
Babuuu
e3591270c6 refactor: Clean up code used to fetch website item stock details 2024-02-07 13:12:14 +00:00
Frappe PR Bot
9a8a9568b1 chore(release): Bumped to Version 14.62.0
# [14.62.0](https://github.com/frappe/erpnext/compare/v14.61.4...v14.62.0) (2024-02-06)

### Bug Fixes

* Blanket Order Ordered Quantity (backport [#39725](https://github.com/frappe/erpnext/issues/39725)) ([#39738](https://github.com/frappe/erpnext/issues/39738)) ([44c09de](44c09de729))
* correctly calculate diff amount for included taxes ([#39655](https://github.com/frappe/erpnext/issues/39655)) ([350b2cd](350b2cdde3))
* don't overwrite existing terms in transaction ([#39519](https://github.com/frappe/erpnext/issues/39519)) ([e49f8d5](e49f8d5f55))
* Exchange rate on MR to PO creation for muticurrency POs ([#39646](https://github.com/frappe/erpnext/issues/39646)) ([4dc5d9a](4dc5d9a6ca))
* incorrect landed cost voucher amount ([4a609d8](4a609d8fa8))
* remove applied pricing rule on qty change (backport [#39688](https://github.com/frappe/erpnext/issues/39688)) ([#39736](https://github.com/frappe/erpnext/issues/39736)) ([46ac4f4](46ac4f4714))
* show warehouse title field in sales docs (backport [#39746](https://github.com/frappe/erpnext/issues/39746)) ([#39754](https://github.com/frappe/erpnext/issues/39754)) ([a6067c6](a6067c6239))

### Features

* copy emails from lead to customer ([#38647](https://github.com/frappe/erpnext/issues/38647)) ([7952bf4](7952bf4318))
* New financial views - Growth and margin views for P&L and balance sheet (backport [#39588](https://github.com/frappe/erpnext/issues/39588)) ([#39601](https://github.com/frappe/erpnext/issues/39601)) ([3808ddb](3808ddbf86))

### Performance Improvements

* memory consumption for the stock balance report ([#39626](https://github.com/frappe/erpnext/issues/39626)) ([b32848d](b32848d69d))
* Move dimension validation out of GL Entry doctype ([#39730](https://github.com/frappe/erpnext/issues/39730)) ([7691256](7691256f4d))
* timeout for auto material request through reorder level ([85e6b39](85e6b39e23))
* timeout while submitting the purchase receipt entry ([a1a70bb](a1a70bbae0))

### Reverts

* Revert "feat: New financial views - Growth and margin views for P&L and balance sheet (#39588)"  ([f01308b](f01308b972)), closes [#39588](https://github.com/frappe/erpnext/issues/39588)
2024-02-06 18:44:23 +00:00
rohitwaghchaure
1cedb4b430 Merge pull request #39759 from frappe/version-14-hotfix
chore: release v14
2024-02-07 00:13:04 +05:30
ruthra kumar
ae4c90766a Merge pull request #39750 from frappe/mergify/bp/version-14-hotfix/pr-39694
refactor: enforce unique GL Account for each 'Bank Account' (backport #39694)
2024-02-06 15:48:29 +05:30
mergify[bot]
a6067c6239 fix: show warehouse title field in sales docs (backport #39746) (#39754)
fix: show warehouse title field in sales docs

(cherry picked from commit ee14faaa39)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-06 12:45:43 +05:30
ruthra kumar
8267fee9b8 refactor(test): make use of test fixtures in Payment Order
(cherry picked from commit 322cdbaccf)
2024-02-06 04:00:13 +00:00
ruthra kumar
f64f0437ae refactor(test): generate uniq GL acc and Bank acc for each test case
(cherry picked from commit a9a2ec81de)
2024-02-06 04:00:13 +00:00
ruthra kumar
0163e13aa3 refactor: ensure unique accounts for each Bank Account's
(cherry picked from commit 2caa2d677c)
2024-02-06 04:00:13 +00:00
rohitwaghchaure
c2486c8d58 Merge pull request #39744 from frappe/mergify/bp/version-14-hotfix/pr-39742
perf: timeout while submitting the purchase receipt entry (backport #39742)
2024-02-05 21:26:47 +05:30
Rohit Waghchaure
a1a70bbae0 perf: timeout while submitting the purchase receipt entry
(cherry picked from commit 1fa6233377)
2024-02-05 15:33:03 +00:00
Frappe PR Bot
99929e9434 chore(release): Bumped to Version 14.61.4
## [14.61.4](https://github.com/frappe/erpnext/compare/v14.61.3...v14.61.4) (2024-02-05)

### Bug Fixes

* correctly calculate diff amount for included taxes ([#39655](https://github.com/frappe/erpnext/issues/39655)) ([33ae0fa](33ae0fa2f4))
2024-02-05 12:09:13 +00:00
mergify[bot]
33ae0fa2f4 fix: correctly calculate diff amount for included taxes (#39655)
fix: correctly calculate diff amount for included taxes (#39655)

(cherry picked from commit 772f540bef)
(cherry picked from commit 350b2cdde3)

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2024-02-05 17:38:00 +05:30
mergify[bot]
44c09de729 fix: Blanket Order Ordered Quantity (backport #39725) (#39738)
* fix: disable no-copy for blanket order in PO

(cherry picked from commit 5ce5c352e4)

# Conflicts:
#	erpnext/buying/doctype/purchase_order_item/purchase_order_item.json

* fix: update BO Ordered Quantity on PO Close/Open

(cherry picked from commit 61ded697a7)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* test: BO on PO Close/Open

(cherry picked from commit 27d6c8b6d5)

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-05 16:52:01 +05:30
mergify[bot]
46ac4f4714 fix: remove applied pricing rule on qty change (backport #39688) (#39736)
fix: remove pricing rule

(cherry picked from commit 7c6a5a0f23)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-02-05 16:09:09 +05:30
rohitwaghchaure
b10c0c9841 Merge pull request #39731 from frappe/mergify/bp/version-14-hotfix/pr-39626
perf: memory consumption for the stock balance report (backport #39626)
2024-02-05 16:02:42 +05:30
rohitwaghchaure
851f1bbdce Merge pull request #39728 from frappe/mergify/bp/version-14-hotfix/pr-39659
perf: timeout for auto material request through reorder level (backport #39659)
2024-02-05 14:46:37 +05:30
mergify[bot]
7691256f4d perf: Move dimension validation out of GL Entry doctype (#39730)
perf: Move dimension validation out of GL Entry doctype (#39730)

(cherry picked from commit b834ed10d6)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2024-02-05 14:34:16 +05:30
mergify[bot]
7952bf4318 feat: copy emails from lead to customer (#38647)
feat: copy emails from lead to customer

(cherry picked from commit 906ac093e3)

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2024-02-05 14:05:24 +05:30
rohitwaghchaure
e38bb836a5 chore: fix conflicts 2024-02-05 13:20:03 +05:30
rohitwaghchaure
a3f05cb0e7 Merge pull request #39726 from frappe/mergify/bp/version-14-hotfix/pr-39684
fix: incorrect landed cost voucher amount (backport #39684)
2024-02-05 13:16:58 +05:30
rohitwaghchaure
26dfbb7a64 chore: fix conflicts 2024-02-05 12:13:14 +05:30
rohitwaghchaure
b32848d69d perf: memory consumption for the stock balance report (#39626)
(cherry picked from commit b70f3de16b)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py
2024-02-05 06:18:51 +00:00
Rohit Waghchaure
85e6b39e23 perf: timeout for auto material request through reorder level
(cherry picked from commit 951023f434)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py
2024-02-05 06:09:53 +00:00
Rohit Waghchaure
4a609d8fa8 fix: incorrect landed cost voucher amount
(cherry picked from commit d78a1e7814)
2024-02-05 06:09:35 +00:00
mergify[bot]
e49f8d5f55 fix: don't overwrite existing terms in transaction (#39519)
* fix: don't overwrite existing terms in transaction

(cherry picked from commit 77b044f1a6)

* refactor: keep the diff small

---------

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2024-02-03 14:05:47 +05:30
Deepesh Garg
f01308b972 Revert "feat: New financial views - Growth and margin views for P&L and balance sheet (#39588)"
Revert "feat: New financial views - Growth and margin views for P&L and balan…"

This reverts commit 3808ddbf86.
2024-02-03 09:11:12 +05:30
mergify[bot]
3808ddbf86 feat: New financial views - Growth and margin views for P&L and balance sheet (backport #39588) (#39601)
feat: New financial views - Growth and margin views for P&L and balance sheet

(cherry picked from commit 92649de5c6)

# Conflicts:
#	erpnext/accounts/report/balance_sheet/balance_sheet.js
#	erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js

Co-authored-by: nitmit <nitali@citrusleaf.in>
2024-02-03 09:10:28 +05:30
ruthra kumar
7ad20a0db7 Merge pull request #39698 from ruthra-kumar/add_disabled_field_in_bank_account
refactor: add disabled field in bank account
2024-02-02 20:49:41 +05:30
ruthra kumar
58ea7f4105 refactor: add 'disabled' field to Bank Account 2024-02-02 17:50:19 +05:30
rohitwaghchaure
c284d2ae07 Merge pull request #39690 from frappe/mergify/bp/version-14/pr-39689
chore: flag for barcode scanner (backport #39689)
2024-02-02 13:34:29 +05:30
Rohit Waghchaure
57cfce08ef chore: flag for barcode scanner
(cherry picked from commit f8ca5c5c8b)
2024-02-02 07:58:38 +00:00
rohitwaghchaure
b193d3cc86 Merge pull request #39689 from rohitwaghchaure/fixed-flag-for-barcode-scanner
chore: flag for barcode scanner
2024-02-02 13:27:43 +05:30
Rohit Waghchaure
f8ca5c5c8b chore: flag for barcode scanner 2024-02-02 13:23:54 +05:30
ruthra kumar
76c652c016 Merge pull request #39667 from frappe/mergify/bp/version-14-hotfix/pr-39655
fix: correctly calculate diff amount for included taxes (backport #39655)
2024-02-01 19:45:07 +05:30
ruthra kumar
49817ab17b Merge pull request #39679 from frappe/mergify/bp/version-14-hotfix/pr-39674
refactor: use pop up to inform of possible data issue (backport #39674)
2024-02-01 17:31:11 +05:30
ruthra kumar
8035f5b951 refactor: use pop up to inform of possible data issue
and leave a comment in communcation trail as well

(cherry picked from commit 78483e2ee6)
2024-02-01 11:43:03 +00:00
ruthra kumar
86d74e7f48 Merge pull request #39675 from frappe/mergify/bp/version-14-hotfix/pr-39671
refactor: move ignore ERR filter from SOA to General Ledger (backport #39671)
2024-02-01 16:53:26 +05:30
ruthra kumar
68c5a6e86f chore: resolve conflict 2024-02-01 16:23:33 +05:30
ruthra kumar
6284553f23 refactor(test): use party with USD billing currency
(cherry picked from commit beff566c82)
2024-02-01 10:41:39 +00:00
ruthra kumar
686da470fa test: ignore_err filter out in General Ledger
(cherry picked from commit affca3a519)
2024-02-01 10:41:39 +00:00
ruthra kumar
2fbd11d646 refactor: move ignore ERR filters from SOA to General Ledger
(cherry picked from commit c077eda64e)

# Conflicts:
#	erpnext/accounts/report/general_ledger/general_ledger.py
2024-02-01 10:41:39 +00:00
Gursheen Kaur Anand
350b2cdde3 fix: correctly calculate diff amount for included taxes (#39655)
(cherry picked from commit 772f540bef)
2024-02-01 04:56:54 +00:00
mergify[bot]
4dc5d9a6ca fix: Exchange rate on MR to PO creation for muticurrency POs (#39646)
fix: Exchange rate on MR to PO creation for muticurrency POs (#39646)

(cherry picked from commit cfd1666181)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2024-01-31 12:49:22 +05:30
51 changed files with 817 additions and 251 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.61.3"
__version__ = "14.62.4"
def get_default_company(user=None):

View File

@@ -58,6 +58,7 @@ class Account(NestedSet):
self.validate_balance_must_be_debit_or_credit()
self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children()
self.validate_receivable_payable_account_type()
def validate_parent(self):
"""Fetch Parent Details and validate parent account"""
@@ -114,6 +115,24 @@ class Account(NestedSet):
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
)
def validate_receivable_payable_account_type(self):
doc_before_save = self.get_doc_before_save()
receivable_payable_types = ["Receivable", "Payable"]
if (
doc_before_save
and doc_before_save.account_type in receivable_payable_types
and doc_before_save.account_type != self.account_type
):
# check for ledger entries
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)
def validate_root_details(self):
# does not exists parent
if frappe.db.exists("Account", self.name):

View File

@@ -13,6 +13,7 @@
"account_type",
"account_subtype",
"column_break_7",
"disabled",
"is_default",
"is_company_account",
"company",
@@ -199,10 +200,16 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Branch Code"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
}
],
"links": [],
"modified": "2022-05-04 15:49:42.620630",
"modified": "2024-02-02 17:50:09.768835",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",

View File

@@ -9,6 +9,7 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact,
)
from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
class BankAccount(Document):
@@ -25,6 +26,17 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
self.validate_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, as_list=1):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_company(self):
if self.is_company_account and not self.company:

View File

@@ -32,8 +32,16 @@ class TestBankTransaction(FrappeTestCase):
frappe.db.delete(dt)
make_pos_profile()
add_transactions()
add_vouchers()
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
bank_account = create_bank_account(
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
)
add_transactions(bank_account=bank_account)
add_vouchers(gl_account=gl_account)
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
@@ -213,7 +221,9 @@ class TestBankTransaction(FrappeTestCase):
self.assertEqual(linked_payments[0][2], repayment_entry.name)
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
def create_bank_account(
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
):
try:
frappe.get_doc(
{
@@ -225,21 +235,35 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
pass
try:
frappe.get_doc(
bank_account = frappe.get_doc(
{
"doctype": "Bank Account",
"account_name": "Checking Account",
"account_name": bank_account_name,
"bank": bank_name,
"account": account_name,
"account": gl_account,
}
).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
return bank_account.name
def add_transactions():
create_bank_account()
def create_gl_account(gl_account_name="_Test Bank - _TC"):
gl_account = frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"parent_account": "Current Assets - _TC",
"account_type": "Bank",
"is_group": 0,
"account_name": gl_account_name,
}
).insert()
return gl_account.name
def add_transactions(bank_account="_Test Bank - _TC"):
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
@@ -247,7 +271,7 @@ def add_transactions():
"date": "2018-10-23",
"deposit": 1200,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"bank_account": bank_account,
}
).insert()
doc.submit()
@@ -259,7 +283,7 @@ def add_transactions():
"date": "2018-10-23",
"deposit": 1700,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"bank_account": bank_account,
}
).insert()
doc.submit()
@@ -271,7 +295,7 @@ def add_transactions():
"date": "2018-10-26",
"withdrawal": 690,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"bank_account": bank_account,
}
).insert()
doc.submit()
@@ -283,7 +307,7 @@ def add_transactions():
"date": "2018-10-27",
"deposit": 3900,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"bank_account": bank_account,
}
).insert()
doc.submit()
@@ -295,13 +319,13 @@ def add_transactions():
"date": "2018-10-27",
"withdrawal": 109080,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"bank_account": bank_account,
}
).insert()
doc.submit()
def add_vouchers():
def add_vouchers(gl_account="_Test Bank - _TC"):
try:
frappe.get_doc(
{
@@ -317,7 +341,7 @@ def add_vouchers():
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
@@ -336,14 +360,14 @@ def add_vouchers():
pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01"
pe.insert()
@@ -374,10 +398,10 @@ def add_vouchers():
pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = "_Test Bank - _TC"
pi.cash_bank_account = gl_account
pi.insert()
pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.paid_amount = 690
@@ -386,7 +410,7 @@ def add_vouchers():
pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account)
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.insert()
@@ -409,16 +433,12 @@ def add_vouchers():
if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
mode_of_payment.append(
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
)
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
)
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
si.insert()
si.submit()

View File

@@ -13,16 +13,9 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.exceptions import (
InvalidAccountCurrency,
InvalidAccountDimensionError,
MandatoryAccountDimensionError,
)
from erpnext.exceptions import InvalidAccountCurrency
exclude_from_linked_with = True
@@ -54,7 +47,6 @@ class GLEntry(Document):
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
@@ -164,42 +156,6 @@ class GLEntry(Document):
)
)
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if self.account == account:
if value["is_mandatory"] and not self.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
else:
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
def check_pl_account(self):
if (
self.is_opening == "Yes"

View File

@@ -874,19 +874,19 @@ class PaymentEntry(AccountsController):
)
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
if self.payment_type == "Receive":
self.difference_amount = base_party_amount - self.base_received_amount
elif self.payment_type == "Pay":
self.difference_amount = self.base_paid_amount - base_party_amount
else:
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
included_taxes = self.get_included_taxes()
if self.payment_type == "Receive":
self.difference_amount = base_party_amount - self.base_received_amount + included_taxes
elif self.payment_type == "Pay":
self.difference_amount = self.base_paid_amount - base_party_amount - included_taxes
else:
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - included_taxes
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
self.difference_amount = flt(
self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")
self.difference_amount - total_deductions, self.precision("difference_amount")
)
def get_included_taxes(self):

View File

@@ -4,9 +4,13 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import getdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
create_bank_account,
create_gl_account,
)
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_payment_entry,
make_payment_order,
@@ -14,28 +18,32 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
class TestPaymentOrder(unittest.TestCase):
class TestPaymentOrder(FrappeTestCase):
def setUp(self):
create_bank_account()
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
self.gl_account = create_gl_account("_Test Bank " + uniq_identifier)
self.bank_account = create_bank_account(
gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier
)
def tearDown(self):
for bt in frappe.get_all("Payment Order"):
doc = frappe.get_doc("Payment Order", bt.name)
doc.cancel()
doc.delete()
frappe.db.rollback()
def test_payment_order_creation_against_payment_entry(self):
purchase_invoice = make_purchase_invoice()
payment_entry = get_payment_entry(
"Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC"
"Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account
)
payment_entry.reference_no = "_Test_Payment_Order"
payment_entry.reference_date = getdate()
payment_entry.party_bank_account = "Checking Account - Citi Bank"
payment_entry.party_bank_account = self.bank_account
payment_entry.insert()
payment_entry.submit()
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
doc = create_payment_order_against_payment_entry(
payment_entry, "Payment Entry", self.bank_account
)
reference_doc = doc.get("references")[0]
self.assertEqual(reference_doc.reference_name, payment_entry.name)
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
@@ -43,13 +51,13 @@ class TestPaymentOrder(unittest.TestCase):
self.assertEqual(reference_doc.amount, 250)
def create_payment_order_against_payment_entry(ref_doc, order_type):
def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
payment_order = frappe.get_doc(
dict(
doctype="Payment Order",
company="_Test Company",
payment_order_type=order_type,
company_bank_account="Checking Account - Citi Bank",
company_bank_account=bank_account,
)
)
doc = make_payment_order(ref_doc.name, payment_order)

View File

@@ -591,6 +591,70 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(si.status, "Paid")
self.assertEqual(si.outstanding_amount, 0)
def test_invoice_status_after_cr_note_cancellation(self):
# This test case is made after the 'always standalone Credit/Debit notes' feature is introduced
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
cr_note = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note.is_return = 1
cr_note.return_against = si.name
cr_note = cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pr.get_unreconciled_entries()
self.assertEqual(pr.get("invoices"), [])
self.assertEqual(pr.get("payments"), [])
journals = frappe.db.get_all(
"Journal Entry",
filters={
"is_system_generated": 1,
"docstatus": 1,
"voucher_type": "Credit Note",
"reference_type": si.doctype,
"reference_name": si.name,
},
pluck="name",
)
self.assertEqual(len(journals), 1)
# assert status and outstanding
si.reload()
self.assertEqual(si.status, "Credit Note Issued")
self.assertEqual(si.outstanding_amount, 0)
cr_note.reload()
cr_note.cancel()
# 'Credit Note' Journal should be auto cancelled
journals = frappe.db.get_all(
"Journal Entry",
filters={
"is_system_generated": 1,
"docstatus": 1,
"voucher_type": "Credit Note",
"reference_type": si.doctype,
"reference_name": si.name,
},
pluck="name",
)
self.assertEqual(len(journals), 0)
# assert status and outstanding
si.reload()
self.assertEqual(si.status, "Unpaid")
self.assertEqual(si.outstanding_amount, 100)
def test_cr_note_partial_against_invoice(self):
transaction_date = nowdate()
amount = 100

View File

@@ -64,18 +64,6 @@ def get_statement_dict(doc, get_statement_dict=False):
statement_dict = {}
ageing = ""
err_journals = None
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": doc.company,
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
for entry in doc.customers:
if doc.include_ageing:
ageing = set_ageing(doc, entry)
@@ -88,8 +76,8 @@ def get_statement_dict(doc, get_statement_dict=False):
)
filters = get_common_filters(doc)
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if doc.ignore_exchange_rate_revaluation_journals:
filters.update({"ignore_err": True})
if doc.report == "General Ledger":
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))

View File

@@ -13,9 +13,13 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.utils import create_payment_ledger_entry
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
def make_gl_entries(
@@ -354,6 +358,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
process_debit_credit_difference(gl_map)
dimension_filter_map = get_dimension_filter_map()
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
@@ -361,6 +366,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
for entry in gl_map:
validate_allowed_dimensions(entry, dimension_filter_map)
make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -672,3 +678,39 @@ def set_as_cancel(voucher_type, voucher_no):
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no),
)
def validate_allowed_dimensions(gl_entry, dimension_filter_map):
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if gl_entry.account == account:
if value["is_mandatory"] and not gl_entry.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(gl_entry.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(gl_entry.account),
),
InvalidAccountDimensionError,
)
else:
if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(gl_entry.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(gl_entry.account),
),
InvalidAccountDimensionError,
)

View File

@@ -193,8 +193,14 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_remarks",
"label": __("Show Remarks"),
"fieldtype": "Check"
},
{
"fieldname": "ignore_err",
"label": __("Ignore Exchange Rate Revaluation Journals"),
"fieldtype": "Check"
}
]
}

View File

@@ -231,6 +231,19 @@ def get_conditions(filters):
if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s")
if filters.get("ignore_err"):
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": filters.get("company"),
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s")

View File

@@ -3,7 +3,7 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
from frappe.utils import flt, today
from erpnext.accounts.report.general_ledger.general_ledger import execute
@@ -148,3 +148,105 @@ class TestGeneralLedger(FrappeTestCase):
self.assertEqual(data[2]["credit"], 900)
self.assertEqual(data[3]["debit"], 100)
self.assertEqual(data[3]["credit"], 100)
def test_ignore_exchange_rate_journals_filter(self):
# create a new account with USD currency
account_name = "Test Debtors USD"
company = "_Test Company"
account = frappe.get_doc(
{
"account_name": account_name,
"is_group": 0,
"company": company,
"root_type": "Asset",
"report_type": "Balance Sheet",
"account_currency": "USD",
"parent_account": "Accounts Receivable - _TC",
"account_type": "Receivable",
"doctype": "Account",
}
)
account.insert(ignore_if_duplicate=True)
# create a JV to debit 1000 USD at 75 exchange rate
jv = frappe.new_doc("Journal Entry")
jv.posting_date = today()
jv.company = company
jv.multi_currency = 1
jv.cost_center = "_Test Cost Center - _TC"
jv.set(
"accounts",
[
{
"account": account.name,
"party_type": "Customer",
"party": "_Test Customer USD",
"debit_in_account_currency": 1000,
"credit_in_account_currency": 0,
"exchange_rate": 75,
"cost_center": "_Test Cost Center - _TC",
},
{
"account": "Cash - _TC",
"debit_in_account_currency": 0,
"credit_in_account_currency": 75000,
"cost_center": "_Test Cost Center - _TC",
},
],
)
jv.save()
jv.submit()
revaluation = frappe.new_doc("Exchange Rate Revaluation")
revaluation.posting_date = today()
revaluation.company = company
accounts = revaluation.get_accounts_data()
revaluation.extend("accounts", accounts)
row = revaluation.accounts[0]
row.new_exchange_rate = 83
row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
revaluation.set_total_gain_loss()
revaluation = revaluation.save().submit()
# post journal entry for Revaluation doc
frappe.db.set_value(
"Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
)
revaluation_jv = revaluation.make_jv_for_revaluation()
revaluation_jv.cost_center = "_Test Cost Center - _TC"
for acc in revaluation_jv.get("accounts"):
acc.cost_center = "_Test Cost Center - _TC"
revaluation_jv.save()
revaluation_jv.submit()
# With ignore_err enabled
columns, data = execute(
frappe._dict(
{
"company": company,
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"ignore_err": True,
}
)
)
self.assertNotIn(revaluation_jv.name, set([x.voucher_no for x in data]))
# Without ignore_err enabled
columns, data = execute(
frappe._dict(
{
"company": company,
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"ignore_err": False,
}
)
)
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))

View File

@@ -105,7 +105,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"to_fiscal_year": data.fiscal_year
};
if(data.based_on == 'cost_center'){
if(data.based_on == 'Cost Center'){
frappe.route_options["cost_center"] = data.account
} else {
frappe.route_options["project"] = data.account

View File

@@ -63,16 +63,14 @@ def get_result(
tax_amount += entry.credit - entry.debit
# infer tax withholding category from the account if it's the single account for this category
tax_withholding_category = tds_accounts.get(entry.account)
rate = tax_rate_map.get(tax_withholding_category)
# or else the consolidated value from the voucher document
if not tax_withholding_category:
# or else from the party default
tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category)
# or else from the party default
if not tax_withholding_category:
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category)
rate = tax_rate_map.get(tax_withholding_category)
if net_total_map.get(name):
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount
@@ -295,7 +293,7 @@ def get_tds_docs(filters):
tds_accounts = {}
for tds_acc in _tds_accounts:
# if it turns out not to be the only tax withholding category, then don't include in the map
if tds_accounts.get(tds_acc["account"]):
if tds_acc["account"] in tds_accounts:
tds_accounts[tds_acc["account"]] = None
else:
tds_accounts[tds_acc["account"]] = tds_acc["parent"]
@@ -408,7 +406,7 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
"paid_amount_after_tax",
"base_paid_amount",
],
"Journal Entry": ["tax_withholding_category", "total_amount"],
"Journal Entry": ["total_amount"],
}
entries = frappe.get_all(

View File

@@ -5,7 +5,6 @@ import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -22,18 +21,42 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
self.create_company()
self.clear_old_entries()
create_tax_accounts()
create_tcs_category()
def test_tax_withholding_for_customers(self):
create_tax_category(cumulative_threshold=300)
frappe.db.set_value("Customer", "_Test Customer", "tax_withholding_category", "TCS")
si = create_sales_invoice(rate=1000)
pe = create_tcs_payment_entry()
jv = create_tcs_journal_entry()
filters = frappe._dict(
company="_Test Company", party_type="Customer", from_date=today(), to_date=today()
)
result = execute(filters)[1]
expected_values = [
# Check for JV totals using back calculation logic
[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
[si.name, "TCS", 0.075, 1000, 0.53, 1000.53],
[si.name, "TCS", 0.075, 1000.0, 0.53, 1000.53],
]
self.check_expected_values(result, expected_values)
def test_single_account_for_multiple_categories(self):
create_tax_category("TDS - 1", rate=10, account="TDS - _TC")
inv_1 = make_purchase_invoice(rate=1000, do_not_submit=True)
inv_1.tax_withholding_category = "TDS - 1"
inv_1.submit()
create_tax_category("TDS - 2", rate=20, account="TDS - _TC")
inv_2 = make_purchase_invoice(rate=1000, do_not_submit=True)
inv_2.tax_withholding_category = "TDS - 2"
inv_2.submit()
result = execute(
frappe._dict(company="_Test Company", party_type="Supplier", from_date=today(), to_date=today())
)[1]
expected_values = [
[inv_1.name, "TDS - 1", 10, 5000, 500, 5500],
[inv_2.name, "TDS - 2", 20, 5000, 1000, 6000],
]
self.check_expected_values(result, expected_values)
@@ -41,12 +64,15 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
for i in range(len(result)):
voucher = frappe._dict(result[i])
voucher_expected_values = expected_values[i]
self.assertEqual(voucher.ref_no, voucher_expected_values[0])
self.assertEqual(voucher.section_code, voucher_expected_values[1])
self.assertEqual(voucher.rate, voucher_expected_values[2])
self.assertEqual(voucher.base_total, voucher_expected_values[3])
self.assertEqual(voucher.tax_amount, voucher_expected_values[4])
self.assertEqual(voucher.grand_total, voucher_expected_values[5])
voucher_actual_values = (
voucher.ref_no,
voucher.section_code,
voucher.rate,
voucher.base_total,
voucher.tax_amount,
voucher.grand_total,
)
self.assertSequenceEqual(voucher_actual_values, voucher_expected_values)
def tearDown(self):
self.clear_old_entries()
@@ -67,24 +93,20 @@ def create_tax_accounts():
).insert(ignore_if_duplicate=True)
def create_tcs_category():
def create_tax_category(category="TCS", rate=0.075, account="TCS - _TC", cumulative_threshold=0):
fiscal_year = get_fiscal_year(today(), company="_Test Company")
from_date = fiscal_year[1]
to_date = fiscal_year[2]
tax_category = create_tax_withholding_category(
category_name="TCS",
rate=0.075,
create_tax_withholding_category(
category_name=category,
rate=rate,
from_date=from_date,
to_date=to_date,
account="TCS - _TC",
cumulative_threshold=300,
account=account,
cumulative_threshold=cumulative_threshold,
)
customer = frappe.get_doc("Customer", "_Test Customer")
customer.tax_withholding_category = "TCS"
customer.save()
def create_tcs_payment_entry():
payment_entry = create_payment_entry(
@@ -109,3 +131,32 @@ def create_tcs_payment_entry():
)
payment_entry.submit()
return payment_entry
def create_tcs_journal_entry():
jv = frappe.new_doc("Journal Entry")
jv.posting_date = today()
jv.company = "_Test Company"
jv.set(
"accounts",
[
{
"account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 10000,
},
{
"account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"debit_in_account_currency": 9992.5,
},
{
"account": "TCS - _TC",
"debit_in_account_currency": 7.5,
},
],
)
jv.insert()
return jv.submit()

View File

@@ -322,7 +322,7 @@ frappe.ui.form.on('Asset', {
},
make_schedules_editable: function(frm) {
if (frm.doc.finance_books.length) {
if (frm.doc.finance_books && frm.doc.finance_books.length) {
var is_manual_hence_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
? true : false;
var is_shift_hence_editable = frm.doc.finance_books.filter(d => d.shift_based).length > 0

View File

@@ -508,7 +508,7 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
def reverse_depreciation_entry_made_after_disposal(asset, date):
if not asset.calculate_depreciation:
if not asset.calculate_depreciation or not asset.get("schedules"):
return
row = -1

View File

@@ -7,6 +7,7 @@ frappe.provide("erpnext.assets");
erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
setup() {
this.setup_posting_date_time_check();
this.frm.ignore_doctypes_on_cancel_all = ["Asset Movement"];
}
onload() {

View File

@@ -77,15 +77,17 @@ class AssetCapitalization(StockController):
"Stock Ledger Entry",
"Repost Item Valuation",
"Asset",
"Asset Movement"
)
self.cancel_target_asset()
self.update_stock_ledger()
self.make_gl_entries()
self.restore_consumed_asset_items()
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
asset_doc.db_set("capitalized_in", None)
if asset_doc.docstatus == 1:
asset_doc.cancel()

View File

@@ -317,6 +317,7 @@ class PurchaseOrder(BuyingController):
self.update_requested_qty()
self.update_ordered_qty()
self.update_reserved_qty_for_subcontract()
self.update_blanket_order()
self.notify_update()
clear_doctype_notifications(self)
@@ -456,6 +457,7 @@ class PurchaseOrder(BuyingController):
)
@frappe.request_cache
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item"""

View File

@@ -814,6 +814,30 @@ class TestPurchaseOrder(FrappeTestCase):
# To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None)
def test_blanket_order_on_po_close_and_open(self):
# Step - 1: Create Blanket Order
bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
# Step - 2: Create Purchase Order
po = create_purchase_order(
item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name
)
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
# Step - 3: Close Purchase Order
po.update_status("Closed")
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 0)
# Step - 4: Re-Open Purchase Order
po.update_status("Re-open")
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
@@ -1016,6 +1040,7 @@ def create_purchase_order(**args):
"schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get("include_exploded_items", 1),
"against_blanket_order": args.against_blanket_order,
"against_blanket": args.against_blanket,
"material_request": args.material_request,
"material_request_item": args.material_request_item,
},

View File

@@ -546,7 +546,6 @@
"fieldname": "blanket_order",
"fieldtype": "Link",
"label": "Blanket Order",
"no_copy": 1,
"options": "Blanket Order"
},
{
@@ -554,7 +553,6 @@
"fieldname": "blanket_order_rate",
"fieldtype": "Currency",
"label": "Blanket Order Rate",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
@@ -918,7 +916,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-11-24 19:07:34.921094",
"modified": "2024-02-05 11:23:24.859435",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -671,7 +671,7 @@ 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)
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
@@ -1374,6 +1374,24 @@ class AccountsController(TransactionBase):
x.update({dim.fieldname: self.get(dim.fieldname)})
reconcile_against_document(lst, active_dimensions=active_dimensions)
def cancel_system_generated_credit_debit_notes(self):
# Cancel 'Credit/Debit' Note Journal Entries, if found.
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
voucher_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
journals = frappe.db.get_all(
"Journal Entry",
filters={
"is_system_generated": 1,
"reference_type": self.doctype,
"reference_name": self.name,
"voucher_type": voucher_type,
"docstatus": 1,
},
pluck="name",
)
for x in journals:
frappe.get_doc("Journal Entry", x).cancel()
def on_cancel(self):
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
remove_from_bank_transaction,
@@ -1386,6 +1404,8 @@ class AccountsController(TransactionBase):
remove_from_bank_transaction(self.doctype, self.name)
if self.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
self.cancel_system_generated_credit_debit_notes()
# Cancel Exchange Gain/Loss Journal before unlinking
cancel_exchange_gain_loss_journal(self)

View File

@@ -190,8 +190,8 @@ class BuyingController(SubcontractingController):
lc_voucher_data = frappe.db.sql(
"""select sum(applicable_charges), cost_center
from `tabLanded Cost Item`
where docstatus = 1 and purchase_receipt_item = %s""",
d.name,
where docstatus = 1 and purchase_receipt_item = %s and receipt_document = %s""",
(d.name, self.name),
)
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:

View File

@@ -679,17 +679,24 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters)
query = """select `tabWarehouse`.name,
warehouse_field = "name"
meta = frappe.get_meta("Warehouse")
if meta.get("show_title_field_in_link") and meta.get("title_field"):
searchfield = meta.get("title_field")
warehouse_field = meta.get("title_field")
query = """select `tabWarehouse`.`{warehouse_field}`,
CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
from `tabWarehouse` left join `tabBin`
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
where
`tabWarehouse`.`{key}` like {txt}
{fcond} {mcond}
order by ifnull(`tabBin`.actual_qty, 0) desc
order by ifnull(`tabBin`.actual_qty, 0) desc, `tabWarehouse`.`{warehouse_field}` asc
limit
{page_len} offset {start}
""".format(
warehouse_field=warehouse_field,
bin_conditions=get_filters_cond(
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
),

View File

@@ -586,7 +586,7 @@ class SellingController(StockController):
if self.doctype in ["Sales Order", "Quotation"]:
for item in self.items:
item.gross_profit = flt(
((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item)
)
def set_customer_address(self):

View File

@@ -831,6 +831,9 @@ class StockController(AccountsController):
"Stock Reconciliation",
)
if not frappe.get_all("Putaway Rule", limit=1):
return
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
valid_doctype = False

View File

@@ -1108,18 +1108,18 @@ class TestAccountsController(FrappeTestCase):
cr_note.reload()
cr_note.cancel()
# Exchange Gain/Loss Journal should've been created.
# with the introduction of 'cancel_system_generated_credit_debit_notes' in accounts controller
# JE(Credit Note) will be cancelled once the parent is cancelled
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_cr = self.get_journals_for(cr_note.doctype, cr_note.name)
self.assertNotEqual(exc_je_for_si, [])
self.assertEqual(len(exc_je_for_si), 1)
self.assertEqual(exc_je_for_si, [])
self.assertEqual(len(exc_je_for_si), 0)
self.assertEqual(len(exc_je_for_cr), 0)
# The Credit Note JE is still active and is referencing the sales invoice
# So, outstanding stays the same
# No references, full outstanding
si.reload()
self.assertEqual(si.outstanding_amount, 1)
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
self.assertEqual(si.outstanding_amount, 2)
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
def test_40_cost_center_from_payment_entry(self):
"""

View File

@@ -65,6 +65,7 @@ def make_order(source_name):
def update_item(source, target, source_parent):
target_qty = source.get("qty") - source.get("ordered_qty")
target.qty = target_qty if not flt(target_qty) < 0 else 0
target.rate = source.get("rate")
item = get_item_defaults(target.item_code, source_parent.company)
if item:
target.item_name = item.get("item_name")
@@ -86,6 +87,10 @@ def make_order(source_name):
},
},
)
if target_doc.doctype == "Purchase Order":
target_doc.set_missing_values()
return target_doc

View File

@@ -38,7 +38,8 @@
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fieldname": "item_name",
@@ -53,7 +54,8 @@
"in_standard_filter": 1,
"label": "For Warehouse",
"options": "Warehouse",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"columns": 1,
@@ -141,7 +143,8 @@
"fieldname": "from_warehouse",
"fieldtype": "Link",
"label": "From Warehouse",
"options": "Warehouse"
"options": "Warehouse",
"search_index": 1
},
{
"fetch_from": "item_code.safety_stock",
@@ -199,7 +202,7 @@
],
"istable": 1,
"links": [],
"modified": "2023-09-12 12:09:08.358326",
"modified": "2024-02-11 16:21:11.977018",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan Item",

View File

@@ -298,7 +298,8 @@
"no_copy": 1,
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nClosed\nCancelled\nMaterial Requested",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "amended_from",
@@ -436,7 +437,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-12-26 16:31:13.740777",
"modified": "2024-02-11 15:42:47.642481",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",

View File

@@ -236,9 +236,10 @@ class ProductionPlan(Document):
so_item.parent,
so_item.item_code,
so_item.warehouse,
(
(so_item.qty - so_item.work_order_qty - so_item.delivered_qty) * so_item.conversion_factor
).as_("pending_qty"),
so_item.qty,
so_item.work_order_qty,
so_item.delivered_qty,
so_item.conversion_factor,
so_item.description,
so_item.name,
so_item.bom_no,
@@ -261,6 +262,11 @@ class ProductionPlan(Document):
items = items_query.run(as_dict=True)
for item in items:
item.pending_qty = (
flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0) * item.conversion_factor
)
pi = frappe.qb.DocType("Packed Item")
packed_items_query = (
@@ -570,7 +576,10 @@ class ProductionPlan(Document):
"project": self.project,
}
key = (d.item_code, d.sales_order, d.warehouse)
key = (d.item_code, d.sales_order, d.sales_order_item, d.warehouse)
if self.combine_items:
key = (d.item_code, d.sales_order, d.warehouse)
if not d.sales_order:
key = (d.name, d.item_code, d.warehouse)
@@ -1685,23 +1694,23 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
return reserved_qty_for_production_plan - reserved_qty_for_production
@frappe.request_cache
def get_non_completed_production_plans():
table = frappe.qb.DocType("Production Plan")
child = frappe.qb.DocType("Production Plan Item")
query = (
return (
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(table.name)
.distinct()
.where(
(table.docstatus == 1)
& (table.status.notin(["Completed", "Closed"]))
& (child.planned_qty > child.ordered_qty)
)
).run(as_dict=True)
return list(set([d.name for d in query]))
).run(pluck="name")
def get_raw_materials_of_sub_assembly_items(

View File

@@ -448,7 +448,8 @@
"no_copy": 1,
"options": "Production Plan",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "production_plan_item",
@@ -600,7 +601,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2023-08-11 18:35:49.852069",
"modified": "2024-02-11 15:47:13.454422",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",

View File

@@ -36,7 +36,8 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item"
"options": "Item",
"search_index": 1
},
{
"fieldname": "source_warehouse",
@@ -141,7 +142,7 @@
],
"istable": 1,
"links": [],
"modified": "2022-09-28 10:50:43.512562",
"modified": "2024-02-11 15:45:32.318374",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Item",

View File

@@ -746,14 +746,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_selling_terms);
}
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
"Material Request", "Purchase Receipt"];
// Purchase Invoice is excluded as per issue #3345
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_buying_terms);
}

View File

@@ -1,6 +1,7 @@
erpnext.utils.BarcodeScanner = class BarcodeScanner {
constructor(opts) {
this.frm = opts.frm;
// frappe.flags.trigger_from_barcode_scanner is used for custom scripts
// field from which to capture input of scanned data
this.scan_field_name = opts.scan_field_name || "scan_barcode";
@@ -84,6 +85,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
update_table(data) {
return new Promise(resolve => {
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
frappe.flags.trigger_from_barcode_scanner = true;
const {item_code, barcode, batch_no, serial_no, uom} = data;
@@ -143,12 +145,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
revert_selector_flag() {
frappe.flags.hide_serial_batch_dialog = false;
frappe.flags.trigger_from_barcode_scanner = false;
}
set_item(row, item_code, barcode, batch_no, serial_no) {
return new Promise(resolve => {
const increment = async (value = 1) => {
const item_data = {item_code: item_code};
frappe.flags.trigger_from_barcode_scanner = true;
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
await frappe.model.set_value(row.doctype, row.name, item_data);
return value;

View File

@@ -171,6 +171,7 @@ class Customer(TransactionBase):
if self.flags.is_new_doc:
self.link_lead_address_and_contact()
self.copy_communication()
self.update_customer_groups()
@@ -224,6 +225,17 @@ class Customer(TransactionBase):
linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name))
linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
def copy_communication(self):
if not self.lead_name or not frappe.db.get_single_value(
"CRM Settings", "carry_forward_communication_and_comments"
):
return
from erpnext.crm.utils import copy_comments, link_communications
copy_comments("Lead", self.lead_name, self)
link_communications("Lead", self.lead_name, self)
def validate_name_with_customer_group(self):
if frappe.db.exists("Customer Group", self.name):
frappe.throw(

View File

@@ -1047,6 +1047,7 @@ def validate_cancelled_item(item_code, docstatus=None):
frappe.throw(_("Item {0} is cancelled").format(item_code))
@frappe.request_cache
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"""returns last purchase details in stock uom"""
# get last purchase order item details

View File

@@ -38,6 +38,7 @@ class LandedCostVoucher(Document):
def validate(self):
self.check_mandatory()
self.validate_receipt_documents()
self.validate_line_items()
init_landed_taxes_and_totals(self)
self.set_total_taxes_and_charges()
if not self.get("items"):
@@ -45,6 +46,26 @@ class LandedCostVoucher(Document):
self.set_applicable_charges_on_item()
def validate_line_items(self):
for d in self.get("items"):
if (
d.docstatus == 0
and d.purchase_receipt_item
and not frappe.db.exists(
d.receipt_document_type + " Item",
{"name": d.purchase_receipt_item, "parent": d.receipt_document},
)
):
frappe.throw(
_("Row {0}: {2} Item {1} does not exist in {2} {3}").format(
d.idx,
frappe.bold(d.purchase_receipt_item),
d.receipt_document_type,
frappe.bold(d.receipt_document),
),
title=_("Incorrect Reference Document (Purchase Receipt Item)"),
)
def check_mandatory(self):
if not self.get("purchase_receipts"):
frappe.throw(_("Please enter Receipt Document"))
@@ -122,6 +143,13 @@ class LandedCostVoucher(Document):
self.get("items")[item_count - 1].applicable_charges += diff
def validate_applicable_charges_for_item(self):
if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1:
frappe.throw(
_(
"Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher."
)
)
based_on = self.distribute_charges_based_on.lower()
if based_on != "distribute manually":

View File

@@ -228,9 +228,17 @@ frappe.ui.form.on('Material Request', {
const qty_fields = ['actual_qty', 'projected_qty', 'min_order_qty'];
if(!r.exc) {
$.each(r.message, function(k, v) {
if(!d[k] || in_list(qty_fields, k)) d[k] = v;
$.each(r.message, function(key, value) {
if(!d[key] || qty_fields.includes(key)) {
d[key] = value;
}
});
if (d.price_list_rate != r.message.price_list_rate) {
d.price_list_rate = r.message.price_list_rate;
frappe.model.set_value(d.doctype, d.name, "rate", d.price_list_rate);
}
}
}
});
@@ -432,7 +440,6 @@ frappe.ui.form.on("Material Request Item", {
item.amount = flt(item.qty) * flt(item.rate);
frappe.model.set_value(doctype, name, "amount", item.amount);
refresh_field("amount", item.name, item.parentfield);
frm.events.get_item_data(frm, item, false);
},
item_code: function(frm, doctype, name) {
@@ -452,7 +459,12 @@ frappe.ui.form.on("Material Request Item", {
set_schedule_date(frm);
}
}
}
},
conversion_factor: function(frm, doctype, name) {
const item = locals[doctype][name];
frm.events.get_item_data(frm, item, false);
},
});
erpnext.buying.MaterialRequestController = class MaterialRequestController extends erpnext.buying.BuyingController {

View File

@@ -417,6 +417,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
postprocess,
)
doclist.set_onload("load_after_mapping", False)
return doclist

View File

@@ -35,6 +35,7 @@
"received_qty",
"rate_and_amount_section_break",
"rate",
"price_list_rate",
"col_break3",
"amount",
"accounting_details_section",
@@ -474,13 +475,22 @@
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"fieldname": "price_list_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Price List Rate",
"options": "currency",
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-11-14 18:37:59.599115",
"modified": "2024-02-08 16:30:56.137858",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",

View File

@@ -572,9 +572,7 @@ class PurchaseReceipt(BuyingController):
)
stock_value_diff = (
flt(d.base_net_amount)
+ flt(d.item_tax_amount / self.conversion_rate)
+ flt(d.landed_cost_voucher_amount)
flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount)
)
elif warehouse_account.get(d.warehouse):
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
@@ -1200,16 +1198,16 @@ def get_item_account_wise_additional_cost(purchase_document):
for lcv in landed_cost_vouchers:
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
based_on_field = None
# Use amount field for total item cost for manually cost distributed LCVs
if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually":
based_on_field = "amount"
else:
if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually":
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
total_item_cost = 0
for item in landed_cost_voucher_doc.items:
total_item_cost += item.get(based_on_field)
if based_on_field:
for item in landed_cost_voucher_doc.items:
total_item_cost += item.get(based_on_field)
for item in landed_cost_voucher_doc.items:
if item.receipt_document == purchase_document:

View File

@@ -1734,6 +1734,48 @@ class TestStockEntry(FrappeTestCase):
self.assertFalse(doc.is_enqueue_action())
frappe.flags.in_test = True
def test_auto_reorder_level(self):
from erpnext.stock.reorder_item import reorder_item
item_doc = make_item(
"Test Auto Reorder Item - 001",
properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
uoms=[{"uom": "Nos", "conversion_factor": 5}],
)
if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
item_doc.append(
"reorder_levels",
{
"warehouse_reorder_level": 0,
"warehouse_reorder_qty": 10,
"warehouse": "_Test Warehouse - _TC",
"material_request_type": "Purchase",
},
)
item_doc.save(ignore_permissions=True)
frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
mr_list = reorder_item()
frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
mrs = frappe.get_all(
"Material Request Item",
fields=["qty", "stock_uom", "stock_qty"],
filters={"item_code": item_doc.name, "uom": "Nos"},
)
for mri in mrs:
self.assertEqual(mri.stock_uom, "Kg")
self.assertEqual(mri.stock_qty, 10)
self.assertEqual(mri.qty, 2)
for mr in mr_list:
mr.cancel()
mr.delete()
def make_serialized_item(**args):
args = frappe._dict(args)

View File

@@ -53,6 +53,9 @@ class StockLedgerEntry(Document):
self.validate_inventory_dimension_negative_stock()
def validate_inventory_dimension_negative_stock(self):
if self.is_cancelled:
return
extra_cond = ""
kwargs = {}

View File

@@ -87,7 +87,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
get_party_item_code(args, item, out)
set_valuation_rate(out, args)
if args.get("doctype") in ["Sales Order", "Quotation"]:
set_valuation_rate(out, args)
update_party_blanket_order(args, out)
@@ -303,7 +304,9 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not item:
item = frappe.get_doc("Item", args.get("item_code"))
if item.variant_of and not item.taxes:
if (
item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
):
item.update_template_tables()
item_defaults = get_item_defaults(item.name, args.company)

View File

@@ -34,73 +34,157 @@ def _reorder_item():
erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
)
items_to_consider = frappe.db.sql_list(
"""select name from `tabItem` item
where is_stock_item=1 and has_variants=0
and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
or (variant_of is not null and variant_of != ''
and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
)""",
{"today": nowdate()},
)
items_to_consider = get_items_for_reorder()
if not items_to_consider:
return
item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
def add_to_material_request(
item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
):
if warehouse not in warehouse_company:
def add_to_material_request(**kwargs):
if isinstance(kwargs, dict):
kwargs = frappe._dict(kwargs)
if kwargs.warehouse not in warehouse_company:
# a disabled warehouse
return
reorder_level = flt(reorder_level)
reorder_qty = flt(reorder_qty)
reorder_level = flt(kwargs.reorder_level)
reorder_qty = flt(kwargs.reorder_qty)
# projected_qty will be 0 if Bin does not exist
if warehouse_group:
projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
if kwargs.warehouse_group:
projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
)
else:
projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
)
if (reorder_level or reorder_qty) and projected_qty < reorder_level:
deficiency = reorder_level - projected_qty
if deficiency > reorder_qty:
reorder_qty = deficiency
company = warehouse_company.get(warehouse) or default_company
company = warehouse_company.get(kwargs.warehouse) or default_company
material_requests[material_request_type].setdefault(company, []).append(
{"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
material_requests[kwargs.material_request_type].setdefault(company, []).append(
{
"item_code": kwargs.item_code,
"warehouse": kwargs.warehouse,
"reorder_qty": reorder_qty,
"item_details": kwargs.item_details,
}
)
for item_code in items_to_consider:
item = frappe.get_doc("Item", item_code)
for item_code, reorder_levels in items_to_consider.items():
for d in reorder_levels:
if d.has_variants:
continue
if item.variant_of and not item.get("reorder_levels"):
item.update_template_tables()
if item.get("reorder_levels"):
for d in item.get("reorder_levels"):
add_to_material_request(
item_code,
d.warehouse,
d.warehouse_reorder_level,
d.warehouse_reorder_qty,
d.material_request_type,
warehouse_group=d.warehouse_group,
)
add_to_material_request(
item_code=item_code,
warehouse=d.warehouse,
reorder_level=d.warehouse_reorder_level,
reorder_qty=d.warehouse_reorder_qty,
material_request_type=d.material_request_type,
warehouse_group=d.warehouse_group,
item_details=frappe._dict(
{
"item_code": item_code,
"name": item_code,
"item_name": d.item_name,
"item_group": d.item_group,
"brand": d.brand,
"description": d.description,
"stock_uom": d.stock_uom,
"purchase_uom": d.purchase_uom,
}
),
)
if material_requests:
return create_material_request(material_requests)
def get_items_for_reorder() -> dict[str, list]:
reorder_table = frappe.qb.DocType("Item Reorder")
item_table = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(reorder_table)
.inner_join(item_table)
.on(reorder_table.parent == item_table.name)
.select(
reorder_table.warehouse,
reorder_table.warehouse_group,
reorder_table.material_request_type,
reorder_table.warehouse_reorder_level,
reorder_table.warehouse_reorder_qty,
item_table.name,
item_table.stock_uom,
item_table.purchase_uom,
item_table.description,
item_table.item_name,
item_table.item_group,
item_table.brand,
item_table.variant_of,
item_table.has_variants,
)
.where(
(item_table.disabled == 0)
& (item_table.is_stock_item == 1)
& (
(item_table.end_of_life.isnull())
| (item_table.end_of_life > nowdate())
| (item_table.end_of_life == "0000-00-00")
)
)
)
data = query.run(as_dict=True)
itemwise_reorder = frappe._dict({})
for d in data:
itemwise_reorder.setdefault(d.name, []).append(d)
itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)
return itemwise_reorder
def get_reorder_levels_for_variants(itemwise_reorder):
item_table = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(item_table)
.select(
item_table.name,
item_table.variant_of,
)
.where(
(item_table.disabled == 0)
& (item_table.is_stock_item == 1)
& (
(item_table.end_of_life.isnull())
| (item_table.end_of_life > nowdate())
| (item_table.end_of_life == "0000-00-00")
)
& (item_table.variant_of.notnull())
)
)
variants_item = query.run(as_dict=True)
for row in variants_item:
if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))
return itemwise_reorder
def get_item_warehouse_projected_qty(items_to_consider):
item_warehouse_projected_qty = {}
items_to_consider = list(items_to_consider.keys())
for item_code, warehouse, projected_qty in frappe.db.sql(
"""select item_code, warehouse, projected_qty
@@ -164,7 +248,7 @@ def create_material_request(material_requests):
for d in items:
d = frappe._dict(d)
item = frappe.get_doc("Item", d.item_code)
item = d.get("item_details")
uom = item.stock_uom
conversion_factor = 1.0
@@ -190,6 +274,7 @@ def create_material_request(material_requests):
"item_code": d.item_code,
"schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
"qty": qty,
"conversion_factor": conversion_factor,
"uom": uom,
"stock_uom": item.stock_uom,
"warehouse": d.warehouse,

View File

@@ -90,8 +90,7 @@ class StockBalanceReport(object):
self.opening_data.setdefault(group_by_key, entry)
def prepare_new_data(self):
if not self.sle_entries:
return
self.item_warehouse_map = self.get_item_warehouse_map()
if self.filters.get("show_stock_ageing_data"):
self.filters["show_warehouse_wise_stock"] = True
@@ -99,7 +98,7 @@ class StockBalanceReport(object):
_func = itemgetter(1)
self.item_warehouse_map = self.get_item_warehouse_map()
del self.sle_entries
variant_values = {}
if self.filters.get("show_variant_attributes"):
@@ -139,15 +138,22 @@ class StockBalanceReport(object):
item_warehouse_map = {}
self.opening_vouchers = self.get_opening_vouchers()
for entry in self.sle_entries:
group_by_key = self.get_group_by_key(entry)
if group_by_key not in item_warehouse_map:
self.initialize_data(item_warehouse_map, group_by_key, entry)
if self.filters.get("show_stock_ageing_data"):
self.sle_entries = self.sle_query.run(as_dict=True)
self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
with frappe.db.unbuffered_cursor():
if not self.filters.get("show_stock_ageing_data"):
self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
if self.opening_data.get(group_by_key):
del self.opening_data[group_by_key]
for entry in self.sle_entries:
group_by_key = self.get_group_by_key(entry)
if group_by_key not in item_warehouse_map:
self.initialize_data(item_warehouse_map, group_by_key, entry)
self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
if self.opening_data.get(group_by_key):
del self.opening_data[group_by_key]
for group_by_key, entry in self.opening_data.items():
if group_by_key not in item_warehouse_map:
@@ -236,7 +242,8 @@ class StockBalanceReport(object):
.where(
(table.docstatus == 1)
& (table.company == self.filters.company)
& ((table.to_date <= self.from_date))
& (table.to_date <= self.from_date)
& (table.status == "Completed")
)
.orderby(table.to_date, order=Order.desc)
.limit(1)
@@ -289,7 +296,7 @@ class StockBalanceReport(object):
if self.filters.get("company"):
query = query.where(sle.company == self.filters.get("company"))
self.sle_entries = query.run(as_dict=True)
self.sle_query = query
def apply_inventory_dimensions_filters(self, query, sle) -> str:
inventory_dimension_fields = self.get_inventory_dimension_fields()

View File

@@ -11,7 +11,6 @@ from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
in_stock, stock_qty = 0, ""
template_item_code, is_stock_item = frappe.db.get_value(
"Item", item_code, ["variant_of", "is_stock_item"]
)
@@ -52,10 +51,11 @@ def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
.where((BIN.item_code == item_code) & (BIN.warehouse == warehouse))
).run()
stock_qty = stock_qty[0][0]
if stock_qty:
total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse)
in_stock = total_stock > 0 and 1 or 0
in_stock = int(total_stock > 0)
return frappe._dict(
{"in_stock": in_stock, "stock_qty": total_stock, "is_stock_item": is_stock_item}
@@ -63,20 +63,16 @@ def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
def adjust_qty_for_expired_items(item_code, stock_qty, warehouse):
batches = frappe.get_all("Batch", filters=[{"item": item_code}], fields=["expiry_date", "name"])
batches = frappe.get_all("Batch", filters={"item": item_code}, fields=["expiry_date", "name"])
expired_batches = get_expired_batches(batches)
stock_qty = [list(item) for item in stock_qty]
for batch in expired_batches:
if warehouse:
stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse))
stock_qty = max(0, stock_qty - get_batch_qty(batch, warehouse))
else:
stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch)))
stock_qty = max(0, stock_qty - qty_from_all_warehouses(get_batch_qty(batch)))
if not stock_qty[0][0]:
break
return stock_qty[0][0] if stock_qty else 0
return stock_qty
def get_expired_batches(batches):