Compare commits

...

355 Commits

Author SHA1 Message Date
Frappe PR Bot
4d804951c6 chore(release): Bumped to Version 14.47.0
# [14.47.0](https://github.com/frappe/erpnext/compare/v14.46.7...v14.47.0) (2023-11-08)

### Bug Fixes

* `TypeError` in PR for non-stock item ([ef4471f](ef4471f8c0))
* add missing disbursement account in update_old_loans patch ([ad64065](ad64065ec6))
* add translation wrapper (backport [#37911](https://github.com/frappe/erpnext/issues/37911)) ([#37946](https://github.com/frappe/erpnext/issues/37946)) ([c53d281](c53d281b9e))
* don't reset rate if greater than zero in standalone debit note (backport [#37935](https://github.com/frappe/erpnext/issues/37935)) ([#37940](https://github.com/frappe/erpnext/issues/37940)) ([64658fc](64658fc790))
* fetch asset received but not billed account only when needed ([7df4009](7df4009dd3))
* gov compliance for tax withholding report ([#37590](https://github.com/frappe/erpnext/issues/37590)) ([273cc35](273cc35b91))
* In-Transit Warehouse company filter (backport [#37796](https://github.com/frappe/erpnext/issues/37796)) ([#37797](https://github.com/frappe/erpnext/issues/37797)) ([0b1da08](0b1da08d78))
* link between parent and child procedure (backport [#37903](https://github.com/frappe/erpnext/issues/37903)) ([#37943](https://github.com/frappe/erpnext/issues/37943)) ([24da29a](24da29a817))
* list index out of range ([#37880](https://github.com/frappe/erpnext/issues/37880)) ([05fed9e](05fed9e352))
* make `Material Request Item` required if `Material Request` is set in PO (backport [#37928](https://github.com/frappe/erpnext/issues/37928)) ([#37936](https://github.com/frappe/erpnext/issues/37936)) ([c870120](c87012046d))
* permission error while creating Supplier Quotation from Portal (backport [#37864](https://github.com/frappe/erpnext/issues/37864)) ([#37870](https://github.com/frappe/erpnext/issues/37870)) ([1a3dae0](1a3dae0f9a))
* **plaid:** Do not sync pending transactions ([ffdc11b](ffdc11bbf6))
* POS change amount gl entry with no amount ([#37799](https://github.com/frappe/erpnext/issues/37799)) ([5e1a89b](5e1a89b3f9))
* Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport [#37499](https://github.com/frappe/erpnext/issues/37499)) ([#37916](https://github.com/frappe/erpnext/issues/37916)) ([eb3630b](eb3630bcf8))
* remove voucher type and no for Item and Warehouse based reposting (backport [#37849](https://github.com/frappe/erpnext/issues/37849)) ([#37850](https://github.com/frappe/erpnext/issues/37850)) ([e19cade](e19cade12d))
* standard submit perm in repost ledger for editable invoices (backport [#37826](https://github.com/frappe/erpnext/issues/37826)) ([#37854](https://github.com/frappe/erpnext/issues/37854)) ([69b569d](69b569df46))
* status for over delivery or billing ([d8e7430](d8e743013f))
* type error on new payment entry ([38184f7](38184f77c7))
* typo in AR report ([3d72ec7](3d72ec7ca4))
* validate so item with qtn ([4a225b0](4a225b069b))

### Features

* reserved production plan sub assembly items (backport [#37884](https://github.com/frappe/erpnext/issues/37884)) ([#37926](https://github.com/frappe/erpnext/issues/37926)) ([4de97a4](4de97a4fcb))
* settings page for repost ([09cde5d](09cde5da08))
* **Stock Balance:** add filters from route (backport [#37836](https://github.com/frappe/erpnext/issues/37836)) ([#37839](https://github.com/frappe/erpnext/issues/37839)) ([e8a706a](e8a706a5ea))

### Performance Improvements

* Add index to supplier invoice field (backport [#37861](https://github.com/frappe/erpnext/issues/37861)) ([#37862](https://github.com/frappe/erpnext/issues/37862)) ([2c93f81](2c93f81d59))
* index return against for purchase invoice (backport [#37881](https://github.com/frappe/erpnext/issues/37881)) ([#37882](https://github.com/frappe/erpnext/issues/37882)) ([fed25fa](fed25fac76))
2023-11-08 06:19:10 +00:00
Deepesh Garg
c5ddb4153a Merge pull request #37965 from frappe/version-14-hotfix
chore: release v14
2023-11-08 11:47:53 +05:30
ruthra kumar
b0ed3c2f0a Merge pull request #37977 from ruthra-kumar/add_voucher_detail_no_to_payment_ledger
refactor: Add voucher_detail_no to payment ledger
2023-11-08 11:23:15 +05:30
ruthra kumar
3f8d58c9c6 refactor: add voucher_detail_no to PLE doctype 2023-11-08 10:53:16 +05:30
ruthra kumar
8a3139f6f5 Merge pull request #37901 from frappe/mergify/bp/version-14-hotfix/pr-37887
chore: performance optimization on payment ledger entry doctype (backport #37887)
2023-11-08 10:48:30 +05:30
ruthra kumar
0b601d8cae chore: performance optimization on payment ledger entry doctype
(cherry picked from commit f14d1eb871)
2023-11-08 10:23:52 +05:30
ruthra kumar
9be71e45b9 Merge pull request #37975 from frappe/mergify/bp/version-14-hotfix/pr-37971
refactor: optimize bulk transaction for speed (backport #37971)
2023-11-08 07:08:42 +05:30
ruthra kumar
e58604a90f refactor: optimize for speed
(cherry picked from commit 416bd400bb)
2023-11-08 01:03:28 +00:00
mergify[bot]
e895bb3a63 chore: typo in Stock Entry enqueue msg (backport #37970) (#37972)
chore: typo in `Stock Entry` enqueue msg

(cherry picked from commit ee60fa940c)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-07 15:57:20 +00:00
ruthra kumar
38a5e6f10b Merge pull request #37967 from frappe/mergify/bp/version-14-hotfix/pr-37954
refactor: expand repost to `Expense Claim` and make it configurable (backport #37954)
2023-11-07 16:24:44 +05:30
ruthra kumar
8f0cf1b7dc chore: resolve conflict 2023-11-07 15:56:35 +05:30
ruthra kumar
3da2597703 refactor: update permissions for repost settings
(cherry picked from commit 10b9570429)
2023-11-07 10:14:36 +00:00
ruthra kumar
82bf726b83 refactor(test): repost test case for purchase invoice
(cherry picked from commit 11c8d9fcf1)
2023-11-07 10:14:35 +00:00
ruthra kumar
7d4dc67b61 refactor: select distinct types
(cherry picked from commit 61705047b0)
2023-11-07 10:14:33 +00:00
ruthra kumar
35f5498913 refactor(test): update repost settings for test cases
(cherry picked from commit ac79b8483f)
2023-11-07 10:14:32 +00:00
ruthra kumar
ed557274d2 refactor: support for expense claim repost
(cherry picked from commit b651b36fff)
2023-11-07 10:14:30 +00:00
ruthra kumar
18e6931d78 refactor: configurable repost settings
(cherry picked from commit 5a068410c6)
2023-11-07 10:14:29 +00:00
ruthra kumar
560a45b343 chore: patch to update default repost settings value
(cherry picked from commit ebb186c8df)

# Conflicts:
#	erpnext/patches.txt
2023-11-07 10:14:28 +00:00
ruthra kumar
09cde5da08 feat: settings page for repost
(cherry picked from commit d582a73795)
2023-11-07 10:14:21 +00:00
ruthra kumar
503c0f33bb Merge pull request #37957 from frappe/mergify/bp/version-14-hotfix/pr-37956
fix: type error on new payment entry (backport #37956)
2023-11-07 12:47:10 +05:30
ruthra kumar
38184f77c7 fix: type error on new payment entry
(cherry picked from commit adff287160)
2023-11-07 12:40:16 +05:30
Frappe PR Bot
449b641560 chore(release): Bumped to Version 14.46.7
## [14.46.7](https://github.com/frappe/erpnext/compare/v14.46.6...v14.46.7) (2023-11-06)

### Bug Fixes

* typo in AR report ([d54e756](d54e756453))
2023-11-06 15:33:32 +00:00
ruthra kumar
3cc5e5591d Merge pull request #37951 from frappe/mergify/bp/version-14/pr-37948
fix: typo in AR report (backport #37948)
2023-11-06 21:00:23 +05:30
ruthra kumar
bc5e01e846 Merge pull request #37949 from frappe/mergify/bp/version-14-hotfix/pr-37948
fix: typo in AR report (backport #37948)
2023-11-06 20:55:22 +05:30
ruthra kumar
d54e756453 fix: typo in AR report
(cherry picked from commit 67e74d03ed)
2023-11-06 15:00:11 +00:00
ruthra kumar
3d72ec7ca4 fix: typo in AR report
(cherry picked from commit 67e74d03ed)
2023-11-06 14:56:44 +00:00
mergify[bot]
c53d281b9e fix: add translation wrapper (backport #37911) (#37946)
fix: add translation wrapper

(cherry picked from commit 8722318081)

Co-authored-by: hyaray <hyaray@vip.qq.com>
2023-11-06 19:52:11 +05:30
Frappe PR Bot
94b38b900c chore(release): Bumped to Version 14.46.6
## [14.46.6](https://github.com/frappe/erpnext/compare/v14.46.5...v14.46.6) (2023-11-06)

### Bug Fixes

* don't reset rate if greater than zero in standalone debit note (backport [#37935](https://github.com/frappe/erpnext/issues/37935)) (backport [#37940](https://github.com/frappe/erpnext/issues/37940)) ([#37945](https://github.com/frappe/erpnext/issues/37945)) ([235f166](235f1664e0))
2023-11-06 14:15:53 +00:00
mergify[bot]
235f1664e0 fix: don't reset rate if greater than zero in standalone debit note (backport #37935) (backport #37940) (#37945)
fix: don't reset rate if greater than zero in standalone debit note (backport #37935) (#37940)

* fix: don't reset rate if greater than zero in standalone debit note

(cherry picked from commit 5cce522ecd)

* fix(test): `test_gl_entries_for_standalone_debit_note`

(cherry picked from commit f9fc6c9c9d)

---------

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

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-06 19:37:15 +05:30
mergify[bot]
24da29a817 fix: link between parent and child procedure (backport #37903) (#37943)
* fix: link between parent and child procedure

(cherry picked from commit 05f24ede96)

* chore: add missing filters for `Parent Procedure`

(cherry picked from commit 8fbd4cea5b)

* test: add test case for Quality Procedure`

(cherry picked from commit 30c6b83a10)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-06 19:28:19 +05:30
mergify[bot]
64658fc790 fix: don't reset rate if greater than zero in standalone debit note (backport #37935) (#37940)
* fix: don't reset rate if greater than zero in standalone debit note

(cherry picked from commit 5cce522ecd)

* fix(test): `test_gl_entries_for_standalone_debit_note`

(cherry picked from commit f9fc6c9c9d)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-06 19:27:28 +05:30
mergify[bot]
c87012046d fix: make Material Request Item required if Material Request is set in PO (backport #37928) (#37936)
fix: make `Material Request Item` required if `Material Request` is set in PO

(cherry picked from commit a9d91189b0)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-06 17:26:37 +05:30
Frappe PR Bot
21ad1dba19 chore(release): Bumped to Version 14.46.5
## [14.46.5](https://github.com/frappe/erpnext/compare/v14.46.4...v14.46.5) (2023-11-06)

### Bug Fixes

* additional filter in ar/ap report ([930aee1](930aee1d70))
2023-11-06 06:42:02 +00:00
ruthra kumar
9c356a0ac2 Merge pull request #37929 from frappe/mergify/bp/version-14/pr-37873
refactor: 'group only by voucher' flag in AR/AP report (backport #37869) (backport #37873)
2023-11-06 12:10:29 +05:30
ruthra kumar
930aee1d70 fix: additional filter in ar/ap report
empty commit
2023-11-06 11:51:39 +05:30
mergify[bot]
4de97a4fcb feat: reserved production plan sub assembly items (backport #37884) (#37926)
feat: reserved production plan sub assembly items (#37884)

(cherry picked from commit 34d3eb88b3)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-11-06 11:40:21 +05:30
ruthra kumar
932d1e7c62 Merge pull request #37924 from frappe/mergify/bp/version-14-hotfix/pr-37879
refactor: flag to toggle billed amy update in DN for Credit Note (backport #37879)
2023-11-06 11:24:08 +05:30
ruthra kumar
a8b26ebc6b refactor: group only by voucher flag in AR/AP report
(cherry picked from commit 23beb46d15)
(cherry picked from commit ee5898a773)
2023-11-06 05:53:50 +00:00
ruthra kumar
71ad448281 refactor(test): enable billed amt update on Sales Return(Cr Note)
(cherry picked from commit 0c5bdbdcf3)
2023-11-06 09:40:32 +05:30
ruthra kumar
6eb867a1e4 refactor: flag to toggle billed amy update in DN for Credit Note
(cherry picked from commit a3191f1c8c)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json
2023-11-06 03:11:20 +00:00
mergify[bot]
5e1a89b3f9 fix: POS change amount gl entry with no amount (#37799)
fix: POS change amount gl entry with no amount (#37799)

(cherry picked from commit 2b02ef0066)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-11-05 17:56:19 +05:30
Frappe PR Bot
b2b8b72952 chore(release): Bumped to Version 14.46.4
## [14.46.4](https://github.com/frappe/erpnext/compare/v14.46.3...v14.46.4) (2023-11-05)

### Bug Fixes

* Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport [#37499](https://github.com/frappe/erpnext/issues/37499)) (backport [#37916](https://github.com/frappe/erpnext/issues/37916)) ([#37919](https://github.com/frappe/erpnext/issues/37919)) ([555ebc1](555ebc190c))
2023-11-05 06:41:12 +00:00
mergify[bot]
555ebc190c fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport #37499) (backport #37916) (#37919)
fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport #37499) (#37916)

fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (#37499)

* fix: account for case-insensitive database primary key for parameter names

* chore: linting

(cherry picked from commit b099590b2c)

Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com>
(cherry picked from commit eb3630bcf8)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-05 12:10:04 +05:30
viralkansodiya15
05fed9e352 fix: list index out of range (#37880)
* fix: list index out of range

* fix: changes in error message

* fix: change error message and date comparision

* fix: change a return a value when disposal date is equal to available for use date

* fix: changes to fix linter test failing
2023-11-05 11:54:39 +05:30
mergify[bot]
eb3630bcf8 fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport #37499) (#37916)
fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (#37499)

* fix: account for case-insensitive database primary key for parameter names

* chore: linting

(cherry picked from commit b099590b2c)

Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com>
2023-11-05 06:16:23 +00:00
ruthra kumar
c2001bec15 Merge pull request #37915 from ruthra-kumar/prevent_precision_based_validation_error_reconciliation
refactor: avoid precision based validation error on reconciliation
2023-11-05 05:58:32 +05:30
ruthra kumar
be925d2d33 refactor: avoid precision based validation error on reconciliation 2023-11-05 05:22:09 +05:30
Anand Baburajan
65dfef021a chore: rename depreciation_amount_based_on_num_days_in_month to daily_prorata_based [v14] (#37898)
chore: rename depreciation_amount_based_on_num_days_in_month to daily_prorata_based
2023-11-03 20:18:20 +00:00
Anand Baburajan
86c6ae6275 chore: rename daily_depreciation in asset to depreciation_amount_based_on_num_days_in_month [v14] (#37895)
chore: rename daily_depreciation to depreciation_amount_based_on_num_days_in_month
2023-11-03 23:26:34 +05:30
mergify[bot]
fed25fac76 perf: index return against for purchase invoice (backport #37881) (#37882)
perf: index return against for purchase invoice (#37881)

(cherry picked from commit 469ae2c7f1)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-11-03 21:59:46 +05:30
Frappe PR Bot
58eaaaa8ad chore(release): Bumped to Version 14.46.3
## [14.46.3](https://github.com/frappe/erpnext/compare/v14.46.2...v14.46.3) (2023-11-03)

### Bug Fixes

* add missing disbursement account in update_old_loans patch ([00bbf0f](00bbf0fbc5))
2023-11-03 16:29:27 +00:00
Anand Baburajan
df00bde748 Merge pull request #37894 from frappe/mergify/bp/version-14/pr-37889
fix: add missing disbursement account in update_old_loans patch (backport #37889)
2023-11-03 21:57:35 +05:30
anandbaburajan
00bbf0fbc5 fix: add missing disbursement account in update_old_loans patch
(cherry picked from commit ad64065ec6)
2023-11-03 16:25:46 +00:00
Anand Baburajan
6b5cffc77a Merge pull request #37889 from anandbaburajan/update_old_loans_disb_account
fix: add missing disbursement account in update_old_loans patch
2023-11-03 21:45:33 +05:30
anandbaburajan
ad64065ec6 fix: add missing disbursement account in update_old_loans patch 2023-11-03 20:57:52 +05:30
mergify[bot]
1a3dae0f9a fix: permission error while creating Supplier Quotation from Portal (backport #37864) (#37870)
* fix: permission error while creating Supplier Quotation from Portal

(cherry picked from commit e019d43d0b)

# Conflicts:
#	erpnext/controllers/buying_controller.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-03 16:24:36 +05:30
ruthra kumar
15308c9aac Merge pull request #37875 from frappe/mergify/bp/version-14-hotfix/pr-37852
refactor: better ledger comparision report (backport #37852)
2023-11-03 13:25:22 +05:30
ruthra kumar
09ba6c1266 Merge pull request #37873 from frappe/mergify/bp/version-14-hotfix/pr-37869
refactor: 'group only by voucher' flag in AR/AP report (backport #37869)
2023-11-03 13:25:04 +05:30
ruthra kumar
afa6ed9e22 refactor(test): for ledger comparision report
(cherry picked from commit 639f427d6d)
2023-11-03 07:22:59 +00:00
ruthra kumar
3d2ff7eea3 refactor: better output on gl and pl comparison report
(cherry picked from commit 539f0251d9)
2023-11-03 07:22:58 +00:00
ruthra kumar
ee5898a773 refactor: group only by voucher flag in AR/AP report
(cherry picked from commit 23beb46d15)
2023-11-03 07:00:25 +00:00
mergify[bot]
2c93f81d59 perf: Add index to supplier invoice field (backport #37861) (#37862)
fix: Add index to supplier invoice field (#37861)

* fix: Add index to supplier invoice field

* chore: remove unintetional changes

(cherry picked from commit c37e374fdd)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-11-03 11:52:07 +05:30
mergify[bot]
e19cade12d fix: remove voucher type and no for Item and Warehouse based reposting (backport #37849) (#37850)
* fix: remove voucher type and no for Item and Warehouse based reposting

(cherry picked from commit 0104897d69)

* chore: fix test cases

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2023-11-02 15:15:53 +05:30
mergify[bot]
273cc35b91 fix: gov compliance for tax withholding report (#37590)
* refactor: avoid relying only on against in tds docs query

(cherry picked from commit 705dadae8e)

* chore: change column order

(cherry picked from commit 7ecc0d5a04)

* fix: sort by section code

(cherry picked from commit 4471ad581e)

* feat: proprietorship & partnership options in entity type

(cherry picked from commit ed2457bddf)

# Conflicts:
#	erpnext/buying/doctype/supplier/supplier.json
#	erpnext/selling/doctype/customer/customer.json

* chore: linting issues

(cherry picked from commit 75441017c6)

* feat: add cols for supplier inv details

(cherry picked from commit 6d5ccde864)

* chore: resolve conflicts

* chore: resolve conflicts

* chore: linting issues

---------

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-11-02 14:34:23 +05:30
mergify[bot]
69b569df46 fix: standard submit perm in repost ledger for editable invoices (backport #37826) (#37854)
fix: standard submit perm in repost ledger for editable invoices (#37826)

* fix: ignore perm while reposting ledger

* fix: use flag in save

* fix: remove unnecessary save

(cherry picked from commit 1b808e1d7c)

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-11-02 14:33:33 +05:30
Frappe PR Bot
99338b6d01 chore(release): Bumped to Version 14.46.2
## [14.46.2](https://github.com/frappe/erpnext/compare/v14.46.1...v14.46.2) (2023-11-02)

### Bug Fixes

* `TypeError` in PR for non-stock item (backport [#37819](https://github.com/frappe/erpnext/issues/37819)) (backport [#37841](https://github.com/frappe/erpnext/issues/37841)) ([#37853](https://github.com/frappe/erpnext/issues/37853)) ([f6418c1](f6418c14dd))
2023-11-02 08:33:41 +00:00
mergify[bot]
f6418c14dd fix: TypeError in PR for non-stock item (backport #37819) (backport #37841) (#37853)
fix: `TypeError` in PR for non-stock item

(cherry picked from commit 028b3e2fbf)
(cherry picked from commit ef4471f8c0)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-02 14:02:07 +05:30
rohitwaghchaure
a07d58bd0e Merge pull request #37841 from frappe/mergify/bp/version-14-hotfix/pr-37819
fix: `TypeError` in PR for non-stock item (backport #37819)
2023-11-02 13:34:23 +05:30
ruthra kumar
50fdc9dda0 Merge pull request #37846 from frappe/mergify/copy/version-14-hotfix/pr-37845
chore: add std permissions for Process Payment Reconciilation log (copy #37845)
2023-11-02 12:10:27 +05:30
ruthra kumar
9b0a46853f chore: std permissions for Process Payment Reconciilation log
(cherry picked from commit a9fceeb00f)
2023-11-02 06:10:54 +00:00
ruthra kumar
64d7590bd0 Merge pull request #37843 from frappe/mergify/bp/version-14-hotfix/pr-37838
refactor: pass limits to JE and PE queries in reconciliation tool (backport #37838)
2023-11-02 11:08:51 +05:30
mergify[bot]
e8a706a5ea feat(Stock Balance): add filters from route (backport #37836) (#37839)
feat(Stock Balance): add filters from route

(cherry picked from commit 38e5e4a893)

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2023-11-02 10:46:42 +05:30
ruthra kumar
289236d065 refactor: pass limits to JE and PE queries in reconciliation tool
(cherry picked from commit 54e8ce1ac5)
2023-11-02 05:13:08 +00:00
s-aga-r
ef4471f8c0 fix: TypeError in PR for non-stock item
(cherry picked from commit 028b3e2fbf)
2023-11-02 05:10:19 +00:00
ruthra kumar
e616a0bb63 Merge pull request #37833 from frappe/mergify/bp/version-14-hotfix/pr-37832
refactor: checkbox to toggle remarks in General Ledger (backport #37832)
2023-11-02 08:06:40 +05:30
ruthra kumar
174b76f6ff refactor: checkbox to toggle remarks in General Ledger
(cherry picked from commit 8fa677b8e8)
2023-11-02 07:28:42 +05:30
mergify[bot]
875d7f8b72 refactor: update fields label and remove unused fields from BIN (backport #37827) (#37829)
* refactor: rearrange fields and update label

(cherry picked from commit ec1a7869f8)

* refactor: remove unused fields `fcfs_rate` and `ma_rate` from Bin

(cherry picked from commit f0a1f4ac7c)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-01 16:41:33 +05:30
Deepesh Garg
f502ec2268 Merge pull request #37821 from frappe/mergify/bp/version-14-hotfix/pr-37635
fix: validate sales order item with quotation (backport #37635)
2023-11-01 15:13:26 +05:30
Deepesh Garg
0196d7ac23 Merge pull request #37717 from frappe/mergify/bp/version-14-hotfix/pr-37690
fix(plaid): Do not sync pending transactions (#37690)
2023-11-01 14:12:33 +05:30
Deepesh Garg
357697702b chore: resolve conflicts 2023-11-01 14:11:36 +05:30
Gursheen Anand
4a225b069b fix: validate so item with qtn
(cherry picked from commit 17ebc1ea80)
2023-11-01 08:34:50 +00:00
Deepesh Garg
28c9458482 Merge pull request #37815 from frappe/mergify/bp/version-14-hotfix/pr-37680
fix: status when over delivery or billing in SO (#37680)
2023-11-01 14:03:59 +05:30
Gursheen Anand
d8e743013f fix: status for over delivery or billing
(cherry picked from commit d69b0d76dd)
2023-11-01 07:00:14 +00:00
Frappe PR Bot
42ccc092fe chore(release): Bumped to Version 14.46.1
## [14.46.1](https://github.com/frappe/erpnext/compare/v14.46.0...v14.46.1) (2023-11-01)

### Bug Fixes

* fetch asset received but not billed account only when needed ([487aa35](487aa35ee3))
2023-11-01 06:43:35 +00:00
Deepesh Garg
222bce62bf Merge pull request #37814 from frappe/mergify/bp/version-14/pr-37812
fix: fetch asset received but not billed account only when needed (#37737)
2023-11-01 12:12:02 +05:30
Deepesh Garg
487aa35ee3 fix: fetch asset received but not billed account only when needed
(cherry picked from commit 3a8736374c)
(cherry picked from commit 7df4009dd3)
2023-11-01 06:41:33 +00:00
Deepesh Garg
555cc62e50 Merge pull request #37812 from frappe/mergify/bp/version-14-hotfix/pr-37737
fix: fetch asset received but not billed account only when needed (backport #37737)
2023-11-01 12:10:04 +05:30
Deepesh Garg
7df4009dd3 fix: fetch asset received but not billed account only when needed
(cherry picked from commit 3a8736374c)
2023-11-01 04:49:59 +00:00
ruthra kumar
08a9a9d188 Merge pull request #37806 from frappe/mergify/bp/version-14-hotfix/pr-37795
chore: update default limit values in reconciliation tool (backport #37795)
2023-10-31 20:37:40 +05:30
ruthra kumar
e98be384f9 Merge pull request #37804 from frappe/mergify/bp/version-14-hotfix/pr-37793
refactor: pull remarks only if needed on AR/AP report (backport #37793)
2023-10-31 20:37:06 +05:30
ruthra kumar
fdf2977ce2 chore: update default limit values in reconciliation tool
(cherry picked from commit 1fd888175f)
2023-10-31 14:22:29 +00:00
ruthra kumar
3276f39740 refactor: pull remarks only if needed on AR/AP report
(cherry picked from commit eb73017798)
2023-10-31 14:21:52 +00:00
mergify[bot]
0b1da08d78 fix: In-Transit Warehouse company filter (backport #37796) (#37797)
fix: In-Transit Warehouse company filter (#37796)

(cherry picked from commit daf2ec063c)

Co-authored-by: hyaray <hyaray@vip.qq.com>
2023-10-31 18:53:37 +05:30
Frappe PR Bot
0f848fd968 chore(release): Bumped to Version 14.46.0
# [14.46.0](https://github.com/frappe/erpnext/compare/v14.45.4...v14.46.0) (2023-10-31)

### Bug Fixes

* add regional support to extend purchase gl entries ([d02ebd6](d02ebd6215))
* avoid name clash in delivery stop (backport [#37306](https://github.com/frappe/erpnext/issues/37306)) ([#37701](https://github.com/frappe/erpnext/issues/37701)) ([556095d](556095daaa))
* Cash flow mapping fix ([#37522](https://github.com/frappe/erpnext/issues/37522)) ([8e31379](8e31379ecc))
* close employee loan on write off ([#37638](https://github.com/frappe/erpnext/issues/37638)) ([922ace4](922ace4076))
* **defaults:** apply discount and provisonal defaults from item group and brand if available (backport [#37466](https://github.com/frappe/erpnext/issues/37466)) ([#37703](https://github.com/frappe/erpnext/issues/37703)) ([a0893dd](a0893ddf96))
* **delivery:** rename dt fetch stop action (backport [#37605](https://github.com/frappe/erpnext/issues/37605)) ([#37606](https://github.com/frappe/erpnext/issues/37606)) ([8660faa](8660faaa54))
* GL Entries for receiving non CWIP assets using Purchase Receipt ([#37660](https://github.com/frappe/erpnext/issues/37660)) ([80774e2](80774e2da1))
* incorrect cost center in the purchase invoice (backport [#37591](https://github.com/frappe/erpnext/issues/37591)) ([#37609](https://github.com/frappe/erpnext/issues/37609)) ([50daf70](50daf701dd))
* incorrect material request quantity in production plan ([#37785](https://github.com/frappe/erpnext/issues/37785)) ([25718d9](25718d9f1b))
* incorrect process loss validation for multiple finished items (backport [#37576](https://github.com/frappe/erpnext/issues/37576)) ([#37656](https://github.com/frappe/erpnext/issues/37656)) ([638c271](638c271d70))
* indexing on Delivery Note Item (backport [#37766](https://github.com/frappe/erpnext/issues/37766)) ([#37777](https://github.com/frappe/erpnext/issues/37777)) ([9b66a06](9b66a06c86))
* make changes that enable gantt view for job cards (backport [#37661](https://github.com/frappe/erpnext/issues/37661)) ([#37756](https://github.com/frappe/erpnext/issues/37756)) ([712ddb7](712ddb75be))
* **minor:** filter bank accounts in bank statement import ([#37525](https://github.com/frappe/erpnext/issues/37525)) ([1cb9f4c](1cb9f4cf8b))
* **minor:** filter tax template based on company in subscription ([#37562](https://github.com/frappe/erpnext/issues/37562)) ([c05e0a4](c05e0a4ffc))
* **minor:** set tax values for item variants (backport [#37674](https://github.com/frappe/erpnext/issues/37674)) ([#37738](https://github.com/frappe/erpnext/issues/37738)) ([fabcfc1](fabcfc1fce))
* negative current qty causing recursion issue ([#37752](https://github.com/frappe/erpnext/issues/37752)) ([f1407bc](f1407bcfd2))
* overallocation on Payment with PO/SO ([d71b885](d71b885fb8))
* purchase receipt with stock and asset items ([375be8c](375be8cd93))
* remove from or target warehouse for non internal transfer entries (backport [#37612](https://github.com/frappe/erpnext/issues/37612)) ([#37628](https://github.com/frappe/erpnext/issues/37628)) ([78b7c26](78b7c26420))
* set correct `purchase_sle` in `get_last_sle()` ([#37708](https://github.com/frappe/erpnext/issues/37708)) ([86cf156](86cf156968))
* set empty value for tax template in item details ([#37496](https://github.com/frappe/erpnext/issues/37496)) ([ec208b8](ec208b8df5))
* typeerror on tds payable monthly report ([fea27d5](fea27d5e2e))
* update existing doc if possible ([89f07dc](89f07dcfac))
* wrong german translation ([#37658](https://github.com/frappe/erpnext/issues/37658)) ([fa5780c](fa5780ca81))

### Features

* allow return of components for SCO that don't have SCR created (backport [#37686](https://github.com/frappe/erpnext/issues/37686)) ([#37692](https://github.com/frappe/erpnext/issues/37692)) ([e96d5b3](e96d5b314a))
* **delivery:** link to delivery notes list view from delivery trip (backport [#37604](https://github.com/frappe/erpnext/issues/37604)) ([#37695](https://github.com/frappe/erpnext/issues/37695)) ([c58fefb](c58fefb359))

### Reverts

* Revert "fix: set empty value for tax template in item details (#37496)" ([ca13816](ca13816ca1)), closes [#37496](https://github.com/frappe/erpnext/issues/37496) [#37496](https://github.com/frappe/erpnext/issues/37496)
2023-10-31 12:45:11 +00:00
rohitwaghchaure
c117ab851a Merge pull request #37788 from frappe/version-14-hotfix
chore: release v14
2023-10-31 18:13:28 +05:30
rohitwaghchaure
b7b62a8966 Merge branch 'version-14' into version-14-hotfix 2023-10-31 17:38:42 +05:30
Samuel Danieli
86cf156968 fix: set correct purchase_sle in get_last_sle() (#37708)
sle_dict may look like this:
{
  'incoming': [
    {... Stock Entry ...},
    {... Purchase Receipt ...}
  ],
  'outgoing': [
    {... Stock Entry ...}
  ]
}
2023-10-31 16:07:10 +05:30
rohitwaghchaure
25718d9f1b fix: incorrect material request quantity in production plan (#37785) 2023-10-31 15:12:35 +05:30
mergify[bot]
9b66a06c86 fix: indexing on Delivery Note Item (backport #37766) (#37777)
fix: indexing on Delivery Note Item (#37766)

fix: added indexing on Delivery Note Item
(cherry picked from commit 056b74b162)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-31 14:31:38 +05:30
ruthra kumar
7c2bd24d0b Merge pull request #37765 from frappe/mergify/bp/version-14/pr-37761
chore: add index to posting_date in PLE (backport #37761)
2023-10-30 17:39:53 +05:30
ruthra kumar
af57cc6c5f Merge pull request #37762 from frappe/mergify/bp/version-14-hotfix/pr-37761
chore: add index to posting_date in PLE (backport #37761)
2023-10-30 17:31:08 +05:30
ruthra kumar
25448fcdbc chore: add index to posting_date in PLE
(cherry picked from commit ca69845238)
2023-10-30 17:01:38 +05:30
ruthra kumar
69e83ff6ab chore: add index to posting_date in PLE
(cherry picked from commit ca69845238)
2023-10-30 16:59:56 +05:30
ruthra kumar
87a8c8d08e Merge pull request #37760 from frappe/mergify/bp/version-14-hotfix/pr-37720
refactor: ignore cancelled GLE's while looking for currency of existing entries (backport #37720)
2023-10-30 16:52:50 +05:30
ruthra kumar
4c01128827 refactor: ignore cancelled GLE's while looking for currency
(cherry picked from commit 8d9b90f3f5)
2023-10-30 10:43:02 +00:00
mergify[bot]
712ddb75be fix: make changes that enable gantt view for job cards (backport #37661) (#37756)
fix: make changes that enable gantt view for job cards (#37661)

* fix: make changes that enable gantt view for job cards

* fix: add fields on listview and remove from json file

* fix: undo modified date

---------

Co-authored-by: Dietmar Fischer <fischer@kk-software.de>
(cherry picked from commit 500435b856)

Co-authored-by: Didiman1998 <118364772+Didiman1998@users.noreply.github.com>
2023-10-30 15:20:26 +05:30
Frappe PR Bot
78a9edf6c9 chore(release): Bumped to Version 14.45.4
## [14.45.4](https://github.com/frappe/erpnext/compare/v14.45.3...v14.45.4) (2023-10-30)

### Bug Fixes

* negative current qty causing recursion issue (backport [#37752](https://github.com/frappe/erpnext/issues/37752)) ([#37753](https://github.com/frappe/erpnext/issues/37753)) ([20ca948](20ca948e6b))
2023-10-30 08:55:38 +00:00
mergify[bot]
20ca948e6b fix: negative current qty causing recursion issue (backport #37752) (#37753)
fix: negative current qty causing recursion issue (#37752)

(cherry picked from commit f1407bcfd2)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-30 14:23:39 +05:30
rohitwaghchaure
f1407bcfd2 fix: negative current qty causing recursion issue (#37752) 2023-10-30 13:53:48 +05:30
mergify[bot]
b605b08ec1 refactor: remove extraneous disabled filters (backport #37732) (#37748)
* refactor: remove extraneous disabled filters

(cherry picked from commit f276fbba4f)

# Conflicts:
#	erpnext/accounts/report/profitability_analysis/profitability_analysis.js
#	erpnext/public/js/controllers/accounts.js

* chore: `conflicts`

---------

Co-authored-by: Bernd Oliver Sünderhauf <46800703+bosue@users.noreply.github.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-30 10:10:16 +05:30
mergify[bot]
fabcfc1fce fix(minor): set tax values for item variants (backport #37674) (#37738)
* fix: copy all child fields to item variant

(cherry picked from commit 5deba1b6f9)

* fix: only update if variant table empty

(cherry picked from commit d436a40739)

---------

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
2023-10-29 12:16:34 +05:30
Anand Baburajan
cfb9d8c6be Merge pull request #37730 from frappe/mergify/copy/version-14/pr-37725
chore: allow wip_composite_asset in the MR PO PR PI flow (copy #37723) (copy #37725)
2023-10-27 19:31:38 +05:30
mergify[bot]
6a6a5b0a75 chore: allow wip_composite_asset in the MR PO PR PI flow (copy #37723) (#37725)
* chore: allow wip_composite_asset in the MR PO PR PI flow

(cherry picked from commit 0e5bea33a3)

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

* chore: resolve conflict

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 3f296eea3a)
2023-10-27 13:26:16 +00:00
mergify[bot]
3f296eea3a chore: allow wip_composite_asset in the MR PO PR PI flow (copy #37723) (#37725)
* chore: allow wip_composite_asset in the MR PO PR PI flow

(cherry picked from commit 0e5bea33a3)

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

* chore: resolve conflict

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
2023-10-27 18:54:47 +05:30
Deepesh Garg
ffdc11bbf6 fix(plaid): Do not sync pending transactions
(cherry picked from commit 46ea868559)

# Conflicts:
#	erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
2023-10-27 06:08:46 +00:00
Frappe PR Bot
8e4e4a9fb4 chore(release): Bumped to Version 14.45.3
## [14.45.3](https://github.com/frappe/erpnext/compare/v14.45.2...v14.45.3) (2023-10-27)

### Bug Fixes

* typeerror on tds payable monthly report ([52cfe3f](52cfe3f612))
2023-10-27 03:54:13 +00:00
ruthra kumar
254dd3d9bf Merge pull request #37715 from frappe/mergify/bp/version-14/pr-37714
fix: typeerror on tds payable monthly report (backport #37714)
2023-10-27 09:22:22 +05:30
Frappe PR Bot
83b3785202 chore(release): Bumped to Version 14.45.2
## [14.45.2](https://github.com/frappe/erpnext/compare/v14.45.1...v14.45.2) (2023-10-27)

### Bug Fixes

* purchase receipt with stock and asset items ([848928e](848928e7d1))
2023-10-27 03:44:35 +00:00
Deepesh Garg
b017f4a817 Merge pull request #37712 from frappe/mergify/bp/version-14/pr-37705
fix: purchase receipt with stock and asset items (#37705)
2023-10-27 09:12:57 +05:30
ruthra kumar
52cfe3f612 fix: typeerror on tds payable monthly report
(cherry picked from commit fea27d5e2e)
2023-10-27 03:23:30 +00:00
ruthra kumar
fd21dcd3b5 Merge pull request #37714 from ruthra-kumar/fix_typeerror_in_tds_montly_report
fix: typeerror on tds payable monthly report
2023-10-27 08:52:11 +05:30
ruthra kumar
fea27d5e2e fix: typeerror on tds payable monthly report 2023-10-27 07:41:30 +05:30
Smit Vora
848928e7d1 fix: purchase receipt with stock and asset items
(cherry picked from commit 375be8cd93)
2023-10-26 17:53:09 +00:00
Deepesh Garg
0f4e50185e Merge pull request #37705 from vorasmit/backport-asserts
fix: purchase receipt with stock and asset items
2023-10-26 23:22:18 +05:30
mergify[bot]
a0893ddf96 fix(defaults): apply discount and provisonal defaults from item group and brand if available (backport #37466) (#37703)
fix(defaults): apply discount and provisonal defaults from item group and brand if available (#37466)

(cherry picked from commit 1612d7ba3f)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-26 18:14:23 +05:30
mergify[bot]
556095daaa fix: avoid name clash in delivery stop (backport #37306) (#37701)
fix: avoid name clash in delivery stop (#37306)

* fix(stock): avoid name clash in delivery stop with Document.lock()

* chore(stock): format delivery stop json according to doctype builder

(cherry picked from commit 681782121c)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-26 18:12:01 +05:30
Smit Vora
375be8cd93 fix: purchase receipt with stock and asset items 2023-10-26 17:59:42 +05:30
Frappe PR Bot
38665760cd chore(release): Bumped to Version 14.45.1
## [14.45.1](https://github.com/frappe/erpnext/compare/v14.45.0...v14.45.1) (2023-10-26)

### Bug Fixes

* add regional support to extend purchase gl entries ([7558b62](7558b622a4))
* update existing doc if possible ([e457d39](e457d39b5d))
2023-10-26 08:52:24 +00:00
Deepesh Garg
f5120a6dd0 Merge pull request #37699 from frappe/mergify/bp/version-14/pr-37687
fix: add regional support to extend purchase gl entries (#37595)
2023-10-26 14:20:48 +05:30
Deepesh Garg
519bf3d377 chore: resolve conflicts
(cherry picked from commit fa490ef2f0)
2023-10-26 08:45:32 +00:00
Deepesh Garg
cef2231d6a chore: resolve conflicts
(cherry picked from commit e01e9ebc37)
2023-10-26 08:45:32 +00:00
Smit Vora
e457d39b5d fix: update existing doc if possible
(cherry picked from commit ff7108a3b1)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
(cherry picked from commit 89f07dcfac)
2023-10-26 08:45:32 +00:00
Smit Vora
7558b622a4 fix: add regional support to extend purchase gl entries
(cherry picked from commit 77cc91d06b)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/controllers/stock_controller.py
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
(cherry picked from commit d02ebd6215)
2023-10-26 08:45:31 +00:00
mergify[bot]
c58fefb359 feat(delivery): link to delivery notes list view from delivery trip (backport #37604) (#37695)
feat(delivery): link to delivery notes list view from delivery trip

(cherry picked from commit 85488cd0dc)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-26 13:12:44 +05:30
mergify[bot]
e96d5b314a feat: allow return of components for SCO that don't have SCR created (backport #37686) (#37692)
* feat: allow return of components for SCO that don't have SCR created

(cherry picked from commit 8e3b9ec879)

* fix: consider returned qty while calculating unsupplied qty

(cherry picked from commit 3290df5593)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-26 06:14:02 +00:00
Deepesh Garg
a615535ca5 Merge pull request #37687 from frappe/mergify/bp/version-14-hotfix/pr-37595
fix: add regional support to extend purchase gl entries (#37595)
2023-10-25 23:09:28 +05:30
Deepesh Garg
fa490ef2f0 chore: resolve conflicts 2023-10-25 19:46:51 +05:30
Deepesh Garg
e01e9ebc37 chore: resolve conflicts 2023-10-25 19:42:02 +05:30
Smit Vora
89f07dcfac fix: update existing doc if possible
(cherry picked from commit ff7108a3b1)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2023-10-25 14:05:19 +00:00
Smit Vora
d02ebd6215 fix: add regional support to extend purchase gl entries
(cherry picked from commit 77cc91d06b)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/controllers/stock_controller.py
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2023-10-25 14:05:17 +00:00
mergify[bot]
f5e9913746 refactor: rename field Over Order Allowance to Blanket Order Allowance (backport #37669) (#37681)
* refactor: rename field `Over Order Allowance` to `Blanket Order Allowance`

(cherry picked from commit 8ffa2bfe25)

* chore: patch to rename field `over_order_allowance`

(cherry picked from commit fcfcf6957e)

# Conflicts:
#	erpnext/patches.txt

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-25 09:55:24 +00:00
mergify[bot]
a7b75a4cc1 chore: fixed test case non_internal_transfer_delivery_note (backport #37671) (#37675)
chore: fixed test case non_internal_transfer_delivery_note (#37671)

(cherry picked from commit 2bcff4c7f2)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-25 13:50:01 +05:30
rohitwaghchaure
6b951bd17a chore(release): Bumped to Version 14.45.0 2023-10-25 12:30:31 +05:30
ruthra kumar
912c315286 Merge pull request #37667 from frappe/mergify/bp/version-14-hotfix/pr-37625
refactor: set exchange rate on foreign currency JE from Bank Reconciliation (backport #37625)
2023-10-25 12:24:37 +05:30
Frappe PR Bot
f35a0c227d chore: release v14 (#37654)
* fix(delivery): rename dt fetch stop action (backport #37605) (#37606)

fix(delivery): rename dt fetch stop action

(cherry picked from commit 79d51a0a0b)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>

* fix: incorrect cost center in the purchase invoice (backport #37591) (#37609)

* fix: incorrect cost center in the purchase invoice (#37591)

(cherry picked from commit 14b009b093)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>

* fix(minor): filter bank accounts in bank statement import (#37525)

fix(minor): filter bank accounts in bank statement import (#37525)

fix: filter by company in bank account
(cherry picked from commit 9d392970f0)

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>

* fix: set empty value for tax template in item details (#37496)

* fix: set empty value for tax template in item details (#37496)

* fix: empty tax template for items with invalid templates

* fix: test for empty tax template

* fix: test for item tax template calculation

* fix: test for pos inv tax template calculation

(cherry picked from commit b0d440c34b)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py

* chore: resolve conflicts

---------

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>

* fix(minor): filter tax template based on company in subscription (#37562)

fix: filter tax template based on company

(cherry picked from commit 1a2f659de2)

Co-authored-by: Gursheen Anand <gursheen@frappe.io>

* fix: Cash flow mapping fix (#37522)

Cash flow mapping fix

* fix: remove from or target warehouse for non internal transfer entries (backport #37612) (#37628)

fix: remove from or target warehouse for non internal transfer entries (#37612)

(cherry picked from commit 5136fe196b)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>

* Revert "fix: set empty value for tax template in item details (#37496)"

Revert "fix: set empty value for tax template in item details (#37496)"

This reverts commit ec208b8df5.

* refactor: gain_loss posting date fields in the allocation table

(cherry picked from commit 55dbcee36a)

* refactor: introduce fields in popup

(cherry picked from commit 5323bb7bee)

* refactor: pass gain loss posting date to controller

(cherry picked from commit 7e600a6494)

* test: varying posting date for gain loss journal

(cherry picked from commit 514d5434a3)

* fix: overallocation on Payment with PO/SO

(cherry picked from commit 23df4205f8)

# Conflicts:
#	erpnext/accounts/utils.py

* test: overalloction on reconciliation when PO is involved

(cherry picked from commit 946228d783)

* refactor(test): make use of utility methods

(cherry picked from commit 547993f801)

* chore: fix flakiness `test_sales_order_partial_advance_payment`

(cherry picked from commit 4dff2c7a0d)

* chore: resolve conflict

* fix: close employee loan on write off (#37638)

* fix: exclude written off amount while calculating loan repayment

* fix: revert exclude written off amount while calculating loan repayment

* fix: close employee loan on write off

* refactor: button on PE to filter associated Journals

(cherry picked from commit 150728deaa)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js

* chore: resolve conflict

* fix: incorrect process loss validation for multiple finished items (backport #37576) (#37656)

fix: incorrect process loss validation for multiple finished items (#37576)

(cherry picked from commit 92cbe580e6)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>

* chore: fixed test cases related to Internal Transfer (#37659)

* fix: GL Entries for receiving non CWIP assets using Purchase Receipt (#37660)

* fix: GL Entries for receiving non CWIP assets using Purchase Receipt

* fix: rearrange functions

* chore: rearrange functions

* chore: rearrange functions

* fix: Purchase Invoice GL entires for assets

* test: cwip accounting unit tests

* chore: Attribute error

* chore: Purchase Invoice tests

* chore: Missing asset account

* chore: Missing asset account

* chore: update tests

* fix: Internal transfer GL Entries

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* chore: make `Reserve Stock` checkbox visible in SO

* refactor: rename field `Auto Reserve Stock for Sales Order`

* feat: add fields to hold SO and SO Item ref in PR Item

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* refactor: Remove expense included in valuation accounts

* chore: Add back default in transit warehousefield

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>

* fix: wrong german translation (#37658)

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: David Arnold <dgx.arnold@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
Co-authored-by: Gursheen Anand <gursheen@frappe.io>
Co-authored-by: saeedkola <mohammedsaeedk@gmail.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-10-25 12:03:24 +05:30
ruthra kumar
2317f6a000 chore: resolve conflict 2023-10-25 11:27:51 +05:30
ruthra kumar
91130854d8 refactor: handle bank transaction in foreign currency
(cherry picked from commit 74a0d6408a)
2023-10-25 05:39:51 +00:00
ruthra kumar
677d728e67 refactor: exc rate on foreign currency JE from Bank Reconciliation
(cherry picked from commit 89f484282a)

# Conflicts:
#	erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
2023-10-25 05:39:51 +00:00
Raffael Meyer
fa5780ca81 fix: wrong german translation (#37658) 2023-10-25 09:13:06 +05:30
Deepesh Garg
80774e2da1 fix: GL Entries for receiving non CWIP assets using Purchase Receipt (#37660)
* fix: GL Entries for receiving non CWIP assets using Purchase Receipt

* fix: rearrange functions

* chore: rearrange functions

* chore: rearrange functions

* fix: Purchase Invoice GL entires for assets

* test: cwip accounting unit tests

* chore: Attribute error

* chore: Purchase Invoice tests

* chore: Missing asset account

* chore: Missing asset account

* chore: update tests

* fix: Internal transfer GL Entries

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* chore: make `Reserve Stock` checkbox visible in SO

* refactor: rename field `Auto Reserve Stock for Sales Order`

* feat: add fields to hold SO and SO Item ref in PR Item

* test: Deprecate tests

* test: Depricate tests

* test: Depricate tests

* refactor: Remove expense included in valuation accounts

* chore: Add back default in transit warehousefield

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-25 09:12:27 +05:30
rohitwaghchaure
72d32a4901 chore: fixed test cases related to Internal Transfer (#37659) 2023-10-24 19:10:23 +05:30
mergify[bot]
638c271d70 fix: incorrect process loss validation for multiple finished items (backport #37576) (#37656)
fix: incorrect process loss validation for multiple finished items (#37576)

(cherry picked from commit 92cbe580e6)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-24 18:03:34 +05:30
ruthra kumar
356b1bdb38 Merge pull request #37647 from frappe/mergify/copy/version-14-hotfix/pr-37200
refactor: button in Payment Entry to filter associated Journals (copy #37200)
2023-10-24 10:23:53 +05:30
ruthra kumar
0b082f0edc chore: resolve conflict 2023-10-24 09:44:41 +05:30
ruthra kumar
be90be37c7 refactor: button on PE to filter associated Journals
(cherry picked from commit 150728deaa)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js
2023-10-24 04:09:06 +00:00
Anand Baburajan
922ace4076 fix: close employee loan on write off (#37638)
* fix: exclude written off amount while calculating loan repayment

* fix: revert exclude written off amount while calculating loan repayment

* fix: close employee loan on write off
2023-10-24 09:38:08 +05:30
ruthra kumar
920de792ce Merge pull request #37646 from frappe/mergify/bp/version-14-hotfix/pr-37586
fix: overallocation on purchase order to multiple invoices (backport #37586)
2023-10-24 09:22:19 +05:30
ruthra kumar
d576fc7490 chore: resolve conflict 2023-10-24 08:48:53 +05:30
ruthra kumar
7b9daeff66 chore: fix flakiness test_sales_order_partial_advance_payment
(cherry picked from commit 4dff2c7a0d)
2023-10-24 03:14:52 +00:00
ruthra kumar
c921d7d409 refactor(test): make use of utility methods
(cherry picked from commit 547993f801)
2023-10-24 03:14:51 +00:00
ruthra kumar
c8922ad566 test: overalloction on reconciliation when PO is involved
(cherry picked from commit 946228d783)
2023-10-24 03:14:51 +00:00
ruthra kumar
d71b885fb8 fix: overallocation on Payment with PO/SO
(cherry picked from commit 23df4205f8)

# Conflicts:
#	erpnext/accounts/utils.py
2023-10-24 03:14:51 +00:00
ruthra kumar
f413530493 Merge pull request #37643 from frappe/mergify/bp/version-14-hotfix/pr-37630
refactor: configurable exchange gain or loss posting date (backport #37630)
2023-10-24 05:50:57 +05:30
ruthra kumar
ae788e8358 test: varying posting date for gain loss journal
(cherry picked from commit 514d5434a3)
2023-10-23 15:57:16 +00:00
ruthra kumar
515bed8c80 refactor: pass gain loss posting date to controller
(cherry picked from commit 7e600a6494)
2023-10-23 15:57:16 +00:00
ruthra kumar
063d658b04 refactor: introduce fields in popup
(cherry picked from commit 5323bb7bee)
2023-10-23 15:57:15 +00:00
ruthra kumar
fa7fa85d92 refactor: gain_loss posting date fields in the allocation table
(cherry picked from commit 55dbcee36a)
2023-10-23 15:57:14 +00:00
Deepesh Garg
ca13816ca1 Revert "fix: set empty value for tax template in item details (#37496)"
Revert "fix: set empty value for tax template in item details (#37496)"

This reverts commit ec208b8df5.
2023-10-23 15:11:33 +05:30
mergify[bot]
78b7c26420 fix: remove from or target warehouse for non internal transfer entries (backport #37612) (#37628)
fix: remove from or target warehouse for non internal transfer entries (#37612)

(cherry picked from commit 5136fe196b)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-23 12:27:01 +05:30
saeedkola
8e31379ecc fix: Cash flow mapping fix (#37522)
Cash flow mapping fix
2023-10-23 11:14:06 +05:30
mergify[bot]
c05e0a4ffc fix(minor): filter tax template based on company in subscription (#37562)
fix: filter tax template based on company

(cherry picked from commit 1a2f659de2)

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
2023-10-23 11:01:23 +05:30
mergify[bot]
ec208b8df5 fix: set empty value for tax template in item details (#37496)
* fix: set empty value for tax template in item details (#37496)

* fix: empty tax template for items with invalid templates

* fix: test for empty tax template

* fix: test for item tax template calculation

* fix: test for pos inv tax template calculation

(cherry picked from commit b0d440c34b)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py

* chore: resolve conflicts

---------

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-10-23 10:15:46 +05:30
mergify[bot]
1cb9f4cf8b fix(minor): filter bank accounts in bank statement import (#37525)
fix(minor): filter bank accounts in bank statement import (#37525)

fix: filter by company in bank account
(cherry picked from commit 9d392970f0)

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-10-23 10:04:02 +05:30
mergify[bot]
50daf701dd fix: incorrect cost center in the purchase invoice (backport #37591) (#37609)
* fix: incorrect cost center in the purchase invoice (#37591)

(cherry picked from commit 14b009b093)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-20 17:02:56 +05:30
mergify[bot]
8660faaa54 fix(delivery): rename dt fetch stop action (backport #37605) (#37606)
fix(delivery): rename dt fetch stop action

(cherry picked from commit 79d51a0a0b)

Co-authored-by: David Arnold <dgx.arnold@gmail.com>
2023-10-20 16:12:42 +05:30
Frappe PR Bot
6931db98f1 chore(release): Bumped to Version 14.44.1
## [14.44.1](https://github.com/frappe/erpnext/compare/v14.44.0...v14.44.1) (2023-10-19)

### Bug Fixes

* billed_qty to show a sum of all invoiced qty from the purchase order item. (backport [#37539](https://github.com/frappe/erpnext/issues/37539)) ([#37558](https://github.com/frappe/erpnext/issues/37558)) ([ac7d6d6](ac7d6d6d59))
* consider received qty while creating SO -> MR (backport [#37414](https://github.com/frappe/erpnext/issues/37414)) ([#37514](https://github.com/frappe/erpnext/issues/37514)) ([1b94510](1b94510f08))
* don't set finance books if gross_purchase_amount is not set (backport [#37480](https://github.com/frappe/erpnext/issues/37480)) ([#37482](https://github.com/frappe/erpnext/issues/37482)) ([0590f21](0590f21814))
* e-commerce permissions for address ([#37554](https://github.com/frappe/erpnext/issues/37554)) ([022f85d](022f85dd08))
* german tranlations of "Is Return" ([f9b2355](f9b2355066))
* GL Entries not getting created for PR Return (backport [#37513](https://github.com/frappe/erpnext/issues/37513)) ([#37516](https://github.com/frappe/erpnext/issues/37516)) ([c32258e](c32258e4b6))
* **gp:** wrong `allocated_amount` on multi sales person invoice ([d266423](d266423011))
* Incorrect vat amount in KSA VAT report ([44f7de0](44f7de0f31))
* inflated total amt in TDS report using back calculation ([78e22af](78e22af3ca))
* Issues related to RFQ and Supplier Quotation on Portal (backport [#37565](https://github.com/frappe/erpnext/issues/37565)) ([#37577](https://github.com/frappe/erpnext/issues/37577)) ([e1504ef](e1504efd40))
* keep customer/supplier website role by default ([76ef61c](76ef61c24f))
* keyerror on gl and pl comparision report ([6f143d3](6f143d35aa))
* payment entry count on supplier dashboard (backport [#37571](https://github.com/frappe/erpnext/issues/37571)) ([#37575](https://github.com/frappe/erpnext/issues/37575)) ([95abd79](95abd7908f))
* same Serial No get mapped while creating SO -> DN ([#37527](https://github.com/frappe/erpnext/issues/37527)) ([5025850](5025850258))
* serial and batch no get removed on save of return DN ([#37476](https://github.com/frappe/erpnext/issues/37476)) ([f1814a1](f1814a1a2a))
* Stock Reconciliation Insufficient Stock Error ([#37494](https://github.com/frappe/erpnext/issues/37494)) ([9406ddb](9406ddbff0))
* **test:** project test case (backport [#37541](https://github.com/frappe/erpnext/issues/37541)) ([#37543](https://github.com/frappe/erpnext/issues/37543)) ([e23710b](e23710bf00))
* use `flt` to ignore TypeError ([#37481](https://github.com/frappe/erpnext/issues/37481)) ([d2b22db](d2b22db500))

### Performance Improvements

* index `dn_detail` in `Delivery Note Item` (backport [#37528](https://github.com/frappe/erpnext/issues/37528)) ([#37530](https://github.com/frappe/erpnext/issues/37530)) ([001c230](001c230688))
2023-10-19 11:36:28 +00:00
Deepesh Garg
fdb2e94b5c Merge pull request #37545 from frappe/version-14-hotfix
chore: release v14
2023-10-19 17:04:34 +05:30
mergify[bot]
e1504efd40 fix: Issues related to RFQ and Supplier Quotation on Portal (backport #37565) (#37577)
* fix: Issues related to RFQ and Supplier Quotation on Portal (#37565)

fix: RFQ and Supplier Quotation for Portal
(cherry picked from commit 2851a41310)

* chore: removed backport changes

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 13:50:55 +05:30
mergify[bot]
95abd7908f fix: payment entry count on supplier dashboard (backport #37571) (#37575)
fix: payment entry count on supplier dashboard (#37571)

(cherry picked from commit 10311ff114)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 13:38:24 +05:30
mergify[bot]
022f85dd08 fix: e-commerce permissions for address (#37554)
* fix: E-commerce permissions

(cherry picked from commit f4d74990fe)

# Conflicts:
#	erpnext/controllers/selling_controller.py

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-10-18 17:21:46 +05:30
Deepesh Garg
837a6b5f50 Merge pull request #37555 from frappe/mergify/bp/version-14-hotfix/pr-37550
chore: Add accounting dimensions to Sales Order Item table (backport #37550)
2023-10-18 17:09:14 +05:30
ruthra kumar
f6a550faae Merge pull request #37569 from frappe/mergify/bp/version-14-hotfix/pr-37105
refactor: move `unreconcile` button into a drop down (backport #37105)
2023-10-18 16:31:52 +05:30
ruthra kumar
54f672e144 refactor: add unreconcile btn to purchase invoice
(cherry picked from commit 94ce43b0d5)
2023-10-18 10:58:20 +00:00
ruthra kumar
e8d082560a refactor: move unreconcile btn inside a drop down
(cherry picked from commit f2b0ac6868)
2023-10-18 10:58:19 +00:00
mergify[bot]
ac7d6d6d59 fix: billed_qty to show a sum of all invoiced qty from the purchase order item. (backport #37539) (#37558)
fix: billed_qty to show a sum of all invoiced qty from the purchase order item.

(cherry picked from commit 8a72f4f58a)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
2023-10-18 06:00:29 +00:00
ruthra kumar
e2e89492e0 Merge pull request #37556 from frappe/mergify/bp/version-14-hotfix/pr-37549
refactor: use account in key while grouping voucher in ar/ap report (backport #37549)
2023-10-18 09:41:24 +05:30
ruthra kumar
760eab961d test: report output if party is missing
(cherry picked from commit 244cec64b2)
2023-10-18 03:40:35 +00:00
ruthra kumar
3499089323 refactor: use account in key while grouping voucher in ar/ap report
(cherry picked from commit 601ab4567e)
2023-10-18 03:40:34 +00:00
Deepesh Garg
7db6988364 chore: resolve conflicts 2023-10-18 09:00:49 +05:30
Deepesh Garg
bfa93cd3f6 chore: Add accounting dimensions to Sales Order Item table
(cherry picked from commit e31db18912)

# Conflicts:
#	erpnext/patches.txt
2023-10-17 17:25:57 +00:00
mergify[bot]
e23710bf00 fix(test): project test case (backport #37541) (#37543)
fix(test): project test case

(cherry picked from commit fd6aee15e6)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-17 15:03:16 +05:30
s-aga-r
5025850258 fix: same Serial No get mapped while creating SO -> DN (#37527)
* fix: same Serial No get mapped while creating SO -> DN

* test: add test case for DN with repetitive serial item
2023-10-17 12:20:23 +05:30
ruthra kumar
473610506c Merge pull request #37540 from frappe/mergify/bp/version-14-hotfix/pr-37330
refactor: checkbox to toggle exchange rate inheritence in PO->PI (backport #37330)
2023-10-17 10:55:27 +05:30
ruthra kumar
71cb7d37ee refactor: checkbox to toggle exchange rate inheritence in PO->PI
(cherry picked from commit 08315522bb)
2023-10-17 04:16:50 +00:00
Deepesh Garg
5b1016c17d Merge pull request #37524 from deepeshgarg007/ksa_vat
fix: Incorrect vat amount in KSA VAT report
2023-10-16 18:41:25 +05:30
Ankush Menat
d598dad50e Merge pull request #37533 from frappe/mergify/bp/version-14-hotfix/pr-37532
fix: keep customer/supplier website role by default (backport #37532)
2023-10-16 17:33:44 +05:30
Ankush Menat
76ef61c24f fix: keep customer/supplier website role by default
(cherry picked from commit d2096cfdb7)
2023-10-16 12:01:17 +00:00
mergify[bot]
001c230688 perf: index dn_detail in Delivery Note Item (backport #37528) (#37530)
* perf: index `dn_detail` in `Delivery Note Item`

(cherry picked from commit 5b4528e614)

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

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-16 16:48:55 +05:30
Deepesh Garg
44f7de0f31 fix: Incorrect vat amount in KSA VAT report 2023-10-16 14:37:16 +05:30
mergify[bot]
c32258e4b6 fix: GL Entries not getting created for PR Return (backport #37513) (#37516)
* fix: GL Entries not getting created for PR Return

(cherry picked from commit 46add06a29)

* test: add test case for PR return with zero rate

(cherry picked from commit 253d4782c6)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-16 01:24:10 +05:30
mergify[bot]
1b94510f08 fix: consider received qty while creating SO -> MR (backport #37414) (#37514)
fix: consider received qty while creating SO -> MR

(cherry picked from commit b2cee396ac)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-16 01:23:13 +05:30
ruthra kumar
65b7fb1293 Merge pull request #37511 from frappe/mergify/bp/version-14-hotfix/pr-37319
test: use fixtures for sales and purchase invoice (backport #37319)
2023-10-15 12:44:13 +05:30
ruthra kumar
d1f6d62d72 chore: fix flaky test case 2023-10-15 12:19:34 +05:30
ruthra kumar
77e7a6cde7 Merge pull request #37512 from ruthra-kumar/back_calculate_tds
fix: inflated total amt in TDS report using back calculation
2023-10-15 11:54:25 +05:30
ruthra kumar
78e22af3ca fix: inflated total amt in TDS report using back calculation 2023-10-15 11:23:52 +05:30
ruthra kumar
7f903532f3 chore: resovle conflicts 2023-10-15 10:44:37 +05:30
ruthra kumar
8d1eac89e3 refactor(test): make sure TDS Payable is available for testing
(cherry picked from commit fbabf4ac2e)
2023-10-15 04:47:43 +00:00
ruthra kumar
d78316869b refactor(test): make use of @change_settings in PI test cases
(cherry picked from commit 0207d6e7c9)
2023-10-15 04:47:43 +00:00
ruthra kumar
33becb7b32 refactor(test): use test fixture in purchase invoice
(cherry picked from commit a2e064d214)
2023-10-15 04:47:43 +00:00
ruthra kumar
b97fdbe6fc refactor(test): use test fixture in subscription
(cherry picked from commit 3bdf4f628c)

# Conflicts:
#	erpnext/accounts/doctype/subscription/test_subscription.py
2023-10-15 04:47:42 +00:00
ruthra kumar
5699a8daa2 refactor(test): use @change_settings to fix failing test cases
(cherry picked from commit de9baef84a)
2023-10-15 04:47:42 +00:00
ruthra kumar
91a5bd8615 refactor(test): fix broken test cases in Sales Invoice
(cherry picked from commit 8ebe5733ac)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2023-10-15 04:47:42 +00:00
ruthra kumar
485cb7dd28 refactor(test): use @change_settings in sales invoice
(cherry picked from commit 58065f31b1)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2023-10-15 04:47:42 +00:00
ruthra kumar
9d6b434d1f refactor(test): unset accounts frozen date
(cherry picked from commit fc50b174eb)
2023-10-15 04:47:41 +00:00
ruthra kumar
405d1528c3 test: use fixtures for sales and purchase invoice
(cherry picked from commit c322e5f381)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2023-10-15 04:47:41 +00:00
s-aga-r
f1814a1a2a fix: serial and batch no get removed on save of return DN (#37476)
* fix: serial and batch no get removed on save of return DN

* test: add test case for DN return with product bundle
2023-10-15 09:57:39 +05:30
s-aga-r
9406ddbff0 fix: Stock Reconciliation Insufficient Stock Error (#37494)
* fix: Stock Reconciliation Insufficient Stock Error

* fix: linter

* test: add test case for Stock Reco Batch Item
2023-10-14 16:53:29 +05:30
ruthra kumar
bae6c5bf5f Merge pull request #37500 from frappe/mergify/bp/version-14-hotfix/pr-37435
fix(gp): wrong `allocated_amount` when grouped by Sales Person (backport #37435)
2023-10-14 12:54:33 +05:30
ruthra kumar
cf9acd1ff6 Merge pull request #37501 from frappe/mergify/bp/version-14-hotfix/pr-37495
fix: keyerror on gl and pl comparision report (backport #37495)
2023-10-14 12:54:09 +05:30
ruthra kumar
6f143d35aa fix: keyerror on gl and pl comparision report
(cherry picked from commit ad00df0af6)
2023-10-14 06:46:18 +00:00
Dany Robert
d266423011 fix(gp): wrong allocated_amount on multi sales person invoice
(cherry picked from commit bda82bf1e9)
2023-10-14 06:43:15 +00:00
s-aga-r
d2b22db500 fix: use flt to ignore TypeError (#37481) 2023-10-13 10:22:20 +05:30
ruthra kumar
c43d0b81e3 Merge pull request #37488 from frappe/mergify/bp/version-14-hotfix/pr-37484
refactor(patch): ignore links on closing balance patch (backport #37484)
2023-10-13 09:59:08 +05:30
ruthra kumar
cf0ab51348 refactor(patch): ignore links on closing balance patch
(cherry picked from commit 17ca8756a7)
2023-10-13 03:50:02 +00:00
mergify[bot]
0590f21814 fix: don't set finance books if gross_purchase_amount is not set (backport #37480) (#37482)
fix: don't set finance books if gross_purchase_amount is not set (#37480)

(cherry picked from commit 18e3a8907a)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-10-12 19:44:36 +05:30
Sagar Vora
bbdf26c82e Merge pull request #37468 from frappe/mergify/bp/version-14-hotfix/pr-37418
fix: german tranlations of "Is Return" (backport #37418)
2023-10-12 13:05:49 +05:30
ruthra kumar
020d2e2ca6 Merge pull request #37464 from frappe/mergify/bp/version-14-hotfix/pr-37436
refactor: for non-repost fields, don't validate (backport #37436)
2023-10-12 10:51:42 +05:30
ruthra kumar
f434548204 Merge pull request #37465 from frappe/mergify/bp/version-14-hotfix/pr-37459
refactor: add validation for Advances in SI/PI (backport #37459)
2023-10-12 10:51:18 +05:30
barredterra
f9b2355066 fix: german tranlations of "Is Return"
(cherry picked from commit 38ca164662)
2023-10-12 02:21:05 +00:00
Frappe PR Bot
2815952a03 chore(release): Bumped to Version 14.44.0
# [14.44.0](https://github.com/frappe/erpnext/compare/v14.43.1...v14.44.0) (2023-10-12)

### Bug Fixes

* added validation for the batch on stock reco ([#37174](https://github.com/frappe/erpnext/issues/37174)) ([4c337a6](4c337a6f44))
* ageing summary in AR ([15d2024](15d2024b8e))
* allocate amt for payment term invoices ([b22ac13](b22ac137f5))
* call validate before setting repost flag ([bec3e8e](bec3e8ed96))
* do not run bg job for single doc ([4123e7b](4123e7b244))
* **Employee:** enable `no_copy` for `relieving_date` (backport [#37344](https://github.com/frappe/erpnext/issues/37344)) ([#37358](https://github.com/frappe/erpnext/issues/37358)) ([2b38b78](2b38b780ba))
* exception on exporting errored rows ([e58b3b1](e58b3b11e9))
* fetch company details for Lead based quotation ([c1d40a6](c1d40a6bfa))
* fetch dependent task subject and project (backport [#37401](https://github.com/frappe/erpnext/issues/37401)) ([#37421](https://github.com/frappe/erpnext/issues/37421)) ([0aad942](0aad942312))
* ignore cancelled gle in voucher-wise balance report ([#36417](https://github.com/frappe/erpnext/issues/36417)) ([ee1255a](ee1255a716))
* incorrect status of the returned purchase receipt ([#37300](https://github.com/frappe/erpnext/issues/37300)) ([63f4573](63f45739e0))
* linting issues ([6c8a65e](6c8a65e03b))
* negative valuation rate in PR return ([#37424](https://github.com/frappe/erpnext/issues/37424)) ([26ad688](26ad688584))
* payment request rounding in multi-currency and on status update ([eed5863](eed58634ba))
* production plan reserved qty incorrect calculation (backport [#37400](https://github.com/frappe/erpnext/issues/37400)) ([#37458](https://github.com/frappe/erpnext/issues/37458)) ([573b159](573b159541))
* split inv allocated amt on server side ([06b0477](06b04770fc))
* typo in doctype name and qb ([606c99e](606c99e57c))
* **ux:** allow MR to Stop until fully received (backport [#37452](https://github.com/frappe/erpnext/issues/37452)) ([#37456](https://github.com/frappe/erpnext/issues/37456)) ([fb0b426](fb0b426fe4))
* validation for si ([3dc68e3](3dc68e3b00))

### Features

* add repost btn in invoice ([cde848d](cde848dc7f))
* allow on submit fields ([f5245f6](f5245f6b3f))
* allow repost for pi ([2d13dda](2d13dda49c))
* composite WIP asset ([#37352](https://github.com/frappe/erpnext/issues/37352)) ([0ecd7d2](0ecd7d2bf5))
* disable currency exchange api. ([#33593](https://github.com/frappe/erpnext/issues/33593)) ([1ca0516](1ca0516fe5))
* filter on voucher no ([cb35218](cb35218eec))
* introduce unreconcile doctype ([ae8355c](ae8355c953))
* UI for unreconcile ([9531a45](9531a45b94))
* unreconcile support for journal entry ([cd2d335](cd2d335256))
* validate negative stock for inventory dimension ([#37373](https://github.com/frappe/erpnext/issues/37373)) ([1480aca](1480acabb0))
2023-10-12 02:15:37 +00:00
Deepesh Garg
33f4fae8cd Merge pull request #37430 from frappe/version-14-hotfix
chore: release v14
2023-10-12 07:43:46 +05:30
ruthra kumar
d37a1811db refactor: add validation for Advances in SI/PI
(cherry picked from commit 0cdd6435a5)
2023-10-11 14:34:38 +00:00
ruthra kumar
8dd26949b7 refactor: for non-repost fields, don't validate
(cherry picked from commit c1782c5015)
2023-10-11 14:34:29 +00:00
s-aga-r
26ad688584 fix: negative valuation rate in PR return (#37424)
* fix: negative valuation rate in PR return

* test: add test case for PR return
2023-10-11 18:44:32 +05:30
Frappe PR Bot
48ceead9d0 chore(release): Bumped to Version 14.43.1
## [14.43.1](https://github.com/frappe/erpnext/compare/v14.43.0...v14.43.1) (2023-10-11)

### Bug Fixes

* fetch company details for Lead based quotation ([e4ed1d6](e4ed1d684d))
2023-10-11 11:14:16 +00:00
ruthra kumar
9dd33739f9 Merge pull request #37460 from frappe/mergify/bp/version-14/pr-37371
fix: fetch company details for Lead based quotation (backport #37370) (backport #37371)
2023-10-11 16:42:42 +05:30
ruthra kumar
e4ed1d684d fix: fetch company details for Lead based quotation
(cherry picked from commit f388864fd5)
(cherry picked from commit c1d40a6bfa)
2023-10-11 10:46:00 +00:00
mergify[bot]
573b159541 fix: production plan reserved qty incorrect calculation (backport #37400) (#37458)
fix: production plan reserved qty incorrect calculation (#37400)

(cherry picked from commit f3238f9105)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-11 14:41:32 +05:30
mergify[bot]
fb0b426fe4 fix(ux): allow MR to Stop until fully received (backport #37452) (#37456)
fix(ux): allow MR to Stop until fully received

(cherry picked from commit 0d7a0f393d)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-11 13:51:29 +05:30
ruthra kumar
63ff1f1eaa Merge pull request #37399 from frappe/mergify/bp/version-14-hotfix/pr-37194
feat: editable purchase invoice  (backport #37194)
2023-10-10 21:05:56 +05:30
ruthra kumar
4d79a4e7b3 Merge pull request #37420 from frappe/mergify/bp/version-14-hotfix/pr-37204
fix: allocate payment amount for split invoices in PE (backport #37204)
2023-10-10 21:04:26 +05:30
ruthra kumar
97f9656460 Merge pull request #37419 from frappe/mergify/bp/version-14-hotfix/pr-37123
fix: payment request rounding in multi-currency and on status update (backport #37123)
2023-10-10 21:03:52 +05:30
ruthra kumar
14ddcd6c24 Merge pull request #37427 from frappe/mergify/bp/version-14-hotfix/pr-36417
fix: ignore cancelled gle in voucher-wise balance report (backport #36417)
2023-10-10 21:03:21 +05:30
Deepesh Garg
9689c1fcec Merge branch 'version-14' into version-14-hotfix 2023-10-10 20:19:26 +05:30
Gursheen Kaur Anand
ee1255a716 fix: ignore cancelled gle in voucher-wise balance report (#36417)
fix: ignore cancelled gle
(cherry picked from commit 1ddfaa7605)
2023-10-10 08:09:32 +00:00
ruthra kumar
b0ac097327 chore: resolve conflicts 2023-10-10 11:31:19 +05:30
ruthra kumar
5d97a69e32 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-37194 2023-10-10 11:10:09 +05:30
mergify[bot]
0aad942312 fix: fetch dependent task subject and project (backport #37401) (#37421)
fix: fetch dependent task subject and project (#37401)

(cherry picked from commit 78eaf5d035)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-10 11:06:33 +05:30
ruthra kumar
debfbc4761 refactor: remove references in repost doctypes upon parent doc delet
(cherry picked from commit ed7f67b1a8)
2023-10-10 11:01:05 +05:30
Gursheen Anand
3dc68e3b00 fix: validation for si
(cherry picked from commit 61c6ebbb95)
2023-10-10 10:58:19 +05:30
Gursheen Anand
bec3e8ed96 fix: call validate before setting repost flag
(cherry picked from commit 8ef0d88708)
2023-10-10 10:58:19 +05:30
Gursheen Anand
4123e7b244 fix: do not run bg job for single doc
(cherry picked from commit 1856050ef9)
2023-10-10 10:58:19 +05:30
Gursheen Anand
c9bcf79e83 refactor: remove repeated validation for voucher
(cherry picked from commit a856091ff4)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2023-10-10 10:58:19 +05:30
Gursheen Anand
677525b2cf refactor: use repost accounting legder
(cherry picked from commit 7ebf083683)
2023-10-10 10:58:19 +05:30
Gursheen Anand
8c83bbc096 refactor: remove unused method
(cherry picked from commit ba7212c98b)
2023-10-10 10:58:19 +05:30
Gursheen Anand
a512d27dbb test: reposted acc entries for pi
(cherry picked from commit c66c438575)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
2023-10-10 10:58:19 +05:30
Gursheen Anand
6c8a65e03b fix: linting issues
(cherry picked from commit c88f6d1fa7)
2023-10-10 10:58:19 +05:30
Gursheen Anand
2d13dda49c feat: allow repost for pi
(cherry picked from commit 23470bf52d)
2023-10-10 10:58:19 +05:30
Gursheen Anand
cde848dc7f feat: add repost btn in invoice
(cherry picked from commit e77814fbc0)
2023-10-10 10:58:19 +05:30
Gursheen Anand
79e414cb97 refactor: move reposting logic to common controller
(cherry picked from commit 68effd93bd)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2023-10-10 10:58:19 +05:30
Gursheen Anand
f5245f6b3f feat: allow on submit fields
(cherry picked from commit e922ec60eb)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
2023-10-10 10:58:19 +05:30
ruthra kumar
dc804f216d Merge pull request #37402 from frappe/mergify/bp/version-14-hotfix/pr-36879
feat: Unreconcile Payments (backport #36879)
2023-10-10 10:39:54 +05:30
Gursheen Anand
bc0db696c9 chore: remove unused variable
(cherry picked from commit 545f2ccdf1)
2023-10-10 04:42:15 +00:00
Gursheen Anand
06b04770fc fix: split inv allocated amt on server side
(cherry picked from commit b3aa201eb5)
2023-10-10 04:42:15 +00:00
Gursheen Anand
b22ac137f5 fix: allocate amt for payment term invoices
(cherry picked from commit ac28a5b372)
2023-10-10 04:42:15 +00:00
David Arnold
eed58634ba fix: payment request rounding in multi-currency and on status update
(cherry picked from commit 6e1ad4c5bd)
2023-10-10 04:38:31 +00:00
mergify[bot]
24852e46c1 chore: rewrite query using query builder (backport #37310) (#37415)
* chore: rewrite query using query builder

(cherry picked from commit 25718f5cc7)

* chore: fix shopping cart tests

(cherry picked from commit fb51cae88b)

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-10-09 20:56:43 +05:30
Deepesh Garg
50521d4efe Merge pull request #37322 from frappe/mergify/bp/version-14-hotfix/pr-37119
chore: add regional support for getting payment entries (#37119)
2023-10-09 19:28:07 +05:30
ruthra kumar
8fc705ea6a chore: resolve conflicts 2023-10-09 15:17:47 +05:30
ruthra kumar
63274527d2 test: multi currency invoice unreconciliation
exchange gain/loss associated with the unreconcile invoice should be
cancelled as well

(cherry picked from commit d398775715)
2023-10-09 06:15:09 +00:00
ruthra kumar
aba51ee352 refactor(test): more modularization
(cherry picked from commit 5c09fdf941)
2023-10-09 06:15:09 +00:00
ruthra kumar
4bd83b5058 refactor: cancel gain/loss JE on multi currency transactions
(cherry picked from commit 1d93d66c30)
2023-10-09 06:15:09 +00:00
ruthra kumar
75d3093aea refactor: only cancel specific gain/loss je
(cherry picked from commit 5dbcf7d2b9)
2023-10-09 06:15:09 +00:00
ruthra kumar
669d692844 refactor: display allocated amount in account currency with symbol
(cherry picked from commit 6fd1c1bca2)
2023-10-09 06:15:09 +00:00
ruthra kumar
606c99e57c fix: typo in doctype name and qb
(cherry picked from commit 9a1588f1cc)
2023-10-09 06:15:08 +00:00
ruthra kumar
cf308912a1 test: more granular unreconciliation
(cherry picked from commit 67980188a7)
2023-10-09 06:15:08 +00:00
ruthra kumar
f5718390b7 refactor: unlink individual vouchers from payments
(cherry picked from commit 9b6eac23b6)
2023-10-09 06:15:08 +00:00
ruthra kumar
8954bd7759 chore: type info
(cherry picked from commit b4dc2bdf28)
2023-10-09 06:15:08 +00:00
ruthra kumar
3cbaea389b refactor: convert raw sql to query_builder
(cherry picked from commit 0130aea2aa)
2023-10-09 06:15:07 +00:00
ruthra kumar
335cb5fd28 refactor: single fetch and unlinking logic for JE and PE
(cherry picked from commit de910ab152)
2023-10-09 06:15:07 +00:00
ruthra kumar
cd2d335256 feat: unreconcile support for journal entry
(cherry picked from commit 285963acdb)
2023-10-09 06:15:07 +00:00
ruthra kumar
1a69db0f80 refactor: modularisation and group by voucher_no
(cherry picked from commit cce96669f0)
2023-10-09 06:15:07 +00:00
ruthra kumar
84e4a2509c chore: rename and add trigger in journal entry
(cherry picked from commit 0ccb6d8242)
2023-10-09 06:15:06 +00:00
ruthra kumar
f4e1959cc7 chore: code cleanup
(cherry picked from commit 69683776a5)
2023-10-09 06:15:06 +00:00
ruthra kumar
7651ecbc2b chore: fetch logic for payment entry
(cherry picked from commit 1981f3837a)
2023-10-09 06:15:06 +00:00
ruthra kumar
e464f5e419 chore: move functions to a separate file in utils
(cherry picked from commit 25fe752185)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js
#	erpnext/public/js/erpnext.bundle.js
2023-10-09 06:15:06 +00:00
ruthra kumar
1e93d0bcc4 chore: move dialog building function to utils.js file
(cherry picked from commit 5981c7e0ad)
2023-10-09 06:15:05 +00:00
ruthra kumar
b886589657 refactor: add UI elements
(cherry picked from commit 58dc0e52e1)
2023-10-09 06:15:05 +00:00
ruthra kumar
3a670264b2 chore: delete unreoncile doc upon parent doc deletion
(cherry picked from commit 6bbe47c671)
2023-10-09 06:15:05 +00:00
ruthra kumar
2fd500ce26 chore: track changes
(cherry picked from commit 489a545bbb)
2023-10-09 06:15:04 +00:00
ruthra kumar
9422422dcc refactor: remove references using framework
(cherry picked from commit 42df0d3d67)
2023-10-09 06:15:04 +00:00
ruthra kumar
37fc82cd11 chore: delete references upon parent deletion
(cherry picked from commit fbdfb8151c)
2023-10-09 06:15:04 +00:00
ruthra kumar
cb35218eec feat: filter on voucher no
(cherry picked from commit 41eb2c9f5a)
2023-10-09 06:15:04 +00:00
ruthra kumar
9531a45b94 feat: UI for unreconcile
(cherry picked from commit fc6be5bfb9)
2023-10-09 06:15:03 +00:00
ruthra kumar
fb41f5f88c test: basic unreconcile function
(cherry picked from commit 0faffaa8db)
2023-10-09 06:15:03 +00:00
ruthra kumar
b9647ac0a4 refactor: adding 'Get Allocations' button
(cherry picked from commit 5114a9580d)
2023-10-09 06:15:03 +00:00
ruthra kumar
77fa0f68df chore: working state on barebones functions
(cherry picked from commit e48a90efe6)
2023-10-09 06:15:03 +00:00
ruthra kumar
ae8355c953 feat: introduce unreconcile doctype
(cherry picked from commit dc71623295)
2023-10-09 06:15:02 +00:00
ruthra kumar
c42ef922d2 Merge pull request #37396 from frappe/mergify/bp/version-14-hotfix/pr-37395
fix: exception on exporting errored rows (backport #37395)
2023-10-08 18:39:49 +05:30
ruthra kumar
e58b3b11e9 fix: exception on exporting errored rows
(cherry picked from commit d3c6000904)
2023-10-08 12:52:16 +00:00
ruthra kumar
8f0e10bfbe Merge pull request #37388 from frappe/mergify/bp/version-14-hotfix/pr-37289
fix: ageing summary in SOA AR (backport #37289)
2023-10-08 12:25:51 +05:30
Gursheen Kaur Anand
77d719af6e chore: linting issues 2023-10-07 15:40:27 +05:30
Gursheen Kaur Anand
3f59518d01 chore: resolve conflicts 2023-10-07 15:21:50 +05:30
Gursheen Anand
24b1100c8f test: process soa for gl and ar
(cherry picked from commit 644e25e587)
2023-10-07 09:40:32 +00:00
Gursheen Anand
c29eab12df refactor: separate function for statement dict
(cherry picked from commit 67f878ff8c)

# Conflicts:
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
2023-10-07 09:40:32 +00:00
Gursheen Anand
15d2024b8e fix: ageing summary in AR
(cherry picked from commit d9eb44e62d)
2023-10-07 09:40:32 +00:00
rohitwaghchaure
1480acabb0 feat: validate negative stock for inventory dimension (#37373)
* feat: validate negative stock for inventory dimension

* test: test case for validate negative stock for inv dimension
2023-10-06 17:55:32 +05:30
rohitwaghchaure
4c337a6f44 fix: added validation for the batch on stock reco (#37174) 2023-10-06 13:06:13 +05:30
rohitwaghchaure
63f45739e0 fix: incorrect status of the returned purchase receipt (#37300) 2023-10-06 11:58:15 +05:30
ruthra kumar
55a9a8fd51 Merge pull request #37371 from frappe/mergify/bp/version-14-hotfix/pr-37370
fix: fetch company details for Lead based quotation (backport #37370)
2023-10-05 14:25:14 +05:30
ruthra kumar
c1d40a6bfa fix: fetch company details for Lead based quotation
(cherry picked from commit f388864fd5)
2023-10-05 08:21:26 +00:00
ruthra kumar
bbc13c719e Merge pull request #37367 from frappe/mergify/bp/version-14-hotfix/pr-37359
refactor: add `access_key` field to facilitate use of exchangerate.host provider (backport #37359)
2023-10-05 12:57:41 +05:30
ruthra kumar
3b49079ad7 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-37359 2023-10-05 12:17:07 +05:30
ruthra kumar
6fad2bad11 Merge pull request #37369 from frappe/mergify/bp/version-14-hotfix/pr-33593
feat: disable currency exchange api. (backport #33593)
2023-10-05 10:25:40 +05:30
Devin Slauenwhite
1ca0516fe5 feat: disable currency exchange api. (#33593)
(cherry picked from commit 179a31ed5e)
2023-10-05 04:03:15 +00:00
ruthra kumar
04b8527ba8 chore: refactor test case for exchangerate.host provider
(cherry picked from commit c8e3dc6c4c)
2023-10-05 03:50:28 +00:00
ruthra kumar
98a9007e9f refactor: introduce access_key field
(cherry picked from commit 81591a34c2)

# Conflicts:
#	erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
2023-10-05 03:50:28 +00:00
ruthra kumar
64a50cfabf Merge pull request #37366 from frappe/mergify/bp/version-14-hotfix/pr-37294
refactor: block Payment Entry as ref in Journals from UI (backport #37294)
2023-10-05 09:20:20 +05:30
ruthra kumar
587a965bdf refactor: block Payment Entry as ref in JE from UI
(cherry picked from commit d391e81505)
2023-10-05 03:25:04 +00:00
ruthra kumar
52c0900576 Merge pull request #37363 from frappe/mergify/copy/version-14-hotfix/pr-37362
test: fixing test_capitalization_with_wip_composite_asset (copy #37362)
2023-10-05 08:38:20 +05:30
anandbaburajan
67a43c353c test: fixing test_capitalization_with_wip_composite_asset
(cherry picked from commit 9468513d7c)
2023-10-04 16:44:31 +00:00
Frappe PR Bot
cf9fc552f0 chore(release): Bumped to Version 14.43.0
# [14.43.0](https://github.com/frappe/erpnext/compare/v14.42.0...v14.43.0) (2023-10-04)

### Features

* composite WIP asset (backport [#37352](https://github.com/frappe/erpnext/issues/37352)) ([#37360](https://github.com/frappe/erpnext/issues/37360)) ([6947686](6947686141))
2023-10-04 14:18:28 +00:00
mergify[bot]
6947686141 feat: composite WIP asset (backport #37352) (#37360)
feat: composite WIP asset (#37352)

feat: wip composite asset
(cherry picked from commit 0ecd7d2bf5)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-10-04 19:46:30 +05:30
mergify[bot]
2b38b780ba fix(Employee): enable no_copy for relieving_date (backport #37344) (#37358)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
Co-authored-by: Jignesh (GreyCube Technologies) <jignesh@greycube.in>
fix(Employee): enable `no_copy` for `relieving_date` (#37344)
2023-10-04 14:29:45 +05:30
Anand Baburajan
0ecd7d2bf5 feat: composite WIP asset (#37352)
feat: wip composite asset
2023-10-04 10:29:14 +05:30
Frappe PR Bot
9f1b9320e9 chore(release): Bumped to Version 14.42.0
# [14.42.0](https://github.com/frappe/erpnext/compare/v14.41.2...v14.42.0) (2023-10-04)

### Bug Fixes

* add only float row values for total ([020aedb](020aedb8b0))
* currency symbol in the Supplier Quotation Comparison report ([#37337](https://github.com/frappe/erpnext/issues/37337)) ([82e8606](82e8606b3c))
* Description field for the 'Ignore Available Stock' ([#37293](https://github.com/frappe/erpnext/issues/37293)) ([7f1483a](7f1483ad70))
* do not consider submitted Work Orders in the Production Plan Res… ([#37343](https://github.com/frappe/erpnext/issues/37343)) ([c3aeb2d](c3aeb2dec5))
* ignore user permissions for `Source Warehouse` (backport [#37313](https://github.com/frappe/erpnext/issues/37313)) ([#37314](https://github.com/frappe/erpnext/issues/37314)) ([04f0dfb](04f0dfb691))
* incorrect qty for material request in Production Plan ([#37270](https://github.com/frappe/erpnext/issues/37270)) ([8fe4a4d](8fe4a4d3aa))
* Not unique table/alias: 'tabTask' (backport [#37285](https://github.com/frappe/erpnext/issues/37285)) ([#37298](https://github.com/frappe/erpnext/issues/37298)) ([95e0bf5](95e0bf5e0e))
* party format in test ([28756bf](28756bf7b6))
* PCV posting issues ([#37029](https://github.com/frappe/erpnext/issues/37029)) ([92eabe3](92eabe3cf5))
* process soa filter for multiselect ([4962b67](4962b67358))
* query for multiselect filter ([6d7aa2a](6d7aa2ae94))
* set route filter values for AP ([49f0f1c](49f0f1ca09))
* set route filter values for AR ([2b30727](2b30727fdc))
* summary report filters ([403ff69](403ff697e9))
* trial balance report freezes when adding filters (backport [#37264](https://github.com/frappe/erpnext/issues/37264)) ([#37265](https://github.com/frappe/erpnext/issues/37265)) ([6a8146b](6a8146ba8a))
* Use default Cost Center of the Company for additional discount ([#37234](https://github.com/frappe/erpnext/issues/37234)) ([e483b4a](e483b4a78a))
* validation message for valuation rate ([#37301](https://github.com/frappe/erpnext/issues/37301)) ([643bb05](643bb0511c))

### Features

* asset salvage_value_percentage (backport [#37302](https://github.com/frappe/erpnext/issues/37302)) ([#37334](https://github.com/frappe/erpnext/issues/37334)) ([6daea6c](6daea6ccb2))
2023-10-04 02:15:16 +00:00
ruthra kumar
12a7cb21b5 Merge pull request #37339 from frappe/version-14-hotfix
chore: release v14
2023-10-04 07:43:39 +05:30
rohitwaghchaure
643bb0511c fix: validation message for valuation rate (#37301) 2023-10-03 22:56:33 +05:30
rohitwaghchaure
e975a10a75 chore: fix linter issue (#37349) 2023-10-03 22:08:37 +05:30
rohitwaghchaure
c3aeb2dec5 fix: do not consider submitted Work Orders in the Production Plan Res… (#37343)
fix: do not consider submitted Work Orders in the Production Plan Reserve qty
2023-10-03 20:34:10 +05:30
ruthra kumar
fe32787e6e Merge pull request #37346 from frappe/mergify/bp/version-14-hotfix/pr-37304
fix: only float row values for total in AP summary (backport #37304)
2023-10-03 20:19:54 +05:30
ruthra kumar
f3b872a8e2 refactor: use isinstance over type
(cherry picked from commit 67440c38ae)
2023-10-03 14:16:27 +00:00
Gursheen Anand
020aedb8b0 fix: add only float row values for total
(cherry picked from commit 1dab195560)
2023-10-03 14:16:27 +00:00
rohitwaghchaure
82e8606b3c fix: currency symbol in the Supplier Quotation Comparison report (#37337)
fix: currency in the Supplier Quotation Comparison report
2023-10-03 18:53:43 +05:30
mergify[bot]
6daea6ccb2 feat: asset salvage_value_percentage (backport #37302) (#37334)
* feat: asset salvage_value_percentage (#37302)

* feat: asset salvage_value_percentage

* chore: add missing parameter in get_item_details

* chore: change asset depr table colors

(cherry picked from commit fed94845ce)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.js
#	erpnext/assets/doctype/asset_activity/asset_activity.json
#	erpnext/assets/doctype/asset_finance_book/asset_finance_book.json

* chore: resolving conflicts

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-10-03 13:35:10 +05:30
Smit Vora
f7de825e89 chore: add regional support for getting payment entries (#37119)
chore: add regional support for get payment entries
(cherry picked from commit 3e282bfbce)
2023-10-02 10:32:40 +00:00
mergify[bot]
04f0dfb691 fix: ignore user permissions for Source Warehouse (backport #37313) (#37314)
* fix: ignore user permissions for `Source Warehouse` (#37313)

(cherry picked from commit e7f4b7b190)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.json

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-01 21:31:17 +05:30
mergify[bot]
95e0bf5e0e fix: Not unique table/alias: 'tabTask' (backport #37285) (#37298)
fix: Not unique table/alias: 'tabTask' (#37285)

(cherry picked from commit 361e555118)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-09-29 15:07:10 +05:30
rohitwaghchaure
7f1483ad70 fix: Description field for the 'Ignore Available Stock' (#37293) 2023-09-29 14:35:45 +05:30
HENRY Florian
21c1c7194b refactor: In Quotation Item, discount_and_margin section should have same collapsible_depends_on as other similar DocType (Sales Order Item,Sales Invoice Item,...) (#37272) 2023-09-29 08:29:18 +05:30
Deepesh Garg
81c4279a30 Merge pull request #37282 from frappe/mergify/bp/version-14-hotfix/pr-37268
fix: AP AR filters from Party link (backport #37268)
2023-09-28 19:34:56 +05:30
ruthra kumar
01b54134ae test: multi select party filter in AR report
(cherry picked from commit 2c7d6aec89)
2023-09-28 06:26:05 +00:00
Gursheen Anand
28756bf7b6 fix: party format in test
(cherry picked from commit 59e8abfd57)
2023-09-28 06:26:05 +00:00
Gursheen Anand
4962b67358 fix: process soa filter for multiselect
(cherry picked from commit 4b28154f5e)
2023-09-28 06:26:05 +00:00
Gursheen Anand
403ff697e9 fix: summary report filters
(cherry picked from commit f7cb68a45f)
2023-09-28 06:26:04 +00:00
Gursheen Anand
6d7aa2ae94 fix: query for multiselect filter
(cherry picked from commit e7239e02d4)
2023-09-28 06:26:04 +00:00
Gursheen Anand
2b30727fdc fix: set route filter values for AR
(cherry picked from commit 9d15124a6a)
2023-09-28 06:26:04 +00:00
Gursheen Anand
49f0f1ca09 fix: set route filter values for AP
(cherry picked from commit 888ed36eed)
2023-09-28 06:26:04 +00:00
rohitwaghchaure
8fe4a4d3aa fix: incorrect qty for material request in Production Plan (#37270) 2023-09-27 20:02:05 +05:30
mergify[bot]
6a8146ba8a fix: trial balance report freezes when adding filters (backport #37264) (#37265)
fix: trial balance report freezes when adding filters (#37264)

fix: Only add onclick if correct data is returned

workaround for https://github.com/frappe/datatable/issues/177

(cherry picked from commit 2dc95e5d59)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-09-27 12:36:58 +05:30
Deepesh Garg
b18678df4d Merge pull request #37262 from frappe/mergify/bp/version-14-hotfix/pr-37029
fix: PCV posting issues (backport #37029)
2023-09-27 12:07:54 +05:30
Deepesh Garg
8d813e3256 chore: resolve conflicts 2023-09-27 11:19:39 +05:30
Deepesh Garg
c6966f4e2d Merge pull request #37263 from frappe/mergify/bp/version-14-hotfix/pr-37234
fix: Use default Cost Center of the Company for additional discount (backport #37234)
2023-09-27 11:18:29 +05:30
vr-greycube
e483b4a78a fix: Use default Cost Center of the Company for additional discount (#37234)
fix: Set cost center as default company cost center

When Discount Accounting in enabled in Selling Settings, use Company default Cost Center while making GL entries for additional_discount_account

(cherry picked from commit 4ada5a488e)
2023-09-27 05:09:46 +00:00
Deepesh Garg
92eabe3cf5 fix: PCV posting issues (#37029)
* fix: PCV posting issues

* fix: process closing entries separately in a background job

* test: Update tests

* chore: fix broken ci

(cherry picked from commit 8c5fcb8257)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/payment_request.json
2023-09-27 04:53:41 +00:00
207 changed files with 5756 additions and 2738 deletions

View File

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

View File

@@ -37,6 +37,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
}
)
cle.flags.ignore_permissions = True
cle.flags.ignore_links = True
cle.submit()

View File

@@ -301,3 +301,30 @@ def get_dimensions(with_cost_center_and_project=False):
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map
def create_accounting_dimensions_for_doctype(doctype):
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -17,6 +17,7 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
get_entries,
)
from erpnext.accounts.utils import get_balance_on
from erpnext.setup.utils import get_exchange_rate
class BankReconciliationTool(Document):
@@ -129,7 +130,7 @@ def create_journal_entry_bts(
bank_transaction = frappe.db.get_values(
"Bank Transaction",
bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account"],
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"],
as_dict=True,
)[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
@@ -143,29 +144,94 @@ def create_journal_entry_bts(
)
company = frappe.get_value("Account", company_account, "company")
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
# determine if multi-currency Journal or not
is_multi_currency = (
True
if company_default_currency != company_account_currency
or company_default_currency != second_account_currency
or company_default_currency != bank_transaction.currency
else False
)
accounts = []
# Multi Currency?
accounts.append(
{
"account": second_account,
"credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
}
)
second_account_dict = {
"account": second_account,
"account_currency": second_account_currency,
"credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
}
accounts.append(
{
"account": company_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company),
}
)
company_account_dict = {
"account": company_account,
"account_currency": company_account_currency,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company),
}
# convert transaction amount to company currency
if is_multi_currency:
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
else:
withdrawal_in_company_currency = bank_transaction.withdrawal
deposit_in_company_currency = bank_transaction.deposit
# if second account is of foreign currency, convert and set debit and credit fields.
if second_account_currency != company_default_currency:
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
second_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
}
)
else:
second_account_dict.update(
{
"exchange_rate": 1,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": deposit_in_company_currency,
"debit_in_account_currency": withdrawal_in_company_currency,
}
)
# if company account is of foreign currency, convert and set debit and credit fields.
if company_account_currency != company_default_currency:
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
company_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
}
)
else:
company_account_dict.update(
{
"exchange_rate": 1,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
"credit_in_account_currency": withdrawal_in_company_currency,
"debit_in_account_currency": deposit_in_company_currency,
}
)
accounts.append(second_account_dict)
accounts.append(company_account_dict)
journal_entry_dict = {
"voucher_type": entry_type,
@@ -175,6 +241,9 @@ def create_journal_entry_bts(
"cheque_no": reference_number,
"mode_of_payment": mode_of_payment,
}
if is_multi_currency:
journal_entry_dict.update({"multi_currency": True})
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts)

View File

@@ -2,6 +2,16 @@
// For license information, please see license.txt
frappe.ui.form.on("Bank Statement Import", {
onload(frm) {
frm.set_query("bank_account", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
},
setup(frm) {
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false;
@@ -352,10 +362,11 @@ frappe.ui.form.on("Bank Statement Import", {
export_errored_rows(frm) {
open_url_post(
"/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template",
{
data_import_name: frm.doc.name,
}
},
true
);
},

View File

@@ -89,7 +89,6 @@ class BankTransaction(StatusUpdater):
- 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date
"""
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
remaining_amount = self.unallocated_amount
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:

View File

@@ -6,8 +6,10 @@
"engine": "InnoDB",
"field_order": [
"api_details_section",
"disabled",
"service_provider",
"api_endpoint",
"access_key",
"url",
"column_break_3",
"help",
@@ -77,12 +79,24 @@
"label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom",
"reqd": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"depends_on": "eval:doc.service_provider == 'exchangerate.host';",
"fieldname": "access_key",
"fieldtype": "Data",
"label": "Access Key"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-01-10 15:51:14.521174",
"modified": "2023-10-04 15:30:25.333860",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",

View File

@@ -18,11 +18,21 @@ class CurrencyExchangeSettings(Document):
def set_parameters_and_result(self):
if self.service_provider == "exchangerate.host":
if not self.access_key:
frappe.throw(
_("Access Key is required for Service Provider: {0}").format(
frappe.bold(self.service_provider)
)
)
self.set("result_key", [])
self.set("req_params", [])
self.api_endpoint = "https://api.exchangerate.host/convert"
self.append("result_key", {"key": "result"})
self.append("req_params", {"key": "access_key", "value": self.access_key})
self.append("req_params", {"key": "amount", "value": "1"})
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
self.append("req_params", {"key": "from", "value": "{from_currency}"})
self.append("req_params", {"key": "to", "value": "{to_currency}"})

View File

@@ -50,8 +50,18 @@ frappe.ui.form.on("Journal Entry", {
frm.trigger("make_inter_company_journal_entry");
}, __('Make'));
}
},
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
},
before_save: function(frm) {
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
let payment_entry_references = frm.doc.accounts.filter(elem => (elem.reference_type == "Payment Entry"));
if (payment_entry_references.length > 0) {
let rows = payment_entry_references.map(x => "#"+x.idx);
frappe.throw(__("Rows: {0} have 'Payment Entry' as reference_type. This should not be set manually.", [frappe.utils.comma_and(rows)]));
}
}
},
make_inter_company_journal_entry: function(frm) {
var d = new frappe.ui.Dialog({
title: __("Select Company"),

View File

@@ -7,7 +7,7 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries'];
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
@@ -152,6 +152,13 @@ frappe.ui.form.on('Payment Entry', {
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
frm.events.show_general_ledger(frm);
if((frm.doc.references) && (frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0}))) {
frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
}, __('Actions'));
}
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
},
validate_company: (frm) => {

View File

@@ -107,6 +107,8 @@ class PaymentEntry(AccountsController):
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
"Unreconcile Payments",
"Unreconcile Payment Entries",
)
super(PaymentEntry, self).on_cancel()
self.make_gl_entries(cancel=1)
@@ -227,16 +229,18 @@ class PaymentEntry(AccountsController):
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
latest = latest.get(d.payment_term) or latest.get(None)
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif latest.outstanding_amount < latest.invoice_amount and flt(
d.outstanding_amount, d.precision("outstanding_amount")
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
and d.payment_term == ""
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
@@ -1600,11 +1604,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
"voucher_type": d.voucher_type,
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"outstanding_amount": flt(d.outstanding_amount),
"payment_term_outstanding": payment_term_outstanding,
"allocated_amount": payment_term_outstanding
"outstanding_amount": payment_term_outstanding
if payment_term_outstanding
else d.outstanding_amount,
"payment_term_outstanding": payment_term_outstanding,
"payment_amount": payment_term.payment_amount,
"payment_term": payment_term.payment_term,
}

View File

@@ -13,6 +13,7 @@
"party_type",
"party",
"due_date",
"voucher_detail_no",
"cost_center",
"finance_book",
"voucher_type",
@@ -29,7 +30,8 @@
{
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
"label": "Posting Date",
"search_index": 1
},
{
"fieldname": "account_type",
@@ -63,7 +65,8 @@
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Voucher Type",
"options": "DocType"
"options": "DocType",
"search_index": 1
},
{
"fieldname": "voucher_no",
@@ -71,14 +74,16 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Voucher No",
"options": "voucher_type"
"options": "voucher_type",
"search_index": 1
},
{
"fieldname": "against_voucher_type",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Against Voucher Type",
"options": "DocType"
"options": "DocType",
"search_index": 1
},
{
"fieldname": "against_voucher_no",
@@ -86,7 +91,8 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Against Voucher No",
"options": "against_voucher_type"
"options": "against_voucher_type",
"search_index": 1
},
{
"fieldname": "amount",
@@ -142,12 +148,18 @@
"fieldname": "remarks",
"fieldtype": "Text",
"label": "Remarks"
},
{
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"label": "Voucher Detail No",
"search_index": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-08-22 15:32:56.629430",
"modified": "2023-11-08 10:53:10.664896",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",

View File

@@ -216,6 +216,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.data = [];
const dialog = new frappe.ui.Dialog({
title: __("Select Difference Account"),
size: 'extra-large',
fields: [
{
fieldname: "allocation",
@@ -239,6 +240,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
in_list_view: 1,
read_only: 1
}, {
fieldtype:'Date',
fieldname:"gain_loss_posting_date",
label: __("Posting Date"),
in_list_view: 1,
reqd: 1,
}, {
fieldtype:'Link',
options: 'Account',
in_list_view: 1,
@@ -272,6 +280,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
args.forEach(d => {
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
"difference_account", d.difference_account);
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
"gain_loss_posting_date", d.gain_loss_posting_date);
});
this.reconcile_payment_entries();
@@ -287,6 +298,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
'reference_name': d.reference_name,
'difference_amount': d.difference_amount,
'difference_account': d.difference_account,
'gain_loss_posting_date': d.gain_loss_posting_date
});
}
});

View File

@@ -19,7 +19,7 @@ from erpnext.accounts.utils import (
get_outstanding_invoices,
reconcile_against_document,
)
from erpnext.controllers.accounts_controller import get_advance_payment_entries
from erpnext.controllers.accounts_controller import get_advance_payment_entries_for_regional
class PaymentReconciliation(Document):
@@ -62,7 +62,7 @@ class PaymentReconciliation(Document):
if self.payment_name:
condition += "name like '%%{0}%%'".format(self.payment_name)
payment_entries = get_advance_payment_entries(
payment_entries = get_advance_payment_entries_for_regional(
self.party_type,
self.party,
self.receivable_payable_account,
@@ -93,6 +93,8 @@ class PaymentReconciliation(Document):
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
)
limit = f"limit {self.payment_limit}" if self.payment_limit else " "
# nosemgrep
journal_entries = frappe.db.sql(
"""
@@ -116,11 +118,13 @@ class PaymentReconciliation(Document):
ELSE {bank_account_condition}
END)
order by t1.posting_date
{limit}
""".format(
**{
"dr_or_cr": dr_or_cr,
"bank_account_condition": bank_account_condition,
"condition": condition,
"limit": limit,
}
),
{
@@ -146,7 +150,7 @@ class PaymentReconciliation(Document):
if self.payment_name:
conditions.append(doc.name.like(f"%{self.payment_name}%"))
self.return_invoices = (
self.return_invoices_query = (
qb.from_(doc)
.select(
ConstantColumn(voucher_type).as_("voucher_type"),
@@ -154,8 +158,11 @@ class PaymentReconciliation(Document):
doc.return_against,
)
.where(Criterion.all(conditions))
.run(as_dict=True)
)
if self.payment_limit:
self.return_invoices_query = self.return_invoices_query.limit(self.payment_limit)
self.return_invoices = self.return_invoices_query.run(as_dict=True)
def get_dr_or_cr_notes(self):
@@ -315,6 +322,7 @@ class PaymentReconciliation(Document):
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
res.update({"gain_loss_posting_date": pay.get("posting_date")})
if pay.get("amount") == 0:
entries.append(res)
@@ -350,6 +358,7 @@ class PaymentReconciliation(Document):
)
def reconcile_allocations(self, skip_ref_details_update_for_pe=False):
adjust_allocations_for_taxes(self)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
@@ -420,6 +429,7 @@ class PaymentReconciliation(Document):
"allocated_amount": flt(row.get("allocated_amount")),
"difference_amount": flt(row.get("difference_amount")),
"difference_account": row.get("difference_account"),
"difference_posting_date": row.get("gain_loss_posting_date"),
"cost_center": row.get("cost_center"),
}
)
@@ -650,3 +660,8 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
None,
inv.cost_center,
)
@erpnext.allow_regional
def adjust_allocations_for_taxes(doc):
pass

View File

@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Item"]
@@ -85,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase):
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
def create_account(self):
account_name = "Debtors EUR"
if not frappe.db.get_value(
"Account", filters={"account_name": account_name, "company": self.company}
):
acc = frappe.new_doc("Account")
acc.account_name = account_name
acc.parent_account = "Accounts Receivable - _PR"
acc.company = self.company
acc.account_currency = "EUR"
acc.account_type = "Receivable"
acc.insert()
else:
name = frappe.db.get_value(
"Account",
filters={"account_name": account_name, "company": self.company},
fieldname="name",
pluck=True,
)
acc = frappe.get_doc("Account", name)
self.debtors_eur = acc.name
accounts = [
{
"attribute": "debtors_eur",
"account_name": "Debtors EUR",
"parent_account": "Accounts Receivable - _PR",
"account_currency": "EUR",
"account_type": "Receivable",
},
{
"attribute": "creditors_usd",
"account_name": "Payable USD",
"parent_account": "Accounts Payable - _PR",
"account_currency": "USD",
"account_type": "Payable",
},
]
for x in accounts:
x = frappe._dict(x)
if not frappe.db.get_value(
"Account", filters={"account_name": x.account_name, "company": self.company}
):
acc = frappe.new_doc("Account")
acc.account_name = x.account_name
acc.parent_account = x.parent_account
acc.company = self.company
acc.account_currency = x.account_currency
acc.account_type = x.account_type
acc.insert()
else:
name = frappe.db.get_value(
"Account",
filters={"account_name": x.account_name, "company": self.company},
fieldname="name",
pluck=True,
)
acc = frappe.get_doc("Account", name)
setattr(self, x.attribute, acc.name)
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
@@ -151,6 +170,64 @@ class TestPaymentReconciliation(FrappeTestCase):
payment.posting_date = posting_date
return payment
def create_purchase_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
pinv = make_purchase_invoice(
qty=qty,
rate=rate,
company=self.company,
customer=self.supplier,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return pinv
def create_purchase_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
pord = create_purchase_order(
qty=qty,
rate=rate,
company=self.company,
customer=self.supplier,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return pord
def clear_old_entries(self):
doctype_list = [
"GL Entry",
@@ -163,13 +240,11 @@ class TestPaymentReconciliation(FrappeTestCase):
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_payment_reconciliation(self):
def create_payment_reconciliation(self, party_is_customer=True):
pr = frappe.new_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = (
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
)
pr.party = self.customer
pr.party_type = "Customer" if party_is_customer else "Supplier"
pr.party = self.customer if party_is_customer else self.supplier
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
return pr
@@ -906,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(pr.allocation[0].difference_amount, 0)
def test_reconciliation_purchase_invoice_against_return(self):
pi = make_purchase_invoice(
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
).submit()
self.supplier = "_Test Supplier USD"
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
pi.supplier = self.supplier
pi.currency = "USD"
pi.conversion_rate = 50
pi.credit_to = self.creditors_usd
pi.save().submit()
pi_return = frappe.get_doc(pi.as_dict())
pi_return.name = None
@@ -918,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase):
pi_return.items[0].qty = -pi_return.items[0].qty
pi_return.submit()
self.company = "_Test Company"
self.party_type = "Supplier"
self.customer = "_Test Supplier USD"
pr = self.create_payment_reconciliation()
pr = frappe.get_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Supplier"
pr.party = self.supplier
pr.receivable_payable_account = self.creditors_usd
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
pr.get_unreconciled_entries()
invoices = []
@@ -931,6 +1011,7 @@ class TestPaymentReconciliation(FrappeTestCase):
if invoice.invoice_number == pi.name:
invoices.append(invoice.as_dict())
break
for payment in pr.payments:
if payment.reference_name == pi_return.name:
payments.append(payment.as_dict())
@@ -941,6 +1022,121 @@ class TestPaymentReconciliation(FrappeTestCase):
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
pr.reconcile()
def test_reconciliation_from_purchase_order_to_multiple_invoices(self):
"""
Reconciling advance payment from PO/SO to multiple invoices should not cause overallocation
"""
self.supplier = "_Test Supplier"
pi1 = self.create_purchase_invoice(qty=10, rate=100)
pi2 = self.create_purchase_invoice(qty=10, rate=100)
po = self.create_purchase_order(qty=20, rate=100)
pay = get_payment_entry(po.doctype, po.name)
# Overpay Puchase Order
pay.paid_amount = 3000
pay.save().submit()
# assert total allocated and unallocated before reconciliation
self.assertEqual(
(
pay.references[0].reference_doctype,
pay.references[0].reference_name,
pay.references[0].allocated_amount,
),
(po.doctype, po.name, 2000),
)
self.assertEqual(pay.total_allocated_amount, 2000)
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 2)
self.assertEqual(len(pr.payments), 2)
for x in pr.payments:
self.assertEqual((x.reference_type, x.reference_name), (pay.doctype, pay.name))
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# partial allocation on pi1 and full allocate on pi2
pr.allocation[0].allocated_amount = 100
pr.reconcile()
# assert references and total allocated and unallocated amount
pay.reload()
self.assertEqual(len(pay.references), 3)
self.assertEqual(
(
pay.references[0].reference_doctype,
pay.references[0].reference_name,
pay.references[0].allocated_amount,
),
(po.doctype, po.name, 900),
)
self.assertEqual(
(
pay.references[1].reference_doctype,
pay.references[1].reference_name,
pay.references[1].allocated_amount,
),
(pi1.doctype, pi1.name, 100),
)
self.assertEqual(
(
pay.references[2].reference_doctype,
pay.references[2].reference_name,
pay.references[2].allocated_amount,
),
(pi2.doctype, pi2.name, 1000),
)
self.assertEqual(pay.total_allocated_amount, 2000)
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 2)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# assert references and total allocated and unallocated amount
pay.reload()
self.assertEqual(len(pay.references), 3)
# PO references should be removed now
self.assertEqual(
(
pay.references[0].reference_doctype,
pay.references[0].reference_name,
pay.references[0].allocated_amount,
),
(pi1.doctype, pi1.name, 100),
)
self.assertEqual(
(
pay.references[1].reference_doctype,
pay.references[1].reference_name,
pay.references[1].allocated_amount,
),
(pi2.doctype, pi2.name, 1000),
)
self.assertEqual(
(
pay.references[2].reference_doctype,
pay.references[2].reference_name,
pay.references[2].allocated_amount,
),
(pi1.doctype, pi1.name, 900),
)
self.assertEqual(pay.total_allocated_amount, 2000)
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -19,6 +19,7 @@
"is_advance",
"section_break_5",
"difference_amount",
"gain_loss_posting_date",
"column_break_7",
"difference_account",
"exchange_rate",
@@ -151,11 +152,16 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
}
],
"istable": 1,
"links": [],
"modified": "2023-09-03 07:52:33.684217",
"modified": "2023-10-23 10:44:56.066303",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",

View File

@@ -249,7 +249,7 @@ class PaymentRequest(Document):
if (
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
):
party_amount = ref_doc.base_grand_total
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
else:
party_amount = self.grand_total

View File

@@ -8,6 +8,7 @@
"transaction_date",
"posting_date",
"fiscal_year",
"year_start_date",
"amended_from",
"company",
"column_break1",
@@ -100,16 +101,22 @@
"fieldtype": "Text",
"label": "Error Message",
"read_only": 1
},
{
"fieldname": "year_start_date",
"fieldtype": "Date",
"label": "Year Start Date"
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-07-20 14:51:04.714154",
"modified": "2023-09-11 20:19:11.810533",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -144,5 +151,6 @@
"search_fields": "posting_date, fiscal_year",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "closing_account_head"
}

View File

@@ -95,15 +95,23 @@ class PeriodClosingVoucher(AccountsController):
self.check_if_previous_year_closed()
pce = frappe.db.sql(
"""select name from `tabPeriod Closing Voucher`
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
(self.posting_date, self.fiscal_year, self.company),
pcv = frappe.qb.DocType("Period Closing Voucher")
existing_entry = (
frappe.qb.from_(pcv)
.select(pcv.name)
.where(
(pcv.posting_date >= self.posting_date)
& (pcv.fiscal_year == self.fiscal_year)
& (pcv.docstatus == 1)
& (pcv.company == self.company)
)
.run()
)
if pce and pce[0][0]:
if existing_entry and existing_entry[0][0]:
frappe.throw(
_("Another Period Closing Entry {0} has been made after {1}").format(
pce[0][0], self.posting_date
existing_entry[0][0], self.posting_date
)
)
@@ -130,18 +138,27 @@ class PeriodClosingVoucher(AccountsController):
frappe.enqueue(
process_gl_entries,
gl_entries=gl_entries,
voucher_name=self.name,
timeout=3000,
)
frappe.enqueue(
process_closing_entries,
gl_entries=gl_entries,
closing_entries=closing_entries,
voucher_name=self.name,
company=self.company,
closing_date=self.posting_date,
queue="long",
timeout=3000,
)
frappe.msgprint(
_("The GL Entries will be processed in the background, it can take a few minutes."),
alert=True,
)
else:
process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
process_gl_entries(gl_entries, self.name)
process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
def get_grouped_gl_entries(self, get_opening_entries=False):
closing_entries = []
@@ -322,17 +339,12 @@ class PeriodClosingVoucher(AccountsController):
return query.run(as_dict=1)
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
def process_gl_entries(gl_entries, voucher_name):
from erpnext.accounts.general_ledger import make_gl_entries
try:
if gl_entries:
make_gl_entries(gl_entries, merge_entries=False)
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
except Exception as e:
frappe.db.rollback()
@@ -340,6 +352,19 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
try:
if gl_entries + closing_entries:
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
def make_reverse_gl_entries(voucher_type, voucher_no):
from erpnext.accounts.general_ledger import make_reverse_gl_entries

View File

@@ -10,7 +10,7 @@ from frappe.utils import add_months, today
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.utils import get_fiscal_year, now
from erpnext.accounts.utils import get_fiscal_year
class TestPeriodClosingVoucher(unittest.TestCase):

View File

@@ -110,7 +110,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-04-21 17:36:26.642617",
"modified": "2023-11-02 11:32:12.254018",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Payment Reconciliation Log",
@@ -125,7 +125,19 @@
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
}

View File

@@ -47,6 +47,20 @@ class ProcessStatementOfAccounts(Document):
def get_report_pdf(doc, consolidated=True):
statement_dict = get_statement_dict(doc)
if not bool(statement_dict):
return False
elif consolidated:
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
result = delimiter.join(list(statement_dict.values()))
return get_pdf(result, {"orientation": doc.orientation})
else:
for customer, statement_html in statement_dict.items():
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
return statement_dict
def get_statement_dict(doc, get_statement_dict=False):
statement_dict = {}
ageing = ""
@@ -77,17 +91,11 @@ def get_report_pdf(doc, consolidated=True):
if not res:
continue
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
statement_dict[entry.customer] = (
[res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing)
)
if not bool(statement_dict):
return False
elif consolidated:
result = "".join(list(statement_dict.values()))
return get_pdf(result, {"orientation": doc.orientation})
else:
for customer, statement_html in statement_dict.items():
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
return statement_dict
return statement_dict
def set_ageing(doc, entry):
@@ -100,7 +108,8 @@ def set_ageing(doc, entry):
"range2": 60,
"range3": 90,
"range4": 120,
"customer": entry.customer,
"party_type": "Customer",
"party": [entry.customer],
}
)
col1, ageing = get_ageing(ageing_filters)
@@ -144,7 +153,7 @@ def get_ar_filters(doc, entry):
return {
"report_date": doc.posting_date if doc.posting_date else None,
"party_type": "Customer",
"party": entry.customer,
"party": [entry.customer],
"customer_name": entry.customer_name if entry.customer_name else None,
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
"sales_partner": doc.sales_partner if doc.sales_partner else None,

View File

@@ -4,39 +4,107 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, getdate, today
from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import (
get_statement_dict,
send_emails,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
class TestProcessStatementOfAccounts(unittest.TestCase):
class TestProcessStatementOfAccounts(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
self.create_customer(customer_name="Other Customer")
self.clear_old_entries()
self.si = create_sales_invoice()
self.process_soa = create_process_soa()
create_sales_invoice(customer="Other Customer")
def test_process_soa_for_gl(self):
"""Tests the utils for Statement of Accounts(General Ledger)"""
process_soa = create_process_soa(
name="_Test Process SOA for GL",
customers=[{"customer": "_Test Customer"}, {"customer": "Other Customer"}],
)
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
# Checks if the statements are filtered based on the Customer
self.assertIn("Other Customer", statement_dict)
self.assertIn("_Test Customer", statement_dict)
# Checks if the correct number of receivable entries exist
# 3 rows for opening and closing and 1 row for SI
receivable_entries = statement_dict["_Test Customer"][0]
self.assertEqual(len(receivable_entries), 4)
# Checks the amount for the receivable entry
self.assertEqual(receivable_entries[1].voucher_no, self.si.name)
self.assertEqual(receivable_entries[1].balance, 100)
def test_process_soa_for_ar(self):
"""Tests the utils for Statement of Accounts(Accounts Receivable)"""
process_soa = create_process_soa(name="_Test Process SOA for AR", report="Accounts Receivable")
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
# Checks if the statements are filtered based on the Customer
self.assertNotIn("Other Customer", statement_dict)
self.assertIn("_Test Customer", statement_dict)
# Checks if the correct number of receivable entries exist
receivable_entries = statement_dict["_Test Customer"][0]
self.assertEqual(len(receivable_entries), 1)
# Checks the amount for the receivable entry
self.assertEqual(receivable_entries[0].voucher_no, self.si.name)
self.assertEqual(receivable_entries[0].total_due, 100)
# Checks the ageing summary for AR
ageing_summary = statement_dict["_Test Customer"][1][0]
expected_summary = frappe._dict(
range1=100,
range2=0,
range3=0,
range4=0,
range5=0,
)
self.check_ageing_summary(ageing_summary, expected_summary)
def test_auto_email_for_process_soa_ar(self):
send_emails(self.process_soa.name, from_scheduler=True)
self.process_soa.load_from_db()
self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7)))
process_soa = create_process_soa(
name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable"
)
send_emails(process_soa.name, from_scheduler=True)
process_soa.load_from_db()
self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7)))
def check_ageing_summary(self, ageing, expected_ageing):
for age_range in expected_ageing:
self.assertEqual(expected_ageing[age_range], ageing.get(age_range))
def tearDown(self):
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
frappe.db.rollback()
def create_process_soa():
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
def create_process_soa(**args):
args = frappe._dict(args)
frappe.delete_doc_if_exists("Process Statement Of Accounts", args.name)
process_soa = frappe.new_doc("Process Statement Of Accounts")
soa_dict = {
"name": "Test Process SOA",
"company": "_Test Company",
}
soa_dict = frappe._dict(
name=args.name,
company=args.company or "_Test Company",
customers=args.customers or [{"customer": "_Test Customer"}],
enable_auto_email=1 if args.enable_auto_email else 0,
frequency=args.frequency or "Weekly",
report=args.report or "General Ledger",
from_date=args.from_date or getdate(today()),
to_date=args.to_date or getdate(today()),
posting_date=args.posting_date or getdate(today()),
include_ageing=1,
)
process_soa.update(soa_dict)
process_soa.set("customers", [{"customer": "_Test Customer"}])
process_soa.enable_auto_email = 1
process_soa.frequency = "Weekly"
process_soa.report = "Accounts Receivable"
process_soa.save()
return process_soa

View File

@@ -59,6 +59,25 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
this.show_stock_ledger();
}
if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) {
this.frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update."));
this.frm.add_custom_button(__('Repost Accounting Entries'),
() => {
this.frm.call({
doc: this.frm.doc,
method: 'repost_accounting_entries',
freeze: true,
freeze_message: __('Reposting...'),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__('Accounting Entries are reposted.'));
me.frm.refresh();
}
}
});
}).removeClass('btn-default').addClass('btn-warning');
}
if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){
if(doc.on_hold) {
this.frm.add_custom_button(
@@ -162,6 +181,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
}
unblock_invoice() {
@@ -460,6 +480,12 @@ cur_frm.set_query("expense_account", "items", function(doc) {
}
});
cur_frm.set_query("wip_composite_asset", "items", function() {
return {
filters: {'is_composite_asset': 1, 'docstatus': 0 }
}
});
cur_frm.cscript.expense_account = function(doc, cdt, cdn){
var d = locals[cdt][cdn];
if(d.idx == 1 && d.expense_account){

View File

@@ -36,6 +36,7 @@
"currency_and_price_list",
"currency",
"conversion_rate",
"use_transaction_date_exchange_rate",
"column_break2",
"buying_price_list",
"price_list_currency",
@@ -166,6 +167,7 @@
"against_expense_account",
"column_break_63",
"unrealized_profit_loss_account",
"repost_required",
"subscription_section",
"auto_repeat",
"update_auto_repeat_reference",
@@ -190,8 +192,7 @@
"inter_company_invoice_reference",
"is_old_subcontracting_flow",
"remarks",
"connections_tab",
"column_break_38"
"connections_tab"
],
"fields": [
{
@@ -382,7 +383,8 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
"print_hide": 1
"print_hide": 1,
"search_index": 1
},
{
"fieldname": "column_break_15",
@@ -405,7 +407,8 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "section_addresses",
@@ -987,6 +990,7 @@
"print_hide": 1
},
{
"allow_on_submit": 1,
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
@@ -1050,6 +1054,7 @@
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"depends_on": "eval:flt(doc.write_off_amount)!=0",
"fieldname": "write_off_account",
"fieldtype": "Link",
@@ -1213,6 +1218,7 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"default": "No",
"fieldname": "is_opening",
"fieldtype": "Select",
@@ -1345,6 +1351,7 @@
"options": "Project"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit/Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
@@ -1377,6 +1384,7 @@
"depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -1494,10 +1502,6 @@
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_38",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_50",
"fieldtype": "Column Break"
@@ -1568,13 +1572,29 @@
"fieldname": "use_company_roundoff_cost_center",
"fieldtype": "Check",
"label": "Use Company Default Round Off Cost Center"
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"options": "Account",
"read_only": 1
},
{
"default": "0",
"fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-07-04 17:23:59.145031",
"modified": "2023-11-03 15:47:30.319200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -11,6 +11,9 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate,
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
)
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
get_total_in_party_account_currency,
@@ -30,7 +33,7 @@ from erpnext.accounts.general_ledger import (
)
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.accounts_controller import validate_account_head
@@ -281,9 +284,6 @@ class PurchaseInvoice(BuyingController):
# in case of auto inventory accounting,
# expense account is always "Stock Received But Not Billed" for a stock item
# except opening entry, drop-ship entry and fixed asset items
if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if (
auto_accounting_for_stock
and item.item_code in stock_items
@@ -350,22 +350,26 @@ class PurchaseInvoice(BuyingController):
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
elif item.is_fixed_asset and item.pr_detail:
if not asset_received_but_not_billed:
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
item.expense_account = asset_received_but_not_billed
elif item.is_fixed_asset:
account_type = (
"capital_work_in_progress_account"
if is_cwip_accounting_enabled(item.asset_category)
else "fixed_asset_account"
)
asset_category_account = get_asset_category_account(
"fixed_asset_account", item=item.item_code, company=self.company
account_type, item=item.item_code, company=self.company
)
if not asset_category_account:
form_link = get_link_to_form("Asset Category", asset_category)
form_link = get_link_to_form("Asset Category", item.asset_category)
throw(
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
title=_("Missing Account"),
)
item.expense_account = asset_category_account
elif item.is_fixed_asset and item.pr_detail:
if not asset_received_but_not_billed:
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
item.expense_account = asset_received_but_not_billed
elif not item.expense_account and for_validate:
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
@@ -487,6 +491,11 @@ class PurchaseInvoice(BuyingController):
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
)
def validate_for_repost(self):
self.validate_write_off_account()
self.validate_expense_account()
validate_docs_for_deferred_accounting([], [self.name])
def on_submit(self):
super(PurchaseInvoice, self).on_submit()
@@ -529,6 +538,19 @@ class PurchaseInvoice(BuyingController):
self.process_common_party_accounting()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
fields_to_check = [
"cash_bank_account",
"write_off_account",
"unrealized_profit_loss_account",
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:
gl_entries = self.get_gl_entries()
@@ -570,12 +592,11 @@ class PurchaseInvoice(BuyingController):
def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if self.auto_accounting_for_stock:
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
else:
self.stock_received_but_not_billed = None
self.expenses_included_in_valuation = None
self.negative_expense_to_be_booked = 0.0
gl_entries = []
@@ -584,9 +605,6 @@ class PurchaseInvoice(BuyingController):
self.make_item_gl_entries(gl_entries)
self.make_precision_loss_gl_entry(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
@@ -690,7 +708,11 @@ class PurchaseInvoice(BuyingController):
if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
if (
self.update_stock
and self.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
# warehouse account
warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency
@@ -805,9 +827,7 @@ class PurchaseInvoice(BuyingController):
)
)
elif not item.is_fixed_asset or (
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
):
else:
expense_account = (
item.expense_account
if (not item.enable_deferred_expense or self.is_return)
@@ -900,40 +920,6 @@ class PurchaseInvoice(BuyingController):
)
)
# If asset is bought through this document and not linked to PR
if self.update_stock and item.landed_cost_voucher_amount:
expenses_included_in_asset_valuation = self.get_company_default(
"expenses_included_in_asset_valuation"
)
# Amount added through landed-cost-voucher
gl_entries.append(
self.get_gl_dict(
{
"account": expenses_included_in_asset_valuation,
"against": expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": expense_account,
"against": expenses_included_in_asset_valuation,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
# update gross amount of asset bought through this document
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
@@ -958,11 +944,17 @@ class PurchaseInvoice(BuyingController):
(item.purchase_receipt, valuation_tax_accounts),
)
stock_rbnb = (
self.get_company_default("asset_received_but_not_billed")
if item.is_fixed_asset
else self.stock_received_but_not_billed
)
if not negative_expense_booked_in_pr:
gl_entries.append(
self.get_gl_dict(
{
"account": self.stock_received_but_not_billed,
"account": stock_rbnb,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or _("Accounting Entry for Stock"),
@@ -977,156 +969,12 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount, item.precision("item_tax_amount")
)
def get_asset_gl_entry(self, gl_entries):
arbnb_account = None
eiiav_account = None
asset_eiiav_currency = None
for item in self.get("items"):
if item.is_fixed_asset:
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
item_exp_acc_type = frappe.db.get_value("Account", item.expense_account, "account_type")
if not item.expense_account or item_exp_acc_type not in [
"Asset Received But Not Billed",
"Fixed Asset",
]:
if not arbnb_account:
arbnb_account = self.get_company_default("asset_received_but_not_billed")
item.expense_account = arbnb_account
if not self.update_stock:
arbnb_currency = get_account_currency(item.expense_account)
gl_entries.append(
self.get_gl_dict(
{
"account": item.expense_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
item=item,
)
)
if item.item_tax_amount:
if not eiiav_account or not asset_eiiav_currency:
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
asset_eiiav_currency = get_account_currency(eiiav_account)
gl_entries.append(
self.get_gl_dict(
{
"account": eiiav_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center,
"project": item.project or self.project,
"credit": item.item_tax_amount,
"credit_in_account_currency": (
item.item_tax_amount
if asset_eiiav_currency == self.company_currency
else item.item_tax_amount / self.conversion_rate
),
},
item=item,
)
)
else:
cwip_account = get_asset_account(
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
)
cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(
self.get_gl_dict(
{
"account": cwip_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
),
"cost_center": self.cost_center,
"project": item.project or self.project,
},
item=item,
)
)
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
if not eiiav_account or not asset_eiiav_currency:
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
asset_eiiav_currency = get_account_currency(eiiav_account)
gl_entries.append(
self.get_gl_dict(
{
"account": eiiav_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center,
"credit": item.item_tax_amount,
"project": item.project or self.project,
"credit_in_account_currency": (
item.item_tax_amount
if asset_eiiav_currency == self.company_currency
else item.item_tax_amount / self.conversion_rate
),
},
item=item,
)
)
# Assets are bought through this document then it will be linked to this document
if flt(item.landed_cost_voucher_amount):
if not eiiav_account:
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
gl_entries.append(
self.get_gl_dict(
{
"account": eiiav_account,
"against": cwip_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": cwip_account,
"against": eiiav_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount),
"project": item.project or self.project,
},
item=item,
)
)
# update gross amount of assets bought through this document
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
return gl_entries
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
def make_stock_adjustment_entry(
self, gl_entries, item, voucher_wise_stock_value, account_currency
@@ -1828,6 +1676,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"po_detail": "purchase_order_item",
"material_request": "material_request",
"material_request_item": "material_request_item",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),

View File

@@ -5,7 +5,7 @@
import unittest
import frappe
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
import erpnext
@@ -33,7 +33,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
test_ignore = ["Serial No"]
class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
@@ -43,6 +43,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def tearDown(self):
frappe.db.rollback()
def test_purchase_invoice_received_qty(self):
"""
1. Test if received qty is validated against accepted + rejected
@@ -417,6 +420,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertEqual(tax.tax_amount, expected_values[i][1])
self.assertEqual(tax.total, expected_values[i][2])
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_purchase_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -471,6 +475,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)
)
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_invoice_with_advance_and_multi_payment_terms(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -1209,6 +1214,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_gain_loss_with_advance_entry(self):
unlink_enabled = frappe.db.get_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
@@ -1411,6 +1417,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_purchase_invoice_advance_taxes(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -1711,9 +1718,14 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertTrue(return_pi.docstatus == 1)
def test_gl_entries_for_standalone_debit_note(self):
make_purchase_invoice(qty=5, rate=500, update_stock=True)
from erpnext.stock.doctype.item.test_item import make_item
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
item_code = make_item(properties={"is_stock_item": 1})
make_purchase_invoice(item_code=item_code, qty=5, rate=500, update_stock=True)
returned_inv = make_purchase_invoice(
item_code=item_code, qty=-5, rate=5, update_stock=True, is_return=True
)
# override the rate with valuation rate
sle = frappe.get_all(
@@ -1723,7 +1735,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)[0]
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
self.assertAlmostEqual(rate, 500)
def test_payment_allocation_for_payment_terms(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import (
@@ -1796,7 +1808,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
pi = make_purchase_invoice(
company="_Test Company",
customer="_Test Supplier",
do_not_save=True,
do_not_submit=True,
rate=1000,
@@ -1826,6 +1837,62 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
clear_dimension_defaults("Branch")
disable_dimension()
def test_repost_accounting_entries(self):
# update repost settings
settings = frappe.get_doc("Repost Accounting Ledger Settings")
if not [x for x in settings.allowed_types if x.document_type == "Purchase Invoice"]:
settings.append("allowed_types", {"document_type": "Purchase Invoice", "allowed": True})
settings.save()
pi = make_purchase_invoice(
rate=1000,
price_list_rate=1000,
qty=1,
)
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate()],
["Creditors - _TC", 0.0, 1000, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
pi.items[0].expense_account = "Service - _TC"
pi.save()
pi.load_from_db()
self.assertTrue(pi.repost_required)
pi.repost_accounting_entries()
expected_gle = [
["Creditors - _TC", 0.0, 1000, nowdate()],
["Service - _TC", 1000, 0.0, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
pi.load_from_db()
self.assertFalse(pi.repost_required)
def test_default_cost_center_for_purchase(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
create_cost_center(cost_center_name=c_center)
item = create_item(
"_Test Cost Center Item For Purchase",
is_stock_item=1,
buying_cost_center="_Test Cost Center Buying - _TC",
selling_cost_center="_Test Cost Center Selling - _TC",
)
pi = make_purchase_invoice(
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
)
pi.items[0].cost_center = ""
pi.set_missing_values()
pi.calculate_taxes_and_totals()
pi.save()
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
def check_gl_entries(
doc,

View File

@@ -75,6 +75,7 @@
"manufacturer_part_no",
"accounting",
"expense_account",
"wip_composite_asset",
"col_break5",
"is_fixed_asset",
"asset_location",
@@ -467,6 +468,7 @@
"label": "Accounting"
},
{
"allow_on_submit": 1,
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Expense Head",
@@ -877,12 +879,18 @@
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
},
{
"fieldname": "wip_composite_asset",
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-07-04 17:22:21.501152",
"modified": "2023-10-03 21:01:01.824892",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -892,4 +900,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -86,6 +86,7 @@
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"columns": 2,
"fieldname": "account_head",
"fieldtype": "Link",
@@ -97,6 +98,7 @@
"reqd": 1
},
{
"allow_on_submit": 1,
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",

View File

@@ -5,9 +5,7 @@ frappe.ui.form.on("Repost Accounting Ledger", {
setup: function(frm) {
frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
return {
filters: {
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']],
}
query: "erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.get_repost_allowed_types"
}
}

View File

@@ -55,7 +55,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-07-27 15:47:58.975034",
"modified": "2023-09-26 14:21:27.362567",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger",
@@ -77,5 +77,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
"states": [],
"track_changes": 1
}

View File

@@ -10,9 +10,12 @@ from frappe.utils.data import comma_and
class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
self._allowed_types = set(
["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]
)
self._allowed_types = [
x.document_type
for x in frappe.db.get_all(
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
)
]
def validate(self):
self.validate_vouchers()
@@ -21,29 +24,8 @@ class RepostAccountingLedger(Document):
def validate_for_deferred_accounting(self):
sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"]
docs_with_deferred_revenue = frappe.db.get_all(
"Sales Invoice Item",
filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
fields=["parent"],
as_list=1,
)
purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"]
docs_with_deferred_expense = frappe.db.get_all(
"Purchase Invoice Item",
filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
fields=["parent"],
as_list=1,
)
if docs_with_deferred_revenue or docs_with_deferred_expense:
frappe.throw(
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
frappe.bold(
comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
)
)
)
validate_docs_for_deferred_accounting(sales_docs, purchase_docs)
def validate_for_closed_fiscal_year(self):
if self.vouchers:
@@ -139,14 +121,17 @@ class RepostAccountingLedger(Document):
return rendered_page
def on_submit(self):
job_name = "repost_accounting_ledger_" + self.name
frappe.enqueue(
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
account_repost_doc=self.name,
is_async=True,
job_name=job_name,
)
frappe.msgprint(_("Repost has started in the background"))
if len(self.vouchers) > 1:
job_name = "repost_accounting_ledger_" + self.name
frappe.enqueue(
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
account_repost_doc=self.name,
is_async=True,
job_name=job_name,
)
frappe.msgprint(_("Repost has started in the background"))
else:
start_repost(self.name)
@frappe.whitelist()
@@ -175,9 +160,47 @@ def start_repost(account_repost_doc=str) -> None:
doc.docstatus = 1
doc.make_gl_entries()
elif doc.doctype in ["Payment Entry", "Journal Entry"]:
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
if not repost_doc.delete_cancelled_entries:
doc.make_gl_entries(1)
doc.make_gl_entries()
frappe.db.commit()
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
docs_with_deferred_revenue = frappe.db.get_all(
"Sales Invoice Item",
filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
fields=["parent"],
as_list=1,
)
docs_with_deferred_expense = frappe.db.get_all(
"Purchase Invoice Item",
filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
fields=["parent"],
as_list=1,
)
if docs_with_deferred_revenue or docs_with_deferred_expense:
frappe.throw(
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
)
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
filters = {"allowed": True}
if txt:
filters.update({"document_type": ("like", f"%{txt}%")})
if allowed_types := frappe.db.get_all(
"Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
):
return allowed_types
return []

View File

@@ -20,10 +20,18 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
self.create_company()
self.create_customer()
self.create_item()
self.update_repost_settings()
def teadDown(self):
frappe.db.rollback()
def update_repost_settings(self):
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
for x in allowed_types:
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
repost_settings.save()
def test_01_basic_functions(self):
si = create_sales_invoice(
item=self.item,

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Repost Accounting Ledger Settings", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,46 @@
{
"actions": [],
"creation": "2023-11-07 09:57:20.619939",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"allowed_types"
],
"fields": [
{
"fieldname": "allowed_types",
"fieldtype": "Table",
"label": "Allowed Doctypes",
"options": "Repost Allowed Types"
}
],
"in_create": 1,
"issingle": 1,
"links": [],
"modified": "2023-11-07 14:24:13.321522",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "System Manager",
"select": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class RepostAccountingLedgerSettings(Document):
pass

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestRepostAccountingLedgerSettings(FrappeTestCase):
pass

View File

@@ -0,0 +1,45 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-11-07 09:58:03.595382",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"column_break_sfzb",
"allowed"
],
"fields": [
{
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Doctype",
"options": "DocType"
},
{
"default": "0",
"fieldname": "allowed",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Allowed"
},
{
"fieldname": "column_break_sfzb",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-11-07 10:01:39.217861",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Allowed Types",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class RepostAllowedTypes(Document):
pass

View File

@@ -99,7 +99,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-11-08 07:38:40.079038",
"modified": "2023-09-26 14:21:35.719727",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",
@@ -155,5 +155,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
"states": [],
"track_changes": 1
}

View File

@@ -34,7 +34,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger"];
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
@@ -177,8 +177,11 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
}, __('Create'));
}
}
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
}
make_maintenance_schedule() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",

View File

@@ -26,6 +26,7 @@
"is_return",
"return_against",
"update_billed_amount_in_sales_order",
"update_billed_amount_in_delivery_note",
"is_debit_note",
"amended_from",
"accounting_dimensions_section",
@@ -2144,6 +2145,13 @@
"fieldname": "use_company_roundoff_cost_center",
"fieldtype": "Check",
"label": "Use Company default Cost Center for Round off"
},
{
"default": "0",
"depends_on": "eval: doc.is_return",
"fieldname": "update_billed_amount_in_delivery_note",
"fieldtype": "Check",
"label": "Update Billed Amount in Delivery Note"
}
],
"icon": "fa fa-file-text",
@@ -2156,7 +2164,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2023-06-19 16:02:05.309332",
"modified": "2023-11-03 14:39:38.012346",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -11,13 +11,13 @@ from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,
validate_loyalty_points,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -176,6 +176,12 @@ class SalesInvoice(SellingController):
self.validate_account_for_change_amount()
self.validate_income_account()
def validate_for_repost(self):
self.validate_write_off_account()
self.validate_account_for_change_amount()
self.validate_income_account()
validate_docs_for_deferred_accounting([self.name], [])
def validate_fixed_asset(self):
for d in self.get("items"):
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
@@ -255,6 +261,7 @@ class SalesInvoice(SellingController):
self.update_status_updater_args()
self.update_prevdoc_status()
self.update_billing_status_in_dn()
self.clear_unallocated_mode_of_payments()
@@ -401,6 +408,8 @@ class SalesInvoice(SellingController):
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
"Unreconcile Payments",
"Unreconcile Payment Entries",
"Payment Ledger Entry",
)
@@ -527,89 +536,22 @@ class SalesInvoice(SellingController):
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
needs_repost = 0
# Check if any field affecting accounting entry is altered
doc_before_update = self.get_doc_before_save()
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Check if opening entry check updated
if doc_before_update.get("is_opening") != self.is_opening:
needs_repost = 1
if not needs_repost:
# Parent Level Accounts excluding party account
for field in (
"additional_discount_account",
"cash_bank_account",
"account_for_change_amount",
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
):
if doc_before_update.get(field) != self.get(field):
needs_repost = 1
break
# Check for parent accounting dimensions
for dimension in accounting_dimensions:
if doc_before_update.get(dimension) != self.get(dimension):
needs_repost = 1
break
# Check for child tables
if self.check_if_child_table_updated(
"items",
doc_before_update,
("income_account", "expense_account", "discount_account"),
accounting_dimensions,
):
needs_repost = 1
if self.check_if_child_table_updated(
"taxes", doc_before_update, ("account_head",), accounting_dimensions
):
needs_repost = 1
self.validate_accounts()
# validate if deferred revenue is enabled for any item
# Don't allow to update the invoice if deferred revenue is enabled
for item in self.get("items"):
if item.enable_deferred_revenue:
frappe.throw(
_(
"Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission."
).format(item.item_code)
)
self.db_set("repost_required", needs_repost)
def check_if_child_table_updated(
self, child_table, doc_before_update, fields_to_check, accounting_dimensions
):
# Check if any field affecting accounting entry is altered
for index, item in enumerate(self.get(child_table)):
for field in fields_to_check:
if doc_before_update.get(child_table)[index].get(field) != item.get(field):
return True
for dimension in accounting_dimensions:
if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension):
return True
return False
@frappe.whitelist()
def repost_accounting_entries(self):
if self.repost_required:
self.docstatus = 2
self.make_gl_entries_on_cancel()
self.docstatus = 1
self.make_gl_entries()
self.db_set("repost_required", 0)
else:
frappe.throw(_("No updates pending for reposting"))
fields_to_check = [
"additional_discount_account",
"cash_bank_account",
"account_for_change_amount",
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
]
child_tables = {
"items": ("income_account", "expense_account", "discount_account"),
"taxes": ("account_head",),
}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def set_paid_amount(self):
paid_amount = 0.0
@@ -1095,7 +1037,7 @@ class SalesInvoice(SellingController):
def make_customer_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
# because rounded_total had value even before introduction of posting GLE based on rounded total
grand_total = (
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
)
@@ -1330,7 +1272,7 @@ class SalesInvoice(SellingController):
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
payment_mode.base_amount -= flt(self.change_amount)
if payment_mode.amount:
if payment_mode.base_amount:
# POS, make payment entries
gl_entries.append(
self.get_gl_dict(
@@ -1492,6 +1434,8 @@ class SalesInvoice(SellingController):
)
def update_billing_status_in_dn(self, update_modified=True):
if self.is_return and not self.update_billed_amount_in_delivery_note:
return
updated_delivery_notes = []
for d in self.get("items"):
if d.dn_detail:

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import make_autoname
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
import erpnext
@@ -38,13 +38,17 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
class TestSalesInvoice(unittest.TestCase):
class TestSalesInvoice(FrappeTestCase):
def setUp(self):
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
create_internal_parties()
setup_accounts()
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
frappe.db.rollback()
def make(self):
w = frappe.copy_doc(test_records[0])
@@ -172,6 +176,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -1293,6 +1298,7 @@ class TestSalesInvoice(unittest.TestCase):
dn.submit()
return dn
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_sales_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -2491,12 +2497,6 @@ class TestSalesInvoice(unittest.TestCase):
"stock_received_but_not_billed",
"Stock Received But Not Billed - _TC1",
)
frappe.db.set_value(
"Company",
"_Test Company 1",
"expenses_included_in_valuation",
"Expenses Included In Valuation - _TC1",
)
# begin test
si = create_sales_invoice(
@@ -2534,7 +2534,7 @@ class TestSalesInvoice(unittest.TestCase):
# tear down
frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock)
def test_sle_for_target_warehouse(self):
se = make_stock_entry(
@@ -2546,6 +2546,7 @@ class TestSalesInvoice(unittest.TestCase):
)
si = frappe.copy_doc(test_records[0])
si.customer = "_Test Internal Customer 3"
si.update_stock = 1
si.set_warehouse = "Finished Goods - _TC"
si.set_target_warehouse = "Stores - _TC"
@@ -2774,6 +2775,13 @@ class TestSalesInvoice(unittest.TestCase):
company="_Test Company",
)
tds_payable_account = create_account(
account_name="TDS Payable",
account_type="Tax",
parent_account="Duties and Taxes - _TC",
company="_Test Company",
)
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
si.apply_discount_on = "Grand Total"
si.additional_discount_account = additional_discount_account
@@ -3072,8 +3080,8 @@ class TestSalesInvoice(unittest.TestCase):
si.commission_rate = commission_rate
self.assertRaises(frappe.ValidationError, si.save)
@change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)})
def test_sales_invoice_submission_post_account_freezing_date(self):
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True)
si.posting_date = add_days(getdate(), 1)
si.save()
@@ -3082,8 +3090,6 @@ class TestSalesInvoice(unittest.TestCase):
si.posting_date = getdate()
si.submit()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_over_billing_case_against_delivery_note(self):
"""
Test a case where duplicating the item with qty = 1 in the invoice
@@ -3112,6 +3118,13 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
@change_settings(
"Accounts Settings",
{
"book_deferred_entries_via_journal_entry": 1,
"submit_journal_entries": 1,
},
)
def test_multi_currency_deferred_revenue_via_journal_entry(self):
deferred_account = create_account(
account_name="Deferred Revenue",
@@ -3119,11 +3132,6 @@ class TestSalesInvoice(unittest.TestCase):
company="_Test Company",
)
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 1
acc_settings.submit_journal_entries = 1
acc_settings.save()
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_expense = 1
item.item_defaults[0].deferred_revenue_account = deferred_account
@@ -3189,13 +3197,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][2], gle.debit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entries = 0
acc_settings.save()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_standalone_serial_no_return(self):
si = create_sales_invoice(
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
@@ -3573,6 +3574,20 @@ def create_internal_parties():
allowed_to_interact_with="_Test Company with perpetual inventory",
)
create_internal_customer(
customer_name="_Test Internal Customer 3",
represents_company="_Test Company",
allowed_to_interact_with="_Test Company",
)
account = create_account(
account_name="Unrealized Profit",
parent_account="Current Liabilities - _TC",
company="_Test Company",
)
frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account)
create_internal_supplier(
supplier_name="_Test Internal Supplier",
represents_company="Wind Power LLC",

View File

@@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', {
}
};
});
frm.set_query('sales_tax_template', function () {
return {
filters: {
company: frm.doc.company
}
};
});
},
refresh: function(frm) {

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils.data import (
add_days,
add_months,
@@ -90,10 +91,14 @@ def create_parties():
customer.insert()
class TestSubscription(unittest.TestCase):
class TestSubscription(FrappeTestCase):
def setUp(self):
create_plan()
create_parties()
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
frappe.db.rollback()
def test_create_subscription_with_trial_with_correct_period(self):
subscription = frappe.new_doc("Subscription")

View File

@@ -0,0 +1,83 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-08-22 10:28:10.196712",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account",
"party_type",
"party",
"reference_doctype",
"reference_name",
"allocated_amount",
"account_currency",
"unlinked"
],
"fields": [
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Reference Name",
"options": "reference_doctype"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated Amount",
"options": "account_currency"
},
{
"default": "0",
"fieldname": "unlinked",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Unlinked",
"read_only": 1
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Reference Type",
"options": "DocType"
},
{
"fieldname": "account",
"fieldtype": "Data",
"label": "Account"
},
{
"fieldname": "party_type",
"fieldtype": "Data",
"label": "Party Type"
},
{
"fieldname": "party",
"fieldtype": "Data",
"label": "Party"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-09-05 09:33:28.620149",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Unreconcile Payment Entries",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class UnreconcilePaymentEntries(Document):
pass

View File

@@ -0,0 +1,316 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
self.create_usd_receivable_account()
self.create_item()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_sales_invoice(self, do_not_submit=False):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
posting_date=today(),
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
rate=100,
price_list_rate=100,
do_not_submit=do_not_submit,
)
return si
def create_payment_entry(self):
pe = create_payment_entry(
company=self.company,
payment_type="Receive",
party_type="Customer",
party=self.customer,
paid_from=self.debit_to,
paid_to=self.cash,
paid_amount=200,
save=True,
)
return pe
def test_01_unreconcile_invoice(self):
si1 = self.create_sales_invoice()
si2 = self.create_sales_invoice()
pe = self.create_payment_entry()
pe.append(
"references",
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 100},
)
pe.append(
"references",
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 100},
)
# Allocation payment against both invoices
pe.save().submit()
# Assert outstanding
[doc.reload() for doc in [si1, si2, pe]]
self.assertEqual(si1.outstanding_amount, 0)
self.assertEqual(si2.outstanding_amount, 0)
self.assertEqual(pe.unallocated_amount, 0)
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payments",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 2)
allocations = [x.reference_name for x in unreconcile.allocations]
self.assertEquals([si1.name, si2.name], allocations)
# unreconcile si1
for x in unreconcile.allocations:
if x.reference_name != si1.name:
unreconcile.remove(x)
unreconcile.save().submit()
# Assert outstanding
[doc.reload() for doc in [si1, si2, pe]]
self.assertEqual(si1.outstanding_amount, 100)
self.assertEqual(si2.outstanding_amount, 0)
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.unallocated_amount, 100)
def test_02_unreconcile_one_payment_from_multi_payments(self):
"""
Scenario: 2 payments, both split against 2 different invoices
Unreconcile only one payment from one invoice
"""
si1 = self.create_sales_invoice()
si2 = self.create_sales_invoice()
pe1 = self.create_payment_entry()
pe1.paid_amount = 100
# Allocate payment against both invoices
pe1.append(
"references",
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
)
pe1.append(
"references",
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
)
pe1.save().submit()
pe2 = self.create_payment_entry()
pe2.paid_amount = 100
# Allocate payment against both invoices
pe2.append(
"references",
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
)
pe2.append(
"references",
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
)
pe2.save().submit()
# Assert outstanding and unallocated
[doc.reload() for doc in [si1, si2, pe1, pe2]]
self.assertEqual(si1.outstanding_amount, 0.0)
self.assertEqual(si2.outstanding_amount, 0.0)
self.assertEqual(pe1.unallocated_amount, 0.0)
self.assertEqual(pe2.unallocated_amount, 0.0)
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payments",
"company": self.company,
"voucher_type": pe2.doctype,
"voucher_no": pe2.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 2)
allocations = [x.reference_name for x in unreconcile.allocations]
self.assertEquals([si1.name, si2.name], allocations)
# unreconcile si1 from pe2
for x in unreconcile.allocations:
if x.reference_name != si1.name:
unreconcile.remove(x)
unreconcile.save().submit()
# Assert outstanding and unallocated
[doc.reload() for doc in [si1, si2, pe1, pe2]]
self.assertEqual(si1.outstanding_amount, 50)
self.assertEqual(si2.outstanding_amount, 0)
self.assertEqual(len(pe1.references), 2)
self.assertEqual(len(pe2.references), 1)
self.assertEqual(pe1.unallocated_amount, 0)
self.assertEqual(pe2.unallocated_amount, 50)
def test_03_unreconciliation_on_multi_currency_invoice(self):
self.create_customer("_Test MC Customer USD", "USD")
si1 = self.create_sales_invoice(do_not_submit=True)
si1.currency = "USD"
si1.debit_to = self.debtors_usd
si1.conversion_rate = 80
si1.save().submit()
si2 = self.create_sales_invoice(do_not_submit=True)
si2.currency = "USD"
si2.debit_to = self.debtors_usd
si2.conversion_rate = 80
si2.save().submit()
pe = self.create_payment_entry()
pe.paid_from = self.debtors_usd
pe.paid_from_account_currency = "USD"
pe.source_exchange_rate = 75
pe.received_amount = 75 * 200
pe.save()
# Allocate payment against both invoices
pe.append(
"references",
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 100},
)
pe.append(
"references",
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 100},
)
pe.save().submit()
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payments",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 2)
allocations = [x.reference_name for x in unreconcile.allocations]
self.assertEquals([si1.name, si2.name], allocations)
# unreconcile si1 from pe
for x in unreconcile.allocations:
if x.reference_name != si1.name:
unreconcile.remove(x)
unreconcile.save().submit()
# Assert outstanding and unallocated
[doc.reload() for doc in [si1, si2, pe]]
self.assertEqual(si1.outstanding_amount, 100)
self.assertEqual(si2.outstanding_amount, 0)
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.unallocated_amount, 100)
# Exc gain/loss JE should've been cancelled as well
self.assertEqual(
frappe.db.count(
"Journal Entry Account",
filters={"reference_type": si1.doctype, "reference_name": si1.name, "docstatus": 1},
),
0,
)
def test_04_unreconciliation_on_multi_currency_invoice(self):
"""
2 payments split against 2 foreign currency invoices
"""
self.create_customer("_Test MC Customer USD", "USD")
si1 = self.create_sales_invoice(do_not_submit=True)
si1.currency = "USD"
si1.debit_to = self.debtors_usd
si1.conversion_rate = 80
si1.save().submit()
si2 = self.create_sales_invoice(do_not_submit=True)
si2.currency = "USD"
si2.debit_to = self.debtors_usd
si2.conversion_rate = 80
si2.save().submit()
pe1 = self.create_payment_entry()
pe1.paid_from = self.debtors_usd
pe1.paid_from_account_currency = "USD"
pe1.source_exchange_rate = 75
pe1.received_amount = 75 * 100
pe1.save()
# Allocate payment against both invoices
pe1.append(
"references",
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
)
pe1.append(
"references",
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
)
pe1.save().submit()
pe2 = self.create_payment_entry()
pe2.paid_from = self.debtors_usd
pe2.paid_from_account_currency = "USD"
pe2.source_exchange_rate = 75
pe2.received_amount = 75 * 100
pe2.save()
# Allocate payment against both invoices
pe2.append(
"references",
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
)
pe2.append(
"references",
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
)
pe2.save().submit()
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payments",
"company": self.company,
"voucher_type": pe2.doctype,
"voucher_no": pe2.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 2)
allocations = [x.reference_name for x in unreconcile.allocations]
self.assertEquals([si1.name, si2.name], allocations)
# unreconcile si1 from pe2
for x in unreconcile.allocations:
if x.reference_name != si1.name:
unreconcile.remove(x)
unreconcile.save().submit()
# Assert outstanding and unallocated
[doc.reload() for doc in [si1, si2, pe1, pe2]]
self.assertEqual(si1.outstanding_amount, 50)
self.assertEqual(si2.outstanding_amount, 0)
self.assertEqual(len(pe1.references), 2)
self.assertEqual(len(pe2.references), 1)
self.assertEqual(pe1.unallocated_amount, 0)
self.assertEqual(pe2.unallocated_amount, 50)
# Exc gain/loss JE from PE1 should be available
self.assertEqual(
frappe.db.count(
"Journal Entry Account",
filters={"reference_type": si1.doctype, "reference_name": si1.name, "docstatus": 1},
),
1,
)

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Unreconcile Payments", {
refresh(frm) {
frm.set_query("voucher_type", function() {
return {
filters: {
name: ["in", ["Payment Entry", "Journal Entry"]]
}
}
});
frm.set_query("voucher_no", function(doc) {
return {
filters: {
company: doc.company,
docstatus: 1
}
}
});
},
get_allocations: function(frm) {
frm.clear_table("allocations");
frappe.call({
method: "get_allocations_from_payment",
doc: frm.doc,
callback: function(r) {
if (r.message) {
r.message.forEach(x => {
frm.add_child("allocations", x)
})
frm.refresh_fields();
}
}
})
}
});

View File

@@ -0,0 +1,93 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:UNREC-{#####}",
"creation": "2023-08-22 10:26:34.421423",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"voucher_type",
"voucher_no",
"get_allocations",
"allocations",
"amended_from"
],
"fields": [
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Unreconcile Payments",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"label": "Voucher No",
"options": "voucher_type"
},
{
"fieldname": "get_allocations",
"fieldtype": "Button",
"label": "Get Allocations"
},
{
"fieldname": "allocations",
"fieldtype": "Table",
"label": "Allocations",
"options": "Unreconcile Payment Entries"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-08-28 17:42:50.261377",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Unreconcile Payments",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"read": 1,
"role": "Accounts Manager",
"select": 1,
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"read": 1,
"role": "Accounts User",
"select": 1,
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,158 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs, Sum
from frappe.utils.data import comma_and
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
unlink_ref_doc_from_payment_entries,
update_voucher_outstanding,
)
class UnreconcilePayments(Document):
def validate(self):
self.supported_types = ["Payment Entry", "Journal Entry"]
if not self.voucher_type in self.supported_types:
frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types)))
@frappe.whitelist()
def get_allocations_from_payment(self):
allocated_references = []
ple = qb.DocType("Payment Ledger Entry")
allocated_references = (
qb.from_(ple)
.select(
ple.account,
ple.party_type,
ple.party,
ple.against_voucher_type.as_("reference_doctype"),
ple.against_voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(
(ple.docstatus == 1)
& (ple.voucher_type == self.voucher_type)
& (ple.voucher_no == self.voucher_no)
& (ple.voucher_no != ple.against_voucher_no)
)
.groupby(ple.against_voucher_type, ple.against_voucher_no)
.run(as_dict=True)
)
return allocated_references
def add_references(self):
allocations = self.get_allocations_from_payment()
for alloc in allocations:
self.append("allocations", alloc)
def on_submit(self):
# todo: more granular unreconciliation
for alloc in self.allocations:
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
update_voucher_outstanding(
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
)
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
@frappe.whitelist()
def doc_has_references(doctype: str = None, docname: str = None):
if doctype in ["Sales Invoice", "Purchase Invoice"]:
return frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
)
else:
return frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
)
@frappe.whitelist()
def get_linked_payments_for_doc(
company: str = None, doctype: str = None, docname: str = None
) -> list:
if company and doctype and docname:
_dt = doctype
_dn = docname
ple = qb.DocType("Payment Ledger Entry")
if _dt in ["Sales Invoice", "Purchase Invoice"]:
criteria = [
(ple.company == company),
(ple.delinked == 0),
(ple.against_voucher_no == _dn),
(ple.amount < 0),
]
res = (
qb.from_(ple)
.select(
ple.company,
ple.voucher_type,
ple.voucher_no,
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(Criterion.all(criteria))
.groupby(ple.voucher_no, ple.against_voucher_no)
.having(qb.Field("allocated_amount") > 0)
.run(as_dict=True)
)
return res
else:
criteria = [
(ple.company == company),
(ple.delinked == 0),
(ple.voucher_no == _dn),
(ple.against_voucher_no != _dn),
]
query = (
qb.from_(ple)
.select(
ple.company,
ple.against_voucher_type.as_("voucher_type"),
ple.against_voucher_no.as_("voucher_no"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(Criterion.all(criteria))
.groupby(ple.against_voucher_no)
)
res = query.run(as_dict=True)
return res
return []
@frappe.whitelist()
def create_unreconcile_doc_for_selection(selections=None):
if selections:
selections = frappe.json.loads(selections)
# assuming each row is a unique voucher
for row in selections:
unrecon = frappe.new_doc("Unreconcile Payments")
unrecon.company = row.get("company")
unrecon.voucher_type = row.get("voucher_type")
unrecon.voucher_no = row.get("voucher_no")
unrecon.add_references()
# remove unselected references
unrecon.allocations = [
x
for x in unrecon.allocations
if x.reference_doctype == row.get("against_voucher_type")
and x.reference_name == row.get("against_voucher_no")
]
unrecon.save().submit()

View File

@@ -41,7 +41,7 @@ def make_gl_entries(
from_repost=from_repost,
)
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries
# Post GL Map process there may no be any GL Entries
elif gl_map:
frappe.throw(
_(

View File

@@ -5,13 +5,8 @@
from typing import Optional
import frappe
from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import (
get_address_display,
get_company_address,
get_default_address,
)
from frappe.contacts.doctype.contact.contact import get_contact_details
from frappe import _, msgprint, qb, scrub
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Abs, Date, Sum
@@ -133,6 +128,7 @@ def _get_party_details(
party_address,
company_address,
shipping_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type)
@@ -193,6 +189,8 @@ def set_address_details(
party_address=None,
company_address=None,
shipping_address=None,
*,
ignore_permissions=False
):
billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -205,13 +203,17 @@ def set_address_details(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
)
# address display
party_details.address_display = get_address_display(party_details[billing_address_field])
party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address
if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name
)
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
@@ -229,7 +231,7 @@ def set_address_details(
if shipping_address:
party_details.update(
shipping_address=shipping_address,
shipping_address_display=get_address_display(shipping_address),
shipping_address_display=render_address(shipping_address),
**get_fetch_values(doctype, "shipping_address", shipping_address)
)
@@ -238,7 +240,8 @@ def set_address_details(
party_details.update(
billing_address=party_details.company_address,
billing_address_display=(
party_details.company_address_display or get_address_display(party_details.company_address)
party_details.company_address_display
or render_address(party_details.company_address, check_permissions=False)
),
**get_fetch_values(doctype, "billing_address", party_details.company_address)
)
@@ -290,7 +293,34 @@ def set_contact_details(party_details, party, party_type):
}
)
else:
party_details.update(get_contact_details(party_details.contact_person))
fields = [
"name as contact_person",
"salutation",
"first_name",
"last_name",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = frappe.db.get_value(
"Contact", party_details.contact_person, fields, as_dict=True
)
contact_details.contact_display = " ".join(
filter(
None,
[
contact_details.get("salutation"),
contact_details.get("first_name"),
contact_details.get("last_name"),
],
)
)
party_details.update(contact_details)
def set_other_values(party_details, party, party_type):
@@ -429,11 +459,19 @@ def get_party_account_currency(party_type, party, company):
def get_party_gle_currency(party_type, party, company):
def generator():
existing_gle_currency = frappe.db.sql(
"""select account_currency from `tabGL Entry`
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
limit 1""",
{"company": company, "party_type": party_type, "party": party},
gl = qb.DocType("GL Entry")
existing_gle_currency = (
qb.from_(gl)
.select(gl.account_currency)
.where(
(gl.docstatus == 1)
& (gl.company == company)
& (gl.party_type == party_type)
& (gl.party == party)
& (gl.is_cancelled == 0)
)
.limit(1)
.run()
)
return existing_gle_currency[0][0] if existing_gle_currency else None
@@ -957,3 +995,13 @@ def add_party_account(party_type, party, company, account):
doc.append("accounts", accounts)
doc.save()
def render_address(address, check_permissions=True):
try:
from frappe.contacts.doctype.address.address import render_address as _render
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render
return frappe.call(_render, address, check_permissions=check_permissions)

View File

@@ -95,18 +95,11 @@ frappe.query_reports["Accounts Payable"] = {
"options": "Payment Terms Template"
},
{
"fieldname": "party_type",
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
get_query: () => {
return {
filters: {
'account_type': 'Payable'
}
};
},
on_change: () => {
"fieldtype": "Autocomplete",
options: get_party_type_options(),
on_change: function() {
frappe.query_report.set_filter_value('party', "");
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
}
@@ -114,8 +107,15 @@ frappe.query_reports["Accounts Payable"] = {
{
"fieldname":"party",
"label": __("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"fieldtype": "MultiSelectList",
get_data: function(txt) {
if (!frappe.query_report.filters) return;
let party_type = frappe.query_report.get_filter_value('party_type');
if (!party_type) return;
return frappe.db.get_link_options(party_type, txt);
},
},
{
"fieldname": "supplier_group",
@@ -143,7 +143,13 @@ frappe.query_reports["Accounts Payable"] = {
"fieldname": "show_future_payments",
"label": __("Show Future Payments"),
"fieldtype": "Check",
},
{
"fieldname": "ignore_accounts",
"label": __("Group by Voucher"),
"fieldtype": "Check",
}
],
"formatter": function(value, row, column, data, default_formatter) {
@@ -164,3 +170,15 @@ frappe.query_reports["Accounts Payable"] = {
}
erpnext.utils.add_dimensions('Accounts Payable', 9);
function get_party_type_options() {
let options = [];
frappe.db.get_list(
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
).then((res) => {
res.forEach((party_type) => {
options.push(party_type.name);
});
});
return options;
}

View File

@@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
filters = {
"company": self.company,
"party_type": "Supplier",
"party": self.supplier,
"party": [self.supplier],
"report_date": today(),
"range1": 30,
"range2": 60,

View File

@@ -72,18 +72,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
}
},
{
"fieldname": "party_type",
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
get_query: () => {
return {
filters: {
'account_type': 'Payable'
}
};
},
on_change: () => {
"fieldtype": "Autocomplete",
options: get_party_type_options(),
on_change: function() {
frappe.query_report.set_filter_value('party', "");
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
}
@@ -91,8 +84,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
{
"fieldname":"party",
"label": __("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"fieldtype": "MultiSelectList",
get_data: function(txt) {
if (!frappe.query_report.filters) return;
let party_type = frappe.query_report.get_filter_value('party_type');
if (!party_type) return;
return frappe.db.get_link_options(party_type, txt);
},
},
{
"fieldname":"payment_terms_template",
@@ -122,3 +122,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
}
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
function get_party_type_options() {
let options = [];
frappe.db.get_list(
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
).then((res) => {
res.forEach((party_type) => {
options.push(party_type.name);
});
});
return options;
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.utils");
frappe.query_reports["Accounts Receivable"] = {
"filters": [
{
@@ -38,19 +40,11 @@ frappe.query_reports["Accounts Receivable"] = {
}
},
{
"fieldname": "party_type",
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
"Default": "Customer",
get_query: () => {
return {
filters: {
'account_type': 'Receivable'
}
};
},
on_change: () => {
"fieldtype": "Autocomplete",
options: get_party_type_options(),
on_change: function() {
frappe.query_report.set_filter_value('party', "");
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
}
@@ -58,8 +52,15 @@ frappe.query_reports["Accounts Receivable"] = {
{
"fieldname":"party",
"label": __("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"fieldtype": "MultiSelectList",
get_data: function(txt) {
if (!frappe.query_report.filters) return;
let party_type = frappe.query_report.get_filter_value('party_type');
if (!party_type) return;
return frappe.db.get_link_options(party_type, txt);
},
},
{
"fieldname": "party_account",
@@ -171,7 +172,13 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldname": "show_remarks",
"label": __("Show Remarks"),
"fieldtype": "Check",
},
{
"fieldname": "ignore_accounts",
"label": __("Group by Voucher"),
"fieldtype": "Check",
}
],
"formatter": function(value, row, column, data, default_formatter) {
@@ -192,3 +199,16 @@ frappe.query_reports["Accounts Receivable"] = {
}
erpnext.utils.add_dimensions('Accounts Receivable', 9);
function get_party_type_options() {
let options = [];
frappe.db.get_list(
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
).then((res) => {
res.forEach((party_type) => {
options.push(party_type.name);
});
});
return options;
}

View File

@@ -116,7 +116,12 @@ class ReceivablePayableReport(object):
# build all keys, since we want to exclude vouchers beyond the report date
for ple in self.ple_entries:
# get the balance object for voucher_type
key = (ple.voucher_type, ple.voucher_no, ple.party)
if self.filters.get("ignore_accounts"):
key = (ple.voucher_type, ple.voucher_no, ple.party)
else:
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
if not key in self.voucher_balance:
self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type,
@@ -183,7 +188,10 @@ class ReceivablePayableReport(object):
):
return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
if self.filters.get("ignore_accounts"):
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
else:
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
# If payment is made against credit note
# and credit note is made against a Sales Invoice
@@ -192,13 +200,19 @@ class ReceivablePayableReport(object):
if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no)
if return_against:
key = (ple.against_voucher_type, return_against, ple.party)
if self.filters.get("ignore_accounts"):
key = (ple.against_voucher_type, return_against, ple.party)
else:
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
row = self.voucher_balance.get(key)
if not row:
# no invoice, this is an invoice / stand-alone payment / credit note
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
if self.filters.get("ignore_accounts"):
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
else:
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
row.party_type = ple.party_type
return row
@@ -718,6 +732,7 @@ class ReceivablePayableReport(object):
query = (
qb.from_(ple)
.select(
ple.name,
ple.account,
ple.voucher_type,
ple.voucher_no,
@@ -731,13 +746,15 @@ class ReceivablePayableReport(object):
ple.account_currency,
ple.amount,
ple.amount_in_account_currency,
ple.remarks,
)
.where(ple.delinked == 0)
.where(Criterion.all(self.qb_selection_filter))
.where(Criterion.any(self.or_filters))
)
if self.filters.get("show_remarks"):
query = query.select(ple.remarks)
if self.filters.get("group_by_party"):
query = query.orderby(self.ple.party, self.ple.posting_date)
else:
@@ -801,7 +818,7 @@ class ReceivablePayableReport(object):
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
if self.filters.get("party"):
self.qb_selection_filter.append(self.filters.party == self.ple.party)
self.qb_selection_filter.append(self.ple.party.isin(self.filters.party))
if self.filters.party_account:
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)

View File

@@ -1,6 +1,7 @@
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, today
@@ -23,29 +24,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_usd_account(self):
name = "Debtors USD"
exists = frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
)
if exists:
self.debtors_usd = exists[0].name
else:
debtors = frappe.get_doc(
"Account",
frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
)[0].name,
)
debtors_usd = frappe.new_doc("Account")
debtors_usd.company = debtors.company
debtors_usd.account_name = "Debtors USD"
debtors_usd.account_currency = "USD"
debtors_usd.parent_account = debtors.parent_account
debtors_usd.account_type = debtors.account_type
self.debtors_usd = debtors_usd.save().name
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator")
si = create_sales_invoice(
@@ -573,7 +551,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
filters = {
"company": self.company,
"party_type": "Customer",
"party": self.customer,
"party": [self.customer],
"report_date": today(),
"range1": 30,
"range2": 60,
@@ -605,3 +583,132 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
for field in expected:
with self.subTest(field=field):
self.assertEqual(report_output.get(field), expected.get(field))
def test_multi_select_party_filter(self):
self.customer1 = self.customer
self.create_customer("_Test Customer 2")
self.customer2 = self.customer
self.create_customer("_Test Customer 3")
self.customer3 = self.customer
filters = {
"company": self.company,
"party_type": "Customer",
"party": [self.customer1, self.customer3],
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si1.customer = self.customer1
si1.save().submit()
si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si2.customer = self.customer2
si2.save().submit()
si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si3.customer = self.customer3
si3.save().submit()
# check invoice grand total and invoiced column's value for 3 payment terms
report = execute(filters)
expected_output = {self.customer1, self.customer3}
self.assertEqual(len(report[1]), 2)
output_for = set([x.party for x in report[1]])
self.assertEqual(output_for, expected_output)
def test_report_output_if_party_is_missing(self):
acc_name = "Additional Debtors"
if not frappe.db.get_value(
"Account", filters={"account_name": acc_name, "company": self.company}
):
additional_receivable_acc = frappe.get_doc(
{
"doctype": "Account",
"account_name": acc_name,
"parent_account": "Accounts Receivable - " + self.company_abbr,
"company": self.company,
"account_type": "Receivable",
}
).save()
self.debtors2 = additional_receivable_acc.name
je = frappe.new_doc("Journal Entry")
je.company = self.company
je.posting_date = today()
je.append(
"accounts",
{
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 150,
"credit_in_account_currency": 0,
"cost_center": self.cost_center,
},
)
je.append(
"accounts",
{
"account": self.debtors2,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 200,
"credit_in_account_currency": 0,
"cost_center": self.cost_center,
},
)
je.append(
"accounts",
{
"account": self.cash,
"debit_in_account_currency": 0,
"credit_in_account_currency": 350,
"cost_center": self.cost_center,
},
)
je.save().submit()
# manually remove party from Payment Ledger
ple = qb.DocType("Payment Ledger Entry")
qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run()
filters = {
"company": self.company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
report_ouput = execute(filters)[1]
expected_data = [
[self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0],
[self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0],
]
self.assertEqual(len(report_ouput), 2)
# fetch only required fields
report_output = [
[
x.party_account,
x.voucher_type,
x.voucher_no,
"Customer",
self.customer,
x.invoiced,
x.paid,
x.credit_note,
x.outstanding,
]
for x in report_ouput
]
# use account name to sort
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
report_output = sorted(report_output, key=lambda x: x[0])
self.assertEqual(expected_data, report_output)

View File

@@ -72,19 +72,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
}
},
{
"fieldname": "party_type",
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
"Default": "Customer",
get_query: () => {
return {
filters: {
'account_type': 'Receivable'
}
};
},
on_change: () => {
"fieldtype": "Autocomplete",
options: get_party_type_options(),
on_change: function() {
frappe.query_report.set_filter_value('party', "");
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
}
@@ -92,8 +84,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
{
"fieldname":"party",
"label": __("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"fieldtype": "MultiSelectList",
get_data: function(txt) {
if (!frappe.query_report.filters) return;
let party_type = frappe.query_report.get_filter_value('party_type');
if (!party_type) return;
return frappe.db.get_link_options(party_type, txt);
},
},
{
"fieldname":"customer_group",
@@ -151,3 +150,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
}
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
function get_party_type_options() {
let options = [];
frappe.db.get_list(
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
).then((res) => {
res.forEach((party_type) => {
options.push(party_type.name);
});
});
return options;
}

View File

@@ -99,13 +99,11 @@ class AccountsReceivableSummary(ReceivablePayableReport):
# Add all amount columns
for k in list(self.party_total[d.party]):
if k not in ["currency", "sales_person"]:
self.party_total[d.party][k] += d.get(k, 0.0)
if isinstance(self.party_total[d.party][k], float):
self.party_total[d.party][k] += d.get(k) or 0.0
# set territory, customer_group, sales person etc
self.set_party_details(d)
self.party_total[d.party].update({"party_type": d.party_type})
def init_party_total(self, row):
self.party_total.setdefault(
@@ -124,6 +122,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"total_due": 0.0,
"future_amount": 0.0,
"sales_person": [],
"party_type": row.party_type,
}
),
)
@@ -133,13 +132,12 @@ class AccountsReceivableSummary(ReceivablePayableReport):
for key in ("territory", "customer_group", "supplier_group"):
if row.get(key):
self.party_total[row.party][key] = row.get(key)
self.party_total[row.party][key] = row.get(key, "")
if row.sales_person:
self.party_total[row.party].sales_person.append(row.sales_person)
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
if self.filters.sales_partner:
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner")
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "")
def get_columns(self):
self.columns = []

View File

@@ -23,6 +23,7 @@ class TestBankReconciliationStatement(FrappeTestCase):
"Payment Entry",
]:
frappe.db.delete(dt)
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def test_loan_entries_in_bank_reco_statement(self):
create_loan_accounts()

View File

@@ -67,7 +67,7 @@ def setup_mappers(mappers):
mapping["finance_costs"] = []
mapping["finance_costs_adjustments"] = []
doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
mapping_names = [item.name for item in doc.accounts]
mapping_names = [item.mapping for item in doc.accounts]
if not mapping_names:
continue

View File

@@ -79,7 +79,9 @@ class General_Payment_Ledger_Comparison(object):
.select(
gle.company,
gle.account,
gle.voucher_type,
gle.voucher_no,
gle.party_type,
gle.party,
outstanding,
)
@@ -89,7 +91,9 @@ class General_Payment_Ledger_Comparison(object):
& (gle.account.isin(val.accounts))
)
.where(Criterion.all(filter_criterion))
.groupby(gle.company, gle.account, gle.voucher_no, gle.party)
.groupby(
gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party
)
.run()
)
@@ -112,7 +116,13 @@ class General_Payment_Ledger_Comparison(object):
self.account_types[acc_type].ple = (
qb.from_(ple)
.select(
ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding")
ple.company,
ple.account,
ple.voucher_type,
ple.voucher_no,
ple.party_type,
ple.party,
Sum(ple.amount).as_("outstanding"),
)
.where(
(ple.company == self.filters.company)
@@ -120,7 +130,9 @@ class General_Payment_Ledger_Comparison(object):
& (ple.account.isin(val.accounts))
)
.where(Criterion.all(filter_criterion))
.groupby(ple.company, ple.account, ple.voucher_no, ple.party)
.groupby(
ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party
)
.run()
)
@@ -133,15 +145,17 @@ class General_Payment_Ledger_Comparison(object):
self.gle_balances = set(val.gle) | self.gle_balances
self.ple_balances = set(val.ple) | self.ple_balances
self.diff1 = self.gle_balances.difference(self.ple_balances)
self.diff2 = self.ple_balances.difference(self.gle_balances)
self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances)
self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances)
self.diff = frappe._dict({})
for x in self.diff1:
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
for x in self.variation_in_payment_ledger:
self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]})
for x in self.diff2:
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
for x in self.variation_in_general_ledger:
self.diff.setdefault(
(x[0], x[1], x[2], x[3], x[4], x[5]), frappe._dict({"gl_balance": 0.0})
).update(frappe._dict({"pl_balance": x[6]}))
def generate_data(self):
self.data = []
@@ -149,8 +163,12 @@ class General_Payment_Ledger_Comparison(object):
self.data.append(
frappe._dict(
{
"voucher_no": key[2],
"party": key[3],
"company": key[0],
"account": key[1],
"voucher_type": key[2],
"voucher_no": key[3],
"party_type": key[4],
"party": key[5],
"gl_balance": val.gl_balance,
"pl_balance": val.pl_balance,
}
@@ -160,12 +178,52 @@ class General_Payment_Ledger_Comparison(object):
def get_columns(self):
self.columns = []
options = None
self.columns.append(
dict(
label=_("Company"),
fieldname="company",
fieldtype="Link",
options="Company",
width="100",
)
)
self.columns.append(
dict(
label=_("Account"),
fieldname="account",
fieldtype="Link",
options="Account",
width="100",
)
)
self.columns.append(
dict(
label=_("Voucher Type"),
fieldname="voucher_type",
fieldtype="Link",
options="DocType",
width="100",
)
)
self.columns.append(
dict(
label=_("Voucher No"),
fieldname="voucher_no",
fieldtype="Data",
options=options,
fieldtype="Dynamic Link",
options="voucher_type",
width="100",
)
)
self.columns.append(
dict(
label=_("Party Type"),
fieldname="party_type",
fieldtype="Link",
options="DocType",
width="100",
)
)
@@ -174,8 +232,8 @@ class General_Payment_Ledger_Comparison(object):
dict(
label=_("Party"),
fieldname="party",
fieldtype="Data",
options=options,
fieldtype="Dynamic Link",
options="party_type",
width="100",
)
)

View File

@@ -50,7 +50,11 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin):
self.assertEqual(len(data), 1)
expected = {
"company": sinv.company,
"account": sinv.debit_to,
"voucher_type": sinv.doctype,
"voucher_no": sinv.name,
"party_type": "Customer",
"party": sinv.customer,
"gl_balance": sinv.grand_total,
"pl_balance": sinv.grand_total - 1,

View File

@@ -188,7 +188,13 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_net_values_in_party_account",
"label": __("Show Net Values in Party Account"),
"fieldtype": "Check"
},
{
"fieldname": "show_remarks",
"label": __("Show Remarks"),
"fieldtype": "Check"
}
]
}

View File

@@ -163,6 +163,9 @@ def get_gl_entries(filters, accounting_dimensions):
select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """
if filters.get("show_remarks"):
select_fields += """,remarks"""
order_by_statement = "order by posting_date, account, creation"
if filters.get("include_dimensions"):
@@ -189,7 +192,7 @@ def get_gl_entries(filters, accounting_dimensions):
voucher_type, voucher_no, {dimension_fields}
cost_center, project,
against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening, creation {select_fields}
against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
{order_by_statement}
@@ -593,8 +596,10 @@ def get_columns(filters):
"width": 100,
},
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
{"label": _("Remarks"), "fieldname": "remarks", "width": 400},
]
)
if filters.get("show_remarks"):
columns.extend([{"label": _("Remarks"), "fieldname": "remarks", "width": 400}])
return columns

View File

@@ -544,6 +544,8 @@ class GrossProfitGenerator(object):
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
if self.filters.get("group_by") == "Sales Person":
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)

View File

@@ -33,13 +33,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Accounting Dimension"),
"fieldtype": "Link",
"options": "Accounting Dimension",
"get_query": () =>{
return {
filters: {
"disabled": 0
}
}
}
},
{
"fieldname": "fiscal_year",

View File

@@ -47,6 +47,7 @@ def get_result(
out = []
for name, details in gle_map.items():
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
bill_no, bill_date = "", ""
tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category)
@@ -68,7 +69,14 @@ def get_result(
tax_amount += entry.credit - entry.debit
if net_total_map.get(name):
total_amount, grand_total, base_total = net_total_map.get(name)
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount
if rate:
total_amount = grand_total = base_total = tax_amount / (rate / 100)
elif voucher_type == "Purchase Invoice":
total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name)
else:
total_amount, grand_total, base_total = net_total_map.get(name)
else:
total_amount += entry.credit
@@ -92,7 +100,7 @@ def get_result(
row.update(
{
"section_code": tax_withholding_category,
"section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type),
"rate": rate,
"total_amount": total_amount,
@@ -102,10 +110,14 @@ def get_result(
"transaction_date": posting_date,
"transaction_type": voucher_type,
"ref_no": name,
"supplier_invoice_no": bill_no,
"supplier_invoice_date": bill_date,
}
)
out.append(row)
out.sort(key=lambda x: x["section_code"])
return out
@@ -153,14 +165,14 @@ def get_gle_map(documents):
def get_columns(filters):
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
columns = [
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
{
"label": _(filters.get("party_type")),
"fieldname": "party",
"fieldtype": "Dynamic Link",
"options": "party_type",
"width": 180,
"label": _("Section Code"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldtype": "Link",
"width": 90,
},
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
]
if filters.naming_series == "Naming Series":
@@ -175,51 +187,60 @@ def get_columns(filters):
columns.extend(
[
{
"label": _("Date of Transaction"),
"fieldname": "transaction_date",
"fieldtype": "Date",
"width": 100,
},
{
"label": _("Section Code"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldtype": "Link",
"width": 90,
},
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
{
"label": _("Total Amount"),
"fieldname": "total_amount",
"fieldtype": "Float",
"width": 90,
},
]
)
if filters.party_type == "Supplier":
columns.extend(
[
{
"label": _("Supplier Invoice No"),
"fieldname": "supplier_invoice_no",
"fieldtype": "Data",
"width": 120,
},
{
"label": _("Supplier Invoice Date"),
"fieldname": "supplier_invoice_date",
"fieldtype": "Date",
"width": 120,
},
]
)
columns.extend(
[
{
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
"fieldname": "rate",
"fieldtype": "Percent",
"width": 90,
"width": 60,
},
{
"label": _("Tax Amount"),
"fieldname": "tax_amount",
"label": _("Total Amount"),
"fieldname": "total_amount",
"fieldtype": "Float",
"width": 90,
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Float",
"width": 90,
"width": 120,
},
{
"label": _("Base Total"),
"fieldname": "base_total",
"fieldtype": "Float",
"width": 90,
"width": 120,
},
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
{
"label": _("Tax Amount"),
"fieldname": "tax_amount",
"fieldtype": "Float",
"width": 120,
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Float",
"width": 120,
},
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
{
"label": _("Reference No."),
"fieldname": "ref_no",
@@ -227,6 +248,12 @@ def get_columns(filters):
"options": "transaction_type",
"width": 180,
},
{
"label": _("Date of Transaction"),
"fieldname": "transaction_date",
"fieldtype": "Date",
"width": 100,
},
]
)
@@ -249,27 +276,7 @@ def get_tds_docs(filters):
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
)
query_filters = {
"account": ("in", tds_accounts),
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
"is_cancelled": 0,
"against": ("not in", bank_accounts),
}
party = frappe.get_all(filters.get("party_type"), pluck="name")
or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"})
if filters.get("party"):
del query_filters["account"]
del query_filters["against"]
or_filters = {"against": filters.get("party"), "party": filters.get("party")}
tds_docs = frappe.get_all(
"GL Entry",
filters=query_filters,
or_filters=or_filters,
fields=["voucher_no", "voucher_type", "against", "party"],
)
tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
@@ -305,6 +312,47 @@ def get_tds_docs(filters):
)
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
if not tds_accounts:
frappe.throw(
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
title=_("Accounts Missing Error"),
)
gle = frappe.qb.DocType("GL Entry")
query = (
frappe.qb.from_(gle)
.select("voucher_no", "voucher_type", "against", "party")
.where((gle.is_cancelled == 0))
)
if filters.get("from_date"):
query = query.where(gle.posting_date >= filters.get("from_date"))
if filters.get("to_date"):
query = query.where(gle.posting_date <= filters.get("to_date"))
if bank_accounts:
query = query.where(gle.against.notin(bank_accounts))
if filters.get("party"):
party = [filters.get("party")]
query = query.where(
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
| gle.party.isin(party)
)
else:
party = frappe.get_all(filters.get("party_type"), pluck="name")
query = query.where(
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
| (
(gle.voucher_type == "Journal Entry")
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
)
| gle.party.isin(party)
)
return query
def get_journal_entry_party_map(journal_entries):
journal_entry_party_map = {}
for d in frappe.db.get_all(
@@ -331,6 +379,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
"base_tax_withholding_net_total",
"grand_total",
"base_total",
"bill_no",
"bill_date",
],
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
"Payment Entry": [
@@ -349,7 +399,13 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
for entry in entries:
tax_category_map.update({entry.name: entry.tax_withholding_category})
if doctype == "Purchase Invoice":
value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]
value = [
entry.base_tax_withholding_net_total,
entry.grand_total,
entry.base_total,
entry.bill_no,
entry.bill_date,
]
elif doctype == "Sales Invoice":
value = [entry.base_net_total, entry.grand_total, entry.base_total]
elif doctype == "Payment Entry":

View File

@@ -46,6 +46,7 @@ def get_data(filters):
.select(
gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")
)
.where(gle.is_cancelled == 0)
.groupby(gle.voucher_no)
)
query = apply_filters(query, filters, gle)

View File

@@ -511,7 +511,7 @@ def check_if_advance_entry_modified(args):
party_account_field = (
"paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to"
)
precision = frappe.get_precision("Payment Entry", "unallocated_amount")
if args.voucher_detail_no:
ret = frappe.db.sql(
"""select t1.name
@@ -533,9 +533,9 @@ def check_if_advance_entry_modified(args):
where
name = %(voucher_no)s and docstatus = 1
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
and unallocated_amount = %(unreconciled_amount)s
and round(unallocated_amount, {1}) = %(unreconciled_amount)s
""".format(
party_account_field
party_account_field, precision
),
args,
)
@@ -624,7 +624,7 @@ def update_reference_in_payment_entry(
"outstanding_amount": d.outstanding_amount,
"allocated_amount": d.allocated_amount,
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
"exchange_gain_loss": d.exchange_gain_loss,
}
if d.voucher_detail_no:
@@ -636,34 +636,37 @@ def update_reference_in_payment_entry(
existing_row.reference_doctype, existing_row.reference_name
).set_total_advance_paid()
original_row = existing_row.as_dict().copy()
existing_row.update(reference_details)
if d.allocated_amount <= existing_row.allocated_amount:
existing_row.allocated_amount -= d.allocated_amount
if d.allocated_amount < original_row.allocated_amount:
new_row = payment_entry.append("references")
new_row.docstatus = 1
for field in list(reference_details):
new_row.set(field, original_row[field])
new_row.set(field, reference_details[field])
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
else:
new_row = payment_entry.append("references")
new_row.docstatus = 1
new_row.update(reference_details)
payment_entry.flags.ignore_validate_update_after_submit = True
payment_entry.clear_unallocated_reference_document_rows()
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details()
payment_entry.set_amounts()
payment_entry.make_exchange_gain_loss_journal()
payment_entry.make_exchange_gain_loss_journal(
frappe._dict({"difference_posting_date": d.difference_posting_date})
)
if not do_not_save:
payment_entry.save(ignore_permissions=True)
def cancel_exchange_gain_loss_journal(parent_doc: dict | object) -> None:
def cancel_exchange_gain_loss_journal(
parent_doc: dict | object, referenced_dt: str = None, referenced_dn: str = None
) -> None:
"""
Cancel Exchange Gain/Loss for Sales/Purchase Invoice, if they have any.
"""
@@ -690,76 +693,147 @@ def cancel_exchange_gain_loss_journal(parent_doc: dict | object) -> None:
as_list=1,
)
for doc in gain_loss_journals:
frappe.get_doc("Journal Entry", doc[0]).cancel()
gain_loss_je = frappe.get_doc("Journal Entry", doc[0])
if referenced_dt and referenced_dn:
references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts]
if (
len(references) == 2
and (referenced_dt, referenced_dn) in references
and (parent_doc.doctype, parent_doc.name) in references
):
# only cancel JE generated against parent_doc and referenced_dn
gain_loss_je.cancel()
else:
gain_loss_je.cancel()
def unlink_ref_doc_from_payment_entries(ref_doc):
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name)
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name)
frappe.db.sql(
"""update `tabGL Entry`
set against_voucher_type=null, against_voucher=null,
modified=%s, modified_by=%s
where against_voucher_type=%s and against_voucher=%s
and voucher_no != ifnull(against_voucher, '')""",
(now(), frappe.session.user, ref_doc.doctype, ref_doc.name),
def update_accounting_ledgers_after_reference_removal(
ref_type: str = None, ref_no: str = None, payment_name: str = None
):
# General Ledger
gle = qb.DocType("GL Entry")
gle_update_query = (
qb.update(gle)
.set(gle.against_voucher_type, None)
.set(gle.against_voucher, None)
.set(gle.modified, now())
.set(gle.modified_by, frappe.session.user)
.where((gle.against_voucher_type == ref_type) & (gle.against_voucher == ref_no))
)
if payment_name:
gle_update_query = gle_update_query.where(gle.voucher_no == payment_name)
gle_update_query.run()
# Payment Ledger
ple = qb.DocType("Payment Ledger Entry")
ple_update_query = (
qb.update(ple)
.set(ple.against_voucher_type, ple.voucher_type)
.set(ple.against_voucher_no, ple.voucher_no)
.set(ple.modified, now())
.set(ple.modified_by, frappe.session.user)
.where(
(ple.against_voucher_type == ref_type)
& (ple.against_voucher_no == ref_no)
& (ple.delinked == 0)
)
)
qb.update(ple).set(ple.against_voucher_type, ple.voucher_type).set(
ple.against_voucher_no, ple.voucher_no
).set(ple.modified, now()).set(ple.modified_by, frappe.session.user).where(
(ple.against_voucher_type == ref_doc.doctype)
& (ple.against_voucher_no == ref_doc.name)
& (ple.delinked == 0)
).run()
if payment_name:
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
ple_update_query.run()
def remove_ref_from_advance_section(ref_doc: object = None):
# TODO: this might need some testing
if ref_doc.doctype in ("Sales Invoice", "Purchase Invoice"):
ref_doc.set("advances", [])
frappe.db.sql(
"""delete from `tab{0} Advance` where parent = %s""".format(ref_doc.doctype), ref_doc.name
)
adv_type = qb.DocType(f"{ref_doc.doctype} Advance")
qb.from_(adv_type).delete().where(adv_type.parent == ref_doc.name).run()
def remove_ref_doc_link_from_jv(ref_type, ref_no):
linked_jv = frappe.db.sql_list(
"""select parent from `tabJournal Entry Account`
where reference_type=%s and reference_name=%s and docstatus < 2""",
(ref_type, ref_no),
def unlink_ref_doc_from_payment_entries(ref_doc: object = None, payment_name: str = None):
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name, payment_name)
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name, payment_name)
update_accounting_ledgers_after_reference_removal(ref_doc.doctype, ref_doc.name, payment_name)
remove_ref_from_advance_section(ref_doc)
def remove_ref_doc_link_from_jv(
ref_type: str = None, ref_no: str = None, payment_name: str = None
):
jea = qb.DocType("Journal Entry Account")
linked_jv = (
qb.from_(jea)
.select(jea.parent)
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no) & (jea.docstatus.lt(2)))
.run(as_list=1)
)
linked_jv = convert_to_list(linked_jv)
# remove reference only from specified payment
linked_jv = [x for x in linked_jv if x == payment_name] if payment_name else linked_jv
if linked_jv:
frappe.db.sql(
"""update `tabJournal Entry Account`
set reference_type=null, reference_name = null,
modified=%s, modified_by=%s
where reference_type=%s and reference_name=%s
and docstatus < 2""",
(now(), frappe.session.user, ref_type, ref_no),
update_query = (
qb.update(jea)
.set(jea.reference_type, None)
.set(jea.reference_name, None)
.set(jea.modified, now())
.set(jea.modified_by, frappe.session.user)
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
)
if payment_name:
update_query = update_query.where(jea.parent == payment_name)
update_query.run()
frappe.msgprint(_("Journal Entries {0} are un-linked").format("\n".join(linked_jv)))
def remove_ref_doc_link_from_pe(ref_type, ref_no):
linked_pe = frappe.db.sql_list(
"""select parent from `tabPayment Entry Reference`
where reference_doctype=%s and reference_name=%s and docstatus < 2""",
(ref_type, ref_no),
def convert_to_list(result):
"""
Convert tuple to list
"""
return [x[0] for x in result]
def remove_ref_doc_link_from_pe(
ref_type: str = None, ref_no: str = None, payment_name: str = None
):
per = qb.DocType("Payment Entry Reference")
pay = qb.DocType("Payment Entry")
linked_pe = (
qb.from_(per)
.select(per.parent)
.where(
(per.reference_doctype == ref_type) & (per.reference_name == ref_no) & (per.docstatus.lt(2))
)
.run(as_list=1)
)
linked_pe = convert_to_list(linked_pe)
# remove reference only from specified payment
linked_pe = [x for x in linked_pe if x == payment_name] if payment_name else linked_pe
if linked_pe:
frappe.db.sql(
"""update `tabPayment Entry Reference`
set allocated_amount=0, modified=%s, modified_by=%s
where reference_doctype=%s and reference_name=%s
and docstatus < 2""",
(now(), frappe.session.user, ref_type, ref_no),
update_query = (
qb.update(per)
.set(per.allocated_amount, 0)
.set(per.modified, now())
.set(per.modified_by, frappe.session.user)
.where(
(per.docstatus.lt(2) & (per.reference_doctype == ref_type) & (per.reference_name == ref_no))
)
)
if payment_name:
update_query = update_query.where(per.parent == payment_name)
update_query.run()
for pe in linked_pe:
try:
pe_doc = frappe.get_doc("Payment Entry", pe)
@@ -772,19 +846,13 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
msg += _("Please cancel payment entry manually first")
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
frappe.db.sql(
"""update `tabPayment Entry` set total_allocated_amount=%s,
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
where name=%s""",
(
pe_doc.total_allocated_amount,
pe_doc.base_total_allocated_amount,
pe_doc.unallocated_amount,
now(),
frappe.session.user,
pe,
),
)
qb.update(pay).set(pay.total_allocated_amount, pe_doc.total_allocated_amount).set(
pay.base_total_allocated_amount, pe_doc.base_total_allocated_amount
).set(pay.unallocated_amount, pe_doc.unallocated_amount).set(pay.modified, now()).set(
pay.modified_by, frappe.session.user
).where(
pay.name == pe
).run()
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))

View File

@@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', {
frm.set_query("item_code", function() {
return {
"filters": {
"disabled": 0,
"is_fixed_asset": 1,
"is_stock_item": 0
}
@@ -147,6 +146,15 @@ frappe.ui.form.on('Asset', {
if (frm.doc.docstatus == 0) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) {
$('.primary-action').prop('hidden', true);
$('.form-message').text('Capitalize this asset to confirm');
frm.add_custom_button(__("Capitalize Asset"), function() {
frm.trigger("create_asset_capitalization");
});
}
}
},
@@ -168,7 +176,7 @@ frappe.ui.form.on('Asset', {
frm.set_df_property('purchase_invoice', 'read_only', 1);
frm.set_df_property('purchase_receipt', 'read_only', 1);
}
else if (frm.doc.is_existing_asset) {
else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) {
frm.toggle_reqd('purchase_receipt', 0);
frm.toggle_reqd('purchase_invoice', 0);
}
@@ -275,7 +283,7 @@ frappe.ui.form.on('Asset', {
item_code: function(frm) {
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
frm.trigger('set_finance_book');
} else {
frm.set_value('finance_books', []);
@@ -287,7 +295,8 @@ frappe.ui.form.on('Asset', {
method: "erpnext.assets.doctype.asset.asset.get_item_details",
args: {
item_code: frm.doc.item_code,
asset_category: frm.doc.asset_category
asset_category: frm.doc.asset_category,
gross_purchase_amount: frm.doc.gross_purchase_amount
},
callback: function(r, rt) {
if(r.message) {
@@ -299,7 +308,17 @@ frappe.ui.form.on('Asset', {
is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
},
is_composite_asset: function(frm) {
if(frm.doc.is_composite_asset) {
frm.set_value('gross_purchase_amount', 0);
frm.set_df_property('gross_purchase_amount', 'read_only', 1);
} else {
frm.set_df_property('gross_purchase_amount', 'read_only', 0);
}
frm.trigger("toggle_reference_doc");
},
make_schedules_editable: function(frm) {
@@ -360,6 +379,19 @@ frappe.ui.form.on('Asset', {
});
},
create_asset_capitalization: function(frm) {
frappe.call({
args: {
"asset": frm.doc.name,
},
method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization",
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
},
split_asset: function(frm) {
const title = __('Split Asset');
@@ -415,7 +447,7 @@ frappe.ui.form.on('Asset', {
calculate_depreciation: function(frm) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
frm.trigger("set_finance_book");
} else {
frm.set_value("finance_books", []);
@@ -423,9 +455,11 @@ frappe.ui.form.on('Asset', {
},
gross_purchase_amount: function(frm) {
frm.doc.finance_books.forEach(d => {
frm.events.set_depreciation_rate(frm, d);
})
if (frm.doc.finance_books) {
frm.doc.finance_books.forEach(d => {
frm.events.set_depreciation_rate(frm, d);
})
}
},
purchase_receipt: (frm) => {
@@ -504,7 +538,21 @@ frappe.ui.form.on('Asset', {
}
});
}
}
},
set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) {
if (expected_value_after_useful_life_changed) {
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row));
frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage);
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
} else if (salvage_value_percentage_changed) {
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount'));
frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life);
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
}
},
});
frappe.ui.form.on('Asset Finance Book', {
@@ -516,9 +564,19 @@ frappe.ui.form.on('Asset Finance Book', {
expected_value_after_useful_life: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true);
}
frm.events.set_depreciation_rate(frm, row);
},
salvage_value_percentage: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false);
}
},
frequency_of_depreciation: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row);

View File

@@ -14,6 +14,7 @@
"asset_owner",
"asset_owner_company",
"is_existing_asset",
"is_composite_asset",
"supplier",
"customer",
"image",
@@ -72,7 +73,8 @@
"purchase_receipt_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from"
"amended_from",
"capitalized_in"
],
"fields": [
{
@@ -199,7 +201,7 @@
"fieldtype": "Date",
"label": "Purchase Date",
"read_only": 1,
"read_only_depends_on": "eval:!doc.is_existing_asset",
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
"reqd": 1
},
{
@@ -219,11 +221,11 @@
"read_only": 1
},
{
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
"options": "Company:company:default_currency",
"read_only": 1,
"read_only_depends_on": "eval:!doc.is_existing_asset",
"reqd": 1
},
@@ -237,10 +239,12 @@
"default": "0",
"fieldname": "calculate_depreciation",
"fieldtype": "Check",
"label": "Calculate Depreciation"
"label": "Calculate Depreciation",
"read_only_depends_on": "eval:doc.is_composite_asset && !doc.gross_purchase_amount"
},
{
"default": "0",
"depends_on": "eval:!doc.is_composite_asset",
"fieldname": "is_existing_asset",
"fieldtype": "Check",
"label": "Is Existing Asset"
@@ -409,6 +413,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_receipt",
"fieldtype": "Link",
"label": "Purchase Receipt",
@@ -426,6 +431,7 @@
"read_only": 1
},
{
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_invoice",
"fieldtype": "Link",
"label": "Purchase Invoice",
@@ -489,10 +495,11 @@
"read_only": 1
},
{
"depends_on": "eval.doc.asset_quantity",
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset"
"read_only": 1
},
{
"fieldname": "depr_entry_posting_status",
@@ -510,6 +517,21 @@
"fieldname": "is_fully_depreciated",
"fieldtype": "Check",
"label": "Is Fully Depreciated"
},
{
"default": "0",
"depends_on": "eval:!doc.is_existing_asset",
"fieldname": "is_composite_asset",
"fieldtype": "Check",
"label": "Is Composite Asset"
},
{
"fieldname": "capitalized_in",
"fieldtype": "Link",
"hidden": 1,
"label": "Capitalized In",
"options": "Asset Capitalization",
"read_only": 1
}
],
"idx": 72,
@@ -536,9 +558,14 @@
"link_doctype": "Journal Entry",
"link_fieldname": "reference_name",
"table_fieldname": "accounts"
},
{
"group": "Asset Capitalization",
"link_doctype": "Asset Capitalization",
"link_fieldname": "target_asset"
}
],
"modified": "2023-08-10 20:25:09.913073",
"modified": "2023-10-27 17:03:46.629617",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -204,7 +204,9 @@ class Asset(AccountsController):
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
if self.item_code and not self.get("finance_books"):
finance_books = get_item_details(self.item_code, self.asset_category)
finance_books = get_item_details(
self.item_code, self.asset_category, self.gross_purchase_amount
)
self.set("finance_books", finance_books)
def validate_finance_books(self):
@@ -232,7 +234,7 @@ class Asset(AccountsController):
if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
if not flt(self.gross_purchase_amount):
if not flt(self.gross_purchase_amount) and not self.is_composite_asset:
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category):
@@ -1164,6 +1166,15 @@ def create_asset_repair(asset, asset_name):
return asset_repair
@frappe.whitelist()
def create_asset_capitalization(asset):
asset_capitalization = frappe.new_doc("Asset Capitalization")
asset_capitalization.update(
{"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"}
)
return asset_capitalization
@frappe.whitelist()
def create_asset_value_adjustment(asset, asset_category, company):
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
@@ -1195,7 +1206,7 @@ def transfer_asset(args):
@frappe.whitelist()
def get_item_details(item_code, asset_category):
def get_item_details(item_code, asset_category, gross_purchase_amount):
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
books = []
for d in asset_category_doc.finance_books:
@@ -1205,7 +1216,11 @@ def get_item_details(item_code, asset_category):
"depreciation_method": d.depreciation_method,
"total_number_of_depreciations": d.total_number_of_depreciations,
"frequency_of_depreciation": d.frequency_of_depreciation,
"start_date": nowdate(),
"daily_prorata_based": d.daily_prorata_based,
"salvage_value_percentage": d.salvage_value_percentage,
"expected_value_after_useful_life": flt(gross_purchase_amount)
* flt(d.salvage_value_percentage / 100),
"depreciation_start_date": d.depreciation_start_date or nowdate(),
}
)
@@ -1381,7 +1396,7 @@ def get_straight_line_or_manual_depr_amount(
)
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
if row.daily_depreciation:
if row.daily_prorata_based:
daily_depr_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / date_diff(
@@ -1426,7 +1441,7 @@ def get_straight_line_or_manual_depr_amount(
) / number_of_pending_depreciations
# if the Depreciation Schedule is being prepared for the first time
else:
if row.daily_depreciation:
if row.daily_prorata_based:
daily_depr_amount = (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)

View File

@@ -740,6 +740,15 @@ def get_disposal_account_and_cost_center(company):
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
asset_doc = frappe.get_doc("Asset", asset)
if asset_doc.available_for_use_date > getdate(disposal_date):
frappe.throw(
"Disposal date {0} cannot be before available for use date {1} of the asset.".format(
disposal_date, asset_doc.available_for_use_date
)
)
elif asset_doc.available_for_use_date == getdate(disposal_date):
return flt(asset_doc.gross_purchase_amount - asset_doc.opening_accumulated_depreciation)
if asset_doc.calculate_depreciation:
asset_doc.prepare_depreciation_data(getdate(disposal_date))

View File

@@ -186,6 +186,7 @@ class TestAsset(AssetSetup):
def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset=1)
doc = frappe.new_doc("Purchase Invoice")
doc.company = "_Test Company"
doc.supplier = "_Test Supplier"
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
@@ -487,7 +488,7 @@ class TestAsset(AssetSetup):
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
# CWIP: Capital Work In Progress
# Capital Work In Progress
def test_cwip_accounting(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
@@ -520,7 +521,8 @@ class TestAsset(AssetSetup):
pr.submit()
expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
("CWIP Account - _TC", 5250.0, 0.0),
)
@@ -539,9 +541,8 @@ class TestAsset(AssetSetup):
expected_gle = (
("_Test Account Service Tax - _TC", 250.0, 0.0),
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
("Creditors - _TC", 0.0, 5500.0),
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
pi_gle = frappe.db.sql(
@@ -730,7 +731,9 @@ class TestDepreciationMethods(AssetSetup):
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_straight_line_method_with_daily_depreciation(self):
def test_schedule_for_straight_line_method_with_daily_prorata_based(
self,
):
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2023-01-01",
@@ -739,7 +742,7 @@ class TestDepreciationMethods(AssetSetup):
depreciation_start_date="2023-01-31",
total_number_of_depreciations=12,
frequency_of_depreciation=1,
daily_depreciation=1,
daily_prorata_based=1,
)
expected_schedules = [
@@ -1686,6 +1689,7 @@ def create_asset(**args):
"location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"is_composite_asset": args.is_composite_asset or 0,
"asset_quantity": args.get("asset_quantity") or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "",
}
@@ -1701,7 +1705,7 @@ def create_asset(**args):
"total_number_of_depreciations": args.total_number_of_depreciations or 5,
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
"depreciation_start_date": args.depreciation_start_date,
"daily_depreciation": args.daily_depreciation or 0,
"daily_prorata_based": args.daily_prorata_based or 0,
},
)
@@ -1730,6 +1734,7 @@ def create_asset_category():
"fixed_asset_account": "_Test Fixed Asset - _TC",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
"depreciation_expense_account": "_Test Depreciations - _TC",
"capital_work_in_progress_account": "CWIP Account - _TC",
},
)
asset_category.append(

View File

@@ -15,9 +15,15 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
refresh() {
this.show_general_ledger();
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
this.show_stock_ledger();
}
if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
this.get_target_asset_details();
}
}
setup_queries() {
@@ -34,18 +40,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
});
me.frm.set_query("target_asset", function() {
var filters = {};
if (me.frm.doc.target_item_code) {
filters['item_code'] = me.frm.doc.target_item_code;
}
filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]];
filters['docstatus'] = 1;
return {
filters: filters
};
filters: {'is_composite_asset': 1, 'docstatus': 0 }
}
});
me.frm.set_query("asset", "asset_items", function() {
@@ -104,6 +101,39 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
return this.get_target_item_details();
}
target_asset() {
if (this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
this.get_target_asset_details();
}
}
set_consumed_stock_items_tagged_to_wip_composite_asset(asset) {
var me = this;
if (asset) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset",
args: {
asset: asset,
},
callback: function (r) {
if (!r.exc && r.message) {
me.frm.clear_table("stock_items");
for (let item of r.message) {
me.frm.add_child("stock_items", item);
}
refresh_field("stock_items");
me.calculate_totals();
}
}
});
}
}
item_code(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Stock Item") {
@@ -218,6 +248,26 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
}
}
get_target_asset_details() {
var me = this;
if (me.frm.doc.target_asset) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
child: me.frm.doc,
args: {
asset: me.frm.doc.target_asset,
company: me.frm.doc.company,
},
callback: function (r) {
if (!r.exc) {
me.frm.refresh_fields();
}
}
});
}
}
get_consumed_stock_item_details(row) {
var me = this;

View File

@@ -8,24 +8,25 @@
"engine": "InnoDB",
"field_order": [
"title",
"company",
"naming_series",
"entry_type",
"target_item_code",
"target_asset",
"target_item_name",
"target_is_fixed_asset",
"target_has_batch_no",
"target_has_serial_no",
"column_break_9",
"target_asset_name",
"capitalization_method",
"target_item_code",
"target_asset_location",
"target_asset",
"target_asset_name",
"target_warehouse",
"target_qty",
"target_stock_uom",
"target_batch_no",
"target_serial_no",
"column_break_5",
"company",
"finance_book",
"posting_date",
"posting_time",
@@ -57,12 +58,13 @@
"label": "Title"
},
{
"depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')",
"fieldname": "target_item_code",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Item Code",
"options": "Item",
"reqd": 1
"mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'",
"options": "Item"
},
{
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
@@ -86,16 +88,18 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
"fieldname": "target_asset",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Asset",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'",
"no_copy": 1,
"options": "Asset",
"read_only": 1
"read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
"fetch_from": "target_asset.asset_name",
"fieldname": "target_asset_name",
"fieldtype": "Data",
@@ -186,12 +190,14 @@
},
{
"default": "1",
"depends_on": "eval:doc.entry_type=='Decapitalization'",
"fieldname": "target_qty",
"fieldtype": "Float",
"label": "Target Qty",
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
},
{
"depends_on": "eval:doc.entry_type=='Decapitalization'",
"fetch_from": "target_item_code.stock_uom",
"fieldname": "target_stock_uom",
"fieldtype": "Link",
@@ -331,18 +337,26 @@
"read_only": 1
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
"fieldname": "target_asset_location",
"fieldtype": "Link",
"label": "Target Asset Location",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
"options": "Location"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"fieldname": "capitalization_method",
"fieldtype": "Select",
"label": "Capitalization Method",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
"options": "\nCreate a new composite asset\nChoose a WIP composite asset"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-06-22 14:17:07.995120",
"modified": "2023-10-03 22:55:59.461456",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization",

View File

@@ -53,6 +53,7 @@ class AssetCapitalization(StockController):
self.validate_posting_time()
self.set_missing_values(for_validate=True)
self.validate_target_item()
self.validate_target_asset()
self.validate_consumed_stock_item()
self.validate_consumed_asset_item()
self.validate_service_item()
@@ -63,12 +64,12 @@ class AssetCapitalization(StockController):
def before_submit(self):
self.validate_source_mandatory()
if self.entry_type == "Capitalization":
self.create_target_asset()
self.create_target_asset()
def on_submit(self):
self.update_stock_ledger()
self.make_gl_entries()
self.update_target_asset()
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
@@ -85,6 +86,11 @@ class AssetCapitalization(StockController):
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
self.set(k, v)
target_asset_details = get_target_asset_details(self.target_asset, self.company)
for k, v in target_asset_details.items():
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
self.set(k, v)
for d in self.stock_items:
args = self.as_dict()
args.update(d.as_dict())
@@ -146,6 +152,33 @@ class AssetCapitalization(StockController):
self.validate_item(target_item)
def validate_target_asset(self):
if self.target_asset:
target_asset = self.get_asset_for_validation(self.target_asset)
if not target_asset.is_composite_asset:
frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name))
if target_asset.item_code != self.target_item_code:
frappe.throw(
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
)
if target_asset.status in ("Scrapped", "Sold", "Capitalized", "Decapitalized"):
frappe.throw(
_("Target Asset {0} cannot be {1}").format(target_asset.name, target_asset.status)
)
if target_asset.docstatus == 1:
frappe.throw(_("Target Asset {0} cannot be submitted").format(target_asset.name))
elif target_asset.docstatus == 2:
frappe.throw(_("Target Asset {0} cannot be cancelled").format(target_asset.name))
if target_asset.company != self.company:
frappe.throw(
_("Target Asset {0} does not belong to company {1}").format(target_asset.name, self.company)
)
def validate_consumed_stock_item(self):
for d in self.stock_items:
if d.item_code:
@@ -170,7 +203,23 @@ class AssetCapitalization(StockController):
)
asset = self.get_asset_for_validation(d.asset)
self.validate_asset(asset)
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
frappe.throw(
_("Row #{0}: Consumed Asset {1} cannot be {2}").format(d.idx, asset.name, asset.status)
)
if asset.docstatus == 0:
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be Draft").format(d.idx, asset.name))
elif asset.docstatus == 2:
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be cancelled").format(d.idx, asset.name))
if asset.company != self.company:
frappe.throw(
_("Row #{0}: Consumed Asset {1} does not belong to company {2}").format(
d.idx, asset.name, self.company
)
)
def validate_service_item(self):
for d in self.service_items:
@@ -205,21 +254,12 @@ class AssetCapitalization(StockController):
def get_asset_for_validation(self, asset):
return frappe.db.get_value(
"Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1
"Asset",
asset,
["name", "item_code", "company", "status", "docstatus", "is_composite_asset"],
as_dict=1,
)
def validate_asset(self, asset):
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status))
if asset.docstatus == 0:
frappe.throw(_("Asset {0} is Draft").format(asset.name))
if asset.docstatus == 2:
frappe.throw(_("Asset {0} is cancelled").format(asset.name))
if asset.company != self.company:
frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company))
@frappe.whitelist()
def set_warehouse_details(self):
for d in self.get("stock_items"):
@@ -485,16 +525,25 @@ class AssetCapitalization(StockController):
)
def create_target_asset(self):
if (
self.entry_type != "Capitalization"
or self.capitalization_method != "Create a new composite asset"
):
return
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
asset_doc = frappe.new_doc("Asset")
asset_doc.company = self.company
asset_doc.item_code = self.target_item_code
asset_doc.is_existing_asset = 1
asset_doc.is_composite_asset = 1
asset_doc.location = self.target_asset_location
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.capitalized_in = self.name
asset_doc.flags.ignore_validate = True
asset_doc.insert()
@@ -510,6 +559,28 @@ class AssetCapitalization(StockController):
).format(get_link_to_form("Asset", asset_doc.name))
)
def update_target_asset(self):
if (
self.entry_type != "Capitalization"
or self.capitalization_method != "Choose a WIP composite asset"
):
return
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
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.capitalized_in = self.name
asset_doc.flags.ignore_validate = True
asset_doc.save()
frappe.msgprint(
_(
"Asset {0} has been updated. Please set the depreciation details if any and submit it."
).format(get_link_to_form("Asset", asset_doc.name))
)
def restore_consumed_asset_items(self):
for item in self.asset_items:
asset = frappe.get_doc("Asset", item.asset)
@@ -568,6 +639,33 @@ def get_target_item_details(item_code=None, company=None):
return out
@frappe.whitelist()
def get_target_asset_details(asset=None, company=None):
out = frappe._dict()
# Get Asset Details
asset_details = frappe._dict()
if asset:
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
if not asset_details:
frappe.throw(_("Asset {0} does not exist").format(asset))
# Re-set item code from Asset
out.target_item_code = asset_details.item_code
# Set Asset Details
out.asset_name = asset_details.asset_name
if asset_details.item_code:
out.target_fixed_asset_account = get_asset_category_account(
"fixed_asset_account", item=asset_details.item_code, company=company
)
else:
out.target_fixed_asset_account = None
return out
@frappe.whitelist()
def get_consumed_stock_item_details(args):
if isinstance(args, string_types):
@@ -716,3 +814,26 @@ def get_service_item_details(args):
)
return out
@frappe.whitelist()
def get_items_tagged_to_wip_composite_asset(asset):
fields = [
"item_code",
"item_name",
"batch_no",
"serial_no",
"stock_qty",
"stock_uom",
"warehouse",
"cost_center",
"qty",
"valuation_rate",
"amount",
]
pr_items = frappe.get_all(
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
)
return pr_items

View File

@@ -50,6 +50,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="Capitalization",
capitalization_method="Create a new composite asset",
target_item_code="Macbook Pro",
target_asset_location="Test Location",
stock_qty=stock_qty,
@@ -139,6 +140,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="Capitalization",
capitalization_method="Create a new composite asset",
target_item_code="Macbook Pro",
target_asset_location="Test Location",
stock_qty=stock_qty,
@@ -203,6 +205,77 @@ class TestAssetCapitalization(unittest.TestCase):
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
def test_capitalization_with_wip_composite_asset(self):
company = "_Test Company with perpetual inventory"
set_depreciation_settings_in_company(company=company)
stock_rate = 1000
stock_qty = 2
stock_amount = 2000
total_amount = 2000
wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset",
is_composite_asset=1,
warehouse="Stores - TCP1",
company=company,
)
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="Capitalization",
capitalization_method="Choose a WIP composite asset",
target_asset=wip_composite_asset.name,
target_asset_location="Test Location",
stock_qty=stock_qty,
stock_rate=stock_rate,
service_expense_account="Expenses Included In Asset Valuation - TCP1",
company=company,
submit=1,
)
# Test Asset Capitalization values
self.assertEqual(asset_capitalization.entry_type, "Capitalization")
self.assertEqual(asset_capitalization.capitalization_method, "Choose a WIP composite asset")
self.assertEqual(asset_capitalization.target_qty, 1)
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
self.assertEqual(asset_capitalization.total_value, total_amount)
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
# 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)
# Test General Ledger Entries
expected_gle = {
"_Test Fixed Asset - TCP1": 2000,
"_Test Warehouse - TCP1": -2000,
}
actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle)
# Test Stock Ledger Entries
expected_sle = {
("Capitalization Source Stock Item", "_Test Warehouse - TCP1"): {
"actual_qty": -stock_qty,
"stock_value_difference": -stock_amount,
}
}
actual_sle = get_actual_sle_dict(asset_capitalization.name)
self.assertEqual(actual_sle, expected_sle)
# Cancel Asset Capitalization and make test entries and status are reversed
asset_capitalization.cancel()
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
def test_decapitalization_with_depreciation(self):
# Variables
purchase_date = "2020-01-01"
@@ -326,6 +399,7 @@ def create_asset_capitalization(**args):
asset_capitalization.update(
{
"entry_type": args.entry_type or "Capitalization",
"capitalization_method": args.capitalization_method or None,
"company": company,
"posting_date": args.posting_date or now.strftime("%Y-%m-%d"),
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),

View File

@@ -8,10 +8,11 @@
"finance_book",
"depreciation_method",
"total_number_of_depreciations",
"daily_depreciation",
"daily_prorata_based",
"column_break_5",
"frequency_of_depreciation",
"depreciation_start_date",
"salvage_value_percentage",
"expected_value_after_useful_life",
"value_after_depreciation",
"rate_of_depreciation"
@@ -81,18 +82,23 @@
"fieldtype": "Percent",
"label": "Rate of Depreciation"
},
{
"fieldname": "salvage_value_percentage",
"fieldtype": "Percent",
"label": "Salvage Value Percentage"
},
{
"default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
"fieldname": "daily_depreciation",
"fieldname": "daily_prorata_based",
"fieldtype": "Check",
"label": "Daily Depreciation"
"label": "Depreciate based on daily pro-rata"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-08-10 18:56:09.022246",
"modified": "2023-11-03 22:21:52.090191",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",

View File

@@ -16,7 +16,7 @@
"transaction_settings_section",
"po_required",
"pr_required",
"over_order_allowance",
"blanket_order_allowance",
"column_break_12",
"maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
@@ -24,6 +24,7 @@
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
"show_pay_button",
"use_transaction_date_exchange_rate",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -160,10 +161,17 @@
},
{
"default": "0",
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
"fieldname": "over_order_allowance",
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
"fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate"
},
{
"default": "0",
"description": "Percentage you are allowed to order beyond the Blanket Order quantity.",
"fieldname": "blanket_order_allowance",
"fieldtype": "Float",
"label": "Over Order Allowance (%)"
"label": "Blanket Order Allowance (%)"
}
],
"icon": "fa fa-cog",
@@ -171,7 +179,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-03-02 17:02:14.404622",
"modified": "2023-10-25 14:03:32.520418",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -475,6 +475,7 @@
"depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"options": "Warehouse"
},
@@ -1272,7 +1273,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-09-13 16:21:07.361700",
"modified": "2023-10-01 20:58:07.851037",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -522,6 +522,9 @@ def make_purchase_receipt(source_name, target_doc=None):
"bom": "bom",
"material_request": "material_request",
"material_request_item": "material_request_item",
"sales_order": "sales_order",
"sales_order_item": "sales_order_item",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
@@ -598,6 +601,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"field_map": {
"name": "po_detail",
"parent": "purchase_order",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),

View File

@@ -86,6 +86,8 @@
"billed_amt",
"accounting_details",
"expense_account",
"column_break_fyqr",
"wip_composite_asset",
"manufacture_details",
"manufacturer",
"manufacturer_part_no",
@@ -469,6 +471,7 @@
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"mandatory_depends_on": "eval: doc.material_request_item",
"no_copy": 1,
"oldfieldname": "prevdoc_docname",
"oldfieldtype": "Link",
@@ -484,6 +487,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Material Request Item",
"mandatory_depends_on": "eval: doc.material_request",
"no_copy": 1,
"oldfieldname": "prevdoc_detail_docname",
"oldfieldtype": "Data",
@@ -897,13 +901,23 @@
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
},
{
"fieldname": "wip_composite_asset",
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"fieldname": "column_break_fyqr",
"fieldtype": "Column Break"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-09-13 16:22:40.825092",
"modified": "2023-11-06 11:00:53.596417",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -68,7 +68,7 @@ frappe.ui.form.on("Supplier", {
}, __("View"));
frm.add_custom_button(__('Accounts Payable'), function () {
frappe.set_route('query-report', 'Accounts Payable', { supplier: frm.doc.name });
frappe.set_route('query-report', 'Accounts Payable', { party_type: "Supplier", party: frm.doc.name });
}, __("View"));
frm.add_custom_button(__('Bank Account'), function () {

View File

@@ -170,7 +170,7 @@
"fieldname": "supplier_type",
"fieldtype": "Select",
"label": "Supplier Type",
"options": "Company\nIndividual",
"options": "Company\nIndividual\nProprietorship\nPartnership",
"reqd": 1
},
{
@@ -457,7 +457,7 @@
"link_fieldname": "party"
}
],
"modified": "2023-05-09 15:34:13.408932",
"modified": "2023-10-19 16:55:15.148325",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
@@ -525,4 +525,4 @@
"states": [],
"title_field": "supplier_name",
"track_changes": 1
}
}

View File

@@ -8,7 +8,7 @@ def get_data():
"This is based on transactions against this Supplier. See timeline below for details"
),
"fieldname": "supplier",
"non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"},
"non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"},
"transactions": [
{"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]},
{"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},

View File

@@ -6,7 +6,7 @@ import copy
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import date_diff, flt, getdate
@@ -57,7 +57,7 @@ def get_data(filters):
po_item.qty,
po_item.received_qty,
(po_item.qty - po_item.received_qty).as_("pending_qty"),
IfNull(pi_item.qty, 0).as_("billed_qty"),
Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"),
po_item.base_amount.as_("amount"),
(po_item.received_qty * po_item.base_rate).as_("received_qty_amount"),
(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),

View File

@@ -44,11 +44,6 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
}
}
}
else {
return {
filters: { "disabled": 0 }
}
}
}
},
{

View File

@@ -35,8 +35,12 @@ def get_data(filters):
sq_item.parent,
sq_item.item_code,
sq_item.qty,
sq.currency,
sq_item.stock_qty,
sq_item.amount,
sq_item.base_rate,
sq_item.base_amount,
sq.price_list_currency,
sq_item.uom,
sq_item.stock_uom,
sq_item.request_for_quotation,
@@ -105,7 +109,11 @@ def prepare_data(supplier_quotation_data, filters):
"qty": data.get("qty"),
"price": flt(data.get("amount") * exchange_rate, float_precision),
"uom": data.get("uom"),
"price_list_currency": data.get("price_list_currency"),
"currency": data.get("currency"),
"stock_uom": data.get("stock_uom"),
"base_amount": flt(data.get("base_amount"), float_precision),
"base_rate": flt(data.get("base_rate"), float_precision),
"request_for_quotation": data.get("request_for_quotation"),
"valid_till": data.get("valid_till"),
"lead_time_days": data.get("lead_time_days"),
@@ -183,6 +191,8 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
def get_columns(filters):
currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
group_by_columns = [
{
"fieldname": "supplier_name",
@@ -203,11 +213,18 @@ def get_columns(filters):
columns = [
{"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90},
{"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"width": 110,
},
{
"fieldname": "price",
"label": _("Price"),
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"options": "currency",
"width": 110,
},
{
@@ -221,9 +238,23 @@ def get_columns(filters):
"fieldname": "price_per_unit",
"label": _("Price per Unit (Stock UOM)"),
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"options": "currency",
"width": 120,
},
{
"fieldname": "base_amount",
"label": _("Price ({0})").format(currency),
"fieldtype": "Currency",
"options": "price_list_currency",
"width": 180,
},
{
"fieldname": "base_rate",
"label": _("Price Per Unit ({0})").format(currency),
"fieldtype": "Currency",
"options": "price_list_currency",
"width": 180,
},
{
"fieldname": "quotation",
"label": _("Supplier Quotation"),

View File

@@ -12,6 +12,7 @@ from frappe.utils import (
add_days,
add_months,
cint,
comma_and,
flt,
fmt_money,
formatdate,
@@ -180,6 +181,17 @@ class AccountsController(TransactionBase):
self.validate_party_account_currency()
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
if invalid_advances := [
x for x in self.advances if not x.reference_type or not x.reference_name
]:
frappe.throw(
_(
"Rows: {0} in {1} section are Invalid. Reference Name should point to a valid Payment Entry or Journal Entry."
).format(
frappe.bold(comma_and([x.idx for x in invalid_advances])), frappe.bold(_("Advance Payments"))
)
)
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
@@ -211,12 +223,70 @@ class AccountsController(TransactionBase):
def before_cancel(self):
validate_einvoice_fields(self)
def _remove_references_in_unreconcile(self):
upe = frappe.qb.DocType("Unreconcile Payment Entries")
rows = (
frappe.qb.from_(upe)
.select(upe.name, upe.parent)
.where((upe.reference_doctype == self.doctype) & (upe.reference_name == self.name))
.run(as_dict=True)
)
if rows:
references_map = frappe._dict()
for x in rows:
references_map.setdefault(x.parent, []).append(x.name)
for doc, rows in references_map.items():
unreconcile_doc = frappe.get_doc("Unreconcile Payments", doc)
for row in rows:
unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0])
unreconcile_doc.flags.ignore_validate_update_after_submit = True
unreconcile_doc.flags.ignore_links = True
unreconcile_doc.save(ignore_permissions=True)
# delete docs upon parent doc deletion
unreconcile_docs = frappe.db.get_all("Unreconcile Payments", filters={"voucher_no": self.name})
for x in unreconcile_docs:
_doc = frappe.get_doc("Unreconcile Payments", x.name)
if _doc.docstatus == 1:
_doc.cancel()
_doc.delete()
def _remove_references_in_repost_doctypes(self):
repost_doctypes = ["Repost Payment Ledger Items", "Repost Accounting Ledger Items"]
for _doctype in repost_doctypes:
dt = frappe.qb.DocType(_doctype)
rows = (
frappe.qb.from_(dt)
.select(dt.name, dt.parent, dt.parenttype)
.where((dt.voucher_type == self.doctype) & (dt.voucher_no == self.name))
.run(as_dict=True)
)
if rows:
references_map = frappe._dict()
for x in rows:
references_map.setdefault((x.parenttype, x.parent), []).append(x.name)
for doc, rows in references_map.items():
repost_doc = frappe.get_doc(doc[0], doc[1])
for row in rows:
if _doctype == "Repost Payment Ledger Items":
repost_doc.remove(repost_doc.get("repost_vouchers", {"name": row})[0])
else:
repost_doc.remove(repost_doc.get("vouchers", {"name": row})[0])
repost_doc.flags.ignore_validate_update_after_submit = True
repost_doc.flags.ignore_links = True
repost_doc.save(ignore_permissions=True)
def on_trash(self):
# delete references in 'Repost Payment Ledger'
rpi = frappe.qb.DocType("Repost Payment Ledger Items")
frappe.qb.from_(rpi).delete().where(
(rpi.voucher_type == self.doctype) & (rpi.voucher_no == self.name)
).run()
self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile()
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
@@ -513,6 +583,17 @@ class AccountsController(TransactionBase):
self.currency, self.company_currency, transaction_date, args
)
if (
self.currency
and buying_or_selling == "Buying"
and frappe.db.get_single_value("Buying Settings", "use_transaction_date_exchange_rate")
and self.doctype == "Purchase Invoice"
):
self.use_transaction_date_exchange_rate = True
self.conversion_rate = get_exchange_rate(
self.currency, self.company_currency, transaction_date, args
)
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -536,6 +617,7 @@ class AccountsController(TransactionBase):
self.pricing_rules = []
selected_serial_nos_map = {}
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
@@ -547,6 +629,7 @@ class AccountsController(TransactionBase):
args["ignore_pricing_rule"] = (
self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0
)
args["ignore_serial_nos"] = selected_serial_nos_map.get(item.get("item_code"))
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")
@@ -603,6 +686,11 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
if ret.get("serial_no"):
selected_serial_nos_map.setdefault(item.get("item_code"), []).extend(
ret.get("serial_no").split("\n")
)
else:
# Transactions line item without item code
@@ -909,7 +997,7 @@ class AccountsController(TransactionBase):
party_type, party, party_account, amount_field, order_doctype, order_list, include_unallocated
)
payment_entries = get_advance_payment_entries(
payment_entries = get_advance_payment_entries_for_regional(
party_type, party, party_account, order_doctype, order_list, include_unallocated
)
@@ -1043,7 +1131,9 @@ class AccountsController(TransactionBase):
self.name,
arg.get("referenced_row"),
):
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
posting_date = arg.get("difference_posting_date") or frappe.db.get_value(
arg.voucher_type, arg.voucher_no, "posting_date"
)
je = create_gain_loss_journal(
self.company,
posting_date,
@@ -1126,7 +1216,7 @@ class AccountsController(TransactionBase):
je = create_gain_loss_journal(
self.company,
self.posting_date,
args.get("difference_posting_date") if args else self.posting_date,
self.party_type,
self.party,
party_account,
@@ -1406,7 +1496,7 @@ class AccountsController(TransactionBase):
"account": self.additional_discount_account,
"against": supplier_or_customer,
dr_or_cr: self.base_discount_amount,
"cost_center": self.cost_center,
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
},
item=self,
)
@@ -2126,6 +2216,46 @@ class AccountsController(TransactionBase):
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
def check_if_fields_updated(self, fields_to_check, child_tables):
# Check if any field affecting accounting entry is altered
doc_before_update = self.get_doc_before_save()
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Check if opening entry check updated
needs_repost = doc_before_update.get("is_opening") != self.is_opening
if not needs_repost:
# Parent Level Accounts excluding party account
fields_to_check += accounting_dimensions
for field in fields_to_check:
if doc_before_update.get(field) != self.get(field):
needs_repost = 1
break
if not needs_repost:
# Check for child tables
for table in child_tables:
needs_repost = check_if_child_table_updated(
doc_before_update.get(table), self.get(table), child_tables[table]
)
if needs_repost:
break
return needs_repost
@frappe.whitelist()
def repost_accounting_entries(self):
if self.repost_required:
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
repost_ledger.company = self.company
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
repost_ledger.flags.ignore_permissions = True
repost_ledger.insert()
repost_ledger.submit()
self.db_set("repost_required", 0)
else:
frappe.throw(_("No updates pending for reposting"))
@frappe.whitelist()
def get_tax_rate(account_head):
@@ -2349,6 +2479,11 @@ def get_advance_journal_entries(
return list(journal_entries)
@erpnext.allow_regional
def get_advance_payment_entries_for_regional(*args, **kwargs):
return get_advance_payment_entries(*args, **kwargs)
def get_advance_payment_entries(
party_type,
party,
@@ -3006,6 +3141,23 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.set_status()
def check_if_child_table_updated(
child_table_before_update, child_table_after_update, fields_to_check
):
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Check if any field affecting accounting entry is altered
for index, item in enumerate(child_table_after_update):
for field in fields_to_check:
if child_table_before_update[index].get(field) != item.get(field):
return True
for dimension in accounting_dimensions:
if child_table_before_update[index].get(dimension) != item.get(dimension):
return True
return False
@erpnext.allow_regional
def validate_regional(doc):
pass

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import ValidationError, _, msgprint
from frappe.contacts.doctype.address.address import get_address_display
from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, cstr, flt, getdate
from frappe.utils.data import nowtime
@@ -14,7 +14,8 @@ from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.subcontracting_controller import SubcontractingController
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
class QtyMismatchError(ValidationError):
@@ -77,26 +78,26 @@ class BuyingController(SubcontractingController):
def set_rate_for_standalone_debit_note(self):
if self.get("is_return") and self.get("update_stock") and not self.return_against:
for row in self.items:
if row.rate <= 0:
# override the rate with valuation rate
row.rate = get_incoming_rate(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.get("posting_date"),
"posting_time": self.get("posting_time"),
"qty": row.qty,
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
},
raise_error_if_no_rate=False,
)
# override the rate with valuation rate
row.rate = get_incoming_rate(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.get("posting_date"),
"posting_time": self.get("posting_time"),
"qty": row.qty,
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
},
raise_error_if_no_rate=False,
)
row.discount_percentage = 0.0
row.discount_amount = 0.0
row.margin_rate_or_amount = 0.0
row.discount_percentage = 0.0
row.discount_amount = 0.0
row.margin_rate_or_amount = 0.0
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@@ -218,7 +219,9 @@ class BuyingController(SubcontractingController):
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def set_total_in_words(self):
from frappe.utils import money_in_words
@@ -514,9 +517,20 @@ class BuyingController(SubcontractingController):
)
if self.is_return:
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
if get_valuation_method(d.item_code) == "Moving Average":
previous_sle = get_previous_sle(
{
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
}
)
outgoing_rate = flt(previous_sle.get("valuation_rate"))
else:
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
if d.from_warehouse:

View File

@@ -4,9 +4,9 @@
import frappe
from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController
@@ -282,7 +282,9 @@ class SellingController(StockController):
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate")
throw_message(
item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate (Moving Average)"
)
def get_item_list(self):
il = []
@@ -589,7 +591,9 @@ class SellingController(StockController):
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []

Some files were not shown because too many files have changed in this diff Show More