Compare commits

...

153 Commits

Author SHA1 Message Date
Frappe PR Bot
c1f7d5a2d1 chore(release): Bumped to Version 15.23.3
## [15.23.3](https://github.com/frappe/erpnext/compare/v15.23.2...v15.23.3) (2024-05-15)

### Bug Fixes

* not able to submit landed cost voucher (backport [#41481](https://github.com/frappe/erpnext/issues/41481)) (backport [#41486](https://github.com/frappe/erpnext/issues/41486)) ([#41487](https://github.com/frappe/erpnext/issues/41487)) ([66684e7](66684e7e23))
2024-05-15 13:27:07 +00:00
mergify[bot]
66684e7e23 fix: not able to submit landed cost voucher (backport #41481) (backport #41486) (#41487)
fix: not able to submit landed cost voucher (backport #41481) (#41486)

fix: not able to submit landed cost voucher (#41481)

(cherry picked from commit 81a9521f04)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit a070ad786d)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-05-15 18:55:40 +05:30
Frappe PR Bot
7b8b58f8ca chore(release): Bumped to Version 15.23.2
## [15.23.2](https://github.com/frappe/erpnext/compare/v15.23.1...v15.23.2) (2024-05-15)

### Bug Fixes

* 'Bill for Rejected Quantity in Purchase Invoice' feature not working (backport [#41437](https://github.com/frappe/erpnext/issues/41437)) ([#41445](https://github.com/frappe/erpnext/issues/41445)) ([b8db903](b8db903320))
* address filter and quotation to for prospect ([37741b5](37741b5b45))
* address filter and quotation to for prospect ([394b1f4](394b1f499d))
* address filter and quotation to for prospect ([cdbb515](cdbb515379))
* Asset cancelation issue ([e18be9b](e18be9b21e))
* consistent use of "Address & Contact" (backport [#41386](https://github.com/frappe/erpnext/issues/41386)) ([#41388](https://github.com/frappe/erpnext/issues/41388)) ([b6f82a6](b6f82a656f))
* daily prorata based depreciation bug in wdv method ([c335c2c](c335c2c85a))
* data getting override in delivery trip (backport [#41431](https://github.com/frappe/erpnext/issues/41431)) ([#41433](https://github.com/frappe/erpnext/issues/41433)) ([15d2881](15d2881bc8))
* Duplicate party name column in AR/AP report ([55edbec](55edbec6fa))
* incorrect  total days calculation ([01b25b5](01b25b5821))
* **minor:** removed extra parameter ([ce2c6c3](ce2c6c3165))
* pro rata based depreciation with opening accumulated depreciation ([72f3fb2](72f3fb2a2c))
* PSOA ageing ([6590d78](6590d78298))
* removed same named function ([955ce9b](955ce9b670))
* removed unrelated code modification ([0aca1e8](0aca1e8e05))
* resolved conflict ([520e1e9](520e1e9c8f))
* resolved conflict ([3a72f4b](3a72f4bd30))
* resolved conflict ([8cca74d](8cca74d5ef))
* Unknown column 'tabBatch.batch_no' in 'where clause' (backport [#41418](https://github.com/frappe/erpnext/issues/41418)) ([#41444](https://github.com/frappe/erpnext/issues/41444)) ([d988c7b](d988c7bd06))
* update description of Supplier Invoice Number ([a7533ff](a7533ff7f6))
* valuation issue for batch (backport [#41425](https://github.com/frappe/erpnext/issues/41425)) ([#41430](https://github.com/frappe/erpnext/issues/41430)) ([f55a131](f55a131dea))
* **wip:** daily depreciation bug ([1016ec2](1016ec2a14))
* **wip:** depreciation calculation after asset value adjustment ([3eff9c9](3eff9c9779))
* zero valuation rate for batched item (backport [#41446](https://github.com/frappe/erpnext/issues/41446)) ([#41447](https://github.com/frappe/erpnext/issues/41447)) ([5c3a096](5c3a0965bc))
2024-05-15 05:12:37 +00:00
Deepesh Garg
71da5d0928 Merge pull request #41459 from frappe/version-15-hotfix
chore: release v15
2024-05-15 10:41:19 +05:30
Nabin Hait
2569b244e2 Merge pull request #41473 from frappe/mergify/bp/version-15-hotfix/pr-41235
Depreciation based on daily prorata (backport #41235)
2024-05-15 09:10:56 +05:30
Nabin Hait
09aefa96ad Merge pull request #41474 from nabinhait/linter-fix5
style: linter issue
2024-05-15 09:10:08 +05:30
Nabin Hait
fd3efd53be style: linter issue 2024-05-15 09:09:16 +05:30
Nabin Hait
6d43d840db style: new line before function
(cherry picked from commit 7d86881579)
2024-05-15 03:11:37 +00:00
Khushi Rawat
955ce9b670 fix: removed same named function
(cherry picked from commit d22df324ec)
2024-05-15 03:11:37 +00:00
Khushi Rawat
b57919e2ce refactor: code optimization
(cherry picked from commit 7b264e5e11)
2024-05-15 03:11:37 +00:00
Khushi Rawat
72f3fb2a2c fix: pro rata based depreciation with opening accumulated depreciation
(cherry picked from commit c0c8c1bb02)
2024-05-15 03:11:37 +00:00
Khushi Rawat
12f383f252 test: made minor change in existing test
(cherry picked from commit d40b55468c)
2024-05-15 03:11:37 +00:00
Khushi Rawat
01b25b5821 fix: incorrect total days calculation
(cherry picked from commit fccd37d32d)
2024-05-15 03:11:36 +00:00
Khushi Rawat
c335c2c85a fix: daily prorata based depreciation bug in wdv method
(cherry picked from commit b8a98a273b)
2024-05-15 03:11:36 +00:00
Khushi Rawat
1016ec2a14 fix(wip): daily depreciation bug
(cherry picked from commit f337392f3e)
2024-05-15 03:11:36 +00:00
Nabin Hait
8da2841fdd Merge pull request #41471 from frappe/mergify/bp/version-15-hotfix/pr-41467
Asset cancelation issue (backport #41467)
2024-05-15 08:19:23 +05:30
Khushi Rawat
ccda17ede2 style: code optimization
(cherry picked from commit e843683ad1)
2024-05-14 17:12:46 +00:00
Khushi Rawat
e18be9b21e fix: Asset cancelation issue
(cherry picked from commit fa2b6c4490)
2024-05-14 17:12:45 +00:00
Deepesh Garg
53ea9ff102 Merge pull request #41259 from frappe/mergify/bp/version-15-hotfix/pr-41258
fix: PSOA ageing (#41258)
2024-05-14 20:08:45 +05:30
Nabin Hait
cb9df5e171 Merge pull request #41428 from frappe/mergify/bp/version-15-hotfix/pr-40468
fix: update description of Supplier Invoice Number (backport #40468)
2024-05-14 20:04:18 +05:30
Nabin Hait
041bcc96a9 Merge pull request #41449 from frappe/mergify/bp/version-15-hotfix/pr-41412
fix: Duplicate party name column in AR/AP report (backport #41412)
2024-05-14 20:03:22 +05:30
Nabin Hait
230cff583f Merge pull request #41461 from frappe/mergify/bp/version-15-hotfix/pr-41089
Refactor: Rename purchase receipt amount field to purchase amount (backport #41089)
2024-05-14 19:58:15 +05:30
Nabin Hait
b543093bde Merge pull request #41462 from frappe/mergify/bp/version-15-hotfix/pr-41420
Get prorata depreciation based on total days per year (backport #41420)
2024-05-14 19:45:44 +05:30
Nabin Hait
520e1e9c8f fix: resolved conflict 2024-05-14 19:37:34 +05:30
Nabin Hait
3a72f4bd30 fix: resolved conflict 2024-05-14 19:34:29 +05:30
Nabin Hait
8cca74d5ef fix: resolved conflict 2024-05-14 19:29:23 +05:30
Khushi Rawat
ce2c6c3165 fix(minor): removed extra parameter
(cherry picked from commit 98e7dfe97f)
2024-05-14 13:56:25 +00:00
Khushi Rawat
0b21026eef refactor: removed code duplicacies
(cherry picked from commit 6b24143f72)
2024-05-14 13:56:25 +00:00
Khushi Rawat
3eff9c9779 fix(wip): depreciation calculation after asset value adjustment
(cherry picked from commit d3200fb67f)
2024-05-14 13:56:24 +00:00
Khushi Rawat
0aca1e8e05 fix: removed unrelated code modification
(cherry picked from commit 360c3b36ed)
2024-05-14 12:47:53 +00:00
“Khushi
9b3f309598 refactor: renamed purchase receipt amount field to purchase amount
(cherry picked from commit 31841b4ab2)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.json
#	erpnext/assets/doctype/asset/asset.py
#	erpnext/patches.txt
2024-05-14 12:47:53 +00:00
mergify[bot]
5c3a0965bc fix: zero valuation rate for batched item (backport #41446) (#41447)
fix: zero valuation rate for batched item (#41446)

(cherry picked from commit e3a80ebdf3)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-14 14:22:51 +05:30
Deepesh Garg
55edbec6fa fix: Duplicate party name column in AR/AP report
(cherry picked from commit 7501fe8ebd)
2024-05-14 02:53:48 +00:00
mergify[bot]
d988c7bd06 fix: Unknown column 'tabBatch.batch_no' in 'where clause' (backport #41418) (#41444)
fix: Unknown column 'tabBatch.batch_no' in 'where clause' (#41418)

fix: unknown column 'tabBatch.batch_no' in 'where clause'
(cherry picked from commit 6bd13d7452)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-14 05:21:17 +05:30
mergify[bot]
b8db903320 fix: 'Bill for Rejected Quantity in Purchase Invoice' feature not working (backport #41437) (#41445)
fix: 'Bill for Rejected Quantity in Purchase Invoice' feature not working (#41437)

(cherry picked from commit 3309359822)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-14 05:20:02 +05:30
Frappe PR Bot
1b2c2515bc chore(release): Bumped to Version 15.23.1
## [15.23.1](https://github.com/frappe/erpnext/compare/v15.23.0...v15.23.1) (2024-05-13)

### Bug Fixes

* valuation issue for batch (backport [#41425](https://github.com/frappe/erpnext/issues/41425)) (backport [#41430](https://github.com/frappe/erpnext/issues/41430)) ([#41434](https://github.com/frappe/erpnext/issues/41434)) ([aa2b644](aa2b64476e))
2024-05-13 11:24:52 +00:00
mergify[bot]
aa2b64476e fix: valuation issue for batch (backport #41425) (backport #41430) (#41434)
fix: valuation issue for batch (backport #41425) (#41430)

fix: valuation issue for batch (#41425)

(cherry picked from commit 065163146c)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit f55a131dea)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-05-13 16:53:35 +05:30
mergify[bot]
15d2881bc8 fix: data getting override in delivery trip (backport #41431) (#41433)
fix: data getting override in delivery trip (#41431)

fix: data getting override
(cherry picked from commit 663fcb374d)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-13 16:31:49 +05:30
mergify[bot]
f55a131dea fix: valuation issue for batch (backport #41425) (#41430)
fix: valuation issue for batch (#41425)

(cherry picked from commit 065163146c)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-13 16:24:59 +05:30
Deepesh Garg
4036ef8764 chore: better description
(cherry picked from commit eccd5b4c5d)
2024-05-13 09:43:36 +00:00
Nihantra Patel
a7533ff7f6 fix: update description of Supplier Invoice Number
(cherry picked from commit d721de13aa)
2024-05-13 09:43:36 +00:00
mergify[bot]
b6f82a656f fix: consistent use of "Address & Contact" (backport #41386) (#41388)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: consistent use of "Address & Contact" (#41386)
2024-05-10 17:42:05 +02:00
mergify[bot]
4190ffc654 chore: typo in Stock Settings (backport #41396) (#41405)
chore: typo in Stock Settings (#41396)

(cherry picked from commit 628d7e6458)

Co-authored-by: rehanrehman389 <32939507+rehanrehman389@users.noreply.github.com>
2024-05-10 15:45:04 +05:30
ruthra kumar
6b5b3afbca Merge pull request #41395 from frappe/mergify/bp/version-15-hotfix/pr-41040
fix: address filter and quotation to for prospect (backport #41040)
2024-05-09 12:48:30 +05:30
ruthra kumar
f337af9e90 refactor: make use of doc.quotation_to
(cherry picked from commit 754c7f6d1c)
2024-05-09 06:46:01 +00:00
Nihantra Patel
37741b5b45 fix: address filter and quotation to for prospect
(cherry picked from commit 2896e3666c)
2024-05-09 06:46:01 +00:00
Nihantra Patel
394b1f499d fix: address filter and quotation to for prospect
(cherry picked from commit 24a68a79df)
2024-05-09 06:46:00 +00:00
Nihantra Patel
cdbb515379 fix: address filter and quotation to for prospect
(cherry picked from commit fe5b88522e)
2024-05-09 06:46:00 +00:00
Frappe PR Bot
5f376009d0 chore(release): Bumped to Version 15.23.0
# [15.23.0](https://github.com/frappe/erpnext/compare/v15.22.2...v15.23.0) (2024-05-09)

### Bug Fixes

* Add PO reference ([c417b0c](c417b0c15a))
* correct ordered qty on SO when removing PO item - v15 ([#41342](https://github.com/frappe/erpnext/issues/41342)) ([5c1043f](5c1043fe88))
* Cost center not getting saved in PSOA ([fbbc0af](fbbc0af7bc))
* Do not deduct TCS for opening invoices ([1999b7a](1999b7a6c5))
* filter validation for batch-wise balance history report (backport [#41356](https://github.com/frappe/erpnext/issues/41356)) ([#41361](https://github.com/frappe/erpnext/issues/41361)) ([913cea0](913cea0018))
* future subscripition updates ([b27a55e](b27a55e802))
* GL Entries against orders as an advance ([e49a401](e49a401c23))
* incorrect qty picked in the pick list (backport [#41378](https://github.com/frappe/erpnext/issues/41378)) ([#41381](https://github.com/frappe/erpnext/issues/41381)) ([a293ec0](a293ec0db3))
* incorrect query for Purchase Invoice rate in GP ([20daae4](20daae4de9))
* **Item:** allow UOM conversion for non-stock items (backport [#41267](https://github.com/frappe/erpnext/issues/41267)) ([#41346](https://github.com/frappe/erpnext/issues/41346)) ([cd3ca1e](cd3ca1ee25))
* Merge debit and credit in transaction currency while merging gle with similar head ([9a58823](9a58823867))
* missing Item Name on Save for Quotation created from Item (backport [#41233](https://github.com/frappe/erpnext/issues/41233)) ([#41304](https://github.com/frappe/erpnext/issues/41304)) ([6491577](6491577501))
* Patch to fix the incorrect debit and credit in transaction currency ([f7e165b](f7e165b5ff))
* Patch to remove cancelled asset capitalization from asset ([a755540](a755540708))
* pick list with multiple batch issue (backport [#41335](https://github.com/frappe/erpnext/issues/41335)) ([#41338](https://github.com/frappe/erpnext/issues/41338)) ([1b1dfa8](1b1dfa8893))
* pricing rule rounding ([b93828c](b93828cd33))
* Purchase Invoice gain loss gl entry for periodic inventory ([0513481](0513481ec8))
* resolved conflict ([5039f45](5039f45be4))
* resolved conflict ([81afdcf](81afdcf745))
* search for item price in stock UOM (backport [#41075](https://github.com/frappe/erpnext/issues/41075)) ([#41313](https://github.com/frappe/erpnext/issues/41313)) ([112f96b](112f96bae0))
* update project URLs (backport [#41331](https://github.com/frappe/erpnext/issues/41331)) ([#41332](https://github.com/frappe/erpnext/issues/41332)) ([bb619a6](bb619a64fe))

### Features

* **Item Price:** make UOM mandatory (backport [#40588](https://github.com/frappe/erpnext/issues/40588)) ([#41312](https://github.com/frappe/erpnext/issues/41312)) ([cab0e30](cab0e30ceb))

### Performance Improvements

* index on item code for the Pick List Item doctype (backport [#41357](https://github.com/frappe/erpnext/issues/41357)) ([#41364](https://github.com/frappe/erpnext/issues/41364)) ([bba738f](bba738f5f4))
2024-05-09 05:31:55 +00:00
ruthra kumar
2ac0009b87 Merge pull request #41355 from frappe/version-15-hotfix
chore: release v15
2024-05-09 11:00:41 +05:30
rohitwaghchaure
307e394da4 Merge branch 'version-15' into version-15-hotfix 2024-05-09 07:30:20 +05:30
mergify[bot]
a293ec0db3 fix: incorrect qty picked in the pick list (backport #41378) (#41381)
fix: incorrect qty picked in the pick list (#41378)

(cherry picked from commit 5ed1b6b8fb)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-08 23:15:21 +05:30
Nabin Hait
d4aae4f311 Merge pull request #41380 from frappe/mergify/bp/version-15-hotfix/pr-41236
fix: Merge debit and credit in transaction currency while merging gle with similar head (backport #41236)
2024-05-08 19:25:17 +05:30
Nabin Hait
4dd58aba78 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-41236 2024-05-08 19:02:23 +05:30
Nabin Hait
0b20a5d82e Merge pull request #41383 from frappe/mergify/bp/version-15-hotfix/pr-40121
fix: Patch to remove cancelled asset capitalization from asset (backport #40121)
2024-05-08 19:01:02 +05:30
Nabin Hait
5039f45be4 fix: resolved conflict 2024-05-08 18:23:30 +05:30
Nabin Hait
a755540708 fix: Patch to remove cancelled asset capitalization from asset
(cherry picked from commit 1951f71eeb)
2024-05-08 12:42:57 +00:00
Nabin Hait
81afdcf745 fix: resolved conflict 2024-05-08 17:57:43 +05:30
Nabin Hait
f7e165b5ff fix: Patch to fix the incorrect debit and credit in transaction currency
(cherry picked from commit e0d12ba4d0)

# Conflicts:
#	erpnext/patches.txt
2024-05-08 12:23:49 +00:00
Nabin Hait
9a58823867 fix: Merge debit and credit in transaction currency while merging gle with similar head
(cherry picked from commit e43697d359)
2024-05-08 12:23:48 +00:00
mergify[bot]
bba738f5f4 perf: index on item code for the Pick List Item doctype (backport #41357) (#41364)
* perf: index on item code for the Pick List Item doctype (#41357)

(cherry picked from commit 0887161f2a)

# Conflicts:
#	erpnext/stock/doctype/pick_list_item/pick_list_item.json

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-08 11:37:51 +05:30
mergify[bot]
913cea0018 fix: filter validation for batch-wise balance history report (backport #41356) (#41361)
fix: filter validation for batch-wise balance history report (#41356)

fix: filter validation for batchwise balance history report
(cherry picked from commit 544fc60093)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-08 00:15:10 +05:30
ruthra kumar
6602ce9960 Merge pull request #41349 from frappe/mergify/bp/version-15-hotfix/pr-41288
fix: pricing rule rounding (backport #41288)
2024-05-07 10:18:26 +05:30
ruthra kumar
f2864ebdf6 refactor(test): test floor based rounding
(cherry picked from commit c41a037174)
2024-05-07 04:32:13 +00:00
ruthra kumar
b93828cd33 fix: pricing rule rounding
Consider a pricing rule of 20:1 with recursion enabled, free items
should follow the below progression

|   Qty | Free item qty |
|-------+---------------|
|  0-19 |             0 |
| 20-39 |             1 |
| 40-59 |             2 |

(cherry picked from commit 9bf37426c1)
2024-05-07 04:32:12 +00:00
mergify[bot]
cd3ca1ee25 fix(Item): allow UOM conversion for non-stock items (backport #41267) (#41346)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Item): allow UOM conversion for non-stock items (#41267)
2024-05-06 20:51:11 +02:00
mergify[bot]
6491577501 fix: missing Item Name on Save for Quotation created from Item (backport #41233) (#41304)
fix: missing Item Name on Save for Quotation created from Item (#41233)

* fix: missing Item Name on Save for Quotation created from Item

* fix: missing Item Name on Save for Quotation created from Item

(cherry picked from commit c8e92cb1b2)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2024-05-06 21:45:27 +05:30
Nihantra C. Patel
5c1043fe88 fix: correct ordered qty on SO when removing PO item - v15 (#41342) 2024-05-06 21:36:27 +05:30
Frappe PR Bot
78637823c8 chore(release): Bumped to Version 15.22.2
## [15.22.2](https://github.com/frappe/erpnext/compare/v15.22.1...v15.22.2) (2024-05-06)

### Bug Fixes

* pick list with multiple batch issue (backport [#41335](https://github.com/frappe/erpnext/issues/41335)) (backport [#41338](https://github.com/frappe/erpnext/issues/41338)) ([#41340](https://github.com/frappe/erpnext/issues/41340)) ([ee12ec3](ee12ec36cc))
2024-05-06 08:41:26 +00:00
mergify[bot]
ee12ec36cc fix: pick list with multiple batch issue (backport #41335) (backport #41338) (#41340)
fix: pick list with multiple batch issue (backport #41335) (#41338)

fix: pick list with multiple batch issue (#41335)

fix: pick list with batchb issue
(cherry picked from commit ebfbe94aaf)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit 1b1dfa8893)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-05-06 14:10:08 +05:30
mergify[bot]
1b1dfa8893 fix: pick list with multiple batch issue (backport #41335) (#41338)
fix: pick list with multiple batch issue (#41335)

fix: pick list with batchb issue
(cherry picked from commit ebfbe94aaf)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-06 13:36:52 +05:30
ruthra kumar
a482ee1fe4 Merge pull request #41337 from frappe/mergify/bp/version-15-hotfix/pr-41334
fix: incorrect query for Purchase Invoice rate in GP (backport #41334)
2024-05-06 13:13:37 +05:30
ruthra kumar
20daae4de9 fix: incorrect query for Purchase Invoice rate in GP
(cherry picked from commit bd8382c592)
2024-05-06 07:24:48 +00:00
mergify[bot]
bb619a64fe fix: update project URLs (backport #41331) (#41332)
fix: update project URLs (#41331)

(cherry picked from commit 6142d07f1a)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2024-05-06 11:42:25 +05:30
Deepesh Garg
67954a93b2 Merge pull request #41325 from frappe/mergify/bp/version-15-hotfix/pr-41318
fix: Cost center not getting saved in PSOA (#41318)
2024-05-06 11:29:01 +05:30
Deepesh Garg
0b44cebe00 chore: resolve conflicts 2024-05-06 11:12:11 +05:30
Deepesh Garg
21ab8fe89c Merge pull request #41323 from frappe/mergify/bp/version-15-hotfix/pr-41314
fix: Do not deduct TCS for opening invoices (#41314)
2024-05-05 23:19:44 +05:30
Deepesh Garg
fbbc0af7bc fix: Cost center not getting saved in PSOA
(cherry picked from commit 58f7039630)

# Conflicts:
#	erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json
2024-05-04 08:27:08 +00:00
Deepesh Garg
1999b7a6c5 fix: Do not deduct TCS for opening invoices
(cherry picked from commit eb9f579b8f)
2024-05-04 08:26:06 +00:00
Frappe PR Bot
fa5462d674 chore(release): Bumped to Version 15.22.1
## [15.22.1](https://github.com/frappe/erpnext/compare/v15.22.0...v15.22.1) (2024-05-03)

### Bug Fixes

* Add PO reference ([44330a6](44330a618b))
* GL Entries against orders as an advance ([4d312cb](4d312cbc65))
2024-05-03 17:32:36 +00:00
Deepesh Garg
b3736d6c6c Merge pull request #41321 from frappe/mergify/bp/version-15-hotfix/pr-41279
fix: GL Entries against orders as an advance (#41279)
2024-05-03 23:01:38 +05:30
Deepesh Garg
031ecb5cb0 Merge pull request #41320 from frappe/mergify/bp/version-15/pr-41279
fix: GL Entries against orders as an advance (#41279)
2024-05-03 23:01:18 +05:30
Deepesh Garg
2faeefb063 test: Add bank account
(cherry picked from commit eac7be2d0f)
2024-05-03 14:12:29 +00:00
Deepesh Garg
d384860c34 test: Update failing tests
(cherry picked from commit 42ef95759d)
2024-05-03 14:12:29 +00:00
Deepesh Garg
c417b0c15a fix: Add PO reference
(cherry picked from commit eb31017058)
2024-05-03 14:12:29 +00:00
Deepesh Garg
e49a401c23 fix: GL Entries against orders as an advance
(cherry picked from commit 8289f3c724)
2024-05-03 14:12:29 +00:00
Deepesh Garg
27f51f1234 test: Add bank account
(cherry picked from commit eac7be2d0f)
2024-05-03 14:12:15 +00:00
Deepesh Garg
ad0f563816 test: Update failing tests
(cherry picked from commit 42ef95759d)
2024-05-03 14:12:14 +00:00
Deepesh Garg
44330a618b fix: Add PO reference
(cherry picked from commit eb31017058)
2024-05-03 14:12:14 +00:00
Deepesh Garg
4d312cbc65 fix: GL Entries against orders as an advance
(cherry picked from commit 8289f3c724)
2024-05-03 14:12:14 +00:00
Deepesh Garg
6bac561789 Merge pull request #41315 from frappe/mergify/bp/version-15-hotfix/pr-41311
fix: future subscription updates (#41311)
2024-05-03 15:57:16 +05:30
Deepesh Garg
31254009cc test: Add posting dates
(cherry picked from commit 7fa22069d8)
2024-05-03 06:35:27 +00:00
Deepesh Garg
b27a55e802 fix: future subscripition updates
(cherry picked from commit 8e30debc10)
2024-05-03 06:35:27 +00:00
mergify[bot]
cab0e30ceb feat(Item Price): make UOM mandatory (backport #40588) (#41312)
* feat(Item Price): make UOM mandatory (#40588)

(cherry picked from commit a61148c464)

# Conflicts:
#	erpnext/stock/doctype/item_price/item_price.json

* chore: `conflicts`

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-05-03 11:17:12 +05:30
mergify[bot]
112f96bae0 fix: search for item price in stock UOM (backport #41075) (#41313)
fix: search for item price in stock UOM (#41075)

(cherry picked from commit e4db0562ac)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2024-05-03 10:28:08 +05:30
Deepesh Garg
66b85c96f4 Merge pull request #41310 from frappe/mergify/bp/version-15-hotfix/pr-41260
fix: Purchase Invoice gain loss gl entry for periodic inventory (#41260)
2024-05-03 09:28:18 +05:30
Deepesh Garg
0513481ec8 fix: Purchase Invoice gain loss gl entry for periodic inventory
(cherry picked from commit 2402447568)
2024-05-03 03:25:54 +00:00
Frappe PR Bot
f62762b4d1 chore(release): Bumped to Version 15.22.0
# [15.22.0](https://github.com/frappe/erpnext/compare/v15.21.2...v15.22.0) (2024-05-02)

### Bug Fixes

* added brand column in Warehouse wise Item Balance Age and Value … (backport [#41280](https://github.com/frappe/erpnext/issues/41280)) ([#41282](https://github.com/frappe/erpnext/issues/41282)) ([4bbf0a4](4bbf0a46b5))
* advance account validation in company master ([dd67b0e](dd67b0ee61))
* args when get the delivery note in delivery trip ([61d6838](61d6838b2c))
* args when get the delivery note in delivery trip ([e9acacd](e9acacdd5d))
* basic rate for SABB ([7b79873](7b7987363f))
* compute tree-view parent field name ([#41234](https://github.com/frappe/erpnext/issues/41234)) ([c3077ee](c3077ee067))
* display term name for single term invoices ([a7d1a88](a7d1a88519))
* duplicate column in the stock ledger report ([a62298a](a62298a635))
* enable advance in separate acc only for customer and Supplier ([c3073d6](c3073d6e74))
* expense causing p&l test case to fail ([acfee42](acfee42735))
* handle and receivable accounts based on response type ([f65b28a](f65b28a189))
* handle stock balance unbuffered_cursor error (backport [#41186](https://github.com/frappe/erpnext/issues/41186)) ([#41188](https://github.com/frappe/erpnext/issues/41188)) ([b34582e](b34582e6ba))
* Ignore user perm in Bank Reco Tool for company ([737c480](737c4809e3))
* incorrectly applying TDS when Advance is in previous FY ([08f888a](08f888a326))
* Invoice with no GLEs in deferred report ([44c3ad6](44c3ad6810))
* missing def expense if no exp in first month ([0a65a37](0a65a37eed))
* mode of payment has precedance ([c6145a1](c6145a1101))
* multiple pricing rules with discount amount and discount percentage not working (backport [#41211](https://github.com/frappe/erpnext/issues/41211)) (backport [#41241](https://github.com/frappe/erpnext/issues/41241)) ([#41275](https://github.com/frappe/erpnext/issues/41275)) ([de77894](de77894e59))
* paid amount in bank reconciliation tool ([759c7f5](759c7f5490))
* party and party type label on accounting preview ([ee9822f](ee9822fd42))
* permission issue when user permission restricts on company ([8d70a0e](8d70a0e0bc))
* rendering the email template when user HTML ([3068dad](3068dad410))
* test case for zero deferred expense ([23c3c3c](23c3c3cc0c))
* validation to prevent foreign currency advance accounts in PE ([fb4a75c](fb4a75c5aa))
* validation to prevent overallocation ([ea596eb](ea596eb4a2))
* warehouse type filter for stock reports ([48351d6](48351d6da8))

### Features

* allow to do reposting for all transactions (audit) ([4555f8a](4555f8a9a6))
2024-05-02 04:23:27 +00:00
ruthra kumar
27e0ed30fe Merge pull request #41263 from frappe/version-15-hotfix
chore: release v15
2024-05-02 09:52:07 +05:30
mergify[bot]
4bbf0a46b5 fix: added brand column in Warehouse wise Item Balance Age and Value … (backport #41280) (#41282)
fix: added brand column in Warehouse wise Item Balance Age and Value … (#41280)

fix: added brand coulmn in Warehouse wise Item Balance Age and Value report
(cherry picked from commit 1cbc200770)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-01 16:03:37 +05:30
mergify[bot]
de77894e59 fix: multiple pricing rules with discount amount and discount percentage not working (backport #41211) (backport #41241) (#41275)
fix: multiple pricing rules with discount amount and discount percentage not working (backport #41211) (#41241)

fix: multiple pricing rules with discount amount and discount percentage not working (#41211)

(cherry picked from commit 54313b5db9)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit da3010a41f)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-05-01 15:46:52 +05:30
ruthra kumar
5a9615a1f2 Merge pull request #41270 from frappe/mergify/bp/version-15-hotfix/pr-41268
fix: validation to prevent overallocation (backport #41268)
2024-04-30 18:40:22 +05:30
Fritz
c3077ee067 fix: compute tree-view parent field name (#41234)
fix: Fixing treeView lookup incompatibility, see : See https://github.com/frappe/frappe/pull/26199
2024-04-30 17:57:58 +05:30
ruthra kumar
ea596eb4a2 fix: validation to prevent overallocation
(cherry picked from commit bf755fab55)
2024-04-30 12:08:48 +00:00
Deepesh Garg
6590d78298 fix: PSOA ageing
(cherry picked from commit fed2d11905)
2024-04-30 07:41:09 +00:00
Deepesh Garg
378605cd00 Merge pull request #41253 from frappe/mergify/bp/version-15-hotfix/pr-41171
fix: Invoice with no GLEs in deferred report (#41171)
2024-04-30 13:10:53 +05:30
ruthra kumar
56e455c745 Merge pull request #41255 from frappe/mergify/bp/version-15-hotfix/pr-41252
fix: permission issue when user permission restricts on company (backport #41252)
2024-04-30 12:40:02 +05:30
ruthra kumar
8d70a0e0bc fix: permission issue when user permission restricts on company 2024-04-30 12:20:10 +05:30
Deepesh Garg
b67e9e4aa8 chore: remove debug flag
(cherry picked from commit ecf07bd128)
2024-04-30 05:46:18 +00:00
Deepesh Garg
44c3ad6810 fix: Invoice with no GLEs in deferred report
(cherry picked from commit 1c613ada6f)
2024-04-30 05:46:17 +00:00
Deepesh Garg
69c05e5843 Merge pull request #41251 from frappe/mergify/bp/version-15-hotfix/pr-41173
fix: paid amount in bank reconciliation tool (#41173)
2024-04-30 11:15:39 +05:30
Deepesh Garg
6ab91a5c88 Merge pull request #41250 from frappe/mergify/bp/version-15-hotfix/pr-41219
fix: Ignore user perm in Bank Reco Tool for company (#41219)
2024-04-30 11:15:19 +05:30
Deepesh Garg
759c7f5490 fix: paid amount in bank reconciliation tool
(cherry picked from commit a48966f08c)
2024-04-30 04:55:51 +00:00
Deepesh Garg
03b4a85825 chore: resolve conflicts 2024-04-30 10:25:51 +05:30
Deepesh Garg
737c4809e3 fix: Ignore user perm in Bank Reco Tool for company
(cherry picked from commit 9f346e7ba0)

# Conflicts:
#	erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
2024-04-30 04:53:24 +00:00
Deepesh Garg
3657badb31 Merge pull request #41239 from frappe/mergify/bp/version-15-hotfix/pr-41237
fix: party and party type label on accounting preview (#41237)
2024-04-30 10:23:17 +05:30
ruthra kumar
ef69a46e6a Merge pull request #41249 from frappe/mergify/bp/version-15-hotfix/pr-41240
fix: display term name for single term invoices in AR/AP (backport #41240)
2024-04-30 07:42:41 +05:30
ruthra kumar
78106836be Merge pull request #41247 from frappe/mergify/bp/version-15-hotfix/pr-41194
fix: TDS incorrectly applied when Advance is in previous FY (backport #41194)
2024-04-30 07:18:04 +05:30
ruthra kumar
a7d1a88519 fix: display term name for single term invoices
(cherry picked from commit 5fa4cfee04)
2024-04-30 01:40:17 +00:00
ruthra kumar
15ac3d8b0b test: TDS deduction across fiscal year
(cherry picked from commit 2f9a144023)
2024-04-30 01:27:55 +00:00
ruthra kumar
08f888a326 fix: incorrectly applying TDS when Advance is in previous FY
(cherry picked from commit b195f519e2)
2024-04-30 01:27:54 +00:00
Nikhil Kothari
ee9822fd42 fix: party and party type label on accounting preview
(cherry picked from commit f7f3b22786)
2024-04-29 13:24:48 +00:00
Deepesh Garg
17eb2f02f4 Merge pull request #41226 from frappe/mergify/bp/version-15-hotfix/pr-40865
fix: missing def expense if no exp in first month (#40865)
2024-04-29 17:25:15 +05:30
Ankush Menat
067419b7cd chore: delete invalid translations (#41227) 2024-04-29 10:31:22 +05:30
Dany Robert
acfee42735 fix: expense causing p&l test case to fail
(cherry picked from commit 01888c98bc)
2024-04-29 04:55:32 +00:00
Dany Robert
62966ac550 chore: semgrep
(cherry picked from commit 581af4eced)
2024-04-29 04:55:32 +00:00
Dany Robert
23c3c3cc0c fix: test case for zero deferred expense
(cherry picked from commit 7ef4dbcaf6)
2024-04-29 04:55:32 +00:00
Dany Robert
0a65a37eed fix: missing def expense if no exp in first month
(cherry picked from commit 5c9ce575f6)
2024-04-29 04:55:31 +00:00
ruthra kumar
0df1005cb2 Merge pull request #41209 from frappe/mergify/bp/version-15-hotfix/pr-41208
fix: set receivable account based on response type (backport #41208)
2024-04-26 20:30:55 +05:30
rohitwaghchaure
807b2d5c0a Merge pull request #41206 from frappe/mergify/bp/version-15-hotfix/pr-41165
feat: allow to do reposting for all stock transactions (audit) (backport #41165)
2024-04-26 14:25:40 +05:30
ruthra kumar
f65b28a189 fix: handle and receivable accounts based on response type
(cherry picked from commit 066859cca0)
2024-04-26 08:43:23 +00:00
rohitwaghchaure
a292e52b34 chore: fix conflicts 2024-04-26 13:47:03 +05:30
Rohit Waghchaure
4555f8a9a6 feat: allow to do reposting for all transactions (audit)
(cherry picked from commit aefbe21b46)

# Conflicts:
#	erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json
2024-04-26 08:15:11 +00:00
rohitwaghchaure
0fea5564b6 Merge pull request #41201 from frappe/mergify/bp/version-15-hotfix/pr-41185
fix: args when get the delivery note in delivery trip (backport #41185)
2024-04-26 13:34:44 +05:30
rohitwaghchaure
476b7f43a2 Merge pull request #41202 from frappe/mergify/bp/version-15-hotfix/pr-41182
fix: rendering the email template when user HTML (backport #41182)
2024-04-26 13:34:27 +05:30
rohitwaghchaure
d5d846f481 Merge pull request #41203 from frappe/mergify/bp/version-15-hotfix/pr-41167
fix: warehouse type filter for stock reports (backport #41167)
2024-04-26 13:34:00 +05:30
rohitwaghchaure
6fd356b654 chore: fix conflicts 2024-04-26 13:13:55 +05:30
Rohit Waghchaure
48351d6da8 fix: warehouse type filter for stock reports
(cherry picked from commit 4250ac821f)
2024-04-26 07:41:48 +00:00
Nihantra C. Patel
3068dad410 fix: rendering the email template when user HTML
(cherry picked from commit 7cb66f7fd3)
2024-04-26 07:41:37 +00:00
Nihantra Patel
61d6838b2c fix: args when get the delivery note in delivery trip
(cherry picked from commit ca577f7aaa)
2024-04-26 07:40:36 +00:00
Nihantra Patel
e9acacdd5d fix: args when get the delivery note in delivery trip
(cherry picked from commit 2f359e201d)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.py
2024-04-26 07:40:36 +00:00
rohitwaghchaure
3bb26fde2b Merge pull request #41199 from frappe/mergify/bp/version-15-hotfix/pr-41195
fix: basic rate for SABB (backport #41195)
2024-04-26 12:54:46 +05:30
Rohit Waghchaure
7b7987363f fix: basic rate for SABB
(cherry picked from commit 7fa94843aa)
2024-04-26 07:21:46 +00:00
rohitwaghchaure
3193ef0a89 Merge pull request #41197 from frappe/mergify/bp/version-15-hotfix/pr-41192
fix: duplicate column in the stock ledger report (backport #41192)
2024-04-26 12:49:25 +05:30
Rohit Waghchaure
a62298a635 fix: duplicate column in the stock ledger report
(cherry picked from commit be7fd6bfb4)
2024-04-26 06:53:14 +00:00
ruthra kumar
e6c7832485 Merge pull request #41189 from frappe/mergify/bp/version-15-hotfix/pr-41183
fix: restrict Advance in separate party to customer and supplier (backport #41183)
2024-04-25 20:35:50 +05:30
ruthra kumar
c3073d6e74 fix: enable advance in separate acc only for customer and Supplier
(cherry picked from commit 3c1af2acf0)
2024-04-25 14:30:58 +00:00
mergify[bot]
b34582e6ba fix: handle stock balance unbuffered_cursor error (backport #41186) (#41188)
fix: handle stock balance unbuffered_cursor error (#41186)

(cherry picked from commit 341fb6d8f3)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2024-04-25 17:26:26 +05:30
ruthra kumar
5136ffd10d Merge pull request #41139 from frappe/mergify/bp/version-15-hotfix/pr-41086
fix: prevent foreign currency  accounts as advance accounts. (backport #41086)
2024-04-25 09:37:49 +05:30
ruthra kumar
5988941032 Merge pull request #41177 from frappe/mergify/bp/version-15-hotfix/pr-41142
fix: mode of payment has precedance in Payment Entry (backport #41142)
2024-04-25 08:55:20 +05:30
ruthra kumar
c6145a1101 fix: mode of payment has precedance
Mode of Payment is given precedence over company/party bank account

(cherry picked from commit 4aef969879)
2024-04-25 03:13:13 +00:00
ruthra kumar
dd67b0ee61 fix: advance account validation in company master
(cherry picked from commit 1ad065fc54)
2024-04-23 01:45:45 +00:00
ruthra kumar
fb4a75c5aa fix: validation to prevent foreign currency advance accounts in PE
(cherry picked from commit e3fc5990ee)
2024-04-23 01:45:45 +00:00
87 changed files with 1578 additions and 378 deletions

View File

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

View File

@@ -360,45 +360,45 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
)
if not amount:
return
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:

View File

@@ -105,7 +105,7 @@
},
{
"default": "0",
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
"description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year",
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness"
@@ -461,7 +461,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-30 14:04:26.553554",
"modified": "2024-03-15 12:11:36.085158",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -490,4 +490,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -26,6 +26,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Company",
"options": "Company"
},
@@ -118,7 +119,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-03-07 11:02:24.535714",
"modified": "2024-04-28 14:40:50.910884",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",
@@ -139,4 +140,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -719,7 +719,7 @@ def get_pe_matching_query(
(ref_rank + amount_rank + party_rank + 1).as_("rank"),
ConstantColumn("Payment Entry").as_("doctype"),
pe.name,
pe.paid_amount,
pe.paid_amount_after_tax.as_("paid_amount"),
pe.reference_no,
pe.reference_date,
pe.party,

View File

@@ -454,7 +454,7 @@ class JournalEntry(AccountsController):
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
and d.debit
):
asset = frappe.get_doc("Asset", d.reference_name)

View File

@@ -76,6 +76,7 @@ class PaymentEntry(AccountsController):
self.setup_party_account_field()
self.set_missing_values()
self.set_liability_account()
self.validate_advance_account_currency()
self.set_missing_ref_details(force=True)
self.validate_payment_type()
self.validate_party_details()
@@ -158,6 +159,22 @@ class PaymentEntry(AccountsController):
alert=True,
)
def validate_advance_account_currency(self):
if self.book_advance_payments_in_separate_party_account is True:
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
if self.payment_type == "Receive" and self.paid_from_account_currency != company_currency:
frappe.throw(
_("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format(
frappe.bold(self.paid_from), frappe.bold(self.paid_from_account_currency)
)
)
if self.payment_type == "Pay" and self.paid_to_account_currency != company_currency:
frappe.throw(
_("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format(
frappe.bold(self.paid_to), frappe.bold(self.paid_to_account_currency)
)
)
def on_cancel(self):
self.ignore_linked_doctypes = (
"GL Entry",
@@ -1114,88 +1131,71 @@ class PaymentEntry(AccountsController):
)
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
if self.book_advance_payments_in_separate_party_account:
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
if self.payment_type == "Receive":
amount = self.base_paid_amount
else:
amount = self.base_received_amount
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
exchange_rate = self.get_exchange_rate()
amount_in_account_currency = amount * exchange_rate
gle.update(
{
dr_or_cr: amount,
dr_or_cr + "_in_account_currency": amount_in_account_currency,
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
"cost_center": self.cost_center,
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)
gl_entries.append(gle)
else:
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(
d.reference_doctype, d.reference_name, "cost_center"
)
gle = party_gl_dict.copy()
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(
d
)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
if self.book_advance_payments_in_separate_party_account:
gle.update(
{
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
}
)
gl_entries.append(gle)
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
gl_entries.append(gle)
gl_entries.append(gle)
def make_advance_gl_entries(
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
@@ -1210,7 +1210,7 @@ class PaymentEntry(AccountsController):
def add_advance_gl_entries(self, gl_entries: list, entry: object | dict | None):
"""
If 'entry' is passed, GL enties only for that reference is added.
If 'entry' is passed, GL entries only for that reference is added.
"""
if self.book_advance_payments_in_separate_party_account:
references = [x for x in self.get("references")]
@@ -1222,8 +1222,6 @@ class PaymentEntry(AccountsController):
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Sales Order",
"Purchase Order",
"Payment Entry",
):
self.add_advance_gl_for_reference(gl_entries, ref)

View File

@@ -1440,6 +1440,68 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries()
self.check_pl_entries()
def test_advance_as_liability_against_order(self):
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as _make_purchase_invoice,
)
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
company = "_Test Company"
advance_account = create_account(
parent_account="Current Liabilities - _TC",
account_name="Advances Paid",
company=company,
account_type="Liability",
)
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": advance_account,
},
)
po = create_purchase_order(supplier="_Test Supplier")
pe = get_payment_entry("Purchase Order", po.name, bank_account="Cash - _TC")
pe.save().submit()
pre_reconciliation_gle = [
{"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0},
{"account": advance_account, "debit": 5000.0, "credit": 0.0},
]
self.voucher_no = pe.name
self.expected_gle = pre_reconciliation_gle
self.check_gl_entries()
# Make Purchase Invoice against the order
pi = _make_purchase_invoice(po.name)
pi.append(
"advances",
{
"reference_type": pe.doctype,
"reference_name": pe.name,
"reference_row": pe.references[0].name,
"advance_amount": 5000,
"allocated_amount": 5000,
},
)
pi.save().submit()
# # assert General and Payment Ledger entries post partial reconciliation
self.expected_gle = [
{"account": pi.credit_to, "debit": 5000.0, "credit": 0.0},
{"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0},
{"account": advance_account, "debit": 5000.0, "credit": 0.0},
{"account": advance_account, "debit": 0.0, "credit": 5000.0},
]
self.voucher_no = pe.name
self.check_gl_entries()
def check_pl_entries(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
pl_entries = (

View File

@@ -176,8 +176,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
},
callback: (r) => {
if (!r.exc && r.message) {
this.frm.set_value("receivable_payable_account", r.message[0]);
this.frm.set_value("default_advance_account", r.message[1]);
if (typeof r.message === "string") {
this.frm.set_value("receivable_payable_account", r.message);
} else if (Array.isArray(r.message)) {
this.frm.set_value("receivable_payable_account", r.message[0]);
this.frm.set_value("default_advance_account", r.message[1]);
}
}
this.frm.refresh();
},

View File

@@ -573,6 +573,22 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
# Apply discount on discounted rate
item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
elif args.price_list_rate:
value = pricing_rule.get(field, 0)
calculate_discount_percentage = False
if field == "discount_percentage":
field = "discount_amount"
value = args.price_list_rate * (value / 100)
calculate_discount_percentage = True
if field not in item_details:
item_details.setdefault(field, 0)
item_details[field] += value if pricing_rule else args.get(field, 0)
if calculate_discount_percentage and args.price_list_rate and item_details.discount_amount:
item_details.discount_percentage = flt(
(flt(item_details.discount_amount) / flt(args.price_list_rate)) * 100
)
else:
if field not in item_details:
item_details.setdefault(field, 0)

View File

@@ -1102,7 +1102,60 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4)
self.assertEqual(so.items[1].qty, 3)
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 1",
"name": "_Test Pricing Rule 1",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"apply_multiple_pricing_rules": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 2",
"name": "_Test Pricing Rule 2",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Amount",
"discount_amount": 100,
"apply_multiple_pricing_rules": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
self.assertEqual(so.items[0].discount_amount, 200)
self.assertEqual(so.items[0].rate, 800)
frappe.delete_doc_if_exists("Sales Order", so.name)
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_dependencies = ["Campaign"]

View File

@@ -6,6 +6,7 @@
import copy
import json
import math
import frappe
from frappe import _, bold
@@ -653,7 +654,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = round(qty)
qty = math.floor(qty)
free_item_data_args = {
"item_code": free_item,

View File

@@ -158,7 +158,7 @@ def set_ageing(doc, entry):
ageing_filters = frappe._dict(
{
"company": doc.company,
"report_date": doc.to_date,
"report_date": doc.posting_date,
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,

View File

@@ -340,10 +340,11 @@
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">30 Days</th>
<th style="width: 25%">60 Days</th>
<th style="width: 25%">90 Days</th>
<th style="width: 25%">120 Days</th>
<th style="width: 25%">0 - 30 Days</th>
<th style="width: 25%">30 - 60 Days</th>
<th style="width: 25%">60 - 90 Days</th>
<th style="width: 25%">90 - 120 Days</th>
<th style="width: 20%">Above 120 Days</th>
</tr>
</thead>
<tbody>
@@ -352,6 +353,7 @@
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>

View File

@@ -11,13 +11,15 @@
{
"fieldname": "cost_center_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-08-03 16:56:45.744905",
"modified": "2024-05-03 17:16:51.666461",
"modified_by": "Administrator",
"module": "Accounts",
"name": "PSOA Cost Center",
@@ -27,4 +29,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -15,7 +15,7 @@ class PSOACostCenter(Document):
if TYPE_CHECKING:
from frappe.types import DF
cost_center_name: DF.Link | None
cost_center_name: DF.Link
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data

View File

@@ -1091,7 +1091,7 @@ class PurchaseInvoice(BuyingController):
)
# check if the exchange rate has changed
if item.get("purchase_receipt"):
if item.get("purchase_receipt") and self.auto_accounting_for_stock:
if (
exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
@@ -1182,7 +1182,7 @@ class PurchaseInvoice(BuyingController):
asset.name,
{
"gross_purchase_amount": purchase_amount,
"purchase_receipt_amount": purchase_amount,
"purchase_amount": purchase_amount,
},
)

View File

@@ -2040,7 +2040,7 @@
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
"label": "Address & Contact"
},
{
"fieldname": "payments_tab",
@@ -2187,7 +2187,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2024-04-11 11:30:26.272441",
"modified": "2024-05-08 18:02:28.549041",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -388,6 +388,9 @@ class SalesInvoice(SellingController):
validate_account_head(item.idx, item.income_account, self.company, "Income")
def set_tax_withholding(self):
if self.get("is_opening") == "Yes":
return
tax_withholding_details = get_party_tax_withholding_details(self)
if not tax_withholding_details:

View File

@@ -1766,6 +1766,49 @@ class TestSalesInvoice(FrappeTestCase):
self.assertTrue(gle)
def test_gle_in_transaction_currency(self):
# create multi currency sales invoice with 2 items with same income account
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
)
# add 2nd item with same income account
si.append(
"items",
{
"item_code": "_Test Item",
"qty": 1,
"rate": 80,
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
si.submit()
gl_entries = frappe.db.sql(
"""select transaction_currency, transaction_exchange_rate,
debit_in_transaction_currency, credit_in_transaction_currency
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC'
order by account asc""",
si.name,
as_dict=1,
)
expected_gle = {
"transaction_currency": "USD",
"transaction_exchange_rate": 50,
"debit_in_transaction_currency": 0,
"credit_in_transaction_currency": 180,
}
for gle in gl_entries:
for field in expected_gle:
self.assertEqual(expected_gle[field], gle[field])
def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",

View File

@@ -112,11 +112,7 @@ class Subscription(Document):
"""
_current_invoice_start = None
if (
self.is_new_subscription()
and self.trial_period_end
and getdate(self.trial_period_end) > getdate(self.start_date)
):
if self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
_current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start
@@ -143,7 +139,7 @@ class Subscription(Document):
else:
billing_cycle_info = self.get_billing_cycle_data()
if billing_cycle_info:
if self.is_new_subscription() and getdate(self.start_date) < getdate(date):
if getdate(self.start_date) < getdate(date):
_current_invoice_end = add_to_date(self.start_date, **billing_cycle_info)
# For cases where trial period is for an entire billing interval
@@ -234,14 +230,14 @@ class Subscription(Document):
self.cancelation_date = getdate(posting_date) if self.status == "Cancelled" else None
elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
self.status = "Past Due Date"
elif not self.has_outstanding_invoice() or self.is_new_subscription():
elif not self.has_outstanding_invoice():
self.status = "Active"
def is_trialling(self) -> bool:
"""
Returns `True` if the `Subscription` is in trial period.
"""
return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription()
return not self.period_has_passed(self.trial_period_end)
@staticmethod
def period_has_passed(
@@ -288,14 +284,6 @@ class Subscription(Document):
def invoice_document_type(self) -> str:
return "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
def is_new_subscription(self) -> bool:
"""
Returns `True` if `Subscription` has never generated an invoice
"""
return self.is_new() or not frappe.db.exists(
{"doctype": self.invoice_document_type, "subscription": self.name}
)
def validate(self) -> None:
self.validate_trial_period()
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
@@ -604,7 +592,7 @@ class Subscription(Document):
return False
if self.generate_invoice_at == "Beginning of the current subscription period" and (
getdate(posting_date) == getdate(self.current_invoice_start) or self.is_new_subscription()
getdate(posting_date) == getdate(self.current_invoice_start)
):
return True
elif self.generate_invoice_at == "Days before the current subscription period" and (

View File

@@ -445,11 +445,11 @@ class TestSubscription(FrappeTestCase):
# Process subscription and create first invoice
# Subscription status will be unpaid since due date has already passed
subscription.process()
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")
subscription.process()
subscription.process(posting_date="2018-04-01")
self.assertEqual(len(subscription.invoices), 1)
def test_multi_currency_subscription(self):
@@ -462,7 +462,7 @@ class TestSubscription(FrappeTestCase):
party=party,
)
subscription.process()
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")

View File

@@ -21,7 +21,7 @@
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
@@ -53,7 +53,7 @@
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
@@ -87,7 +87,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-04-13 18:44:25.055382",
"modified": "2024-04-30 10:26:48.21829",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Account",

View File

@@ -9,6 +9,8 @@ from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt, getdate
from erpnext.controllers.accounts_controller import validate_account_head
class TaxWithholdingCategory(Document):
# begin: auto-generated types
@@ -53,6 +55,7 @@ class TaxWithholdingCategory(Document):
if d.get("account") in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
validate_account_head(d.idx, d.get("account"), d.get("company"))
existing_accounts.append(d.get("account"))
def validate_thresholds(self):
@@ -282,6 +285,14 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if taxable_vouchers:
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
# If advance is outside the current tax withholding period (usually a fiscal year), `get_deducted_tax` won't fetch it.
# updating `tax_deducted` with correct advance tax value (from current and previous previous withholding periods), will allow the
# rest of the below logic to function properly
# ---FY 2023-------------||---------------------FY 2024-----------------------||--
# ---Advance-------------||---------Inv_1--------Inv_2------------------------||--
if tax_deducted_on_advances:
tax_deducted += get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details)
tax_amount = 0
if party_type == "Supplier":
@@ -418,7 +429,7 @@ def get_taxes_deducted_on_advances_allocated(inv, tax_details):
frappe.qb.from_(at)
.inner_join(pe)
.on(pe.name == at.parent)
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
.select(pe.posting_date, at.parent, at.name, at.tax_amount, at.allocated_amount)
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
.where(at.parent.isin(advances))
.where(at.account_head == tax_details.account_head)
@@ -443,6 +454,16 @@ def get_deducted_tax(taxable_vouchers, tax_details):
return sum(entries)
def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details):
"""
Only applies for Taxes deducted on Advance Payments
"""
advance_tax_from_across_fiscal_year = sum(
[adv.tax_amount for adv in tax_deducted_on_advances if adv.posting_date < tax_details.from_date]
)
return advance_tax_from_across_fiscal_year
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}

View File

@@ -1,18 +1,22 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import datetime
import unittest
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.utils import today
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
test_dependencies = ["Supplier Group", "Customer Group"]
class TestTaxWithholdingCategory(unittest.TestCase):
class TestTaxWithholdingCategory(FrappeTestCase):
@classmethod
def setUpClass(self):
# create relevant supplier, etc
@@ -21,7 +25,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
make_pan_no_field()
def tearDown(self):
cancel_invoices()
frappe.db.rollback()
def test_cumulative_threshold_tds(self):
frappe.db.set_value(
@@ -317,8 +321,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
d.cancel()
def test_tds_deduction_for_po_via_payment_entry(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
frappe.db.set_value(
"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
)
@@ -485,6 +487,133 @@ class TestTaxWithholdingCategory(unittest.TestCase):
pi2.cancel()
pi3.cancel()
def set_previous_fy_and_tax_category(self):
test_company = "_Test Company"
category = "Cumulative Threshold TDS"
def add_company_to_fy(fy, company):
if not [x.company for x in fy.companies if x.company == company]:
fy.append("companies", {"company": company})
fy.save()
# setup previous fiscal year
fiscal_year = get_fiscal_year(today(), company=test_company)
if prev_fiscal_year := get_fiscal_year(add_days(fiscal_year[1], -10)):
self.prev_fy = frappe.get_doc("Fiscal Year", prev_fiscal_year[0])
add_company_to_fy(self.prev_fy, test_company)
else:
# make previous fiscal year
start = datetime.date(fiscal_year[1].year - 1, fiscal_year[1].month, fiscal_year[1].day)
end = datetime.date(fiscal_year[2].year - 1, fiscal_year[2].month, fiscal_year[2].day)
self.prev_fy = frappe.get_doc(
{
"doctype": "Fiscal Year",
"year_start_date": start,
"year_end_date": end,
"companies": [{"company": test_company}],
}
)
self.prev_fy.save()
# setup tax withholding category for previous fiscal year
cat = frappe.get_doc("Tax Withholding Category", category)
cat.append(
"rates",
{
"from_date": self.prev_fy.year_start_date,
"to_date": self.prev_fy.year_end_date,
"tax_withholding_rate": 10,
"single_threshold": 0,
"cumulative_threshold": 30000,
},
)
cat.save()
def test_tds_across_fiscal_year(self):
"""
Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year
--||-----FY 2023-----||-----FY 2024-----||--
--||-----Advance-----||---Inv1---Inv2---||--
"""
self.set_previous_fy_and_tax_category()
supplier = "Test TDS Supplier"
# Cumulative threshold 30000 and tax rate 10%
category = "Cumulative Threshold TDS"
frappe.db.set_value(
"Supplier",
supplier,
{
"tax_withholding_category": category,
"pan": "ABCTY1234D",
},
)
po_and_advance_posting_date = add_days(self.prev_fy.year_end_date, -10)
po = create_purchase_order(supplier=supplier, qty=10, rate=10000)
po.transaction_date = po_and_advance_posting_date
po.taxes = []
po.apply_tds = False
po.tax_withholding_category = None
po.save().submit()
# Partial advance
payment = get_payment_entry(po.doctype, po.name)
payment.posting_date = po_and_advance_posting_date
payment.paid_amount = 60000
payment.apply_tax_withholding_amount = 1
payment.tax_withholding_category = category
payment.references = []
payment.taxes = []
payment.save().submit()
self.assertEqual(len(payment.taxes), 1)
self.assertEqual(payment.taxes[0].tax_amount, 6000)
# Multiple partial invoices
payment.reload()
pi1 = make_purchase_invoice(source_name=po.name)
pi1.apply_tds = True
pi1.tax_withholding_category = category
pi1.items[0].qty = 3
pi1.items[0].rate = 10000
advances = pi1.get_advance_entries()
pi1.append(
"advances",
{
"reference_type": advances[0].reference_type,
"reference_name": advances[0].reference_name,
"advance_amount": advances[0].amount,
"allocated_amount": 30000,
},
)
pi1.save().submit()
pi1.reload()
payment.reload()
self.assertEqual(pi1.taxes, [])
self.assertEqual(payment.taxes[0].tax_amount, 6000)
self.assertEqual(payment.taxes[0].allocated_amount, 3000)
pi2 = make_purchase_invoice(source_name=po.name)
pi2.apply_tds = True
pi2.tax_withholding_category = category
pi2.items[0].qty = 3
pi2.items[0].rate = 10000
advances = pi2.get_advance_entries()
pi2.append(
"advances",
{
"reference_type": advances[0].reference_type,
"reference_name": advances[0].reference_name,
"advance_amount": advances[0].amount,
"allocated_amount": 30000,
},
)
pi2.save().submit()
pi2.reload()
payment.reload()
self.assertEqual(pi2.taxes, [])
self.assertEqual(payment.taxes[0].tax_amount, 6000)
self.assertEqual(payment.taxes[0].allocated_amount, 6000)
def cancel_invoices():
purchase_invoices = frappe.get_all(

View File

@@ -238,10 +238,16 @@ def merge_similar_entries(gl_map, precision=None):
same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
entry.debit_in_account_currency
)
same_head.debit_in_transaction_currency = flt(same_head.debit_in_transaction_currency) + flt(
entry.debit_in_transaction_currency
)
same_head.credit = flt(same_head.credit) + flt(entry.credit)
same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
entry.credit_in_account_currency
)
same_head.credit_in_transaction_currency = flt(same_head.credit_in_transaction_currency) + flt(
entry.credit_in_transaction_currency
)
else:
merged_gl_map.append(entry)

View File

@@ -188,7 +188,9 @@ def set_address_details(
*,
ignore_permissions=False,
):
billing_address_field = "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
billing_address_field = (
"customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address"
)
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
if doctype:
party_details.update(

View File

@@ -501,8 +501,9 @@ class ReceivablePayableReport:
# Deduct that from paid amount pre allocation
row.paid -= flt(payment_terms_details[0].total_advance)
# If no or single payment terms, no need to split the row
if len(payment_terms_details) <= 1:
# If single payment terms, no need to split the row
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
self.append_payment_term(row, payment_terms_details[0], original_row)
return
for d in payment_terms_details:
@@ -1027,20 +1028,6 @@ class ReceivablePayableReport:
fieldtype="Link",
options="Contact",
)
if self.filters.party_type == "Customer":
self.add_column(
_("Customer Name"),
fieldname="customer_name",
fieldtype="Link",
options="Customer",
)
elif self.filters.party_type == "Supplier":
self.add_column(
_("Supplier Name"),
fieldname="supplier_name",
fieldtype="Link",
options="Supplier",
)
self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")

View File

@@ -58,9 +58,9 @@ class Deferred_Item:
For a given GL/Journal posting, get balance based on item type
"""
if self.type == "Deferred Sale Item":
return entry.debit - entry.credit
return flt(entry.debit) - flt(entry.credit)
elif self.type == "Deferred Purchase Item":
return -(entry.credit - entry.debit)
return -(flt(entry.credit) - flt(entry.debit))
return 0
def get_item_total(self):
@@ -147,7 +147,7 @@ class Deferred_Item:
actual = 0
for posting in self.gle_entries:
# if period.from_date <= posting.posting_date <= period.to_date:
if period.from_date <= posting.gle_posting_date <= period.to_date:
if period.from_date <= getdate(posting.gle_posting_date) <= period.to_date:
period_sum += self.get_amount(posting)
if posting.posted == "posted":
actual += self.get_amount(posting)
@@ -285,7 +285,7 @@ class Deferred_Revenue_and_Expense_Report:
qb.from_(inv_item)
.join(inv)
.on(inv.name == inv_item.parent)
.join(gle)
.left_join(gle)
.on((inv_item.name == gle.voucher_detail_no) & (deferred_account_field == gle.account))
.select(
inv.name.as_("doc"),

View File

@@ -279,3 +279,79 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
{"key": "aug_2021", "total": 0, "actual": 0},
]
self.assertEqual(report.period_total, expected)
@change_settings(
"Accounts Settings",
{"book_deferred_entries_based_on": "Months", "book_deferred_entries_via_journal_entry": 0},
)
def test_zero_amount(self):
self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
item = frappe.get_doc("Item", self.item)
item.enable_deferred_expense = 1
item.item_defaults[0].deferred_expense_account = self.deferred_expense_account
item.no_of_months_exp = 12
item.save()
pi = make_purchase_invoice(
item=self.item,
company=self.company,
supplier=self.supplier,
is_return=False,
update_stock=False,
posting_date=frappe.utils.datetime.date(2021, 12, 30),
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
do_not_save=True,
rate=3910,
price_list_rate=3910,
warehouse=self.warehouse,
qty=1,
)
pi.set_posting_time = True
pi.items[0].enable_deferred_expense = 1
pi.items[0].service_start_date = "2021-12-30"
pi.items[0].service_end_date = "2022-12-30"
pi.items[0].deferred_expense_account = self.deferred_expense_account
pi.items[0].expense_account = self.expense_account
pi.save()
pi.submit()
pda = frappe.get_doc(
doctype="Process Deferred Accounting",
posting_date=nowdate(),
start_date="2022-01-01",
end_date="2022-01-31",
type="Expense",
company=self.company,
)
pda.insert()
pda.submit()
# execute report
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2022-01-31"))
self.filters = frappe._dict(
{
"company": self.company,
"filter_based_on": "Date Range",
"period_start_date": "2022-01-01",
"period_end_date": "2022-01-31",
"from_fiscal_year": fiscal_year.year,
"to_fiscal_year": fiscal_year.year,
"periodicity": "Monthly",
"type": "Expense",
"with_upcoming_postings": False,
}
)
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
report.run()
# fetch the invoice from deferred invoices list
inv = [d for d in report.deferred_invoices if d.name == pi.name]
# make sure the list isn't empty
self.assertTrue(inv)
# calculate the total deferred expense for the period
inv = inv[0].calculate_invoice_revenue_expense_for_period()
deferred_exp = sum([inv[idx].actual for idx in range(len(report.period_list))])
# make sure the total deferred expense is greater than 0
self.assertLess(deferred_exp, 0)

View File

@@ -720,20 +720,22 @@ class GrossProfitGenerator:
frappe.qb.from_(purchase_invoice_item)
.inner_join(purchase_invoice)
.on(purchase_invoice.name == purchase_invoice_item.parent)
.select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor)
.select(
purchase_invoice.name,
purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor,
)
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.posting_date <= self.filters.to_date)
.where(purchase_invoice_item.item_code == item_code)
)
if row.project:
query.where(purchase_invoice_item.project == row.project)
query = query.where(purchase_invoice_item.project == row.project)
if row.cost_center:
query.where(purchase_invoice_item.cost_center == row.cost_center)
query = query.where(purchase_invoice_item.cost_center == row.cost_center)
query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc)
query.limit(1)
query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1)
last_purchase_rate = query.run()
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0

View File

@@ -516,6 +516,10 @@ def reconcile_against_document(
doc.make_advance_gl_entries()
else:
gl_map = doc.build_gl_map()
# Make sure there is no overallocation
from erpnext.accounts.general_ledger import process_debit_credit_difference
process_debit_credit_difference(gl_map)
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
# Only update outstanding for newly linked vouchers
@@ -1094,7 +1098,7 @@ def get_companies():
def get_children(doctype, parent, company, is_root=False):
from erpnext.accounts.report.financial_statements import sort_accounts
parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
parent_fieldname = "parent_" + frappe.scrub(doctype)
fields = ["name as value", "is_group as expandable"]
filters = [["docstatus", "<", 2]]

View File

@@ -652,7 +652,7 @@ frappe.ui.form.on("Asset", {
);
frm.set_value("gross_purchase_amount", purchase_amount);
frm.set_value("purchase_receipt_amount", purchase_amount);
frm.set_value("purchase_amount", purchase_amount);
frm.set_value("asset_quantity", asset_quantity);
frm.set_value("cost_center", item.cost_center || purchase_doc.cost_center);
if (item.asset_location) {

View File

@@ -72,7 +72,7 @@
"status",
"booked_fixed_asset",
"column_break_51",
"purchase_receipt_amount",
"purchase_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from",
@@ -408,15 +408,6 @@
"options": "Purchase Receipt",
"print_hide": 1
},
{
"fieldname": "purchase_receipt_amount",
"fieldtype": "Currency",
"hidden": 1,
"label": "Purchase Receipt Amount",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_invoice",
@@ -546,6 +537,15 @@
"label": "Additional Asset Cost",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "purchase_amount",
"fieldtype": "Currency",
"hidden": 1,
"label": "Purchase Amount",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"idx": 72,
@@ -589,7 +589,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-01-15 17:35:49.226603",
"modified": "2024-04-18 16:45:47.306032",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -633,4 +633,4 @@
"states": [],
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -92,10 +92,10 @@ class Asset(AccountsController):
number_of_depreciations_booked: DF.Int
opening_accumulated_depreciation: DF.Currency
policy_number: DF.Data | None
purchase_amount: DF.Currency
purchase_date: DF.Date | None
purchase_invoice: DF.Link | None
purchase_receipt: DF.Link | None
purchase_receipt_amount: DF.Currency
split_from: DF.Link | None
status: DF.Literal[
"Draft",
@@ -354,7 +354,7 @@ class Asset(AccountsController):
if self.is_existing_asset:
return
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_amount:
error_message = _(
"Gross Purchase Amount should be <b>equal</b> to purchase amount of one single Asset."
)
@@ -696,7 +696,7 @@ class Asset(AccountsController):
purchase_document = self.get_purchase_document()
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
if purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate():
if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate():
gl_entries.append(
self.get_gl_dict(
{
@@ -704,8 +704,8 @@ class Asset(AccountsController):
"against": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount,
"credit": self.purchase_amount,
"credit_in_account_currency": self.purchase_amount,
"cost_center": self.cost_center,
},
item=self,
@@ -719,8 +719,8 @@ class Asset(AccountsController):
"against": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount,
"debit": self.purchase_amount,
"debit_in_account_currency": self.purchase_amount,
"cost_center": self.cost_center,
},
item=self,
@@ -1116,8 +1116,8 @@ def create_new_asset_after_split(asset, split_qty):
)
new_asset.gross_purchase_amount = new_gross_purchase_amount
if asset.purchase_receipt_amount:
new_asset.purchase_receipt_amount = new_gross_purchase_amount
if asset.purchase_amount:
new_asset.purchase_amount = new_gross_purchase_amount
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name

View File

@@ -1000,7 +1000,7 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
depreciation_amount = get_depreciation_amount(
depreciation_amount, prev_per_day_depr = get_depreciation_amount(
asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0]
)
self.assertEqual(depreciation_amount, 30000)
@@ -1698,7 +1698,7 @@ def create_asset(**args):
"opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0,
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
"gross_purchase_amount": args.gross_purchase_amount or 100000,
"purchase_receipt_amount": args.purchase_receipt_amount or 100000,
"purchase_amount": args.purchase_amount or 100000,
"maintenance_required": args.maintenance_required or 0,
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"available_for_use_date": args.available_for_use_date or "2020-06-06",
@@ -1723,6 +1723,7 @@ def create_asset(**args):
"depreciation_start_date": args.depreciation_start_date,
"daily_prorata_based": args.daily_prorata_based or 0,
"shift_based": args.shift_based or 0,
"rate_of_depreciation": args.rate_of_depreciation or 0,
},
)

View File

@@ -143,6 +143,10 @@ class AssetCapitalization(StockController):
self.make_gl_entries()
self.restore_consumed_asset_items()
def on_trash(self):
frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
super().on_trash()
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
@@ -612,8 +616,7 @@ class AssetCapitalization(StockController):
asset_doc.available_for_use_date = self.posting_date
asset_doc.purchase_date = self.posting_date
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.purchase_amount = total_target_asset_value
asset_doc.capitalized_in = self.name
asset_doc.flags.ignore_validate = True
asset_doc.flags.asset_created_via_asset_capitalization = True
@@ -649,7 +652,7 @@ class AssetCapitalization(StockController):
asset_doc = frappe.get_doc("Asset", self.target_asset)
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.purchase_amount = total_target_asset_value
asset_doc.capitalized_in = self.name
asset_doc.flags.ignore_validate = True
asset_doc.save()

View File

@@ -89,7 +89,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Test Target Asset values
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
self.assertEqual(target_asset.purchase_amount, total_amount)
# Test Consumed Asset values
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
@@ -179,7 +179,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Test Target Asset values
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
self.assertEqual(target_asset.purchase_amount, total_amount)
# Test Consumed Asset values
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
@@ -256,7 +256,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Test Target Asset values
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
self.assertEqual(target_asset.purchase_amount, total_amount)
# Test General Ledger Entries
expected_gle = {
@@ -526,7 +526,7 @@ def create_depreciation_asset(**args):
asset.available_for_use_date = args.available_for_use_date or asset.purchase_date
asset.gross_purchase_amount = args.asset_value or 100000
asset.purchase_receipt_amount = asset.gross_purchase_amount
asset.purchase_amount = asset.gross_purchase_amount
finance_book = asset.append("finance_books")
finance_book.depreciation_start_date = args.depreciation_start_date or "2020-12-31"

View File

@@ -285,6 +285,7 @@ class AssetDepreciationSchedule(Document):
number_of_pending_depreciations = final_number_of_depreciations - start
yearly_opening_wdv = value_after_depreciation
current_fiscal_year_end_date = None
prev_per_day_depr = True
for n in range(start, final_number_of_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
@@ -301,8 +302,7 @@ class AssetDepreciationSchedule(Document):
prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
else:
prev_depreciation_amount = 0
depreciation_amount = get_depreciation_amount(
depreciation_amount, prev_per_day_depr = get_depreciation_amount(
self,
asset_doc,
value_after_depreciation,
@@ -312,6 +312,7 @@ class AssetDepreciationSchedule(Document):
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
number_of_pending_depreciations,
prev_per_day_depr,
)
if not has_pro_rata or (
n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
@@ -599,11 +600,12 @@ def get_depreciation_amount(
prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False,
number_of_pending_depreciations=0,
prev_per_day_depr=0,
):
if fb_row.depreciation_method in ("Straight Line", "Manual"):
return get_straight_line_or_manual_depr_amount(
asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
)
), None
else:
return get_wdv_or_dd_depr_amount(
asset,
@@ -614,6 +616,7 @@ def get_depreciation_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
)
@@ -637,49 +640,14 @@ def get_straight_line_or_manual_depr_amount(
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
if row.daily_prorata_based:
amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
total_days = (
date_diff(
get_last_day(
add_months(
row.depreciation_start_date,
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
* row.frequency_of_depreciation,
)
),
add_days(
get_last_day(
add_months(
row.depreciation_start_date,
flt(
row.total_number_of_depreciations
- asset.number_of_depreciations_booked
- number_of_pending_depreciations
- 1
)
* row.frequency_of_depreciation,
)
),
1,
),
)
+ 1
)
daily_depr_amount = amount / total_days
to_date = get_last_day(
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
return get_daily_prorata_based_straight_line_depr(
asset,
row,
schedule_idx,
number_of_pending_depreciations,
amount,
)
from_date = add_days(
get_last_day(
add_months(
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
)
),
1,
)
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
else:
return (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
@@ -692,40 +660,9 @@ def get_straight_line_or_manual_depr_amount(
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
)
total_days = (
date_diff(
get_last_day(
add_months(
row.depreciation_start_date,
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
* row.frequency_of_depreciation,
)
),
add_days(
get_last_day(
add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)
),
1,
),
)
+ 1
return get_daily_prorata_based_straight_line_depr(
asset, row, schedule_idx, number_of_pending_depreciations, amount
)
daily_depr_amount = amount / total_days
to_date = get_last_day(
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
)
from_date = add_days(
get_last_day(
add_months(
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
)
),
1,
)
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
else:
return (
flt(asset.gross_purchase_amount)
@@ -734,6 +671,23 @@ def get_straight_line_or_manual_depr_amount(
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
def get_daily_prorata_based_straight_line_depr(
asset, row, schedule_idx, number_of_pending_depreciations, amount
):
total_years = flt(number_of_pending_depreciations * row.frequency_of_depreciation) / 12
every_year_depr = amount / total_years
year_start_date = add_years(
row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12
)
year_end_date = add_days(add_years(year_start_date, 1), -1)
daily_depr_amount = every_year_depr / (date_diff(year_end_date, year_start_date) + 1)
from_date, total_depreciable_days = _get_total_days(
row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation
)
return daily_depr_amount * total_depreciable_days
def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
return (
@@ -779,6 +733,7 @@ def get_wdv_or_dd_depr_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
):
return get_default_wdv_or_dd_depr_amount(
asset,
@@ -788,6 +743,7 @@ def get_wdv_or_dd_depr_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
)
@@ -799,6 +755,39 @@ def get_default_wdv_or_dd_depr_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
):
if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12:
return _get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
), None
else:
return _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
)
def _get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
):
if cint(fb_row.frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
@@ -825,6 +814,75 @@ def get_default_wdv_or_dd_depr_amount(
return prev_depreciation_amount
def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
):
if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month
if schedule_idx == 0:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100), None
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes
return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
else:
return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
else:
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes
return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
else:
return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value):
""" "
Returns monthly depreciation amount when year changes
1. Calculate per day depr based on new year
2. Calculate monthly amount based on new per day amount
"""
from_date, days_in_month = _get_total_days(
fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
)
per_day_depr = get_per_day_depr(fb_row, depreciable_value, from_date)
return (per_day_depr * days_in_month), per_day_depr
def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr):
""" "
Returns monthly depreciation amount based on prev per day depr
Calculate per day depr only for the first month
"""
from_date, days_in_month = _get_total_days(
fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
)
return (prev_per_day_depr * days_in_month), prev_per_day_depr
def get_per_day_depr(
fb_row,
depreciable_value,
from_date,
):
to_date = add_days(add_years(from_date, 1), -1)
total_days = date_diff(to_date, from_date) + 1
per_day_depr = (flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days
return per_day_depr
def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation):
from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation)
to_date = add_months(from_date, frequency_of_depreciation)
if is_last_day_of_the_month(depreciation_start_date):
to_date = get_last_day(to_date)
from_date = add_days(get_last_day(from_date), 1)
return from_date, date_diff(to_date, from_date) + 1
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
asset_depr_schedules_names = []

View File

@@ -3,10 +3,12 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import cstr
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
get_depr_schedule,
)
@@ -25,3 +27,136 @@ class TestAssetDepreciationSchedule(FrappeTestCase):
)
self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert)
def test_daily_prorata_based_depr_on_sl_methond(self):
asset = create_asset(
calculate_depreciation=1,
depreciation_method="Straight Line",
daily_prorata_based=1,
available_for_use_date="2020-01-01",
depreciation_start_date="2020-01-31",
frequency_of_depreciation=1,
total_number_of_depreciations=24,
)
expected_schedules = [
["2020-01-31", 4234.97, 4234.97],
["2020-02-29", 3961.75, 8196.72],
["2020-03-31", 4234.97, 12431.69],
["2020-04-30", 4098.36, 16530.05],
["2020-05-31", 4234.97, 20765.02],
["2020-06-30", 4098.36, 24863.38],
["2020-07-31", 4234.97, 29098.35],
["2020-08-31", 4234.97, 33333.32],
["2020-09-30", 4098.36, 37431.68],
["2020-10-31", 4234.97, 41666.65],
["2020-11-30", 4098.36, 45765.01],
["2020-12-31", 4234.97, 49999.98],
["2021-01-31", 4246.58, 54246.56],
["2021-02-28", 3835.62, 58082.18],
["2021-03-31", 4246.58, 62328.76],
["2021-04-30", 4109.59, 66438.35],
["2021-05-31", 4246.58, 70684.93],
["2021-06-30", 4109.59, 74794.52],
["2021-07-31", 4246.58, 79041.1],
["2021-08-31", 4246.58, 83287.68],
["2021-09-30", 4109.59, 87397.27],
["2021-10-31", 4246.58, 91643.85],
["2021-11-30", 4109.59, 95753.44],
["2021-12-31", 4246.56, 100000.0],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Test for Written Down Value Method
# Frequency of deprciation = 3
def test_for_daily_prorata_based_depreciation_wdv_method_frequency_3_months(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
depreciation_method="Written Down Value",
daily_prorata_based=1,
available_for_use_date="2021-02-20",
depreciation_start_date="2021-03-31",
frequency_of_depreciation=3,
total_number_of_depreciations=6,
rate_of_depreciation=40,
)
expected_schedules = [
["2021-03-31", 4383.56, 4383.56],
["2021-06-30", 9535.45, 13919.01],
["2021-09-30", 9640.23, 23559.24],
["2021-12-31", 9640.23, 33199.47],
["2022-03-31", 9430.66, 42630.13],
["2022-06-30", 5721.27, 48351.4],
["2022-08-20", 51648.6, 100000.0],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Frequency of deprciation = 6
def test_for_daily_prorata_based_depreciation_wdv_method_frequency_6_months(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
depreciation_method="Written Down Value",
daily_prorata_based=1,
available_for_use_date="2020-02-20",
depreciation_start_date="2020-02-29",
frequency_of_depreciation=6,
total_number_of_depreciations=6,
rate_of_depreciation=40,
)
expected_schedules = [
["2020-02-29", 1092.90, 1092.90],
["2020-08-31", 19944.01, 21036.91],
["2021-02-28", 19618.83, 40655.74],
["2021-08-31", 11966.4, 52622.14],
["2022-02-28", 11771.3, 64393.44],
["2022-08-31", 7179.84, 71573.28],
["2023-02-20", 28426.72, 100000.0],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Frequency of deprciation = 12
def test_for_daily_prorata_based_depreciation_wdv_method_frequency_12_months(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
depreciation_method="Written Down Value",
daily_prorata_based=1,
available_for_use_date="2020-02-20",
depreciation_start_date="2020-03-31",
frequency_of_depreciation=12,
total_number_of_depreciations=4,
rate_of_depreciation=40,
)
expected_schedules = [
["2020-03-31", 4480.87, 4480.87],
["2021-03-31", 38207.65, 42688.52],
["2022-03-31", 22924.59, 65613.11],
["2023-03-31", 13754.76, 79367.87],
["2024-02-20", 20632.13, 100000],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)

View File

@@ -612,6 +612,20 @@ class PurchaseOrder(BuyingController):
return result
def update_ordered_qty_in_so_for_removed_items(self, removed_items):
"""
Updates ordered_qty in linked SO when item rows are removed using Update Items
"""
if not self.is_against_so():
return
for item in removed_items:
prev_ordered_qty = frappe.get_cached_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty"
)
frappe.db.set_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
)
def auto_create_subcontracting_order(self):
if self.is_subcontracted and not self.is_old_subcontracting_flow:
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):

View File

@@ -764,12 +764,7 @@ class TestPurchaseOrder(FrappeTestCase):
}
).insert()
else:
account = frappe.db.get_value(
"Account",
filters={"account_name": account_name, "company": company},
fieldname="name",
pluck=True,
)
account = frappe.get_doc("Account", {"account_name": account_name, "company": company})
return account
@@ -800,22 +795,6 @@ class TestPurchaseOrder(FrappeTestCase):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
pi = make_purchase_invoice(po_doc.name)
pi.append(
"advances",
{
"reference_type": pe.doctype,
"reference_name": pe.name,
"reference_row": pe.references[0].name,
"advance_amount": 5000,
"allocated_amount": 5000,
},
)
pi.save().submit()
pe.reload()
po_doc.reload()
self.assertEqual(po_doc.advance_paid, 0)
company_doc.book_advance_payments_in_separate_party_account = False
company_doc.save()

View File

@@ -406,7 +406,7 @@
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
"label": "Address & Contact"
},
{
"fieldname": "accounting_tab",
@@ -485,7 +485,7 @@
"link_fieldname": "party"
}
],
"modified": "2024-03-13 11:14:06.516519",
"modified": "2024-05-08 18:02:57.342931",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -3158,6 +3158,9 @@ def validate_and_delete_children(parent, data) -> bool:
d.cancel()
d.delete()
if parent.doctype == "Purchase Order":
parent.update_ordered_qty_in_so_for_removed_items(deleted_children)
# need to update ordered qty in Material Request first
# bin uses Material Request Items to recalculate & update
parent.update_prevdoc_status()

View File

@@ -787,7 +787,7 @@ class BuyingController(SubcontractingController):
"supplier": self.supplier,
"purchase_date": self.posting_date,
"calculate_depreciation": 0,
"purchase_receipt_amount": purchase_amount,
"purchase_amount": purchase_amount,
"gross_purchase_amount": purchase_amount,
"asset_quantity": asset_quantity,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,

View File

@@ -205,6 +205,7 @@ class StockController(AccountsController):
"company": self.company,
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
"use_serial_batch_fields": row.use_serial_batch_fields,
"via_landed_cost_voucher": via_landed_cost_voucher,
"do_not_submit": True if not via_landed_cost_voucher else False,
}
@@ -1227,8 +1228,8 @@ def get_accounting_ledger_preview(doc, filters):
"debit",
"credit",
"against",
"party",
"party_type",
"party",
"cost_center",
"against_voucher_type",
"against_voucher",
@@ -1404,7 +1405,12 @@ def is_reposting_pending():
)
def future_sle_exists(args, sl_entries=None):
def future_sle_exists(args, sl_entries=None, allow_force_reposting=True):
if allow_force_reposting and frappe.db.get_single_value(
"Stock Reposting Settings", "do_reposting_for_each_stock_transaction"
):
return True
key = (args.voucher_type, args.voucher_no)
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}

View File

@@ -121,7 +121,7 @@ def send_mail(entry, email_campaign):
doctype="Email Campaign",
name=email_campaign.name,
subject=frappe.render_template(email_template.get("subject"), context),
content=frappe.render_template(email_template.get("response"), context),
content=frappe.render_template(email_template.response_, context),
sender=sender,
recipients=recipient_list,
communication_medium="Email",

View File

@@ -360,4 +360,7 @@ erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency
erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount

View File

@@ -0,0 +1,21 @@
import frappe
def execute():
# update debit and credit in transaction currency:
# if transaction currency is same as account currency,
# then debit and credit in transaction currency is same as debit and credit in account currency
# else debit and credit divided by exchange rate
# nosemgrep
frappe.db.sql(
"""
UPDATE `tabGL Entry`
SET
debit_in_transaction_currency = IF(transaction_currency = account_currency, debit_in_account_currency, debit / transaction_exchange_rate),
credit_in_transaction_currency = IF(transaction_currency = account_currency, credit_in_account_currency, credit / transaction_exchange_rate)
WHERE
transaction_exchange_rate > 0
and transaction_currency is not null
"""
)

View File

@@ -0,0 +1,11 @@
import frappe
def execute():
cancelled_asset_capitalizations = frappe.get_all(
"Asset Capitalization",
filters={"docstatus": 2},
fields=["name", "target_asset"],
)
for asset_capitalization in cancelled_asset_capitalizations:
frappe.db.set_value("Asset", asset_capitalization.target_asset, "capitalized_in", None)

View File

@@ -0,0 +1,8 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
frappe.reload_doc("assets", "doctype", "asset")
if frappe.db.has_column("Asset", "purchase_receipt_amount"):
rename_field("Asset", "purchase_receipt_amount", "purchase_amount")

View File

@@ -373,6 +373,7 @@ erpnext.sales_common = {
frappe.model.set_value(item.doctype, item.name, {
serial_and_batch_bundle: r.name,
use_serial_batch_fields: 0,
incoming_rate: r.avg_rate,
qty:
qty /
flt(

View File

@@ -482,7 +482,7 @@
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
"label": "Address & Contact"
},
{
"fieldname": "defaults_tab",
@@ -583,7 +583,7 @@
"link_fieldname": "party"
}
],
"modified": "2024-03-16 19:41:47.971815",
"modified": "2024-05-08 18:03:20.716169",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",

View File

@@ -71,6 +71,8 @@ frappe.ui.form.on("Quotation", {
frm.trigger("set_label");
frm.trigger("toggle_reqd_lead_customer");
frm.trigger("set_dynamic_field_label");
frm.set_value("party_name", "");
frm.set_value("customer_name", "");
},
set_label: function (frm) {
@@ -97,7 +99,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
frappe.dynamic_link = {
doc: this.frm.doc,
fieldname: "party_name",
doctype: doc.quotation_to == "Customer" ? "Customer" : "Lead",
doctype: doc.quotation_to,
};
var me = this;
@@ -197,6 +199,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
};
} else if (this.frm.doc.quotation_to == "Prospect") {
this.frm.set_df_property("party_name", "label", "Prospect");
this.frm.fields_dict.party_name.get_query = null;
}
}

View File

@@ -139,6 +139,7 @@ class Company(NestedSet):
self.validate_abbr()
self.validate_default_accounts()
self.validate_currency()
self.validate_advance_account_currency()
self.validate_coa_input()
self.validate_perpetual_inventory()
self.validate_provisional_account_for_non_stock_items()
@@ -192,6 +193,29 @@ class Company(NestedSet):
).format(frappe.bold(account[0]))
frappe.throw(error_message)
def validate_advance_account_currency(self):
if (
self.default_advance_received_account
and frappe.get_cached_value("Account", self.default_advance_received_account, "account_currency")
!= self.default_currency
):
frappe.throw(
_("'{0}' should be in company currency {1}.").format(
frappe.bold("Default Advance Received Account"), frappe.bold(self.default_currency)
)
)
if (
self.default_advance_paid_account
and frappe.get_cached_value("Account", self.default_advance_paid_account, "account_currency")
!= self.default_currency
):
frappe.throw(
_("'{0}' should be in company currency {1}.").format(
frappe.bold("Default Advance Paid Account"), frappe.bold(self.default_currency)
)
)
def validate_currency(self):
if self.is_new():
return

View File

@@ -238,7 +238,7 @@ def update_qty(bin_name, args):
sle = frappe.qb.DocType("Stock Ledger Entry")
# actual qty is not up to date in case of backdated transaction
if future_sle_exists(args):
if future_sle_exists(args, allow_force_reposting=False):
last_sle_qty = (
frappe.qb.from_(sle)
.select(sle.qty_after_transaction)

View File

@@ -1066,7 +1066,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
@frappe.whitelist()
def make_delivery_trip(source_name, target_doc=None):
def make_delivery_trip(source_name, target_doc=None, kwargs=None):
def update_stop_details(source_doc, target_doc, source_parent):
target_doc.customer = source_parent.customer
target_doc.address = source_parent.shipping_address_name

View File

@@ -15,6 +15,9 @@ frappe.ui.form.on("Item", {
frm.add_fetch("tax_type", "tax_rate", "tax_rate");
frm.make_methods = {
Quotation: () => {
open_form(frm, "Quotation", "Quotation Item", "items");
},
"Sales Order": () => {
open_form(frm, "Sales Order", "Sales Order Item", "items");
},

View File

@@ -36,6 +36,8 @@
"section_break_11",
"description",
"brand",
"unit_of_measure_conversion",
"uoms",
"dashboard_tab",
"inventory_section",
"inventory_settings_section",
@@ -52,8 +54,6 @@
"barcodes",
"reorder_section",
"reorder_levels",
"unit_of_measure_conversion",
"uoms",
"serial_nos_and_batches",
"has_batch_no",
"create_new_batch",
@@ -891,7 +891,7 @@
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
"modified": "2024-01-08 18:09:30.225085",
"modified": "2024-04-30 13:46:39.098753",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
@@ -964,4 +964,4 @@
"states": [],
"title_field": "item_name",
"track_changes": 1
}
}

View File

@@ -65,15 +65,13 @@ class Item(Document):
from erpnext.stock.doctype.item_reorder.item_reorder import ItemReorder
from erpnext.stock.doctype.item_supplier.item_supplier import ItemSupplier
from erpnext.stock.doctype.item_tax.item_tax import ItemTax
from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import (
ItemVariantAttribute,
)
from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import ItemVariantAttribute
from erpnext.stock.doctype.uom_conversion_detail.uom_conversion_detail import UOMConversionDetail
allow_alternative_item: DF.Check
allow_negative_stock: DF.Check
asset_category: DF.Link | None
asset_naming_series: DF.Literal
asset_naming_series: DF.Literal[None]
attributes: DF.Table[ItemVariantAttribute]
auto_create_assets: DF.Check
barcodes: DF.Table[ItemBarcode]

View File

@@ -52,10 +52,13 @@
"search_index": 1
},
{
"fetch_from": "item_code.stock_uom",
"fetch_if_empty": 1,
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
"options": "UOM"
"options": "UOM",
"reqd": 1
},
{
"default": "0",
@@ -220,7 +223,7 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-01-30 14:02:19.304854",
"modified": "2024-04-02 22:18:00.450641",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",

View File

@@ -38,7 +38,7 @@ class ItemPrice(Document):
reference: DF.Data | None
selling: DF.Check
supplier: DF.Link | None
uom: DF.Link | None
uom: DF.Link
valid_from: DF.Date | None
valid_upto: DF.Date | None
# end: auto-generated types

View File

@@ -946,6 +946,128 @@ class TestLandedCostVoucher(FrappeTestCase):
frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"),
)
def test_do_not_validate_against_landed_cost_voucher_for_serial_for_legacy_pr(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_auto_batch_nos
frappe.flags.ignore_serial_batch_bundle_validation = True
frappe.flags.use_serial_and_batch_fields = True
sn_item = "Test Don't Validate Against LCV For Serial NO for Legacy PR"
sn_item_doc = make_item(
sn_item,
{
"has_serial_no": 1,
"serial_no_series": "SN-ALCVTDVLCVSNO-.####",
"is_stock_item": 1,
},
)
serial_nos = [
"SN-ALCVTDVLCVSNO-0001",
"SN-ALCVTDVLCVSNO-0002",
"SN-ALCVTDVLCVSNO-0003",
"SN-ALCVTDVLCVSNO-0004",
"SN-ALCVTDVLCVSNO-0005",
]
for sn in serial_nos:
if not frappe.db.exists("Serial No", sn):
sn_doc = frappe.get_doc(
{
"doctype": "Serial No",
"item_code": sn_item,
"serial_no": sn,
}
)
sn_doc.insert()
warehouse = "_Test Warehouse - _TC"
company = frappe.db.get_value("Warehouse", warehouse, "company")
pr = make_purchase_receipt(
company=company,
warehouse=warehouse,
item_code=sn_item,
qty=5,
rate=100,
uom=sn_item_doc.stock_uom,
stock_uom=sn_item_doc.stock_uom,
)
pr.reload()
for sn in serial_nos:
sn_doc = frappe.get_doc("Serial No", sn)
sn_doc.db_set(
{
"warehouse": warehouse,
"status": "Active",
}
)
for row in pr.items:
if row.item_code == sn_item:
row.db_set("serial_no", ", ".join(serial_nos))
stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": pr.name})
for sle in stock_ledger_entries:
doc = frappe.get_doc("Stock Ledger Entry", sle.name)
if doc.item_code == sn_item:
doc.db_set("serial_no", ", ".join(serial_nos))
dn = create_delivery_note(
company=company,
warehouse=warehouse,
item_code=sn_item,
qty=5,
rate=100,
uom=sn_item_doc.stock_uom,
stock_uom=sn_item_doc.stock_uom,
)
stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": dn.name})
for sle in stock_ledger_entries:
doc = frappe.get_doc("Stock Ledger Entry", sle.name)
if doc.item_code == sn_item:
doc.db_set("serial_no", ", ".join(serial_nos))
frappe.flags.ignore_serial_batch_bundle_validation = False
frappe.flags.use_serial_and_batch_fields = False
lcv = make_landed_cost_voucher(
company=pr.company,
receipt_document_type="Purchase Receipt",
receipt_document=pr.name,
charges=20,
distribute_charges_based_on="Qty",
do_not_save=True,
)
lcv.get_items_from_purchase_receipts()
lcv.save()
lcv.submit()
pr.reload()
for row in pr.items:
self.assertEqual(row.valuation_rate, 104)
self.assertTrue(row.serial_and_batch_bundle)
self.assertEqual(
row.valuation_rate,
frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"),
)
lcv.cancel()
pr.reload()
for row in pr.items:
self.assertEqual(row.valuation_rate, 100)
self.assertTrue(row.serial_and_batch_bundle)
self.assertEqual(
row.valuation_rate,
frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"),
)
def make_landed_cost_voucher(**args):
args = frappe._dict(args)

View File

@@ -790,7 +790,7 @@ def get_available_item_locations(
locations = get_locations_based_on_required_qty(locations, required_qty)
if not ignore_validation:
validate_picked_materials(item_code, required_qty, locations)
validate_picked_materials(item_code, required_qty, locations, picked_item_details)
return locations
@@ -810,7 +810,7 @@ def get_locations_based_on_required_qty(locations, required_qty):
return filtered_locations
def validate_picked_materials(item_code, required_qty, locations):
def validate_picked_materials(item_code, required_qty, locations, picked_item_details=None):
for location in list(locations):
if location["qty"] < 0:
locations.remove(location)
@@ -819,15 +819,25 @@ def validate_picked_materials(item_code, required_qty, locations):
remaining_qty = required_qty - total_qty_available
if remaining_qty > 0:
frappe.msgprint(
_("{0} units of Item {1} is picked in another Pick List.").format(
remaining_qty, get_link_to_form("Item", item_code)
),
title=_("Already Picked"),
)
if picked_item_details:
frappe.msgprint(
_("{0} units of Item {1} is picked in another Pick List.").format(
remaining_qty, get_link_to_form("Item", item_code)
),
title=_("Already Picked"),
)
else:
frappe.msgprint(
_("{0} units of Item {1} is not available in any of the warehouses.").format(
remaining_qty, get_link_to_form("Item", item_code)
),
title=_("Insufficient Stock"),
)
def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]:
filterd_locations = []
for row in locations:
key = row.warehouse
if row.batch_no:
@@ -835,6 +845,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list
picked_qty = picked_item_details.get(key, {}).get("picked_qty", 0)
if not picked_qty:
filterd_locations.append(row)
continue
if picked_qty > row.qty:
row.qty = 0
@@ -845,7 +856,10 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list
if row.serial_nos:
row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no")))
return locations
if row.qty > 0:
filterd_locations.append(row)
return filterd_locations
def get_available_item_locations_for_serial_and_batched_item(

View File

@@ -977,3 +977,157 @@ class TestPickList(FrappeTestCase):
so = make_sales_order(item_code=item, qty=4, rate=100)
pl = create_pick_list(so.name)
self.assertFalse(hasattr(pl, "locations"))
def test_pick_list_validation_for_multiple_batches_and_sales_order(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Batch Pick List Item For Multiple Batches",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"batch_number_series": "SN-BT-BATCH-SPLIMBATCH-.####",
"create_new_batch": 1,
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=5)
make_stock_entry(item=item, to_warehouse=warehouse, qty=5)
so = make_sales_order(item_code=item, qty=6, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 5.0)
self.assertEqual(pl1.locations[1].qty, 1.0)
so = make_sales_order(item_code=item, qty=4, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 4.0)
self.assertTrue(hasattr(pl, "locations"))
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 4.0)
self.assertTrue(hasattr(pl, "locations"))
def test_pick_list_for_multiple_sales_order_with_multiple_batches(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Batch Pick List Item For Multiple Batches and Sales Order",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"batch_number_series": "SN-SOO-BT-SPLIMBATCH-.####",
"create_new_batch": 1,
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
so = make_sales_order(item_code=item, qty=10, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 10)
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 90.0)
self.assertEqual(pl.locations[1].qty, 20.0)
self.assertTrue(hasattr(pl, "locations"))
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 90.0)
self.assertEqual(pl.locations[1].qty, 20.0)
self.assertTrue(hasattr(pl, "locations"))
def test_pick_list_for_multiple_sales_order_with_multiple_serial_nos(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Serial No Pick List Item For Multiple Batches and Sales Order",
properties={
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "SNNN-SOO-BT-SPLIMBATCH-.####",
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
so = make_sales_order(item_code=item, qty=10, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 10)
serial_nos = pl1.locations[0].serial_no.split("\n")
self.assertEqual(len(serial_nos), 10)
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
new_serial_nos = pl.locations[0].serial_no.split("\n")
self.assertEqual(len(new_serial_nos), 110)
for sn in serial_nos:
self.assertFalse(sn in new_serial_nos)
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
def test_pick_list_for_multiple_sales_orders_for_non_serialized_item(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Non Serialized Pick List Item For Multiple Batches and Sales Order",
properties={
"is_stock_item": 1,
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
so = make_sales_order(item_code=item, qty=10, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 10)
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 80.0)

View File

@@ -132,7 +132,8 @@
"in_list_view": 1,
"label": "Item",
"options": "Item",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fieldname": "quantity_section",
@@ -240,7 +241,7 @@
],
"istable": 1,
"links": [],
"modified": "2024-02-04 16:12:16.257951",
"modified": "2024-05-07 15:32:42.905446",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",
@@ -251,4 +252,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -858,7 +858,7 @@ class PurchaseReceipt(BuyingController):
asset.name,
{
"gross_purchase_amount": purchase_amount,
"purchase_receipt_amount": purchase_amount,
"purchase_amount": purchase_amount,
},
)
@@ -1163,7 +1163,12 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
qty = item_row.qty
if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
qty = item_row.received_qty
pending_qty = qty - invoiced_qty_map.get(item_row.name, 0)
if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
return pending_qty, 0
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
if returned_qty:
if returned_qty >= pending_qty:
@@ -1172,6 +1177,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
else:
pending_qty -= returned_qty
returned_qty = 0
return pending_qty, returned_qty
doclist = get_mapped_doc(

View File

@@ -895,6 +895,8 @@ class TestPurchaseReceipt(FrappeTestCase):
create_purchase_order,
)
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
po = create_purchase_order()
pr = create_pr_against_po(po.name)
@@ -914,6 +916,7 @@ class TestPurchaseReceipt(FrappeTestCase):
po.cancel()
def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self):
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
pr1.append(
"items",
@@ -2783,6 +2786,84 @@ class TestPurchaseReceipt(FrappeTestCase):
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
def test_purchase_receipt_bill_for_rejected_quantity_in_purchase_invoice(self):
item_code = make_item(
"_Test Purchase Receipt Bill For Rejected Quantity",
properties={"is_stock_item": 1},
).name
pr = make_purchase_receipt(item_code=item_code, qty=5, rate=100)
return_pr = make_purchase_receipt(
item_code=item_code,
is_return=1,
return_against=pr.name,
qty=-2,
do_not_submit=1,
)
return_pr.items[0].purchase_receipt_item = pr.items[0].name
return_pr.submit()
old_value = frappe.db.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
pi = make_purchase_invoice(pr.name)
self.assertEqual(pi.items[0].qty, 3)
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)
pi = make_purchase_invoice(pr.name)
pi.submit()
self.assertEqual(pi.items[0].qty, 5)
frappe.db.set_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", old_value
)
def test_zero_valuation_rate_for_batched_item(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
item = make_item(
"_Test Zero Valuation Rate For the Batch Item",
{
"is_purchase_item": 1,
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TZVRFORBATCH.#####",
"valuation_rate": 200,
},
)
pi = make_purchase_receipt(
qty=10,
rate=0,
item_code=item.name,
)
pi.reload()
batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle)
se = make_stock_entry(
purpose="Material Issue",
item_code=item.name,
source=pi.items[0].warehouse,
qty=10,
batch_no=batch_no,
use_serial_batch_fields=0,
)
se.submit()
se.reload()
self.assertEqual(se.items[0].valuation_rate, 0)
self.assertEqual(se.items[0].basic_rate, 0)
sabb_doc = frappe.get_doc("Serial and Batch Bundle", se.items[0].serial_and_batch_bundle)
for row in sabb_doc.entries:
self.assertEqual(row.incoming_rate, 0)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -249,8 +249,7 @@ class SerialandBatchBundle(Document):
if self.has_serial_no:
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
else:
if sn_obj.batch_avg_rate.get(d.batch_no):
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
if self.docstatus == 1:
@@ -429,6 +428,9 @@ class SerialandBatchBundle(Document):
self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} should be submit first.")
def check_future_entries_exists(self):
if self.flags and self.flags.via_landed_cost_voucher:
return
if not self.has_serial_no:
return
@@ -1149,7 +1151,18 @@ def make_batch_nos(item_code, batch_nos):
continue
batch_nos_details.append(
(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
(
batch_no,
batch_no,
now(),
now(),
user,
user,
item.item_code,
item.item_name,
item.description,
1,
)
)
fields = [
@@ -1162,6 +1175,7 @@ def make_batch_nos(item_code, batch_nos):
"item",
"item_name",
"description",
"use_batchwise_valuation",
]
frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))

View File

@@ -498,6 +498,8 @@ class TestSerialandBatchBundle(FrappeTestCase):
make_batch_nos(item_code, batch_nos)
self.assertTrue(frappe.db.exists("Batch", batch_id))
use_batchwise_valuation = frappe.db.get_value("Batch", batch_id, "use_batchwise_valuation")
self.assertEqual(use_batchwise_valuation, 1)
batch_id = "TEST-BATTCCH-VAL-00001"
batch_nos = [{"batch_no": batch_id, "qty": 1}]

View File

@@ -1340,6 +1340,7 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
frappe.model.set_value(item.doctype, item.name, {
serial_and_batch_bundle: r.name,
use_serial_batch_fields: 0,
basic_rate: r.avg_rate,
qty:
Math.abs(r.total_qty) /
flt(item.conversion_factor || 1, precision("conversion_factor", item)),

View File

@@ -13,6 +13,7 @@
"end_time",
"limits_dont_apply_on",
"item_based_reposting",
"do_reposting_for_each_stock_transaction",
"errors_notification_section",
"notify_reposting_error_to_role"
],
@@ -65,12 +66,18 @@
"fieldname": "errors_notification_section",
"fieldtype": "Section Break",
"label": "Errors Notification"
},
{
"default": "0",
"fieldname": "do_reposting_for_each_stock_transaction",
"fieldtype": "Check",
"label": "Do reposting for each Stock Transaction"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-11-01 16:14:29.080697",
"modified": "2024-04-24 12:19:40.204888",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reposting Settings",
@@ -91,4 +98,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -16,6 +16,7 @@ class StockRepostingSettings(Document):
if TYPE_CHECKING:
from frappe.types import DF
do_reposting_for_each_stock_transaction: DF.Check
end_time: DF.Time | None
item_based_reposting: DF.Check
limit_reposting_timeslot: DF.Check
@@ -29,6 +30,10 @@ class StockRepostingSettings(Document):
def validate(self):
self.set_minimum_reposting_time_slot()
def before_save(self):
if self.do_reposting_for_each_stock_transaction:
self.item_based_reposting = 1
def set_minimum_reposting_time_slot(self):
"""Ensure that timeslot for reposting is at least 12 hours."""
if not self.limit_reposting_timeslot:

View File

@@ -38,3 +38,51 @@ class TestStockRepostingSettings(unittest.TestCase):
users = get_recipients()
self.assertTrue(user in users)
def test_do_reposting_for_each_stock_transaction(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1)
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
item = make_item(
"_Test item for reposting check for each transaction", properties={"is_stock_item": 1}
).name
stock_entry = make_stock_entry(
item_code=item,
qty=1,
rate=100,
stock_entry_type="Material Receipt",
target="_Test Warehouse - _TC",
)
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
self.assertTrue(riv)
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
def test_do_not_reposting_for_each_stock_transaction(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
item = make_item(
"_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1}
).name
stock_entry = make_stock_entry(
item_code=item,
qty=1,
rate=100,
stock_entry_type="Material Receipt",
target="_Test Warehouse - _TC",
)
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
self.assertFalse(riv)

View File

@@ -418,7 +418,7 @@
{
"default": "0",
"depends_on": "eval: doc.enable_stock_reservation",
"description": "Stock will be reserved on submission of <b>Purchase Receipt</b> created against Material Receipt for Sales Order.",
"description": "Stock will be reserved on submission of <b>Purchase Receipt</b> created against Material Request for Sales Order.",
"fieldname": "auto_reserve_stock_for_sales_order_on_purchase",
"fieldtype": "Check",
"label": "Auto Reserve Stock for Sales Order on Purchase"
@@ -469,4 +469,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -838,7 +838,12 @@ def insert_item_price(args):
item_price = frappe.db.get_value(
"Item Price",
{"item_code": args.item_code, "price_list": args.price_list, "currency": args.currency},
{
"item_code": args.item_code,
"price_list": args.price_list,
"currency": args.currency,
"uom": args.stock_uom,
},
["name", "price_list_rate"],
as_dict=1,
)

View File

@@ -40,16 +40,25 @@ frappe.query_reports["Batch-Wise Balance History"] = {
};
},
},
{
fieldname: "warehouse_type",
label: __("Warehouse Type"),
fieldtype: "Link",
width: "80",
options: "Warehouse Type",
},
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
get_query: function () {
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
let company = frappe.query_report.get_filter_value("company");
return {
filters: {
company: company,
...(warehouse_type && { warehouse_type }),
...(company && { company }),
},
};
},

View File

@@ -30,8 +30,15 @@ def execute(filters=None):
sle_count = _estimate_table_row_count("Stock Ledger Entry")
if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"):
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))
if (
sle_count > SLE_COUNT_LIMIT
and not filters.get("item_code")
and not filters.get("warehouse")
and not filters.get("warehouse_type")
):
frappe.throw(
_("Please select either the Item or Warehouse or Warehouse Type filter to generate the report.")
)
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
@@ -121,6 +128,16 @@ def get_stock_ledger_entries_for_batch_no(filters):
)
query = apply_warehouse_filter(query, sle, filters)
if filters.warehouse_type and not filters.warehouse:
warehouses = frappe.get_all(
"Warehouse",
filters={"warehouse_type": filters.warehouse_type, "is_group": 0},
pluck="name",
)
if warehouses:
query = query.where(sle.warehouse.isin(warehouses))
for field in ["item_code", "batch_no", "company"]:
if filters.get(field):
query = query.where(sle[field] == filters.get(field))
@@ -154,6 +171,16 @@ def get_stock_ledger_entries_for_batch_bundle(filters):
)
query = apply_warehouse_filter(query, sle, filters)
if filters.warehouse_type and not filters.warehouse:
warehouses = frappe.get_all(
"Warehouse",
filters={"warehouse_type": filters.warehouse_type, "is_group": 0},
pluck="name",
)
if warehouses:
query = query.where(sle.warehouse.isin(warehouses))
for field in ["item_code", "batch_no", "company"]:
if filters.get(field):
if field == "batch_no":

View File

@@ -220,7 +220,7 @@ def get_serial_nos(doctype, txt, searchfield, start, page_len, filters):
def get_batch_nos(doctype, txt, searchfield, start, page_len, filters):
query_filters = {}
if txt:
if filters.get("voucher_no") and txt:
query_filters["batch_no"] = ["like", f"%{txt}%"]
if filters.get("voucher_no"):
@@ -239,5 +239,8 @@ def get_batch_nos(doctype, txt, searchfield, start, page_len, filters):
)
else:
if txt:
query_filters["name"] = ["like", f"%{txt}%"]
query_filters["item"] = filters.get("item_code")
return frappe.get_all("Batch", filters=query_filters, as_list=True)

View File

@@ -18,15 +18,24 @@ frappe.query_reports["Stock Ageing"] = {
default: frappe.datetime.get_today(),
reqd: 1,
},
{
fieldname: "warehouse_type",
label: __("Warehouse Type"),
fieldtype: "Link",
width: "80",
options: "Warehouse Type",
},
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
get_query: () => {
const company = frappe.query_report.get_filter_value("company");
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
let company = frappe.query_report.get_filter_value("company");
return {
filters: {
...(warehouse_type && { warehouse_type }),
...(company && { company }),
},
};

View File

@@ -434,6 +434,15 @@ class FIFOSlots:
if self.filters.get("warehouse"):
sle_query = self.__get_warehouse_conditions(sle, sle_query)
elif self.filters.get("warehouse_type"):
warehouses = frappe.get_all(
"Warehouse",
filters={"warehouse_type": self.filters.get("warehouse_type"), "is_group": 0},
pluck="name",
)
if warehouses:
sle_query = sle_query.where(sle.warehouse.isin(warehouses))
sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)

View File

@@ -146,6 +146,8 @@ class StockBalanceReport:
if self.filters.get("show_stock_ageing_data"):
self.sle_entries = self.sle_query.run(as_dict=True)
# HACK: This is required to avoid causing db query in flt
_system_settings = frappe.get_cached_doc("System Settings")
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)

View File

@@ -231,13 +231,6 @@ def get_columns(filters):
"width": 100,
"convertible": "qty",
},
{
"label": _("Voucher #"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
"width": 150,
},
{
"label": _("Warehouse"),
"fieldname": "warehouse",

View File

@@ -56,13 +56,14 @@ def execute(filters=None):
item_value.setdefault((item, item_map[item]["item_group"]), [])
item_value[(item, item_map[item]["item_group"])].append(total_stock_value)
itemwise_brand = frappe._dict(get_itemwise_brand(items))
# sum bal_qty by item
for (item, item_group), wh_balance in item_balance.items():
if not item_ageing.get(item):
continue
total_stock_value = sum(item_value[(item, item_group)])
row = [item, item_map[item]["item_name"], item_group, total_stock_value]
row = [item, item_map[item]["item_name"], item_group, itemwise_brand.get(item), total_stock_value]
fifo_queue = item_ageing[item]["fifo_queue"]
average_age = 0.00
@@ -85,6 +86,10 @@ def execute(filters=None):
return columns, data
def get_itemwise_brand(items):
return frappe.get_all("Item", filters={"name": ("in", items)}, fields=["name", "brand"], as_list=1)
def get_columns(filters):
"""return columns"""
@@ -92,6 +97,7 @@ def get_columns(filters):
_("Item") + ":Link/Item:150",
_("Item Name") + ":Link/Item:150",
_("Item Group") + "::120",
_("Brand") + ":Link/Brand:120",
_("Value") + ":Currency:120",
_("Age") + ":Float:120",
]

View File

@@ -553,7 +553,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
self.set_stock_value_difference()
def get_batch_no_ledgers(self) -> list[dict]:
if not self.batchwise_valuation_batches:
if not self.batches:
return []
parent = frappe.qb.DocType("Serial and Batch Bundle")
@@ -575,7 +575,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
Sum(child.qty).as_("qty"),
)
.where(
(child.batch_no.isin(self.batchwise_valuation_batches))
(child.batch_no.isin(self.batches))
& (parent.warehouse == self.sle.warehouse)
& (parent.item_code == self.sle.item_code)
& (parent.docstatus == 1)
@@ -840,6 +840,9 @@ class SerialBatchCreation:
self.set_auto_serial_batch_entries_for_inward()
self.add_serial_nos_for_batch_item()
if hasattr(self, "via_landed_cost_voucher") and self.via_landed_cost_voucher:
doc.flags.via_landed_cost_voucher = self.via_landed_cost_voucher
self.set_serial_batch_entries(doc)
if not doc.get("entries"):
return frappe._dict({})

View File

@@ -1 +0,0 @@
Married,既婚,
1 Married 既婚

View File

@@ -67,3 +67,9 @@ typing-modules = ["frappe.types.DF"]
quote-style = "double"
indent-style = "tab"
docstring-code-format = true
[project.urls]
Homepage = "https://erpnext.com/"
Repository = "https://github.com/frappe/erpnext.git"
"Bug Reports" = "https://github.com/frappe/erpnext/issues"