Compare commits

...

176 Commits

Author SHA1 Message Date
Frappe PR Bot
d420eeb884 chore(release): Bumped to Version 14.30.4
## [14.30.4](https://github.com/frappe/erpnext/compare/v14.30.3...v14.30.4) (2023-07-14)

### Bug Fixes

* Handle multi-company in patch ([#36127](https://github.com/frappe/erpnext/issues/36127)) ([43d6cc0](43d6cc087e))
2023-07-14 10:43:22 +00:00
mergify[bot]
43d6cc087e fix: Handle multi-company in patch (#36127)
fix: Handle multi-company in patch (#36127)

* fix: Handle multi-company in patch (#36127)

fix: Handle multi-compnay in patch
(cherry picked from commit ac9ad8ec36)

* chore: re trigger patch

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit e7f57542ab)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-07-14 16:02:32 +05:30
Frappe PR Bot
2f2b45bd6d chore(release): Bumped to Version 14.30.3
## [14.30.3](https://github.com/frappe/erpnext/compare/v14.30.2...v14.30.3) (2023-07-14)

### Bug Fixes

* Account balance patch and query fixes ([#36117](https://github.com/frappe/erpnext/issues/36117)) ([495a8a9](495a8a9ce1))
2023-07-14 05:53:54 +00:00
mergify[bot]
495a8a9ce1 fix: Account balance patch and query fixes (#36117)
fix: Account balance patch and query fixes (#36117)

(cherry picked from commit b4bd978791)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 0147754273)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-07-14 11:21:59 +05:30
Frappe PR Bot
1d9c28ec5e chore(release): Bumped to Version 14.30.2
## [14.30.2](https://github.com/frappe/erpnext/compare/v14.30.1...v14.30.2) (2023-07-13)

### Bug Fixes

* Accounts closing balance patch ([#36113](https://github.com/frappe/erpnext/issues/36113)) ([49f28b0](49f28b0dbb))
2023-07-13 10:52:30 +00:00
mergify[bot]
49f28b0dbb fix: Accounts closing balance patch (#36113)
fix: Accounts closing balance patch (#36113)

fix: Accounts closing balance patch (#36113)

(cherry picked from commit d631c7dffa)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit cf29156139)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-07-13 16:17:04 +05:30
Frappe PR Bot
d43cf0eca4 chore(release): Bumped to Version 14.30.1
## [14.30.1](https://github.com/frappe/erpnext/compare/v14.30.0...v14.30.1) (2023-07-11)

### Bug Fixes

* circular dependency during reposting causing timeout error ([026c608](026c6085cc))
2023-07-11 14:13:02 +00:00
rohitwaghchaure
a5a41f7347 Merge pull request #36091 from frappe/mergify/bp/version-14/pr-36089
fix: circular dependency during reposting causing timeout error (backport #36088) (backport #36089)
2023-07-11 19:41:35 +05:30
Rohit Waghchaure
026c6085cc fix: circular dependency during reposting causing timeout error
(cherry picked from commit c16a5814d4)
(cherry picked from commit 2c21404813)
2023-07-11 13:41:40 +00:00
Frappe PR Bot
2b72d143ca chore(release): Bumped to Version 14.30.0
# [14.30.0](https://github.com/frappe/erpnext/compare/v14.29.2...v14.30.0) (2023-07-11)

### Bug Fixes

* accepted warehouse and rejected warehouse can't be same ([5d77e3c](5d77e3ce05))
* also check on_hold ([#35910](https://github.com/frappe/erpnext/issues/35910)) ([59b3277](59b3277696))
* conflicts ([e55a264](e55a264e57))
* conflicts ([79f9785](79f9785d15))
* conflicts ([a178e66](a178e6693c))
* conflicts ([6459c28](6459c28316))
* deferred accounting entries on accounts frozen ([#35978](https://github.com/frappe/erpnext/issues/35978)) ([573183c](573183cff5))
* Delivery Note return valuation ([296f312](296f312e7f))
* German translations ([#35990](https://github.com/frappe/erpnext/issues/35990)) ([b7b864e](b7b864e68c))
* incorrect status in MR created from PP (backport [#36085](https://github.com/frappe/erpnext/issues/36085)) ([#36086](https://github.com/frappe/erpnext/issues/36086)) ([6dc7a19](6dc7a192ab))
* incorrect TCS amount while customer has advance payment ([#35397](https://github.com/frappe/erpnext/issues/35397)) ([2a4bbf3](2a4bbf34b4))
* labels and translations ([#35963](https://github.com/frappe/erpnext/issues/35963)) ([04b1d45](04b1d459eb))
* Opening balance in presentation currency in Trial Balance report ([#36036](https://github.com/frappe/erpnext/issues/36036)) ([39e38bf](39e38bf083))
* payment entry `voucher_type` error ([#35779](https://github.com/frappe/erpnext/issues/35779)) ([f3af0b2](f3af0b2d2e))
* **Payment Entry:** compare rounded amount ([#36011](https://github.com/frappe/erpnext/issues/36011)) ([d80b0aa](d80b0aa157))
* possible type error on ERR creation ([0569899](0569899499))
* precision causing outstanding issue on partly paid invoices ([#36030](https://github.com/frappe/erpnext/issues/36030)) ([3e711e8](3e711e888d))
* Share ledger showing cancelled docs ([#35993](https://github.com/frappe/erpnext/issues/35993)) ([5102d0c](5102d0c3f7))
* Validate for missing expense account ([#36078](https://github.com/frappe/erpnext/issues/36078)) ([f4f886c](f4f886c7d1))
* Vietnamese translation of "Company" ([#35887](https://github.com/frappe/erpnext/issues/35887)) ([e443e6c](e443e6c02a))

### Features

* Closing balance for period closing and reporting ([#34257](https://github.com/frappe/erpnext/issues/34257)) ([ebf3c01](ebf3c0173e))
2023-07-11 12:51:51 +00:00
Deepesh Garg
078383f086 Merge pull request #36082 from frappe/version-14-hotfix
chore: release v14
2023-07-11 18:20:08 +05:30
mergify[bot]
6dc7a192ab fix: incorrect status in MR created from PP (backport #36085) (#36086)
fix: incorrect status in MR created from PP (#36085)

(cherry picked from commit be5881280f)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-07-11 17:31:06 +05:30
Deepesh Garg
a76285b349 Merge branch 'version-14' into version-14-hotfix 2023-07-11 16:14:57 +05:30
mergify[bot]
f4f886c7d1 fix: Validate for missing expense account (#36078)
fix: Validate for missing expense account (#36078)

* fix: Validate for missing expense account

* fix: Validate for missing expense account

(cherry picked from commit ce9164ec69)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-11 13:38:13 +05:30
ruthra kumar
7925fc7130 Merge pull request #36077 from frappe/mergify/bp/version-14-hotfix/pr-36076
fix: possible type error on ERR creation (backport #36076)
2023-07-11 11:12:24 +05:30
ruthra kumar
0569899499 fix: possible type error on ERR creation
(cherry picked from commit 176966daab)
2023-07-11 05:08:28 +00:00
mergify[bot]
44f509f989 refactor(Payment Entry): translatable strings (#36017)
* refactor(Payment Entry): translatable strings (#36017)

* refactor(Payment Entry): translatable strings

* fix: German translations

(cherry picked from commit af28f95c60)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
#	erpnext/translations/de.csv

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-07-10 21:01:44 +05:30
mergify[bot]
f3af0b2d2e fix: payment entry voucher_type error (#35779)
fix: payment entry `voucher_type` error (#35779)

* fix: payment entry `voucher_type` error

* chore: linters

(cherry picked from commit 361a357088)

Co-authored-by: Dany Robert <danyrt@wahni.com>
2023-07-10 20:52:36 +05:30
mergify[bot]
59b3277696 fix: also check on_hold (#35910)
fix: also check on_hold (#35910)

(cherry picked from commit 5aa02b8571)

Co-authored-by: RJPvT <48353029+RJPvT@users.noreply.github.com>
2023-07-10 20:34:32 +05:30
Frappe PR Bot
a299092337 chore(release): Bumped to Version 14.29.2
## [14.29.2](https://github.com/frappe/erpnext/compare/v14.29.1...v14.29.2) (2023-07-10)

### Bug Fixes

* Delivery Note return valuation ([8c041eb](8c041eb424))
2023-07-10 14:10:51 +00:00
rohitwaghchaure
db809cb066 Merge pull request #36066 from frappe/mergify/bp/version-14/pr-36065
fix: Delivery Note return valuation (backport #36063) (backport #36065)
2023-07-10 19:39:19 +05:30
mergify[bot]
39e38bf083 fix: Opening balance in presentation currency in Trial Balance report (#36036)
fix: Opening balance in presentation currency in Trial Balance report (#36036)

(cherry picked from commit 4d07e20b05)

# Conflicts:
#	erpnext/accounts/report/trial_balance/trial_balance.py

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-10 19:26:32 +05:30
Rohit Waghchaure
8c041eb424 fix: Delivery Note return valuation
(cherry picked from commit 6a10ae662c)
(cherry picked from commit 296f312e7f)
2023-07-10 12:50:08 +00:00
rohitwaghchaure
7c092a6b5f Merge pull request #36065 from frappe/mergify/bp/version-14-hotfix/pr-36063
fix: Delivery Note return valuation (backport #36063)
2023-07-10 18:18:57 +05:30
mergify[bot]
ebf3c0173e feat: Closing balance for period closing and reporting (#34257)
* feat: Introduce opening entry for reporting

(cherry picked from commit 9739d8b52a)

* feat: Introduce opening entry for reporting

(cherry picked from commit b44a19bd1a)

* fix: Add patches to create accounting dimension in Closing Balance

(cherry picked from commit 36c08d0835)

* feat: Cascade closing balances on PCV submit

(cherry picked from commit c3f39c3f32)

# Conflicts:
#	erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
#	erpnext/patches.txt

* feat: Add views in standard filter

(cherry picked from commit e18336ebe7)

* chore: Rewrite query using query builder

(cherry picked from commit 7fa7d6b5e4)

# Conflicts:
#	erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py

* feat: Add validations against period closing voucher

(cherry picked from commit f92c63fb10)

* fix: Order by issue in aggregation query

(cherry picked from commit 6607c8bd82)

* fix: Add patch to update closing balances

(cherry picked from commit a663df376c)

* fix: Closing balance entries for period closing voucher

(cherry picked from commit 436fc03eda)

* fix: Update patch to generate closing balance entries

(cherry picked from commit 95c9aafda9)

# Conflicts:
#	erpnext/patches.txt

* perf: Apply closing balance in Trial Balance report

(cherry picked from commit e5f603c9d9)

# Conflicts:
#	erpnext/accounts/report/trial_balance/trial_balance.py

* chore: Minor fixes

(cherry picked from commit c089c4156c)

# Conflicts:
#	erpnext/patches.txt

* fix: Aggregation with previous closing balance

(cherry picked from commit 4a2046dfb6)

# Conflicts:
#	erpnext/patches.txt

* test: Add static posting dates to tests

(cherry picked from commit 310f71c313)

* chore: Add index to period closing voucher column

(cherry picked from commit 5dabc98ba5)

* chore: rename Closing Balance to Account Closing Balance

(cherry picked from commit 3249a79f07)

* test: Add test case for closing balance

(cherry picked from commit f0267feca8)

* chore: Use account closing balance in set gl entries

(cherry picked from commit 0157fa15eb)

# Conflicts:
#	erpnext/accounts/report/financial_statements.py

* fix: Update patch

(cherry picked from commit 76775a3e49)

* fix: Account sub query

(cherry picked from commit 7f11373b58)

* chore: Simplify query

(cherry picked from commit 00fe3042b2)

# Conflicts:
#	erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py

* chore: Add missing validations

(cherry picked from commit 0aadb680eb)

* chore: Remove unnecessary list comprehension

(cherry picked from commit 44053db010)

# Conflicts:
#	erpnext/accounts/report/financial_statements.py

* fix: Ignore opening entries if PCV posted

(cherry picked from commit f9397a87ac)

# Conflicts:
#	erpnext/accounts/report/financial_statements.py

* fix: Don't validate if no GL Entry exists

(cherry picked from commit 528ab503f2)

* chore: Fix Typo

(cherry picked from commit 3fd95200da)

* fix: Validation for cancelation

(cherry picked from commit 30eb6c8512)

* fix: Partial trial balance view

(cherry picked from commit b7dcf27b01)

* fix: CS financial statement param

(cherry picked from commit f8cff09129)

* chore: Improve validation message

(cherry picked from commit 8ce1da111e)

* chore: Resolve conflicts

* chore: Resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-10 18:04:45 +05:30
Rohit Waghchaure
296f312e7f fix: Delivery Note return valuation
(cherry picked from commit 6a10ae662c)
2023-07-10 12:19:37 +00:00
rohitwaghchaure
16943ac248 Merge pull request #36057 from frappe/mergify/bp/version-14-hotfix/pr-36001
fix: accepted warehouse and rejected warehouse can't be same (backport #36001)
2023-07-10 17:48:29 +05:30
mergify[bot]
573183cff5 fix: deferred accounting entries on accounts frozen (#35978)
* fix: deferred accounting entries on accounts frozen (#35978)

* fix: accounts frozen entries in deferred accounting

* test: accounts frozen date in deferred accounting

* fix: reset account settings after running test

* fix: resolve conflicts

* fix: modify expected gle when deferred accounting is disabled through JE

* fix: change posting date when accounts not frozen

(cherry picked from commit 674af15696)

# Conflicts:
#	erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py

* chore: resolve conflicts

* fix: test for deferred accounting

* chore: Linting Issues

---------

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-10 17:31:17 +05:30
rohitwaghchaure
e55a264e57 fix: conflicts 2023-07-10 17:14:25 +05:30
rohitwaghchaure
79f9785d15 fix: conflicts 2023-07-10 17:13:55 +05:30
rohitwaghchaure
a178e6693c fix: conflicts 2023-07-10 17:13:23 +05:30
rohitwaghchaure
6459c28316 fix: conflicts 2023-07-10 17:12:41 +05:30
mergify[bot]
3e711e888d fix: precision causing outstanding issue on partly paid invoices (#36030)
fix: precision causing outstanding issue on partly paid invoices (#36030)

* fix: precision causing outstanding issue on partly paid invoices

* chore: linters

(cherry picked from commit 5c820ecc20)

Co-authored-by: Dany Robert <danyrt@wahni.com>
2023-07-10 16:49:22 +05:30
mergify[bot]
04b1d459eb fix: labels and translations (#35963)
fix: labels and translations (#35963)

fix: labels and translations
* fix: Vietnamese translation of customer
* fix: Vietnamese translation of bill

(cherry picked from commit 46fe9ac5cd)

Co-authored-by: aioaccount <92444849+aioaccount@users.noreply.github.com>
2023-07-10 16:19:36 +05:30
ruthra kumar
3c563b8cea Merge pull request #35994 from frappe/mergify/bp/version-14-hotfix/pr-35397
fix: incorrect TCS amount while customer has advance payment (backport #35397)
2023-07-10 16:18:47 +05:30
ruthra kumar
4fa3f96121 chore: fix failing test case 2023-07-10 15:48:56 +05:30
ruthra kumar
681d48a7f9 Merge pull request #36053 from frappe/mergify/bp/version-14-hotfix/pr-36051
feat: Provision to auto create Exchange Rate Revaluation (backport #36051)
2023-07-10 15:36:16 +05:30
ruthra kumar
881476b4fb chore: fix merge conflict 2023-07-10 15:09:37 +05:30
Rohit Waghchaure
5d77e3ce05 fix: accepted warehouse and rejected warehouse can't be same
(cherry picked from commit d618aaef32)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
#	erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
#	erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
2023-07-10 08:26:13 +00:00
Anand Baburajan
0f9a6ee70a chore: add asset depr posting error in error log (#36052) 2023-07-10 13:45:56 +05:30
ruthra kumar
862c5145c1 refactor: submit and make JV through background job
(cherry picked from commit 4f51c5a433)

# Conflicts:
#	erpnext/setup/doctype/company/company.json
2023-07-10 07:31:19 +00:00
ruthra kumar
e0fbf0c042 refactor: checkbox for enabling auto ERR creation
(cherry picked from commit 6644311c8b)

# Conflicts:
#	erpnext/setup/doctype/company/company.json
2023-07-10 07:31:19 +00:00
mergify[bot]
e443e6c02a fix: Vietnamese translation of "Company" (#35887)
fix: Vietnamese translation of "Company" (#35887)

fix: Vietnamese translation of "Company"
(cherry picked from commit ef7fd7548c)

Co-authored-by: aioaccount <92444849+aioaccount@users.noreply.github.com>
2023-07-09 20:34:26 +05:30
mergify[bot]
b7b864e68c fix: German translations (#35990)
fix: German translations (#35990)

* fix: add missing German translation

* fix: wrong German translation

(cherry picked from commit 353d765140)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-07-09 20:18:22 +05:30
mergify[bot]
29ac533af4 fix:bank reconciliation tool variable issue (#36022)
fix: bank reconciliation tool variable issue (#36022)

fix: bank reconciliation tool variable issue (#36022)
(cherry picked from commit 828e647019)

Co-authored-by: Navin Balaji <54995833+navinbalaji@users.noreply.github.com>
2023-07-09 20:17:54 +05:30
Deepesh Garg
2deb1edec1 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-35397 2023-07-09 14:12:59 +05:30
Frappe PR Bot
78318964c7 chore(release): Bumped to Version 14.29.1
## [14.29.1](https://github.com/frappe/erpnext/compare/v14.29.0...v14.29.1) (2023-07-05)

### Bug Fixes

* **Payment Entry:** compare rounded amount ([#36011](https://github.com/frappe/erpnext/issues/36011)) ([03e4583](03e458390b))
2023-07-05 16:23:44 +00:00
Deepesh Garg
279f51e636 Merge pull request #36016 from frappe/mergify/bp/version-14/pr-36013
fix(Payment Entry): compare rounded amount (#36011)
2023-07-05 21:47:31 +05:30
mergify[bot]
5102d0c3f7 fix: Share ledger showing cancelled docs (#35993)
fix: Share ledger showing cancelled docs (#35993)

(cherry picked from commit 0a17c78a36)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-05 21:39:38 +05:30
mergify[bot]
03e458390b fix(Payment Entry): compare rounded amount (#36011)
fix(Payment Entry): compare rounded amount (#36011)

(cherry picked from commit 4badac8e9e)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit d80b0aa157)
2023-07-05 16:09:11 +00:00
mergify[bot]
d80b0aa157 fix(Payment Entry): compare rounded amount (#36011)
fix(Payment Entry): compare rounded amount (#36011)

(cherry picked from commit 4badac8e9e)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-07-05 21:38:30 +05:30
Frappe PR Bot
f360bdcbf6 chore(release): Bumped to Version 14.29.0
# [14.29.0](https://github.com/frappe/erpnext/compare/v14.28.1...v14.29.0) (2023-07-05)

### Bug Fixes

* Expense Account filter in Sales Invoice ([#35944](https://github.com/frappe/erpnext/issues/35944)) ([a485e4e](a485e4e802))
* get base grand total while pulling reference details in PE ([2e2c23a](2e2c23aa0f))
* handle loan_repayment's posting_date datetime in bank_clearance_summary report ([#36004](https://github.com/frappe/erpnext/issues/36004)) ([937e1fb](937e1fb024))
* Netherlands - Grootboekschema COA structure ([#35991](https://github.com/frappe/erpnext/issues/35991)) ([13f3ebf](13f3ebf915))
* project filtering based on company in P&L Report ([#35943](https://github.com/frappe/erpnext/issues/35943)) ([8de1d86](8de1d8663f))
* remove debug flag from sql ([646440f](646440fd55))
* reposting has not changed valuation rate ([c0c693d](c0c693d8b0))
* Update no copy for received_qty field ([#35965](https://github.com/frappe/erpnext/issues/35965)) ([c330f47](c330f47680))

### Features

* **accounts:** standardize additional columns implementation for sales/purchase reports ([#36000](https://github.com/frappe/erpnext/issues/36000)) ([47c6d90](47c6d9099b))
* add method for ordered quantity in supplier scorecard (backport [#35930](https://github.com/frappe/erpnext/issues/35930)) ([#35968](https://github.com/frappe/erpnext/issues/35968)) ([a974091](a974091678))
* add voucher-wise balance report logic ([6842902](684290233f))
* allow the partial return of components against SCO (backport [#35935](https://github.com/frappe/erpnext/issues/35935)) ([#35938](https://github.com/frappe/erpnext/issues/35938)) ([6f50ad6](6f50ad685e))
2023-07-05 09:48:12 +00:00
Deepesh Garg
4fa412fe3f Merge pull request #35996 from frappe/version-14-hotfix
chore: release v14
2023-07-05 15:16:47 +05:30
Anand Baburajan
937e1fb024 fix: handle loan_repayment's posting_date datetime in bank_clearance_summary report (#36004) 2023-07-04 19:06:58 +05:30
Sagar Vora
dfd4ef178e Merge pull request #36002 from frappe/mergify/bp/version-14-hotfix/pr-36000 2023-07-04 17:42:51 +05:30
Sagar Vora
47c6d9099b feat(accounts): standardize additional columns implementation for sales/purchase reports (#36000)
(cherry picked from commit 30e4052a76)
2023-07-04 12:12:34 +00:00
mergify[bot]
13f3ebf915 fix: Netherlands - Grootboekschema COA structure (#35991)
fix: Netherlands - Grootboekschema COA structure (#35991)

fix: Netherlands - Grootboekschema coa structure
(cherry picked from commit 2f169575e9)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-04 16:22:17 +05:30
ruthra kumar
2a4bbf34b4 fix: incorrect TCS amount while customer has advance payment (#35397)
* fix: incorrect TCS amount while customer has advance payment

* test: only unallocated advance should for threshold breach validation

(cherry picked from commit dcbd7d5f1f)
2023-07-04 16:21:54 +05:30
ruthra kumar
ae4a8c8788 Merge pull request #35981 from frappe/mergify/bp/version-14-hotfix/pr-35868
fix: incorrect outstanding and total amount in reference table of payment entry (backport #35868)
2023-07-04 08:09:05 +05:30
ruthra kumar
3d1942571d test: test reference details response
(cherry picked from commit 9655d78642)
2023-07-03 15:33:20 +00:00
ruthra kumar
2e2c23aa0f fix: get base grand total while pulling reference details in PE
(cherry picked from commit 9e73af891d)
2023-07-03 15:33:20 +00:00
mergify[bot]
c330f47680 fix: Update no copy for received_qty field (#35965)
* fix: Update no copy for received_qty field (#35965)

(cherry picked from commit 5448859254)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
#	erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-03 16:38:08 +05:30
mergify[bot]
a974091678 feat: add method for ordered quantity in supplier scorecard (backport #35930) (#35968)
feat: add method for ordered quantity in supplier scorecard (#35930)

fix: add method for getting ordered quantity in the supplier scorecard variable.

Co-authored-by: vishnu <vishnuviswambara2002@gmail.com>
(cherry picked from commit e05b33a6c2)

Co-authored-by: Vishnu  VS <Vishnuviswambaran2002@gmail.com>
2023-07-03 10:23:36 +05:30
Frappe PR Bot
87c0417e22 chore(release): Bumped to Version 14.28.1
## [14.28.1](https://github.com/frappe/erpnext/compare/v14.28.0...v14.28.1) (2023-07-02)

### Bug Fixes

* reposting has not changed valuation rate ([cec3cde](cec3cdec66))
2023-07-02 05:49:11 +00:00
rohitwaghchaure
dbae36ece3 Merge pull request #35962 from frappe/mergify/bp/version-14/pr-35955
fix: incorrect reposting causing stock adjustment entry (backport #35955)
2023-07-02 11:17:48 +05:30
Rohit Waghchaure
cec3cdec66 fix: reposting has not changed valuation rate
(cherry picked from commit c0c693d8b0)
2023-07-02 05:18:37 +00:00
rohitwaghchaure
c3eab84e37 Merge pull request #35955 from rohitwaghchaure/fixed-incorrect-valuation-rate
fix: incorrect reposting causing stock adjustment entry
2023-07-02 08:22:16 +05:30
Rohit Waghchaure
c0c693d8b0 fix: reposting has not changed valuation rate 2023-07-01 23:50:27 +05:30
mergify[bot]
a485e4e802 fix: Expense Account filter in Sales Invoice (#35944)
fix: Expense Account filter in Sales Invoice (#35944)

(cherry picked from commit d54f52474a)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-01 18:13:46 +05:30
mergify[bot]
8de1d8663f fix: project filtering based on company in P&L Report (#35943)
* fix: project filtering in P&L Report

(cherry picked from commit 904ca746a6)

* fix: show projects with no company value set

(cherry picked from commit ce252a0d45)

* fix: make company field mandatory in project doctype

(cherry picked from commit 84d4888f5f)

---------

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
2023-07-01 10:42:33 +05:30
mergify[bot]
6f50ad685e feat: allow the partial return of components against SCO (backport #35935) (#35938)
* fix: don't update SCO status to closed until full return

(cherry picked from commit 2f6d56dd62)

* fix: reduce return qty while calculating transferred qty

(cherry picked from commit 2a60884abc)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-29 22:16:53 +05:30
mergify[bot]
a880bdec5e chore: update translations (#35905)
chore: update translations

chore: update translations
(cherry picked from commit 1d1103f39c)

Co-authored-by: RJPvT <48353029+RJPvT@users.noreply.github.com>
2023-06-29 13:30:29 +05:30
Deepesh Garg
5a9bd3bac6 Merge pull request #35926 from frappe/mergify/bp/version-14-hotfix/pr-35904
feat: add voucher-wise balance report for unequal dr/cr GL entries (backport #35904)
2023-06-29 09:45:22 +05:30
Gursheen Anand
646440fd55 fix: remove debug flag from sql
(cherry picked from commit 6b9f9f9b0e)
2023-06-29 03:38:17 +00:00
Gursheen Anand
684290233f feat: add voucher-wise balance report logic
(cherry picked from commit 5d726ef037)
2023-06-29 03:38:17 +00:00
Frappe PR Bot
29ea5cfc21 chore(release): Bumped to Version 14.28.0
# [14.28.0](https://github.com/frappe/erpnext/compare/v14.27.14...v14.28.0) (2023-06-28)

### Bug Fixes

* asset capitalization ([#35832](https://github.com/frappe/erpnext/issues/35832)) ([fb823b5](fb823b53d1))
* asset movement ([#35918](https://github.com/frappe/erpnext/issues/35918)) ([e16c148](e16c14863b))
* delivery trip driver is only required on submit (backport [#35876](https://github.com/frappe/erpnext/issues/35876)) ([#35893](https://github.com/frappe/erpnext/issues/35893)) ([fc051d1](fc051d143c))
* don't allow to make reposting entry for closing stock balance period ([e68b088](e68b08817e))
* filter parent warehouses not showing (backport [#35897](https://github.com/frappe/erpnext/issues/35897)) ([#35899](https://github.com/frappe/erpnext/issues/35899)) ([87ba196](87ba196473))
* incorrect cost center error in bank reconciliation ([cacb0f6](cacb0f6fde))
* issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry (backport [#35821](https://github.com/frappe/erpnext/issues/35821)) ([#35827](https://github.com/frappe/erpnext/issues/35827)) ([20f2bef](20f2bef55f))
* make credit note and debit note exclusive ([#35781](https://github.com/frappe/erpnext/issues/35781)) ([fafb46e](fafb46eebd))
* multiple Work Orders agaist same production plan ([8ddfc34](8ddfc34c30))
* no permission for accounts settings on payment reconciliation ([200ddbf](200ddbf66c))
* Payment Term must be mandatory if `Allocate Payment based on ..` is checked ([#35798](https://github.com/frappe/erpnext/issues/35798)) ([3dd3935](3dd3935e76))
* POS Closing Entry load all invoices with one request on save ([#35819](https://github.com/frappe/erpnext/issues/35819)) ([8ecca2a](8ecca2a1cf))
* reconcile invoice against credit note. ([#35604](https://github.com/frappe/erpnext/issues/35604)) ([5c388a1](5c388a132f))
* Remove special treatment for P&L Accounts ([#35602](https://github.com/frappe/erpnext/issues/35602)) ([b023448](b0234489ca))
* Set Asset cost center default as PR or PI Item Cost Center while auto creating ([#35844](https://github.com/frappe/erpnext/issues/35844)) ([4a7d75b](4a7d75b5cc))
* show non-depreciable assets in fixed asset register ([#35858](https://github.com/frappe/erpnext/issues/35858)) ([42d0944](42d09448ee))
* TDS amount calculation post LDC breach ([851b887](851b8871b2))
* use correct fieldname for purchase receipt column in item_wise_purchase_register report ([#35828](https://github.com/frappe/erpnext/issues/35828)) ([8f13b48](8f13b484a9))
* **ux:** PO Get Items From Open Material Requests (backport [#35894](https://github.com/frappe/erpnext/issues/35894)) ([#35895](https://github.com/frappe/erpnext/issues/35895)) ([2ef2057](2ef2057f44))

### Features

* Auto set Party in Bank Transaction ([#34675](https://github.com/frappe/erpnext/issues/34675)) ([d53b197](d53b197896))
* Provision to send Accounts Receivable Reports using Process SOA ([#35789](https://github.com/frappe/erpnext/issues/35789)) ([21d560c](21d560cd19)), closes [#35707](https://github.com/frappe/erpnext/issues/35707)

### Performance Improvements

* improve item wise register reports ([#35908](https://github.com/frappe/erpnext/issues/35908)) ([33ee011](33ee01174b))
2023-06-28 16:04:31 +00:00
Deepesh Garg
eb1081664a Merge pull request #35901 from frappe/version-14-hotfix
chore: release v14
2023-06-28 21:33:03 +05:30
Frappe PR Bot
5b27642880 chore(release): Bumped to Version 14.27.14
## [14.27.14](https://github.com/frappe/erpnext/compare/v14.27.13...v14.27.14) (2023-06-28)

### Bug Fixes

* asset movement ([#35918](https://github.com/frappe/erpnext/issues/35918)) ([4f9c28c](4f9c28cd22))
2023-06-28 15:26:36 +00:00
Anand Baburajan
973611a356 Merge pull request #35922 from frappe/mergify/bp/version-14/pr-35918
fix: asset movement (backport #35918)
2023-06-28 20:54:05 +05:30
Anand Baburajan
4f9c28cd22 fix: asset movement (#35918)
fix: asset movement fixes
(cherry picked from commit e16c14863b)
2023-06-28 14:46:23 +00:00
Anand Baburajan
e16c14863b fix: asset movement (#35918)
fix: asset movement fixes
2023-06-28 20:15:40 +05:30
Frappe PR Bot
e1a5a7006f chore(release): Bumped to Version 14.27.13
## [14.27.13](https://github.com/frappe/erpnext/compare/v14.27.12...v14.27.13) (2023-06-28)

### Performance Improvements

* improve item wise register reports ([#35908](https://github.com/frappe/erpnext/issues/35908)) ([189954e](189954eaf1))
2023-06-28 04:56:35 +00:00
Anand Baburajan
93940f30b7 Merge pull request #35913 from frappe/mergify/bp/version-14/pr-35908
perf: improve item wise register reports (backport #35908)
2023-06-28 10:24:42 +05:30
Anand Baburajan
189954eaf1 perf: improve item wise register reports (#35908)
(cherry picked from commit 33ee01174b)
2023-06-28 04:21:39 +00:00
Anand Baburajan
33ee01174b perf: improve item wise register reports (#35908) 2023-06-28 09:49:30 +05:30
mergify[bot]
87ba196473 fix: filter parent warehouses not showing (backport #35897) (#35899)
fix: filter parent warehouses not showing (#35897)

(cherry picked from commit af418d2342)

Co-authored-by: HLD <hanglaoda@hotmail.com>
2023-06-27 14:19:57 +05:30
Deepesh Garg
017729d545 Merge pull request #35890 from frappe/mergify/bp/version-14-hotfix/pr-35886
fix: TDS amount calculation post LDC breach (backport #35886)
2023-06-27 13:11:47 +05:30
mergify[bot]
2ef2057f44 fix(ux): PO Get Items From Open Material Requests (backport #35894) (#35895)
fix(ux): PO Get Items From Open Material Requests

(cherry picked from commit 3a00bf83d6)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-27 12:30:23 +05:30
Deepesh Garg
04fdaaffbd Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-35886 2023-06-27 12:02:30 +05:30
mergify[bot]
fc051d143c fix: delivery trip driver is only required on submit (backport #35876) (#35893)
fix: delivery trip driver is only required on submit (#35876)

This allows drafting trips and stops without yet deciding on the
assignable driver which, in real life, may well be decided on after
preparing and planning the trip.

(cherry picked from commit 742df8a25e)

Co-authored-by: David Arnold <david.arnold@iohk.io>
2023-06-27 11:38:44 +05:30
Deepesh Garg
851b8871b2 fix: TDS amount calculation post LDC breach
(cherry picked from commit 1f9ef6c48f)
2023-06-27 04:10:06 +00:00
ruthra kumar
3ed42e180c Merge pull request #35883 from frappe/mergify/bp/version-14-hotfix/pr-35882
refactor: simplify exchange logic on cr/dr note reconciliation (backport #35882)
2023-06-26 20:29:53 +05:30
ruthra kumar
3ca4f24d21 refactor: simplify exchange logic on cr/dr note reconciliation
(cherry picked from commit af75f6cea7)
2023-06-26 12:04:02 +00:00
mergify[bot]
21d560cd19 feat: Provision to send Accounts Receivable Reports using Process SOA (#35789)
* feat: Provision to send Accounts Receivable Reports using Process Statement of Accounts

Issue #35707

(cherry picked from commit b3d565c91f)

# Conflicts:
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py

* fix: add patch for setting default value of report field

(cherry picked from commit 555c126eb9)

# Conflicts:
#	erpnext/patches.txt

* fix: modify patch

(cherry picked from commit cde82bc0cc)

* chore: update typo in patch

(cherry picked from commit 4de7a4c571)

* chore: Resolve conflicts

---------

Co-authored-by: Gursheen Anand <gursheen@frappe.io>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-25 16:21:18 +05:30
saeedkola
4a7d75b5cc fix: Set Asset cost center default as PR or PI Item Cost Center while auto creating (#35844)
* fix: Set Asset cost center default as PR or PI Item Cost Center while auto creating

* chore: Linting Issues

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-24 19:58:31 +05:30
mergify[bot]
8f13b484a9 fix: use correct fieldname for purchase receipt column in item_wise_purchase_register report (#35828)
fix: use correct fieldname for purchase receipt column in item_wise_purcchase_register report

(cherry picked from commit dcfc86e3af)

Co-authored-by: phot0n <ritwikpuri5678@gmail.com>
2023-06-24 19:57:13 +05:30
mergify[bot]
8ecca2a1cf fix: POS Closing Entry load all invoices with one request on save (#35819)
fix: POS Closing Entry load all invoices with one request on save (#35819)

fix: load all invoices with one request
(cherry picked from commit 1e20016059)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
2023-06-24 18:58:13 +05:30
mergify[bot]
fafb46eebd fix: make credit note and debit note exclusive (#35781)
* fix: make credit note and debit note exclusive (#35781)

(cherry picked from commit 4fbff20954)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json

* chore: resolve conflicts

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-24 12:30:40 +05:30
Marica
d53b197896 feat: Auto set Party in Bank Transaction (#34675)
* feat: Party auto-matcher from Bank Transaction data

- Created Bank Party Mapper
- Created class to auto match by account/iban or party name/description(fuzzy)
- Automatch and set in transaction or create mapper
- `rapidfuzz` introduced

* chore: Single query with or filter to search Party Mapper by name/desc

* feat: Store Party bank details in party records (Customer/Supplier/Employee/Shareholder)

* fix: Don't set description as key in Mapper doc if matched by description

- Description is volatile and will keep changing
- It will lead to multiple Bank Party Mapper docs for the same party that will never be referenced again
- Parts of the descripton keep changing which is why it will never match a mapper record
- If matched by desc, dont create mapper record.

* feat: Manually Update/Correct Party in Bank Transaction

- On updating bank trans.n party after submit, the corresponding mapper doc will be updated too
- The mapper doc in turn will update all linked bank transactions that do not have this updated value
- Added Bank Party Mapper hidden link in Bank Transaction
- Rename field in BPM to `Party Name` as it does not hold description data
- If a BT matches with a BPM record, link that record in the BT

* chore: Perform automatch on submit

- misc: Clearer naming

* chore: Make auto matching party configurable

- Checkbox in Accounts settings "Enable Automatic Party Matching"
- Check before invoking automatching methods
- misc: Remove TODO comments

* fix: Match by both Account No and IBAN & other cleanups

- A BT could have both account and iban, and a Supplier could have only IBAN set
- In this case, matching by either (only account) gives no match
- Match by Account OR IBAN, use `or_filters`
- If matched, set both account no. and IBAN in Bank Party Mapper

- Explain AutoMatchParty
- Add type hints to return values
- Use `set_value` to set values in BT after matching since its an after submit event

* test: Match by Account No, IBAN, Party Name, Desc and match correction

* fix: Remove bank details fields from Shareholder

* fix: Use existing bank fields to match by bank account no/IBAN

- Remove newly added fields in Party doctypes to store bank details
- Use Bank Account's fields to match against account no/iban
- For employee, if Bank Account does not exist, find in Employee doctype against account no/iban

* fix: Tests

* feat: Optional Fuzzy Matching & Skip Matches for multiple similar matches

- Fuzzy matching can be enabled optionally in the settings
- If a query gets multiple matches with the same score, do not set a party as it is an extremely close call
- misc: Add 'cancelled' status to Bank transaction
- Test for skipping matching with extremely close matches

* chore: Remove Bank Party Mapper implementation

- Matching by Acc No/IBAN can easily happen with Bank Accounts. It's not a tedious query
- Historical lookups for  Party Name/Desc match are very tricky. The user could have manually set a match and we would not know. Also this leaves the Bank Party Mapper only useful for Party Name/Desc lookups, which feels excessive.
- We want to reduce the number of places the same data is stored and reduce confusion
- The Party Name/Desc will optionally happen fuzzily, or not at all
- There will be no Mapper lookups

* chore: Remove instances of `bank_party_mapper` and use `new_doc`
2023-06-24 12:30:08 +05:30
mergify[bot]
3dd3935e76 fix: Payment Term must be mandatory if Allocate Payment based on .. is checked (#35798)
fix: Payment Term must be mandatory if `Allocate Payment based on ..` is checked (#35798)

- Front and Back end validation of condition
- Fix test to accomodate fix

(cherry picked from commit 2868baebab)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2023-06-24 12:29:26 +05:30
mergify[bot]
5c388a132f fix: reconcile invoice against credit note. (#35604)
* test: reconcile credit against invoice

(cherry picked from commit f68ab3dfff)

* fix: missing attribute error

(cherry picked from commit 7973951c37)

* fix: reconcile invoice against credit note

(cherry picked from commit 54935438e1)

---------

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
2023-06-24 12:28:43 +05:30
mergify[bot]
b0234489ca fix: Remove special treatment for P&L Accounts (#35602)
fix: Remove special treatment for P&L Accounts

(cherry picked from commit 0bd4de4504)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-23 15:55:51 +05:30
mergify[bot]
21336f1a2c ci: use multiple python version in patch test (#35846)
ci: use multiple python version in patch test

(cherry picked from commit 56e81ada56)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-23 12:39:47 +05:30
rohitwaghchaure
888118e3e1 Merge pull request #35852 from frappe/mergify/bp/version-14-hotfix/pr-35842
fix: multiple Work Orders against same production plan (backport #35842)
2023-06-23 10:34:50 +05:30
Frappe PR Bot
d9008779de chore(release): Bumped to Version 14.27.12
## [14.27.12](https://github.com/frappe/erpnext/compare/v14.27.11...v14.27.12) (2023-06-23)

### Bug Fixes

* show non-depreciable assets in fixed asset register ([#35858](https://github.com/frappe/erpnext/issues/35858)) ([2240aeb](2240aeb307))
2023-06-23 02:55:10 +00:00
Anand Baburajan
c66c4e3aab Merge pull request #35859 from frappe/mergify/bp/version-14/pr-35858
fix: show non-depreciable assets in fixed asset register (backport #35858)
2023-06-23 08:22:43 +05:30
Anand Baburajan
2240aeb307 fix: show non-depreciable assets in fixed asset register (#35858)
fix: show non-depr assets in fixed asset register
(cherry picked from commit 42d09448ee)
2023-06-23 02:52:01 +00:00
Anand Baburajan
42d09448ee fix: show non-depreciable assets in fixed asset register (#35858)
fix: show non-depr assets in fixed asset register
2023-06-23 08:21:32 +05:30
Anand Baburajan
8049582652 Merge pull request #35853 from frappe/mergify/bp/version-14/pr-35851
chore: asset scrap and restore fixes [v14] (backport #35851)
2023-06-22 22:46:05 +05:30
Anand Baburajan
1d5415f39d chore: asset scrap and restore fixes [v14] (#35851)
chore: better err msg on cancelling JE for asset scrap and allow restoring non-depr assets
(cherry picked from commit 69780da099)
2023-06-22 16:53:00 +00:00
Anand Baburajan
69780da099 chore: asset scrap and restore fixes [v14] (#35851)
chore: better err msg on cancelling JE for asset scrap and allow restoring non-depr assets
2023-06-22 22:22:18 +05:30
Rohit Waghchaure
8ddfc34c30 fix: multiple Work Orders agaist same production plan
(cherry picked from commit 80fffbd64b)
2023-06-22 16:42:13 +00:00
Anand Baburajan
fb823b53d1 fix: asset capitalization (#35832)
* fix: misc asset capitalisation fixes

* chore: add location in tests and remove unnecessary code

* chore: more fixes and removals

* chore: show company and fix tests

* chore: make target qty read only on capitalization
2023-06-22 17:14:24 +05:30
ruthra kumar
23dacbe9b2 Merge pull request #35840 from frappe/mergify/bp/version-14/pr-35837
refactor: increase precision for current exc rate in Exchange Rate Revaluation (backport #35837)
2023-06-22 14:34:52 +05:30
ruthra kumar
0138595000 Merge pull request #35838 from frappe/mergify/bp/version-14-hotfix/pr-35837
refactor: increase precision for current exc rate in Exchange Rate Revaluation (backport #35837)
2023-06-22 14:11:58 +05:30
ruthra kumar
f45e8b9c16 refactor: increase precision for current exc rate in ERR
(cherry picked from commit b4db25dd18)
2023-06-22 08:37:20 +00:00
ruthra kumar
e44783a3c5 refactor: increase precision for current exc rate in ERR
(cherry picked from commit b4db25dd18)
2023-06-22 08:10:41 +00:00
rohitwaghchaure
85ad34672c Merge pull request #35824 from frappe/mergify/bp/version-14-hotfix/pr-35611
fix: don't allow to make reposting entry for closing stock balance period (backport #35611)
2023-06-22 12:03:59 +05:30
Frappe PR Bot
71207a7dae chore(release): Bumped to Version 14.27.10
## [14.27.10](https://github.com/frappe/erpnext/compare/v14.27.9...v14.27.10) (2023-06-22)

### Bug Fixes

* incorrect cost center error in bank reconciliation ([4be554d](4be554d8b4))
* no permission for accounts settings on payment reconciliation ([3194e3a](3194e3a020))
2023-06-22 06:26:14 +00:00
ruthra kumar
71e14b3dbb Merge pull request #35835 from frappe/mergify/bp/version-14/pr-35825
fix: multiple fixes in reconciliation tools (backport #35825)
2023-06-22 11:54:51 +05:30
ruthra kumar
41f1c11e85 Merge pull request #35834 from frappe/mergify/bp/version-14-hotfix/pr-35825
fix: multiple fixes in reconciliation tools (backport #35825)
2023-06-22 11:54:35 +05:30
ruthra kumar
4be554d8b4 fix: incorrect cost center error in bank reconciliation
(cherry picked from commit 41b9e92868)
2023-06-22 05:58:00 +00:00
ruthra kumar
3194e3a020 fix: no permission for accounts settings on payment reconciliation
(cherry picked from commit ad758b8d85)
2023-06-22 05:58:00 +00:00
ruthra kumar
cacb0f6fde fix: incorrect cost center error in bank reconciliation
(cherry picked from commit 41b9e92868)
2023-06-22 05:57:51 +00:00
ruthra kumar
200ddbf66c fix: no permission for accounts settings on payment reconciliation
(cherry picked from commit ad758b8d85)
2023-06-22 05:57:51 +00:00
mergify[bot]
20f2bef55f fix: issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry (backport #35821) (#35827)
Fixes issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry (#35821)

* Fixes issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry

* chore: remove unnecessary line break

* chore: formatting

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit 000ebe4479)

Co-authored-by: saeedkola <mohammedsaeedk@gmail.com>
2023-06-21 15:37:21 +05:30
Rohit Waghchaure
e68b08817e fix: don't allow to make reposting entry for closing stock balance period
(cherry picked from commit 96c5c7b1df)
2023-06-21 08:46:30 +00:00
Frappe PR Bot
2561cf2072 chore(release): Bumped to Version 14.27.9
## [14.27.9](https://github.com/frappe/erpnext/compare/v14.27.8...v14.27.9) (2023-06-20)

### Bug Fixes

* `Process Loss Report` (backport [#35712](https://github.com/frappe/erpnext/issues/35712)) ([#35719](https://github.com/frappe/erpnext/issues/35719)) ([55a8be5](55a8be5cad))
* add validation for QI in PR (backport [#35677](https://github.com/frappe/erpnext/issues/35677)) ([#35757](https://github.com/frappe/erpnext/issues/35757)) ([59ab13c](59ab13c34f))
* Allocated amount validation for other party types ([#35741](https://github.com/frappe/erpnext/issues/35741)) ([5541d68](5541d68477))
* cannot start / stop jobs ([53ec2a9](53ec2a9268))
* conflicts ([697fcef](697fcef98b))
* consider field precision while setting sle actual_qty ([#35717](https://github.com/frappe/erpnext/issues/35717)) ([3f62e85](3f62e854e5))
* date and finance book fixes in fixed asset register (backport [#35751](https://github.com/frappe/erpnext/issues/35751)) ([#35799](https://github.com/frappe/erpnext/issues/35799)) ([8b57ecd](8b57ecd8ef))
* don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport [#35714](https://github.com/frappe/erpnext/issues/35714)) ([#35715](https://github.com/frappe/erpnext/issues/35715)) ([77b0c5f](77b0c5f722))
* Duplicate addresses are creating while using the E-commerce ([703e4f4](703e4f4f5d))
* fix get outstanding invoices btn and add get outstanding orders btn (backport [#35776](https://github.com/frappe/erpnext/issues/35776)) ([#35787](https://github.com/frappe/erpnext/issues/35787)) ([42e25d4](42e25d4cdf))
* for zero bal accounts, dr/cr only on currency that has balance ([7da461b](7da461b862))
* incorrect gl entries for standalone debit note with update stock ([3355dc2](3355dc2a41))
* incorrect stock value for purchase returned with rejected qty (backport [#35747](https://github.com/frappe/erpnext/issues/35747)) ([#35752](https://github.com/frappe/erpnext/issues/35752)) ([c11d950](c11d950fc5))
* keyerror while checking the stock balance report ([baf014f](baf014fc61))
* loan interest accrual date ([#35695](https://github.com/frappe/erpnext/issues/35695)) ([070df97](070df97663))
* **patch:** enable existing serial no in stock settings ([#35762](https://github.com/frappe/erpnext/issues/35762)) ([3c790c1](3c790c12f2))
* stock error for service item ([2bbea63](2bbea63de1))
* test case ([4af0a9b](4af0a9b192))
* unsupported operand type(s) for //: 'float' and 'NoneType' for POS Barcode search ([#35710](https://github.com/frappe/erpnext/issues/35710)) ([58a6bbc](58a6bbcf6d))
* update `Stock Reconciliation` diff qty while reposting ([bdb5cc8](bdb5cc8ad4))
* **ux:** set route options for new `Batch` ([b261242](b261242792))
* validation of job card in stock entry ([ce2bf5f](ce2bf5fb1c))
* work order serial no issue ([50a8907](50a8907e8c))

### Performance Improvements

* Duplicate queries for UOM (backport [#35744](https://github.com/frappe/erpnext/issues/35744)) ([#35745](https://github.com/frappe/erpnext/issues/35745)) ([632b67c](632b67cbc8))
* duplicate queries while checking prevdoc (backport [#35746](https://github.com/frappe/erpnext/issues/35746)) ([#35749](https://github.com/frappe/erpnext/issues/35749)) ([a0fc8e2](a0fc8e252c))
* Ignore cancelled pick lists while fetching picked items (backport [#35737](https://github.com/frappe/erpnext/issues/35737)) ([#35740](https://github.com/frappe/erpnext/issues/35740)) ([01ac54d](01ac54d65d))
* index `purpose` in `Stock Entry` (backport [#35782](https://github.com/frappe/erpnext/issues/35782)) ([#35783](https://github.com/frappe/erpnext/issues/35783)) ([3bac2a8](3bac2a88bd))
* Index pick list field in stock entry and DN (backport [#35738](https://github.com/frappe/erpnext/issues/35738)) ([#35742](https://github.com/frappe/erpnext/issues/35742)) ([b875de6](b875de6fb7))
* Index sales_order_item in Pick list item (backport [#35735](https://github.com/frappe/erpnext/issues/35735)) ([#35736](https://github.com/frappe/erpnext/issues/35736)) ([0e57f4d](0e57f4dd3c))
2023-06-20 16:06:29 +00:00
Deepesh Garg
abbbfe6240 Merge pull request #35807 from frappe/version-14-hotfix
chore: release v14
2023-06-20 21:34:30 +05:30
rohitwaghchaure
4e1b2c6f8d Merge branch 'version-14' into version-14-hotfix 2023-06-20 19:41:56 +05:30
rohitwaghchaure
c669dba691 Merge pull request #35813 from frappe/mergify/bp/version-14-hotfix/pr-35810
fix: stock error for service item (backport #35810)
2023-06-20 17:49:32 +05:30
rohitwaghchaure
8c183741bd Merge pull request #35812 from frappe/mergify/bp/version-14-hotfix/pr-35809
fix: key error while checking the stock balance report (backport #35809)
2023-06-20 17:30:51 +05:30
Rohit Waghchaure
2bbea63de1 fix: stock error for service item
(cherry picked from commit 32965f1af9)
2023-06-20 11:51:27 +00:00
Rohit Waghchaure
baf014fc61 fix: keyerror while checking the stock balance report
(cherry picked from commit a627d2a38c)
2023-06-20 11:32:06 +00:00
rohitwaghchaure
95e3dc9b81 Merge pull request #35805 from rohitwaghchaure/fixed-address-issue-from-e-cart
fix: Duplicate addresses are creating while using the E-commerce
2023-06-20 15:43:58 +05:30
Rohit Waghchaure
703e4f4f5d fix: Duplicate addresses are creating while using the E-commerce 2023-06-20 14:50:40 +05:30
ruthra kumar
bf2ebce6f4 Merge pull request #35803 from frappe/mergify/bp/version-14-hotfix/pr-35794
fix: Exchange Rate Revaluation should only post on the currency that has balance in a 'zero' balance account (backport #35794)
2023-06-20 14:49:06 +05:30
ruthra kumar
65d24ea9ea refactor: higher precision for rounding loss and allow '0'
(cherry picked from commit 6694175a51)
2023-06-20 14:02:45 +05:30
ruthra kumar
6c9c3426f8 refactor: allow '0' rounding allowance
(cherry picked from commit 4567474418)
2023-06-20 14:02:40 +05:30
ruthra kumar
146d41ee81 refactor: allow higher precision for new exchange rate
(cherry picked from commit 9d04af9ecc)
2023-06-20 08:29:53 +00:00
ruthra kumar
7da461b862 fix: for zero bal accounts, dr/cr only on currency that has balance
(cherry picked from commit 1b33afd699)
2023-06-20 08:29:53 +00:00
mergify[bot]
8b57ecd8ef fix: date and finance book fixes in fixed asset register (backport #35751) (#35799)
* fix: date and finance book fixes in fixed asset register (#35751)

* fix: handle finance books properly and show all assets by default in fixed asset register

* chore: rename value to depr amount

* chore: get asset value for correct fb properly

* chore: rename include_default_book_entries to include_default_book_assets

(cherry picked from commit 0d12588583)

# Conflicts:
#	erpnext/assets/report/fixed_asset_register/fixed_asset_register.py

* chore: resolving conflicts and renaming entries to assets

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-20 12:52:18 +05:30
rohitwaghchaure
7d010adcd4 Merge pull request #35786 from rohitwaghchaure/fixed-work-order-serial-no-issue
fix: work order serial no issue
2023-06-20 12:13:17 +05:30
Rohit Waghchaure
4af0a9b192 fix: test case 2023-06-20 11:42:02 +05:30
mergify[bot]
3bac2a88bd perf: index purpose in Stock Entry (backport #35782) (#35783)
perf: index `purpose` in `Stock Entry`

(cherry picked from commit 4f941ac5c0)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-19 22:22:50 +05:30
Rohit Waghchaure
50a8907e8c fix: work order serial no issue 2023-06-19 21:29:34 +05:30
mergify[bot]
42e25d4cdf fix: fix get outstanding invoices btn and add get outstanding orders btn (backport #35776) (#35787)
fix: fix get outstanding invoices btn and add get outstanding orders btn (#35776)

* fix: fix get outstanding invoices btn and add get outstanding orders btn

* chore: remove unnecessary arg

(cherry picked from commit c1da3ddbbf)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-19 20:17:56 +05:30
Frappe PR Bot
4c1befab8f chore(release): Bumped to Version 14.27.8
## [14.27.8](https://github.com/frappe/erpnext/compare/v14.27.7...v14.27.8) (2023-06-19)

### Bug Fixes

* Allocated amount validation for other party types ([#35741](https://github.com/frappe/erpnext/issues/35741)) ([3bf7115](3bf7115cdc))
2023-06-19 11:38:53 +00:00
Deepesh Garg
d928a5c3aa Merge pull request #35774 from frappe/mergify/bp/version-14/pr-35770
fix: Allocated amount validation for other party types (#35741)
2023-06-19 17:07:06 +05:30
mergify[bot]
3bf7115cdc fix: Allocated amount validation for other party types (#35741)
fix: Allocated amount validation for other party types (#35741)

* fix: Allocated amount validation for other party types

* chore: Validation for return allocations

* chore: minor typo

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 9d27a25e5f)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 5541d68477)
2023-06-19 07:22:55 +00:00
mergify[bot]
5541d68477 fix: Allocated amount validation for other party types (#35741)
fix: Allocated amount validation for other party types (#35741)

* fix: Allocated amount validation for other party types

* chore: Validation for return allocations

* chore: minor typo

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 9d27a25e5f)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-19 12:52:06 +05:30
mergify[bot]
070df97663 fix: loan interest accrual date (#35695)
fix: loan interest accrual date (#35695)

fix: loan interest accrual date

---------

Co-authored-by: Abhinav Raut <abhinav.raut@zerodha.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 2a24423ad2)

Co-authored-by: Abhinav Raut <abhinavrautcs@gmail.com>
2023-06-19 09:14:21 +05:30
Vishal Dhayagude
58a6bbcf6d fix: unsupported operand type(s) for //: 'float' and 'NoneType' for POS Barcode search (#35710) 2023-06-18 23:00:01 +05:30
Sagar Sharma
6650373c9f Merge pull request #35743 from s-aga-r/FIX-35493-V14
fix(ux): set route options for new `Batch`
2023-06-18 15:12:12 +05:30
Sagar Sharma
3c790c12f2 fix(patch): enable existing serial no in stock settings (#35762) 2023-06-17 23:21:30 +05:30
mergify[bot]
b875de6fb7 perf: Index pick list field in stock entry and DN (backport #35738) (#35742)
* perf: Index pick list field in stock entry and DN (#35738)

We check if pick list is created against them but there's no index so we
end up reading entire table.

```
+------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+
| id   | select_type | table            | type  | possible_keys | key      | key_len | ref  | rows   | r_rows    | filtered | r_filtered | Extra       |
+------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+
|    1 | SIMPLE      | tabDelivery Note | index | NULL          | modified | 9       | NULL | 207015 | 348940.00 |   100.00 |       0.00 | Using where |
+------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+
```

After

```
+------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+------------------------------->
| id   | select_type | table            | type | possible_keys   | key             | key_len | ref   | rows | r_rows | filtered | r_filtered | Extra                         >
+------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+------------------------------->
|    1 | SIMPLE      | tabDelivery Note | ref  | pick_list_index | pick_list_index | 563     | const | 1    | 0.00   |   100.00 |     100.00 | Using index condition; Using w>
+------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+------------------------------->
```

(cherry picked from commit 433489a9e6)

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

* chore: conflict

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-17 21:25:30 +05:30
rohitwaghchaure
fc103ab4ce Merge pull request #35759 from frappe/mergify/bp/version-14-hotfix/pr-35756
fix: validation of job card in stock entry (backport #35756)
2023-06-17 14:02:11 +05:30
Rohit Waghchaure
ce2bf5fb1c fix: validation of job card in stock entry
(cherry picked from commit df8c3f0888)
2023-06-17 08:03:08 +00:00
mergify[bot]
59ab13c34f fix: add validation for QI in PR (backport #35677) (#35757)
fix: add validation for QI in PR

(cherry picked from commit 2c1ab569a7)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-17 12:52:18 +05:30
rohitwaghchaure
9792099cea Merge pull request #35753 from akurungadam/jobcard-fix
fix: cannot start / stop Job Card
2023-06-17 12:04:49 +05:30
mergify[bot]
c11d950fc5 fix: incorrect stock value for purchase returned with rejected qty (backport #35747) (#35752)
fix: incorrect stock value for purchase returned with rejected qty

(cherry picked from commit 28dd758aa3)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2023-06-17 11:33:17 +05:30
Anoop Kurungadam
53ec2a9268 fix: cannot start / stop jobs 2023-06-17 11:21:03 +05:30
mergify[bot]
a0fc8e252c perf: duplicate queries while checking prevdoc (backport #35746) (#35749)
perf: duplicate queries while checking prevdoc (#35746)

These values can't change durning DB transaction AFAIK

(cherry picked from commit 6086d1a99d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 18:45:23 +05:30
mergify[bot]
632b67cbc8 perf: Duplicate queries for UOM (backport #35744) (#35745)
perf: Duplicate queries for UOM (#35744)

This query repeats for every item, UOMs rarely if ever change

(cherry picked from commit 29da1db516)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 16:40:40 +05:30
s-aga-r
b261242792 fix(ux): set route options for new Batch 2023-06-16 16:28:49 +05:30
mergify[bot]
01ac54d65d perf: Ignore cancelled pick lists while fetching picked items (backport #35737) (#35740)
perf: Ignore cancelled pick lists while fetching picked items (#35737)

(cherry picked from commit 81f916b7d3)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 15:29:20 +05:30
mergify[bot]
0e57f4dd3c perf: Index sales_order_item in Pick list item (backport #35735) (#35736)
* perf: Index sales_order_item in Pick list item (#35735)

- `get_picked_items_qty` does full table scan
- because it also locks, it does full table lock.

(cherry picked from commit 07d748c290)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 15:22:44 +05:30
rohitwaghchaure
7339e447bb Merge pull request #35718 from frappe/mergify/bp/version-14-hotfix/pr-35711
fix: incorrect gl entries for standalone debit note (backport #35711)
2023-06-16 13:46:16 +05:30
mergify[bot]
55a8be5cad fix: Process Loss Report (backport #35712) (#35719)
fix: `Process Loss Report`

(cherry picked from commit d176d86e2c)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-15 19:42:53 +05:30
Sagar Sharma
3f62e854e5 fix: consider field precision while setting sle actual_qty (#35717) 2023-06-15 19:26:38 +05:30
rohitwaghchaure
697fcef98b fix: conflicts 2023-06-15 19:14:41 +05:30
Rohit Waghchaure
e2c4e16d72 test: added test case
(cherry picked from commit f9f662679f)
2023-06-15 13:43:20 +00:00
Rohit Waghchaure
3355dc2a41 fix: incorrect gl entries for standalone debit note with update stock
(cherry picked from commit 6e198188ff)

# Conflicts:
#	erpnext/controllers/buying_controller.py
2023-06-15 13:43:20 +00:00
mergify[bot]
77b0c5f722 fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport #35714) (#35715)
fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (#35714)

fix: on asset scrap, don't add gl entry for acc. depr. if no acc. depr.
(cherry picked from commit bb39a2cac7)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-15 17:37:12 +05:30
Sagar Sharma
9d1fac19e5 Merge pull request #35702 from frappe/mergify/bp/version-14-hotfix/pr-35700
fix: update `Stock Reconciliation` diff qty while reposting (backport #35700)
2023-06-15 11:44:14 +05:30
s-aga-r
bdb5cc8ad4 fix: update Stock Reconciliation diff qty while reposting
(cherry picked from commit 6a1b0a2fab)
2023-06-15 06:13:10 +00:00
134 changed files with 4488 additions and 1660 deletions

View File

@@ -43,9 +43,11 @@ jobs:
fi
- name: Setup Python
uses: "gabrielfalcao/pyenv-action@v9"
uses: "actions/setup-python@v4"
with:
versions: 3.10:latest, 3.7:latest
python-version: |
3.7
3.10
- name: Setup Node
uses: actions/setup-node@v2
@@ -92,7 +94,6 @@ jobs:
- name: Install
run: |
pip install frappe-bench
pyenv global $(pyenv versions | grep '3.10')
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
@@ -107,7 +108,6 @@ jobs:
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
pyenv global $(pyenv versions | grep '3.7')
for version in $(seq 12 13)
do
echo "Updating to v$version"
@@ -120,7 +120,7 @@ jobs:
git -C "apps/erpnext" checkout -q -f $branch_name
rm -rf ~/frappe-bench/env
bench setup env
bench setup env --python python3.7
bench pip install -e ./apps/payments
bench pip install -e ./apps/erpnext
@@ -132,9 +132,8 @@ jobs:
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
pyenv global $(pyenv versions | grep '3.10')
rm -rf ~/frappe-bench/env
bench -v setup env
bench -v setup env --python python3.10
bench pip install -e ./apps/payments
bench pip install -e ./apps/erpnext

View File

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

View File

@@ -136,7 +136,7 @@ def convert_deferred_revenue_to_income(
send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None):
def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
if not posting_date:
posting_date = add_days(today(), -1)
@@ -146,39 +146,42 @@ def get_booking_dates(doc, item, posting_date=None):
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
)
prev_gl_entry = frappe.db.sql(
"""
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
order by posting_date desc limit 1
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
if not prev_posting_date:
prev_gl_entry = frappe.db.sql(
"""
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
order by posting_date desc limit 1
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
prev_gl_via_je = frappe.db.sql(
"""
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s
and c.reference_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
prev_gl_via_je = frappe.db.sql(
"""
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s
and c.reference_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
if prev_gl_via_je:
if (not prev_gl_entry) or (
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
):
prev_gl_entry = prev_gl_via_je
if prev_gl_via_je:
if (not prev_gl_entry) or (
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
):
prev_gl_entry = prev_gl_via_je
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
start_date = item.service_start_date
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
start_date = item.service_start_date
start_date = getdate(add_days(prev_posting_date, 1))
end_date = get_last_day(start_date)
if end_date >= item.service_end_date:
end_date = item.service_end_date
@@ -341,9 +344,15 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
def _book_deferred_revenue_or_expense(
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
item,
via_journal_entry,
submit_journal_entry,
book_deferred_entries_based_on,
prev_posting_date=None,
):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
start_date, end_date, last_gl_entry = get_booking_dates(
doc, item, posting_date=posting_date, prev_posting_date=prev_posting_date
)
if not (start_date and end_date):
return
@@ -377,9 +386,12 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if not amount:
return
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
@@ -388,7 +400,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
debit_account,
amount,
base_amount,
end_date,
gl_posting_date,
project,
account_currency,
item.cost_center,
@@ -404,7 +416,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
against,
amount,
base_amount,
end_date,
gl_posting_date,
project,
account_currency,
item.cost_center,
@@ -418,7 +430,11 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
item,
via_journal_entry,
submit_journal_entry,
book_deferred_entries_based_on,
prev_posting_date,
)
via_journal_entry = cint(

View File

@@ -2,75 +2,13 @@
"country_code": "nl",
"name": "Netherlands - Grootboekschema",
"tree": {
"FABRIKAGEREKENINGEN": {
"is_group": 1,
"root_type": "Expense"
},
"FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": {
"Bank": {
"RABO Bank": {
"account_type": "Bank"
},
"account_type": "Bank"
},
"KORTLOPENDE SCHULDEN": {
"Af te dragen Btw-verlegd": {
"account_type": "Tax"
},
"Afdracht loonheffing": {},
"Btw af te dragen hoog": {
"account_type": "Tax"
},
"Btw af te dragen laag": {
"account_type": "Tax"
},
"Btw af te dragen overig": {
"account_type": "Tax"
},
"Btw oude jaren": {
"account_type": "Tax"
},
"Btw te vorderen hoog": {
"account_type": "Tax"
},
"Btw te vorderen laag": {
"account_type": "Tax"
},
"Btw te vorderen overig": {
"account_type": "Tax"
},
"Btw-afdracht": {
"account_type": "Tax"
},
"Crediteuren": {
"account_type": "Payable"
},
"Dividend": {},
"Dividendbelasting": {},
"Energiekosten 1": {},
"Investeringsaftrek": {},
"Loonheffing": {},
"Overige te betalen posten": {},
"Pensioenpremies 1": {},
"Premie WIR": {},
"Rekening-courant inkoopvereniging": {},
"Rente": {},
"Sociale lasten 1": {},
"Stock Recieved niet gefactureerd": {
"account_type": "Stock Received But Not Billed"
},
"Tanti\u00e8mes 1": {},
"Te vorderen Btw-verlegd": {
"account_type": "Tax"
},
"Telefoon/telefax 1": {},
"Termijnen onderh. werk": {},
"Vakantiedagen": {},
"Vakantiegeld 1": {},
"Vakantiezegels": {},
"Vennootschapsbelasting": {},
"Vooruit ontvangen bedr.": {}
},
},
"LIQUIDE MIDDELEN": {
"ABN-AMRO bank": {},
"Bankbetaalkaarten": {},
@@ -91,6 +29,110 @@
},
"account_type": "Cash"
},
"TUSSENREKENINGEN": {
"Betaalwijze cadeaubonnen": {
"account_type": "Cash"
},
"Betaalwijze chipknip": {
"account_type": "Cash"
},
"Betaalwijze contant": {
"account_type": "Cash"
},
"Betaalwijze pin": {
"account_type": "Cash"
},
"Inkopen Nederland hoog": {
"account_type": "Cash"
},
"Inkopen Nederland laag": {
"account_type": "Cash"
},
"Inkopen Nederland onbelast": {
"account_type": "Cash"
},
"Inkopen Nederland overig": {
"account_type": "Cash"
},
"Inkopen Nederland verlegd": {
"account_type": "Cash"
},
"Inkopen binnen EU hoog": {
"account_type": "Cash"
},
"Inkopen binnen EU laag": {
"account_type": "Cash"
},
"Inkopen binnen EU overig": {
"account_type": "Cash"
},
"Inkopen buiten EU hoog": {
"account_type": "Cash"
},
"Inkopen buiten EU laag": {
"account_type": "Cash"
},
"Inkopen buiten EU overig": {
"account_type": "Cash"
},
"Kassa 1": {
"account_type": "Cash"
},
"Kassa 2": {
"account_type": "Cash"
},
"Netto lonen": {
"account_type": "Cash"
},
"Tegenrekening Inkopen": {
"account_type": "Cash"
},
"Tussenrek. autom. betalingen": {
"account_type": "Cash"
},
"Tussenrek. autom. loonbetalingen": {
"account_type": "Cash"
},
"Tussenrek. cadeaubonbetalingen": {
"account_type": "Cash"
},
"Tussenrekening balans": {
"account_type": "Cash"
},
"Tussenrekening chipknip": {
"account_type": "Cash"
},
"Tussenrekening correcties": {
"account_type": "Cash"
},
"Tussenrekening pin": {
"account_type": "Cash"
},
"Vraagposten": {
"account_type": "Cash"
},
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
"Emballage": {},
"Gereed product 1": {},
"Gereed product 2": {},
"Goederen 1": {},
"Goederen 2": {},
"Goederen in consignatie": {},
"Goederen onderweg": {},
"Grondstoffen 1": {},
"Grondstoffen 2": {},
"Halffabrikaten 1": {},
"Halffabrikaten 2": {},
"Hulpstoffen 1": {},
"Hulpstoffen 2": {},
"Kantoorbenodigdheden": {},
"Onderhanden werk": {},
"Verpakkingsmateriaal": {},
"Zegels": {},
"root_type": "Asset"
},
"root_type": "Asset"
},
"VORDERINGEN": {
"Debiteuren": {
"account_type": "Receivable"
@@ -104,278 +146,299 @@
"Voorziening dubieuze debiteuren": {}
},
"root_type": "Asset"
},
"INDIRECTE KOSTEN": {
},
"KORTLOPENDE SCHULDEN": {
"Af te dragen Btw-verlegd": {
"account_type": "Tax"
},
"Afdracht loonheffing": {},
"Btw af te dragen hoog": {
"account_type": "Tax"
},
"Btw af te dragen laag": {
"account_type": "Tax"
},
"Btw af te dragen overig": {
"account_type": "Tax"
},
"Btw oude jaren": {
"account_type": "Tax"
},
"Btw te vorderen hoog": {
"account_type": "Tax"
},
"Btw te vorderen laag": {
"account_type": "Tax"
},
"Btw te vorderen overig": {
"account_type": "Tax"
},
"Btw-afdracht": {
"account_type": "Tax"
},
"Crediteuren": {
"account_type": "Payable"
},
"Dividend": {},
"Dividendbelasting": {},
"Energiekosten 1": {},
"Investeringsaftrek": {},
"Loonheffing": {},
"Overige te betalen posten": {},
"Pensioenpremies 1": {},
"Premie WIR": {},
"Rekening-courant inkoopvereniging": {},
"Rente": {},
"Sociale lasten 1": {},
"Stock Recieved niet gefactureerd": {
"account_type": "Stock Received But Not Billed"
},
"Tanti\u00e8mes 1": {},
"Te vorderen Btw-verlegd": {
"account_type": "Tax"
},
"Telefoon/telefax 1": {},
"Termijnen onderh. werk": {},
"Vakantiedagen": {},
"Vakantiegeld 1": {},
"Vakantiezegels": {},
"Vennootschapsbelasting": {},
"Vooruit ontvangen bedr.": {},
"is_group": 1,
"root_type": "Liability"
},
"FABRIKAGEREKENINGEN": {
"is_group": 1,
"root_type": "Expense"
},
"KOSTENREKENINGEN": {
"AFSCHRIJVINGEN": {
"Aanhangwagens": {},
"Aankoopkosten": {},
"Aanloopkosten": {},
"Auteursrechten": {},
"Bedrijfsgebouwen": {},
"Bedrijfsinventaris": {
"root_type": "Expense",
"INDIRECTE KOSTEN": {
"is_group": 1,
"root_type": "Expense"
},
"KOSTENREKENINGEN": {
"AFSCHRIJVINGEN": {
"Aanhangwagens": {},
"Aankoopkosten": {},
"Aanloopkosten": {},
"Auteursrechten": {},
"Bedrijfsgebouwen": {},
"Bedrijfsinventaris": {
"account_type": "Depreciation"
},
"Drankvergunningen": {},
"Fabrieksinventaris": {
"account_type": "Depreciation"
},
"Gebouwen": {},
"Gereedschappen": {},
"Goodwill": {},
"Grondverbetering": {},
"Heftrucks": {},
"Kantine-inventaris": {},
"Kantoorinventaris": {
"account_type": "Depreciation"
},
"Kantoormachines": {},
"Licenties": {},
"Machines 1": {},
"Magazijninventaris": {},
"Octrooien": {},
"Ontwikkelingskosten": {},
"Pachtersinvestering": {},
"Parkeerplaats": {},
"Personenauto's": {
"account_type": "Depreciation"
},
"Rijwielen en bromfietsen": {},
"Tonnagevergunningen": {},
"Verbouwingen": {},
"Vergunningen": {},
"Voorraadverschillen": {},
"Vrachtauto's": {},
"Winkels": {},
"Woon-winkelhuis": {},
"account_type": "Depreciation"
},
"Drankvergunningen": {},
"Fabrieksinventaris": {
"account_type": "Depreciation"
"ALGEMENE KOSTEN": {
"Accountantskosten": {},
"Advieskosten": {},
"Assuranties 1": {},
"Bankkosten": {},
"Juridische kosten": {},
"Overige algemene kosten": {},
"Toev. Ass. eigen risico": {}
},
"Gebouwen": {},
"Gereedschappen": {},
"Goodwill": {},
"Grondverbetering": {},
"Heftrucks": {},
"Kantine-inventaris": {},
"Kantoorinventaris": {
"account_type": "Depreciation"
"BEDRIJFSKOSTEN": {
"Assuranties 2": {},
"Energie (krachtstroom)": {},
"Gereedschappen 1": {},
"Hulpmaterialen 1": {},
"Huur inventaris": {},
"Huur machines": {},
"Leasing invent.operational": {},
"Leasing mach. operational": {},
"Onderhoud inventaris": {},
"Onderhoud machines": {},
"Ophalen/vervoer afval": {},
"Overige bedrijfskosten": {}
},
"Kantoormachines": {},
"Licenties": {},
"Machines 1": {},
"Magazijninventaris": {},
"Octrooien": {},
"Ontwikkelingskosten": {},
"Pachtersinvestering": {},
"Parkeerplaats": {},
"Personenauto's": {
"account_type": "Depreciation"
"FINANCIERINGSKOSTEN 1": {
"Overige rentebaten": {},
"Overige rentelasten": {},
"Rente bankkrediet": {},
"Rente huurkoopcontracten": {},
"Rente hypotheek": {},
"Rente leasecontracten": {},
"Rente lening o/g": {},
"Rente lening u/g": {}
},
"Rijwielen en bromfietsen": {},
"Tonnagevergunningen": {},
"Verbouwingen": {},
"Vergunningen": {},
"Voorraadverschillen": {},
"Vrachtauto's": {},
"Winkels": {},
"Woon-winkelhuis": {},
"account_type": "Depreciation"
},
"ALGEMENE KOSTEN": {
"Accountantskosten": {},
"Advieskosten": {},
"Assuranties 1": {},
"Bankkosten": {},
"Juridische kosten": {},
"Overige algemene kosten": {},
"Toev. Ass. eigen risico": {}
},
"BEDRIJFSKOSTEN": {
"Assuranties 2": {},
"Energie (krachtstroom)": {},
"Gereedschappen 1": {},
"Hulpmaterialen 1": {},
"Huur inventaris": {},
"Huur machines": {},
"Leasing invent.operational": {},
"Leasing mach. operational": {},
"Onderhoud inventaris": {},
"Onderhoud machines": {},
"Ophalen/vervoer afval": {},
"Overige bedrijfskosten": {}
},
"FINANCIERINGSKOSTEN 1": {
"Overige rentebaten": {},
"Overige rentelasten": {},
"Rente bankkrediet": {},
"Rente huurkoopcontracten": {},
"Rente hypotheek": {},
"Rente leasecontracten": {},
"Rente lening o/g": {},
"Rente lening u/g": {}
},
"HUISVESTINGSKOSTEN": {
"Assurantie onroerend goed": {},
"Belastingen onr. Goed": {},
"Energiekosten": {},
"Groot onderhoud onr. Goed": {},
"Huur": {},
"Huurwaarde woongedeelte": {},
"Onderhoud onroerend goed": {},
"Ontvangen huren": {},
"Overige huisvestingskosten": {},
"Pacht": {},
"Schoonmaakkosten": {},
"Toevoeging egalisatieres. Groot onderhoud": {}
},
"KANTOORKOSTEN": {
"Administratiekosten": {},
"Contributies/abonnementen": {},
"Huur kantoorapparatuur": {},
"Internetaansluiting": {},
"Kantoorbenodigdh./drukw.": {},
"Onderhoud kantoorinvent.": {},
"Overige kantoorkosten": {},
"Porti": {},
"Telefoon/telefax": {}
},
"OVERIGE BATEN EN LASTEN": {
"Betaalde schadevergoed.": {},
"Boekverlies vaste activa": {},
"Boekwinst van vaste activa": {},
"K.O. regeling OB": {},
"Kasverschillen": {},
"Kosten loonbelasting": {},
"Kosten omzetbelasting": {},
"Nadelige koersverschillen": {},
"Naheffing bedrijfsver.": {},
"Ontvangen schadevergoed.": {},
"Overige baten": {},
"Overige lasten": {},
"Voordelige koersverschil.": {}
},
"PERSONEELSKOSTEN": {
"Autokostenvergoeding": {},
"Bedrijfskleding": {},
"Belastingvrije uitkeringen": {},
"Bijzondere beloningen": {},
"Congressen, seminars en symposia": {},
"Gereedschapsgeld": {},
"Geschenken personeel": {},
"Gratificaties": {},
"Inhouding pensioenpremies": {},
"Inhouding sociale lasten": {},
"Kantinekosten": {},
"Lonen en salarissen": {},
"Loonwerk": {},
"Managementvergoedingen": {},
"Opleidingskosten": {},
"Oprenting stamrechtverpl.": {},
"Overhevelingstoeslag": {},
"Overige kostenverg.": {},
"Overige personeelskosten": {},
"Overige uitkeringen": {},
"Pensioenpremies": {},
"Provisie 1": {},
"Reiskosten": {},
"Rijwielvergoeding": {},
"Sociale lasten": {},
"Tanti\u00e8mes": {},
"Thuiswerkers": {},
"Toev. Backservice pens.verpl.": {},
"Toevoeging pensioenverpl.": {},
"Uitkering ziekengeld": {},
"Uitzendkrachten": {},
"Vakantiebonnen": {},
"Vakantiegeld": {},
"Vergoeding studiekosten": {},
"Wervingskosten personeel": {}
},
"VERKOOPKOSTEN": {
"Advertenties": {},
"Afschrijving dubieuze deb.": {},
"Beurskosten": {},
"Etalagekosten": {},
"Exportkosten": {},
"Kascorrecties": {},
"Overige verkoopkosten": {},
"Provisie": {},
"Reclame": {},
"Reis en verblijfkosten": {},
"Relatiegeschenken": {},
"Representatiekosten": {},
"Uitgaande vrachten": {},
"Veilingkosten": {},
"Verpakkingsmateriaal 1": {},
"Websitekosten": {}
},
"VERVOERSKOSTEN": {
"Assuranties auto's": {},
"Brandstoffen": {},
"Leasing auto's": {},
"Onderhoud personenauto's": {},
"Onderhoud vrachtauto's": {},
"Overige vervoerskosten": {},
"Priv\u00e9-gebruik auto's": {},
"Wegenbelasting": {}
},
"root_type": "Expense"
},
"TUSSENREKENINGEN": {
"Betaalwijze cadeaubonnen": {
"account_type": "Cash"
},
"Betaalwijze chipknip": {
"account_type": "Cash"
},
"Betaalwijze contant": {
"account_type": "Cash"
},
"Betaalwijze pin": {
"account_type": "Cash"
},
"Inkopen Nederland hoog": {
"account_type": "Cash"
},
"Inkopen Nederland laag": {
"account_type": "Cash"
},
"Inkopen Nederland onbelast": {
"account_type": "Cash"
},
"Inkopen Nederland overig": {
"account_type": "Cash"
},
"Inkopen Nederland verlegd": {
"account_type": "Cash"
},
"Inkopen binnen EU hoog": {
"account_type": "Cash"
},
"Inkopen binnen EU laag": {
"account_type": "Cash"
},
"Inkopen binnen EU overig": {
"account_type": "Cash"
},
"Inkopen buiten EU hoog": {
"account_type": "Cash"
},
"Inkopen buiten EU laag": {
"account_type": "Cash"
},
"Inkopen buiten EU overig": {
"account_type": "Cash"
},
"Kassa 1": {
"account_type": "Cash"
},
"Kassa 2": {
"account_type": "Cash"
},
"Netto lonen": {
"account_type": "Cash"
},
"Tegenrekening Inkopen": {
"account_type": "Cash"
},
"Tussenrek. autom. betalingen": {
"account_type": "Cash"
},
"Tussenrek. autom. loonbetalingen": {
"account_type": "Cash"
},
"Tussenrek. cadeaubonbetalingen": {
"account_type": "Cash"
},
"Tussenrekening balans": {
"account_type": "Cash"
},
"Tussenrekening chipknip": {
"account_type": "Cash"
},
"Tussenrekening correcties": {
"account_type": "Cash"
},
"Tussenrekening pin": {
"account_type": "Cash"
},
"Vraagposten": {
"account_type": "Cash"
},
"root_type": "Asset"
"HUISVESTINGSKOSTEN": {
"Assurantie onroerend goed": {},
"Belastingen onr. Goed": {},
"Energiekosten": {},
"Groot onderhoud onr. Goed": {},
"Huur": {},
"Huurwaarde woongedeelte": {},
"Onderhoud onroerend goed": {},
"Ontvangen huren": {},
"Overige huisvestingskosten": {},
"Pacht": {},
"Schoonmaakkosten": {},
"Toevoeging egalisatieres. Groot onderhoud": {}
},
"KANTOORKOSTEN": {
"Administratiekosten": {},
"Contributies/abonnementen": {},
"Huur kantoorapparatuur": {},
"Internetaansluiting": {},
"Kantoorbenodigdh./drukw.": {},
"Onderhoud kantoorinvent.": {},
"Overige kantoorkosten": {},
"Porti": {},
"Telefoon/telefax": {}
},
"OVERIGE BATEN EN LASTEN": {
"Betaalde schadevergoed.": {},
"Boekverlies vaste activa": {},
"Boekwinst van vaste activa": {},
"K.O. regeling OB": {},
"Kasverschillen": {},
"Kosten loonbelasting": {},
"Kosten omzetbelasting": {},
"Nadelige koersverschillen": {},
"Naheffing bedrijfsver.": {},
"Ontvangen schadevergoed.": {},
"Overige baten": {},
"Overige lasten": {},
"Voordelige koersverschil.": {}
},
"PERSONEELSKOSTEN": {
"Autokostenvergoeding": {},
"Bedrijfskleding": {},
"Belastingvrije uitkeringen": {},
"Bijzondere beloningen": {},
"Congressen, seminars en symposia": {},
"Gereedschapsgeld": {},
"Geschenken personeel": {},
"Gratificaties": {},
"Inhouding pensioenpremies": {},
"Inhouding sociale lasten": {},
"Kantinekosten": {},
"Lonen en salarissen": {},
"Loonwerk": {},
"Managementvergoedingen": {},
"Opleidingskosten": {},
"Oprenting stamrechtverpl.": {},
"Overhevelingstoeslag": {},
"Overige kostenverg.": {},
"Overige personeelskosten": {},
"Overige uitkeringen": {},
"Pensioenpremies": {},
"Provisie 1": {},
"Reiskosten": {},
"Rijwielvergoeding": {},
"Sociale lasten": {},
"Tanti\u00e8mes": {},
"Thuiswerkers": {},
"Toev. Backservice pens.verpl.": {},
"Toevoeging pensioenverpl.": {},
"Uitkering ziekengeld": {},
"Uitzendkrachten": {},
"Vakantiebonnen": {},
"Vakantiegeld": {},
"Vergoeding studiekosten": {},
"Wervingskosten personeel": {}
},
"VERKOOPKOSTEN": {
"Advertenties": {},
"Afschrijving dubieuze deb.": {},
"Beurskosten": {},
"Etalagekosten": {},
"Exportkosten": {},
"Kascorrecties": {},
"Overige verkoopkosten": {},
"Provisie": {},
"Reclame": {},
"Reis en verblijfkosten": {},
"Relatiegeschenken": {},
"Representatiekosten": {},
"Uitgaande vrachten": {},
"Veilingkosten": {},
"Verpakkingsmateriaal 1": {},
"Websitekosten": {}
},
"VERVOERSKOSTEN": {
"Assuranties auto's": {},
"Brandstoffen": {},
"Leasing auto's": {},
"Onderhoud personenauto's": {},
"Onderhoud vrachtauto's": {},
"Overige vervoerskosten": {},
"Priv\u00e9-gebruik auto's": {},
"Wegenbelasting": {}
},
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
"Betalingskort. crediteuren": {},
"Garantiekosten": {},
"Hulpmaterialen": {},
"Inkomende vrachten": {
"account_type": "Expenses Included In Valuation"
},
"Inkoop import buiten EU hoog": {},
"Inkoop import buiten EU laag": {},
"Inkoop import buiten EU overig": {},
"Inkoopbonussen": {},
"Inkoopkosten": {},
"Inkoopprovisie": {},
"Inkopen BTW verlegd": {},
"Inkopen EU hoog tarief": {},
"Inkopen EU laag tarief": {},
"Inkopen EU overig": {},
"Inkopen hoog": {},
"Inkopen laag": {},
"Inkopen nul": {},
"Inkopen overig": {},
"Invoerkosten": {},
"Kosten inkoopvereniging": {},
"Kostprijs omzet grondstoffen": {
"account_type": "Cost of Goods Sold"
},
"Kostprijs omzet handelsgoederen": {},
"Onttrekking uitgev.garantie": {},
"Priv\u00e9-gebruik goederen": {},
"Stock aanpassing": {
"account_type": "Stock Adjustment"
},
"Tegenrekening inkoop": {},
"Toev. Voorz. incour. grondst.": {},
"Toevoeging garantieverpl.": {},
"Toevoeging voorz. incour. handelsgoed.": {},
"Uitbesteed werk": {},
"Voorz. Incourourant grondst.": {},
"Voorz.incour. handelsgoed.": {},
"root_type": "Expense"
},
"root_type": "Expense"
}
},
"VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": {
"EIGEN VERMOGEN": {
@@ -602,7 +665,7 @@
"account_type": "Equity"
}
},
"root_type": "Asset"
"root_type": "Equity"
},
"VERKOOPRESULTATEN": {
"Diensten fabric. 0% niet-EU": {},
@@ -627,67 +690,6 @@
"Verleende Kredietbep. fabricage": {},
"Verleende Kredietbep. handel": {},
"root_type": "Income"
},
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
"Betalingskort. crediteuren": {},
"Garantiekosten": {},
"Hulpmaterialen": {},
"Inkomende vrachten": {
"account_type": "Expenses Included In Valuation"
},
"Inkoop import buiten EU hoog": {},
"Inkoop import buiten EU laag": {},
"Inkoop import buiten EU overig": {},
"Inkoopbonussen": {},
"Inkoopkosten": {},
"Inkoopprovisie": {},
"Inkopen BTW verlegd": {},
"Inkopen EU hoog tarief": {},
"Inkopen EU laag tarief": {},
"Inkopen EU overig": {},
"Inkopen hoog": {},
"Inkopen laag": {},
"Inkopen nul": {},
"Inkopen overig": {},
"Invoerkosten": {},
"Kosten inkoopvereniging": {},
"Kostprijs omzet grondstoffen": {
"account_type": "Cost of Goods Sold"
},
"Kostprijs omzet handelsgoederen": {},
"Onttrekking uitgev.garantie": {},
"Priv\u00e9-gebruik goederen": {},
"Stock aanpassing": {
"account_type": "Stock Adjustment"
},
"Tegenrekening inkoop": {},
"Toev. Voorz. incour. grondst.": {},
"Toevoeging garantieverpl.": {},
"Toevoeging voorz. incour. handelsgoed.": {},
"Uitbesteed werk": {},
"Voorz. Incourourant grondst.": {},
"Voorz.incour. handelsgoed.": {},
"root_type": "Expense"
},
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
"Emballage": {},
"Gereed product 1": {},
"Gereed product 2": {},
"Goederen 1": {},
"Goederen 2": {},
"Goederen in consignatie": {},
"Goederen onderweg": {},
"Grondstoffen 1": {},
"Grondstoffen 2": {},
"Halffabrikaten 1": {},
"Halffabrikaten 2": {},
"Hulpstoffen 1": {},
"Hulpstoffen 2": {},
"Kantoorbenodigdheden": {},
"Onderhanden werk": {},
"Verpakkingsmateriaal": {},
"Zegels": {},
"root_type": "Asset"
}
}
}

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("Account Closing Balance", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,164 @@
{
"actions": [],
"creation": "2023-02-21 15:20:59.586811",
"default_view": "List",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"closing_date",
"account",
"cost_center",
"debit",
"credit",
"account_currency",
"debit_in_account_currency",
"credit_in_account_currency",
"project",
"company",
"finance_book",
"period_closing_voucher",
"is_period_closing_voucher_entry"
],
"fields": [
{
"fieldname": "closing_date",
"fieldtype": "Date",
"in_filter": 1,
"in_list_view": 1,
"label": "Closing Date",
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
"search_index": 1
},
{
"fieldname": "account",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Account",
"oldfieldname": "account",
"oldfieldtype": "Link",
"options": "Account",
"search_index": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Cost Center",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Cost Center"
},
{
"fieldname": "debit",
"fieldtype": "Currency",
"label": "Debit Amount",
"oldfieldname": "debit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
},
{
"fieldname": "credit",
"fieldtype": "Currency",
"label": "Credit Amount",
"oldfieldname": "credit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency"
},
{
"fieldname": "debit_in_account_currency",
"fieldtype": "Currency",
"label": "Debit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "credit_in_account_currency",
"fieldtype": "Currency",
"label": "Credit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"search_index": 1
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "period_closing_voucher",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Period Closing Voucher",
"options": "Period Closing Voucher",
"search_index": 1
},
{
"default": "0",
"fieldname": "is_period_closing_voucher_entry",
"fieldtype": "Check",
"label": "Is Period Closing Voucher Entry"
}
],
"icon": "fa fa-list",
"in_create": 1,
"links": [],
"modified": "2023-03-06 08:56:36.393237",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Closing Balance",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User"
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager"
},
{
"export": 1,
"read": 1,
"report": 1,
"role": "Auditor"
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,127 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.utils import cint, cstr
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
class AccountClosingBalance(Document):
pass
def make_closing_entries(closing_entries, voucher_name):
accounting_dimensions = get_accounting_dimensions()
company = closing_entries[0].get("company")
closing_date = closing_entries[0].get("closing_date")
previous_closing_entries = get_previous_closing_entries(
company, closing_date, accounting_dimensions
)
combined_entries = closing_entries + previous_closing_entries
merged_entries = aggregate_with_last_account_closing_balance(
combined_entries, accounting_dimensions
)
for key, value in merged_entries.items():
cle = frappe.new_doc("Account Closing Balance")
cle.update(value)
cle.update(value["dimensions"])
cle.update(
{
"period_closing_voucher": voucher_name,
"closing_date": closing_date,
}
)
cle.submit()
def aggregate_with_last_account_closing_balance(entries, accounting_dimensions):
merged_entries = {}
for entry in entries:
key, key_values = generate_key(entry, accounting_dimensions)
merged_entries.setdefault(
key,
{
"debit": 0,
"credit": 0,
"debit_in_account_currency": 0,
"credit_in_account_currency": 0,
},
)
merged_entries[key]["dimensions"] = key_values
merged_entries[key]["debit"] += entry.get("debit")
merged_entries[key]["credit"] += entry.get("credit")
merged_entries[key]["debit_in_account_currency"] += entry.get("debit_in_account_currency")
merged_entries[key]["credit_in_account_currency"] += entry.get("credit_in_account_currency")
return merged_entries
def generate_key(entry, accounting_dimensions):
key = [
cstr(entry.get("account")),
cstr(entry.get("account_currency")),
cstr(entry.get("cost_center")),
cstr(entry.get("project")),
cstr(entry.get("finance_book")),
cint(entry.get("is_period_closing_voucher_entry")),
]
key_values = {
"company": cstr(entry.get("company")),
"account": cstr(entry.get("account")),
"account_currency": cstr(entry.get("account_currency")),
"cost_center": cstr(entry.get("cost_center")),
"project": cstr(entry.get("project")),
"finance_book": cstr(entry.get("finance_book")),
"is_period_closing_voucher_entry": cint(entry.get("is_period_closing_voucher_entry")),
}
for dimension in accounting_dimensions:
key.append(cstr(entry.get(dimension)))
key_values[dimension] = cstr(entry.get(dimension))
return tuple(key), key_values
def get_previous_closing_entries(company, closing_date, accounting_dimensions):
entries = []
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
fields=["name"],
order_by="posting_date desc",
limit=1,
)
if last_period_closing_voucher:
account_closing_balance = frappe.qb.DocType("Account Closing Balance")
query = frappe.qb.from_(account_closing_balance).select(
account_closing_balance.company,
account_closing_balance.account,
account_closing_balance.account_currency,
account_closing_balance.debit,
account_closing_balance.credit,
account_closing_balance.debit_in_account_currency,
account_closing_balance.credit_in_account_currency,
account_closing_balance.cost_center,
account_closing_balance.project,
account_closing_balance.finance_book,
account_closing_balance.is_period_closing_voucher_entry,
)
for dimension in accounting_dimensions:
query = query.select(account_closing_balance[dimension])
query = query.where(
account_closing_balance.period_closing_voucher == last_period_closing_voucher[0].name
)
entries = query.run(as_dict=1)
return entries

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 TestAccountClosingBalance(FrappeTestCase):
pass

View File

@@ -62,7 +62,10 @@
"acc_frozen_upto",
"column_break_25",
"frozen_accounts_modifier",
"report_settings_sb"
"report_settings_sb",
"banking_tab",
"enable_party_matching",
"enable_fuzzy_matching"
],
"fields": [
{
@@ -385,6 +388,26 @@
"fieldname": "show_taxes_as_table_in_print",
"fieldtype": "Check",
"label": "Show Taxes as Table in Print"
},
{
"fieldname": "banking_tab",
"fieldtype": "Tab Break",
"label": "Banking"
},
{
"default": "0",
"description": "Auto match and set the Party in Bank Transactions",
"fieldname": "enable_party_matching",
"fieldtype": "Check",
"label": "Enable Automatic Party Matching"
},
{
"default": "0",
"depends_on": "enable_party_matching",
"description": "Approximately match the description/party name against parties",
"fieldname": "enable_fuzzy_matching",
"fieldtype": "Check",
"label": "Enable Fuzzy Matching"
}
],
"icon": "icon-cog",
@@ -392,7 +415,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-06-13 18:47:46.430291",
"modified": "2023-06-15 18:47:46.430291",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -19,7 +19,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
onload: function (frm) {
// Set default filter dates
today = frappe.datetime.get_today()
let today = frappe.datetime.get_today()
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account');

View File

@@ -10,6 +10,7 @@ from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
@@ -140,6 +141,9 @@ def create_journal_entry_bts(
second_account
)
)
company = frappe.get_value("Account", company_account, "company")
accounts = []
# Multi Currency?
accounts.append(
@@ -149,6 +153,7 @@ def create_journal_entry_bts(
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
}
)
@@ -158,11 +163,10 @@ def create_journal_entry_bts(
"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 = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
"voucher_type": entry_type,
"company": company,

View File

@@ -0,0 +1,178 @@
from typing import Tuple, Union
import frappe
from frappe.utils import flt
from rapidfuzz import fuzz, process
class AutoMatchParty:
"""
Matches by Account/IBAN and then by Party Name/Description sequentially.
Returns when a result is obtained.
Result (if present) is of the form: (Party Type, Party,)
"""
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> Union[Tuple, None]:
result = None
result = AutoMatchbyAccountIBAN(
bank_party_account_number=self.bank_party_account_number,
bank_party_iban=self.bank_party_iban,
deposit=self.deposit,
).match()
fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching")
if not result and fuzzy_matching_enabled:
result = AutoMatchbyPartyNameDescription(
bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit
).match()
return result
class AutoMatchbyAccountIBAN:
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
def get(self, key):
return self.__dict__.get(key, None)
def match(self):
if not (self.bank_party_account_number or self.bank_party_iban):
return None
result = self.match_account_in_party()
return result
def match_account_in_party(self) -> Union[Tuple, None]:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None
parties = get_parties_in_order(self.deposit)
or_filters = self.get_or_filters()
for party in parties:
party_result = frappe.db.get_all(
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
)
if party == "Employee" and not party_result:
# Search in Bank Accounts first for Employee, and then Employee record
if "bank_account_no" in or_filters:
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
party_result = frappe.db.get_all(
party, or_filters=or_filters, pluck="name", limit_page_length=1
)
if party_result:
result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
or_filters = {}
if self.bank_party_account_number:
or_filters["bank_account_no"] = self.bank_party_account_number
if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban
return or_filters
class AutoMatchbyPartyNameDescription:
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> Union[Tuple, None]:
# fuzzy search by customer/supplier & employee
if not (self.bank_party_name or self.description):
return None
result = self.match_party_name_desc_in_party()
return result
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
"""Fuzzy search party name and/or description against parties in the system"""
result = None
parties = get_parties_in_order(self.deposit)
for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
for field in ["bank_party_name", "description"]:
if not self.get(field):
continue
result, skip = self.fuzzy_search_and_return_result(party, names, field)
if result or skip:
break
if result or skip:
# Skip If: It was hard to distinguish between close matches and so match is None
# OR if the right match was found
break
return result
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
skip = False
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
party_name, skip = self.process_fuzzy_result(result)
if not party_name:
return None, skip
return (
party,
party_name,
), skip
def process_fuzzy_result(self, result: Union[list, None]):
"""
If there are multiple valid close matches return None as result may be faulty.
Return the result only if one accurate match stands out.
Returns: Result, Skip (whether or not to discontinue matching)
"""
PARTY, SCORE, CUTOFF = 0, 1, 80
if not result or not len(result):
return None, False
first_result = result[0]
if len(result) == 1:
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1]
if first_result[SCORE] > CUTOFF:
# If multiple matches with the same score, return None but discontinue matching
# Matches were found but were too close to distinguish between
if first_result[SCORE] == second_result[SCORE]:
return None, True
return first_result[PARTY], True
else:
return None, False
def get_parties_in_order(deposit: float) -> list:
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
if flt(deposit) > 0:
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
return parties

View File

@@ -33,7 +33,11 @@
"unallocated_amount",
"party_section",
"party_type",
"party"
"party",
"column_break_3czf",
"bank_party_name",
"bank_party_account_number",
"bank_party_iban"
],
"fields": [
{
@@ -63,7 +67,7 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
"options": "\nPending\nSettled\nUnreconciled\nReconciled"
"options": "\nPending\nSettled\nUnreconciled\nReconciled\nCancelled"
},
{
"fieldname": "bank_account",
@@ -202,11 +206,30 @@
"fieldtype": "Data",
"label": "Transaction Type",
"length": 50
},
{
"fieldname": "column_break_3czf",
"fieldtype": "Column Break"
},
{
"fieldname": "bank_party_name",
"fieldtype": "Data",
"label": "Party Name/Account Holder (Bank Statement)"
},
{
"fieldname": "bank_party_iban",
"fieldtype": "Data",
"label": "Party IBAN (Bank Statement)"
},
{
"fieldname": "bank_party_account_number",
"fieldtype": "Data",
"label": "Party Account No. (Bank Statement)"
}
],
"is_submittable": 1,
"links": [],
"modified": "2022-05-29 18:36:50.475964",
"modified": "2023-06-06 13:58:12.821411",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
@@ -260,4 +283,4 @@
"states": [],
"title_field": "bank_account",
"track_changes": 1
}
}

View File

@@ -15,6 +15,9 @@ class BankTransaction(StatusUpdater):
self.clear_linked_payment_entries()
self.set_status()
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
self.auto_set_party()
_saving_flag = False
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
@@ -146,6 +149,26 @@ class BankTransaction(StatusUpdater):
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
)
def auto_set_party(self):
from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty
if self.party_type and self.party:
return
result = AutoMatchParty(
bank_party_account_number=self.bank_party_account_number,
bank_party_iban=self.bank_party_iban,
bank_party_name=self.bank_party_name,
description=self.description,
deposit=self.deposit,
).match()
if result:
party_type, party = result
frappe.db.set_value(
"Bank Transaction", self.name, field={"party_type": party_type, "party": party}
)
@frappe.whitelist()
def get_doctypes_for_bank_reconciliation():

View File

@@ -0,0 +1,151 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
class TestAutoMatchParty(FrappeTestCase):
@classmethod
def setUpClass(cls):
create_bank_account()
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1)
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1)
return super().setUpClass()
@classmethod
def tearDownClass(cls):
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0)
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0)
def test_match_by_account_number(self):
create_supplier_for_match(account_no="000000003716541159")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b",
account_no="000000003716541159",
iban="DE02000000003716541159",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "John Doe & Co.")
def test_match_by_iban(self):
create_supplier_for_match(iban="DE02000000003716541159")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="c5455a224602afaa51592a9d9250600d",
account_no="000000003716541159",
iban="DE02000000003716541159",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "John Doe & Co.")
def test_match_by_party_name(self):
create_supplier_for_match(supplier_name="Jackson Ella W.")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="1f6f661f347ff7b1ea588665f473adb1",
party_name="Ella Jackson",
iban="DE04000000003716545346",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "Jackson Ella W.")
def test_match_by_description(self):
create_supplier_for_match(supplier_name="Microsoft")
doc = create_bank_transaction(
description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536",
withdrawal=1200,
transaction_id="8df880a2d09c3bed3fea358ca5168c5a",
party_name="",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "Microsoft")
def test_skip_match_if_multiple_close_results(self):
create_supplier_for_match(supplier_name="Adithya Medical & General Stores")
create_supplier_for_match(supplier_name="Adithya Medical And General Stores")
doc = create_bank_transaction(
description="Paracetamol Consignment, SINV-0009",
withdrawal=24.85,
transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9",
party_name="Adithya Medical & General",
)
# Mapping is skipped as both Supplier names have the same match score
self.assertEqual(doc.party_type, None)
self.assertEqual(doc.party, None)
def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None):
if frappe.db.exists("Supplier", {"supplier_name": supplier_name}):
# Update related Bank Account details
if not (iban or account_no):
return
frappe.db.set_value(
dt="Bank Account",
dn={"party": supplier_name},
field={"iban": iban, "bank_account_no": account_no},
)
return
# Create Supplier and Bank Account for the same
supplier = frappe.new_doc("Supplier")
supplier.supplier_name = supplier_name
supplier.supplier_group = "Services"
supplier.supplier_type = "Company"
supplier.insert()
if not frappe.db.exists("Bank", "TestBank"):
bank = frappe.new_doc("Bank")
bank.bank_name = "TestBank"
bank.insert(ignore_if_duplicate=True)
if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"):
bank_account = frappe.new_doc("Bank Account")
bank_account.account_name = supplier.name
bank_account.bank = "TestBank"
bank_account.iban = iban
bank_account.bank_account_no = account_no
bank_account.party_type = "Supplier"
bank_account.party = supplier.name
bank_account.insert()
def create_bank_transaction(
description=None,
withdrawal=0,
deposit=0,
transaction_id=None,
party_name=None,
account_no=None,
iban=None,
):
doc = frappe.new_doc("Bank Transaction")
doc.update(
{
"doctype": "Bank Transaction",
"description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": nowdate(),
"withdrawal": withdrawal,
"deposit": deposit,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"transaction_id": transaction_id,
"bank_party_name": party_name,
"bank_party_account_number": account_no,
"bank_party_iban": iban,
}
)
doc.insert()
doc.submit()
doc.reload()
return doc

View File

@@ -37,7 +37,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
validate_rounding_loss: function(frm) {
let allowance = frm.doc.rounding_loss_allowance;
if (!(allowance > 0 && allowance < 1)) {
if (!(allowance >= 0 && allowance < 1)) {
frappe.throw(__("Rounding Loss Allowance should be between 0 and 1"));
}
},

View File

@@ -100,15 +100,16 @@
},
{
"default": "0.05",
"description": "Only values between 0 and 1 are allowed. \nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account",
"description": "Only values between [0,1) are allowed. Like {0.00, 0.04, 0.09, ...}\nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account",
"fieldname": "rounding_loss_allowance",
"fieldtype": "Float",
"label": "Rounding Loss Allowance"
"label": "Rounding Loss Allowance",
"precision": "9"
}
],
"is_submittable": 1,
"links": [],
"modified": "2023-06-12 21:02:09.818208",
"modified": "2023-06-20 07:29:06.972434",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation",

View File

@@ -22,7 +22,7 @@ class ExchangeRateRevaluation(Document):
self.set_total_gain_loss()
def validate_rounding_loss_allowance(self):
if not (self.rounding_loss_allowance > 0 and self.rounding_loss_allowance < 1):
if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1):
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
def set_total_gain_loss(self):
@@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document):
return True
def fetch_and_calculate_accounts_data(self):
accounts = self.get_accounts_data()
if accounts:
for acc in accounts:
self.append("accounts", acc)
@frappe.whitelist()
def get_accounts_data(self):
self.validate_mandatory()
@@ -252,8 +258,8 @@ class ExchangeRateRevaluation(Document):
new_balance_in_base_currency = 0
new_balance_in_account_currency = 0
current_exchange_rate = calculate_exchange_rate_using_last_gle(
company, d.account, d.party_type, d.party
current_exchange_rate = (
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
)
gain_loss = new_balance_in_account_currency - (
@@ -373,6 +379,24 @@ class ExchangeRateRevaluation(Document):
"credit": 0,
}
)
journal_entry_accounts.append(journal_account)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit": 0,
"credit": 0,
"debit_in_account_currency": abs(d.gain_loss) if d.gain_loss < 0 else 0,
"credit_in_account_currency": abs(d.gain_loss) if d.gain_loss > 0 else 0,
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
elif d.get("balance_in_base_currency") and not d.get("new_balance_in_base_currency"):
# Base currency has balance
dr_or_cr = "credit" if d.get("balance_in_base_currency") > 0 else "debit"
@@ -388,22 +412,22 @@ class ExchangeRateRevaluation(Document):
}
)
journal_entry_accounts.append(journal_account)
journal_entry_accounts.append(journal_account)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0,
"credit": abs(self.gain_loss_booked) if self.gain_loss_booked > 0 else 0,
"debit_in_account_currency": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0,
"credit_in_account_currency": self.gain_loss_booked if self.gain_loss_booked > 0 else 0,
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit": abs(d.gain_loss) if d.gain_loss < 0 else 0,
"credit": abs(d.gain_loss) if d.gain_loss > 0 else 0,
"debit_in_account_currency": 0,
"credit_in_account_currency": 0,
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
journal_entry.set("accounts", journal_entry_accounts)
journal_entry.set_total_debit_credit()

View File

@@ -73,6 +73,7 @@
"fieldname": "current_exchange_rate",
"fieldtype": "Float",
"label": "Current Exchange Rate",
"precision": "9",
"read_only": 1
},
{
@@ -92,6 +93,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "New Exchange Rate",
"precision": "9",
"reqd": 1
},
{
@@ -147,7 +149,7 @@
],
"istable": 1,
"links": [],
"modified": "2022-12-29 19:38:52.915295",
"modified": "2023-06-22 12:39:56.446722",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation Account",

View File

@@ -326,12 +326,10 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
for d in self.get("accounts"):
if (
d.reference_type == "Asset"
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and d.debit
@@ -358,6 +356,15 @@ class JournalEntry(AccountsController):
else:
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
asset.set_status()
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
journal_entry_for_scrap = frappe.db.get_value(
"Asset", d.reference_name, "journal_entry_for_scrap"
)
if journal_entry_for_scrap == self.name:
frappe.throw(
_("Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset.")
)
def unlink_inter_company_jv(self):
if (

View File

@@ -613,7 +613,7 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_unallocated_amount(frm);
},
get_outstanding_invoice: function(frm) {
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today();
const fields = [
{fieldtype:"Section Break", label: __("Posting Date")},
@@ -643,12 +643,29 @@ frappe.ui.form.on('Payment Entry', {
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
];
let btn_text = "";
if (get_outstanding_invoices) {
btn_text = "Get Outstanding Invoices";
}
else if (get_orders_to_be_billed) {
btn_text = "Get Outstanding Orders";
}
frappe.prompt(fields, function(filters){
frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters);
frm.doc.cost_center = filters.cost_center;
frm.events.get_outstanding_documents(frm, filters);
}, __("Filters"), __("Get Outstanding Documents"));
frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed);
}, __("Filters"), __(btn_text));
},
get_outstanding_invoices: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, true, false);
},
get_outstanding_orders: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, false, true);
},
validate_filters_data: function(frm, filters) {
@@ -674,7 +691,7 @@ frappe.ui.form.on('Payment Entry', {
}
},
get_outstanding_documents: function(frm, filters) {
get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) {
frm.clear_table("references");
if(!frm.doc.party) {
@@ -698,6 +715,13 @@ frappe.ui.form.on('Payment Entry', {
args[key] = filters[key];
}
if (get_outstanding_invoices) {
args["get_outstanding_invoices"] = true;
}
else if (get_orders_to_be_billed) {
args["get_orders_to_be_billed"] = true;
}
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
return frappe.call({

View File

@@ -48,7 +48,8 @@
"base_received_amount",
"base_received_amount_after_tax",
"section_break_14",
"get_outstanding_invoice",
"get_outstanding_invoices",
"get_outstanding_orders",
"references",
"section_break_34",
"total_allocated_amount",
@@ -355,12 +356,6 @@
"fieldtype": "Section Break",
"label": "Reference"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoice",
"fieldtype": "Button",
"label": "Get Outstanding Invoice"
},
{
"fieldname": "references",
"fieldtype": "Table",
@@ -728,12 +723,24 @@
"fieldname": "section_break_60",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoices",
"fieldtype": "Button",
"label": "Get Outstanding Invoices"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_orders",
"fieldtype": "Button",
"label": "Get Outstanding Orders"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-02-14 04:52:30.478523",
"modified": "2023-06-19 11:38:04.387219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -8,6 +8,7 @@ from functools import reduce
import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils.data import comma_and, fmt_money
import erpnext
from erpnext.accounts.doctype.bank_account.bank_account import (
@@ -151,6 +152,19 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Internal Transfer":
return
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
@@ -159,6 +173,8 @@ class PaymentEntry(AccountsController):
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
}
)
@@ -168,37 +184,33 @@ class PaymentEntry(AccountsController):
d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
for d in self.get("references").copy():
for d in self.get("references"):
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
# 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)
_("{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 d.outstanding_amount != latest.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")):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount."
).format(d.reference_doctype, d.reference_name)
"{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."
).format(_(d.reference_doctype), d.reference_name)
)
d.outstanding_amount = latest.outstanding_amount
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0:
if flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0:
if flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self):
for reference in self.references:
@@ -290,7 +302,7 @@ class PaymentEntry(AccountsController):
def validate_party_details(self):
if self.party:
if not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
@@ -339,7 +351,9 @@ class PaymentEntry(AccountsController):
continue
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
_("Reference Doctype must be one of {0}").format(comma_or(valid_reference_doctypes))
_("Reference Doctype must be one of {0}").format(
comma_or((_(d) for d in valid_reference_doctypes))
)
)
elif d.reference_name:
@@ -352,7 +366,7 @@ class PaymentEntry(AccountsController):
if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw(
_("{0} {1} is not associated with {2} {3}").format(
d.reference_doctype, d.reference_name, self.party_type, self.party
_(d.reference_doctype), d.reference_name, _(self.party_type), self.party
)
)
else:
@@ -371,18 +385,18 @@ class PaymentEntry(AccountsController):
if ref_party_account != self.party_account:
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
d.reference_doctype, d.reference_name, ref_party_account, self.party_account
_(d.reference_doctype), d.reference_name, ref_party_account, self.party_account
)
)
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw(
_("{0} {1} is on hold").format(d.reference_doctype, d.reference_name),
title=_("Invalid Invoice"),
_("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
title=_("Invalid Purchase Invoice"),
)
if ref_doc.docstatus != 1:
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
frappe.throw(_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name))
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
@@ -408,14 +422,13 @@ class PaymentEntry(AccountsController):
if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items():
for reference_doctype, references in no_oustanding_refs.items():
frappe.msgprint(
_(
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
"References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
).format(
_(k),
frappe.bold(", ".join(d.reference_name for d in v)),
frappe.bold(_("negative outstanding amount")),
frappe.bold(comma_and((d.reference_name for d in references))),
_(reference_doctype),
)
+ "<br><br>"
+ _("If this is undesirable please cancel the corresponding Payment Entry."),
@@ -450,7 +463,7 @@ class PaymentEntry(AccountsController):
if not valid:
frappe.throw(
_("Against Journal Entry {0} does not have any unmatched {1} entry").format(
d.reference_name, dr_or_cr
d.reference_name, _(dr_or_cr)
)
)
@@ -517,7 +530,7 @@ class PaymentEntry(AccountsController):
if allocated_amount > outstanding:
frappe.throw(
_("Row #{0}: Cannot allocate more than {1} against payment term {2}").format(
idx, outstanding, key[0]
idx, fmt_money(outstanding), key[0]
)
)
@@ -821,7 +834,7 @@ class PaymentEntry(AccountsController):
elif paid_amount - additional_charges > total_negative_outstanding:
frappe.throw(
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
total_negative_outstanding
fmt_money(total_negative_outstanding)
),
InvalidPaymentEntry,
)
@@ -1297,6 +1310,9 @@ def get_outstanding_reference_documents(args):
if args.get("party_type") == "Member":
return
if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"):
args["get_outstanding_invoices"] = True
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
accounting_dimensions_filter = []
@@ -1347,62 +1363,75 @@ def get_outstanding_reference_documents(args):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company"))
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
args.get("party"),
args.get("company"),
party_account_currency,
company_currency,
filters=args,
)
# Get negative outstanding sales /purchase invoices
outstanding_invoices = []
negative_outstanding_invoices = []
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get negative outstanding sales /purchase invoices
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
party_account_currency,
company_currency,
condition=condition,
)
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
if args.get("get_orders_to_be_billed"):
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
args.get("party"),
args.get("company"),
party_account_currency,
company_currency,
condition=condition,
filters=args,
)
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data:
if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"):
ref_document_type = "invoices or orders"
elif args.get("get_outstanding_invoices"):
ref_document_type = "invoices"
elif args.get("get_orders_to_be_billed"):
ref_document_type = "orders"
frappe.msgprint(
_(
"No outstanding invoices found for the {0} {1} which qualify the filters you have specified."
).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))
"No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
).format(
_(ref_document_type), _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
)
)
return data
@@ -1476,60 +1505,59 @@ def get_orders_to_be_billed(
cost_center=None,
filters=None,
):
voucher_type = None
if party_type == "Customer":
voucher_type = "Sales Order"
elif party_type == "Supplier":
voucher_type = "Purchase Order"
elif party_type == "Employee":
voucher_type = None
if not voucher_type:
return []
# Add cost center condition
if voucher_type:
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
orders = []
if voucher_type:
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql(
"""
select
name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
where
{party_type} = %s
and docstatus = 1
and company = %s
and ifnull(status, "") != "Closed"
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(
**{
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition,
}
),
(party, company),
as_dict=True,
)
orders = frappe.db.sql(
"""
select
name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
where
{party_type} = %s
and docstatus = 1
and company = %s
and ifnull(status, "") != "Closed"
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(
**{
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition,
}
),
(party, company),
as_dict=True,
)
order_list = []
for d in orders:
@@ -1562,6 +1590,8 @@ def get_negative_outstanding_invoices(
cost_center=None,
condition=None,
):
if party_type not in ["Customer", "Supplier"]:
return []
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
@@ -1610,7 +1640,7 @@ def get_negative_outstanding_invoices(
def get_party_details(company, party_type, party, date, cost_center=None):
bank_account = ""
if not frappe.db.exists(party_type, party):
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
frappe.throw(_("{0} {1} does not exist").format(_(party_type), party))
party_account = get_party_account(party_type, party, company)
@@ -1711,7 +1741,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
if not total_amount:
if party_account_currency == company_currency:
# for handling cases that don't have multi-currency (base field)
total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
total_amount = ref_doc.get("base_grand_total") or ref_doc.get("grand_total")
exchange_rate = 1
else:
total_amount = ref_doc.get("grand_total")
@@ -1759,7 +1789,7 @@ def get_payment_entry(
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance
):
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
if not party_type:
party_type = set_party_type(dt)

View File

@@ -11,6 +11,7 @@ from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import (
InvalidPaymentEntry,
get_payment_entry,
get_reference_details,
)
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
make_purchase_invoice,
@@ -1037,6 +1038,29 @@ class TestPaymentEntry(FrappeTestCase):
self.assertRaises(frappe.ValidationError, pe_draft.submit)
def test_details_update_on_reference_table(self):
so = make_sales_order(
customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True
)
so.conversion_rate = 50
so.submit()
pe = get_payment_entry("Sales Order", so.name)
pe.references.clear()
pe.paid_from = "Debtors - _TC"
pe.paid_from_account_currency = "INR"
pe.source_exchange_rate = 50
pe.save()
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
expected_response = {
"total_amount": 5000.0,
"outstanding_amount": 5000.0,
"exchange_rate": 1.0,
"due_date": None,
"bill_no": None,
}
self.assertDictEqual(ref_details, expected_response)
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -85,25 +85,29 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
// check for any running reconciliation jobs
if (this.frm.doc.receivable_payable_account) {
frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments").then((enabled) => {
if(enabled) {
this.frm.call({
'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running",
"args": {
for_filter: {
company: this.frm.doc.company,
party_type: this.frm.doc.party_type,
party: this.frm.doc.party,
receivable_payable_account: this.frm.doc.receivable_payable_account
this.frm.call({
doc: this.frm.doc,
method: 'is_auto_process_enabled',
callback: (r) => {
if (r.message) {
this.frm.call({
'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running",
"args": {
for_filter: {
company: this.frm.doc.company,
party_type: this.frm.doc.party_type,
party: this.frm.doc.party,
receivable_payable_account: this.frm.doc.receivable_payable_account
}
}
}
}).then(r => {
if (r.message) {
let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true);
let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]);
this.frm.dashboard.add_comment(msg, "yellow");
}
});
}).then(r => {
if (r.message) {
let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true);
let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]);
this.frm.dashboard.add_comment(msg, "yellow");
}
});
}
}
});
}

View File

@@ -252,6 +252,10 @@ class PaymentReconciliation(Document):
return difference_amount
@frappe.whitelist()
def is_auto_process_enabled(self):
return frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments")
@frappe.whitelist()
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
@@ -343,7 +347,10 @@ class PaymentReconciliation(Document):
payment_details = self.get_payment_details(row, dr_or_cr)
reconciled_entry.append(payment_details)
if payment_details.difference_amount:
if payment_details.difference_amount and row.reference_type not in [
"Sales Invoice",
"Purchase Invoice",
]:
self.make_difference_entry(payment_details)
if entry_list:
@@ -429,6 +436,8 @@ class PaymentReconciliation(Document):
journal_entry.save()
journal_entry.submit()
return journal_entry
def get_payment_details(self, row, dr_or_cr):
return frappe._dict(
{
@@ -594,6 +603,16 @@ class PaymentReconciliation(Document):
def reconcile_dr_cr_note(dr_cr_notes, company):
def get_difference_row(inv):
if inv.difference_amount != 0 and inv.difference_account:
difference_row = {
"account": inv.difference_account,
inv.dr_or_cr: abs(inv.difference_amount) if inv.difference_amount > 0 else 0,
reconcile_dr_or_cr: abs(inv.difference_amount) if inv.difference_amount < 0 else 0,
"cost_center": erpnext.get_default_cost_center(company),
}
return difference_row
for inv in dr_cr_notes:
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
@@ -638,5 +657,9 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
],
}
)
if difference_entry := get_difference_row(inv):
jv.append("accounts", difference_entry)
jv.flags.ignore_mandatory = True
jv.submit()

View File

@@ -11,10 +11,13 @@ from frappe.utils import add_days, flt, nowdate
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Item"]
class TestPaymentReconciliation(FrappeTestCase):
def setUp(self):
@@ -163,7 +166,9 @@ class TestPaymentReconciliation(FrappeTestCase):
def create_payment_reconciliation(self):
pr = frappe.new_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Customer"
pr.party_type = (
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
)
pr.party = self.customer
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()
@@ -890,6 +895,42 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(pr.allocation[0].allocated_amount, 85)
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()
pi_return = frappe.get_doc(pi.as_dict())
pi_return.name = None
pi_return.docstatus = 0
pi_return.is_return = 1
pi_return.conversion_rate = 80
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.get_unreconciled_entries()
invoices = []
payments = []
for invoice in pr.invoices:
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())
break
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
pr.reconcile()
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -2,7 +2,11 @@
// For license information, please see license.txt
frappe.ui.form.on('Payment Terms Template', {
setup: function(frm) {
refresh: function(frm) {
frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms);
},
allocate_payment_based_on_payment_terms: function(frm) {
frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms);
}
});

View File

@@ -11,7 +11,7 @@ from frappe.utils import flt
class PaymentTermsTemplate(Document):
def validate(self):
self.validate_invoice_portion()
self.check_duplicate_terms()
self.validate_terms()
def validate_invoice_portion(self):
total_portion = 0
@@ -23,9 +23,12 @@ class PaymentTermsTemplate(Document):
_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red"
)
def check_duplicate_terms(self):
def validate_terms(self):
terms = []
for term in self.terms:
if self.allocate_payment_based_on_payment_terms and not term.payment_term:
frappe.throw(_("Row {0}: Payment Term is mandatory").format(term.idx))
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
if term_info in terms:
frappe.msgprint(

View File

@@ -4,12 +4,13 @@
import frappe
from frappe import _
from frappe.utils import flt
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, validate_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
@@ -20,9 +21,17 @@ class PeriodClosingVoucher(AccountsController):
def on_submit(self):
self.db_set("gle_processing_status", "In Progress")
self.make_gl_entries()
get_opening_entries = False
if not frappe.db.exists(
"Period Closing Voucher", {"company": self.company, "docstatus": 1, "name": ("!=", self.name)}
):
get_opening_entries = True
self.make_gl_entries(get_opening_entries=get_opening_entries)
def on_cancel(self):
self.validate_future_closing_vouchers()
self.db_set("gle_processing_status", "In Progress")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
gle_count = frappe.db.count(
@@ -43,8 +52,27 @@ class PeriodClosingVoucher(AccountsController):
else:
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
self.delete_closing_entries()
def validate_future_closing_vouchers(self):
if frappe.db.exists(
"Period Closing Voucher",
{"posting_date": (">", self.posting_date), "docstatus": 1, "company": self.company},
):
frappe.throw(
_(
"You can not cancel this Period Closing Voucher, please cancel the future Period Closing Vouchers first"
)
)
def delete_closing_entries(self):
closing_balance = frappe.qb.DocType("Account Closing Balance")
frappe.qb.from_(closing_balance).delete().where(
closing_balance.period_closing_voucher == self.name
).run()
def validate_account_head(self):
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
if closing_account_type not in ["Liability", "Equity"]:
frappe.throw(
@@ -57,8 +85,6 @@ class PeriodClosingVoucher(AccountsController):
frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency))
def validate_posting_date(self):
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
validate_fiscal_year(
self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
)
@@ -67,6 +93,8 @@ class PeriodClosingVoucher(AccountsController):
self.posting_date, self.fiscal_year, company=self.company
)[1]
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""",
@@ -79,28 +107,64 @@ class PeriodClosingVoucher(AccountsController):
)
)
def make_gl_entries(self):
def check_if_previous_year_closed(self):
last_year_closing = add_days(self.year_start_date, -1)
previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
if previous_fiscal_year and not frappe.db.exists(
"GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
):
return
if previous_fiscal_year and not frappe.db.exists(
"Period Closing Voucher",
{"posting_date": ("<=", last_year_closing), "docstatus": 1, "company": self.company},
):
frappe.throw(_("Previous Year is not closed, please close it first"))
def make_gl_entries(self, get_opening_entries=False):
gl_entries = self.get_gl_entries()
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
if gl_entries:
if len(gl_entries) > 5000:
frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
frappe.enqueue(
process_gl_entries,
gl_entries=gl_entries,
closing_entries=closing_entries,
voucher_name=self.name,
queue="long",
)
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)
process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
def get_grouped_gl_entries(self, get_opening_entries=False):
closing_entries = []
for acc in self.get_balances_based_on_dimensions(
group_by_account=True, for_aggregation=True, get_opening_entries=get_opening_entries
):
closing_entries.append(self.get_closing_entries(acc))
return closing_entries
def get_gl_entries(self):
gl_entries = []
# pl account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
for acc in self.get_balances_based_on_dimensions(
group_by_account=True, report_type="Profit and Loss"
):
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_pl_account(acc))
# closing liability account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
for acc in self.get_balances_based_on_dimensions(
group_by_account=False, report_type="Profit and Loss"
):
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_closing_account(acc))
@@ -109,6 +173,8 @@ class PeriodClosingVoucher(AccountsController):
def get_gle_for_pl_account(self, acc):
gl_entry = self.get_gl_dict(
{
"company": self.company,
"closing_date": self.posting_date,
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
@@ -121,6 +187,7 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.bal_in_account_currency) > 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
"is_period_closing_voucher_entry": 1,
},
item=acc,
)
@@ -130,6 +197,8 @@ class PeriodClosingVoucher(AccountsController):
def get_gle_for_closing_account(self, acc):
gl_entry = self.get_gl_dict(
{
"company": self.company,
"closing_date": self.posting_date,
"account": self.closing_account_head,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
@@ -142,12 +211,36 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
"is_period_closing_voucher_entry": 1,
},
item=acc,
)
self.update_default_dimensions(gl_entry, acc)
return gl_entry
def get_closing_entries(self, acc):
closing_entry = self.get_gl_dict(
{
"company": self.company,
"closing_date": self.posting_date,
"period_closing_voucher": self.name,
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": flt(acc.debit_in_account_currency),
"debit": flt(acc.debit),
"credit_in_account_currency": flt(acc.credit_in_account_currency),
"credit": flt(acc.credit),
},
item=acc,
)
for dimension in self.accounting_dimensions:
closing_entry.update({dimension: acc.get(dimension)})
return closing_entry
def update_default_dimensions(self, gl_entry, acc):
if not self.accounting_dimensions:
self.accounting_dimensions = get_accounting_dimensions()
@@ -155,44 +248,88 @@ class PeriodClosingVoucher(AccountsController):
for dimension in self.accounting_dimensions:
gl_entry.update({dimension: acc.get(dimension)})
def get_pl_balances_based_on_dimensions(self, group_by_account=False):
def get_balances_based_on_dimensions(
self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False
):
"""Get balance for dimension-wise pl accounts"""
dimension_fields = ["t1.cost_center", "t1.finance_book"]
qb_dimension_fields = ["cost_center", "finance_book", "project"]
self.accounting_dimensions = get_accounting_dimensions()
for dimension in self.accounting_dimensions:
dimension_fields.append("t1.{0}".format(dimension))
qb_dimension_fields.append(dimension)
if group_by_account:
dimension_fields.append("t1.account")
qb_dimension_fields.append("account")
return frappe.db.sql(
"""
select
t1.account_currency,
{dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1
where
t1.is_cancelled = 0
and t1.account in (select name from `tabAccount` where report_type = 'Profit and Loss' and docstatus < 2 and company = %s)
and t1.posting_date between %s and %s
group by {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields),
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,
account_filters = {
"company": self.company,
"is_group": 0,
}
if report_type:
account_filters.update({"report_type": report_type})
accounts = frappe.get_all("Account", filters=account_filters, pluck="name")
gl_entry = frappe.qb.DocType("GL Entry")
query = frappe.qb.from_(gl_entry).select(gl_entry.account, gl_entry.account_currency)
if not for_aggregation:
query = query.select(
(Sum(gl_entry.debit_in_account_currency) - Sum(gl_entry.credit_in_account_currency)).as_(
"bal_in_account_currency"
),
(Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("bal_in_company_currency"),
)
else:
query = query.select(
(Sum(gl_entry.debit_in_account_currency)).as_("debit_in_account_currency"),
(Sum(gl_entry.credit_in_account_currency)).as_("credit_in_account_currency"),
(Sum(gl_entry.debit)).as_("debit"),
(Sum(gl_entry.credit)).as_("credit"),
)
for dimension in qb_dimension_fields:
query = query.select(gl_entry[dimension])
query = query.where(
(gl_entry.company == self.company)
& (gl_entry.is_cancelled == 0)
& (gl_entry.account.isin(accounts))
)
if get_opening_entries:
query = query.where(
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
| gl_entry.is_opening
== "Yes"
)
else:
query = query.where(
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
& gl_entry.is_opening
== "No"
)
def process_gl_entries(gl_entries):
if for_aggregation:
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
for dimension in qb_dimension_fields:
query = query.groupby(gl_entry[dimension])
return query.run(as_dict=1)
def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
from erpnext.accounts.general_ledger import make_gl_entries
try:
make_gl_entries(gl_entries, merge_entries=False)
make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
)

View File

@@ -16,16 +16,17 @@ from erpnext.accounts.utils import get_fiscal_year, now
class TestPeriodClosingVoucher(unittest.TestCase):
def test_closing_entry(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
jv1 = make_journal_entry(
posting_date="2021-03-15",
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
posting_date=now(),
save=False,
)
jv1.company = company
@@ -33,18 +34,18 @@ class TestPeriodClosingVoucher(unittest.TestCase):
jv1.submit()
jv2 = make_journal_entry(
posting_date="2021-03-15",
amount=600,
account1="Cost of Goods Sold - TPC",
account2="Cash - TPC",
cost_center=cost_center,
posting_date=now(),
save=False,
)
jv2.company = company
jv2.save()
jv2.submit()
pcv = self.make_period_closing_voucher()
pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
surplus_account = pcv.closing_account_head
expected_gle = (
@@ -65,6 +66,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
def test_cost_center_wise_posting(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
@@ -81,6 +83,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC",
currency="USD",
customer="_Test Customer USD",
posting_date="2021-03-15",
)
create_sales_invoice(
company=company,
@@ -91,9 +94,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC",
currency="USD",
customer="_Test Customer USD",
posting_date="2021-03-15",
)
pcv = self.make_period_closing_voucher(submit=False)
pcv = self.make_period_closing_voucher(posting_date="2021-03-31", submit=False)
pcv.save()
pcv.submit()
surplus_account = pcv.closing_account_head
@@ -128,12 +132,13 @@ class TestPeriodClosingVoucher(unittest.TestCase):
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
si = create_sales_invoice(
create_sales_invoice(
company=company,
income_account="Sales - TPC",
expense_account="Cost of Goods Sold - TPC",
@@ -142,6 +147,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC",
currency="USD",
customer="_Test Customer USD",
posting_date="2021-03-15",
)
jv = make_journal_entry(
@@ -149,14 +155,14 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account2="Sales - TPC",
amount=400,
cost_center=cost_center,
posting_date=now(),
posting_date="2021-03-15",
)
jv.company = company
jv.finance_book = create_finance_book().name
jv.save()
jv.submit()
pcv = self.make_period_closing_voucher()
pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
surplus_account = pcv.closing_account_head
expected_gle = (
@@ -194,14 +200,130 @@ class TestPeriodClosingVoucher(unittest.TestCase):
repost_doc.posting_date = add_months(today(), 13)
repost_doc.save()
def make_period_closing_voucher(self, submit=True):
def test_gl_entries_restrictions(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
self.make_period_closing_voucher(posting_date="2021-03-31")
jv1 = make_journal_entry(
posting_date="2021-03-15",
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
save=False,
)
jv1.company = company
jv1.save()
self.assertRaises(frappe.ValidationError, jv1.submit)
def test_closing_balance_with_dimensions(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
company = create_company()
cost_center1 = create_cost_center("Test Cost Center 1")
cost_center2 = create_cost_center("Test Cost Center 2")
jv1 = make_journal_entry(
posting_date="2021-03-15",
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center1,
save=False,
)
jv1.company = company
jv1.save()
jv1.submit()
jv2 = make_journal_entry(
posting_date="2021-03-15",
amount=200,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
save=False,
)
jv2.company = company
jv2.save()
jv2.submit()
pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31")
closing_balance = frappe.db.get_value(
"Account Closing Balance",
{
"account": "Sales - TPC",
"cost_center": cost_center1,
"period_closing_voucher": pcv1.name,
"is_period_closing_voucher_entry": 0,
},
["credit", "credit_in_account_currency"],
as_dict=1,
)
self.assertEqual(closing_balance.credit, 400)
self.assertEqual(closing_balance.credit_in_account_currency, 400)
jv3 = make_journal_entry(
posting_date="2022-03-15",
amount=300,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
save=False,
)
jv3.company = company
jv3.save()
jv3.submit()
pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31")
cc1_closing_balance = frappe.db.get_value(
"Account Closing Balance",
{
"account": "Sales - TPC",
"cost_center": cost_center1,
"period_closing_voucher": pcv2.name,
"is_period_closing_voucher_entry": 0,
},
["credit", "credit_in_account_currency"],
as_dict=1,
)
cc2_closing_balance = frappe.db.get_value(
"Account Closing Balance",
{
"account": "Sales - TPC",
"cost_center": cost_center2,
"period_closing_voucher": pcv2.name,
"is_period_closing_voucher_entry": 0,
},
["credit", "credit_in_account_currency"],
as_dict=1,
)
self.assertEqual(cc1_closing_balance.credit, 400)
self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400)
self.assertEqual(cc2_closing_balance.credit, 500)
self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500)
def make_period_closing_voucher(self, posting_date=None, submit=True):
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": today(),
"posting_date": today(),
"transaction_date": posting_date or today(),
"posting_date": posting_date or today(),
"company": "Test PCV Company",
"fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
"cost_center": cost_center,

View File

@@ -123,22 +123,29 @@ frappe.ui.form.on('POS Closing Entry', {
row.expected_amount = row.opening_amount;
}
const pos_inv_promises = frm.doc.pos_transactions.map(
row => frappe.db.get_doc("POS Invoice", row.pos_invoice)
);
const pos_invoices = await Promise.all(pos_inv_promises);
for (let doc of pos_invoices) {
frm.doc.grand_total += flt(doc.grand_total);
frm.doc.net_total += flt(doc.net_total);
frm.doc.total_quantity += flt(doc.total_qty);
refresh_payments(doc, frm);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
}
await Promise.all([
frappe.call({
method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices',
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user
},
callback: (r) => {
let pos_invoices = r.message;
for (let doc of pos_invoices) {
frm.doc.grand_total += flt(doc.grand_total);
frm.doc.net_total += flt(doc.net_total);
frm.doc.total_quantity += flt(doc.total_qty);
refresh_payments(doc, frm);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
}
}
})
])
frappe.dom.unfreeze();
}
});

View File

@@ -16,8 +16,10 @@ from erpnext.stock.doctype.item.test_item import create_item
class TestProcessDeferredAccounting(unittest.TestCase):
def test_creation_of_ledger_entry_on_submit(self):
"""test creation of gl entries on submission of document"""
change_acc_settings(acc_frozen_upto="2023-05-31", book_deferred_entries_based_on="Months")
deferred_account = create_account(
account_name="Deferred Revenue",
account_name="Deferred Revenue for Accounts Frozen",
parent_account="Current Liabilities - _TC",
company="_Test Company",
)
@@ -29,11 +31,11 @@ class TestProcessDeferredAccounting(unittest.TestCase):
item.save()
si = create_sales_invoice(
item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True
item=item.name, rate=3000, update_stock=0, posting_date="2023-07-01", do_not_submit=True
)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
si.items[0].service_start_date = "2023-05-01"
si.items[0].service_end_date = "2023-07-31"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
@@ -41,9 +43,9 @@ class TestProcessDeferredAccounting(unittest.TestCase):
process_deferred_accounting = doc = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
posting_date="2019-01-01",
start_date="2019-01-01",
end_date="2019-01-31",
posting_date="2023-07-01",
start_date="2023-05-01",
end_date="2023-06-30",
type="Income",
)
)
@@ -52,11 +54,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
process_deferred_accounting.submit()
expected_gle = [
[deferred_account, 33.85, 0.0, "2019-01-31"],
["Sales - _TC", 0.0, 33.85, "2019-01-31"],
["Debtors - _TC", 3000, 0.0, "2023-07-01"],
[deferred_account, 0.0, 3000, "2023-07-01"],
["Sales - _TC", 0.0, 1000, "2023-06-30"],
[deferred_account, 1000, 0.0, "2023-06-30"],
["Sales - _TC", 0.0, 1000, "2023-06-30"],
[deferred_account, 1000, 0.0, "2023-06-30"],
]
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
check_gl_entries(self, si.name, expected_gle, "2023-07-01")
change_acc_settings()
def test_pda_submission_and_cancellation(self):
pda = frappe.get_doc(
@@ -70,3 +77,10 @@ class TestProcessDeferredAccounting(unittest.TestCase):
)
pda.submit()
pda.cancel()
def change_acc_settings(acc_frozen_upto="", book_deferred_entries_based_on="Days"):
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
acc_settings.acc_frozen_upto = acc_frozen_upto
acc_settings.book_deferred_entries_based_on = book_deferred_entries_based_on
acc_settings.save()

View File

@@ -1,6 +1,6 @@
<div class="page-break">
<div id="header-html" class="hidden-pdf">
{% if letter_head %}
{% if letter_head.content %}
<div class="letter-head text-center">{{ letter_head.content }}</div>
<hr style="height:2px;border-width:0;color:black;background-color:black;">
{% endif %}

View File

@@ -63,6 +63,20 @@ frappe.ui.form.on('Process Statement Of Accounts', {
frm.set_value('to_date', frappe.datetime.get_today());
}
},
report: function(frm){
let filters = {
'company': frm.doc.company,
}
if(frm.doc.report == 'Accounts Receivable'){
filters['account_type'] = 'Receivable';
}
frm.set_query("account", function() {
return {
filters: filters
};
});
},
customer_collection: function(frm){
frm.set_value('collection_name', '');
if(frm.doc.customer_collection){

View File

@@ -6,17 +6,24 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"report",
"section_break_11",
"from_date",
"posting_date",
"company",
"account",
"group_by",
"cost_center",
"territory",
"column_break_14",
"to_date",
"finance_book",
"currency",
"project",
"payment_terms_template",
"sales_partner",
"sales_person",
"based_on_payment_terms",
"section_break_3",
"customer_collection",
"collection_name",
@@ -65,14 +72,14 @@
"reqd": 1
},
{
"depends_on": "eval:doc.enable_auto_email == 0;",
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
},
{
"depends_on": "eval:doc.enable_auto_email == 0;",
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
@@ -85,6 +92,7 @@
"options": "PSOA Cost Center"
},
{
"depends_on": "eval: (doc.report == 'General Ledger');",
"fieldname": "project",
"fieldtype": "Table MultiSelect",
"label": "Project",
@@ -102,7 +110,7 @@
{
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"label": "General Ledger Filters"
"label": "Report Filters"
},
{
"fieldname": "column_break_14",
@@ -162,12 +170,14 @@
},
{
"default": "Group by Voucher (Consolidated)",
"depends_on": "eval:(doc.report == 'General Ledger');",
"fieldname": "group_by",
"fieldtype": "Select",
"label": "Group By",
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
},
{
"depends_on": "eval: (doc.report == 'General Ledger');",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@@ -295,6 +305,7 @@
},
{
"default": "0",
"depends_on": "eval: (doc.report == 'General Ledger');",
"fieldname": "show_net_values_in_party_account",
"fieldtype": "Check",
"label": "Show Net Values in Party Account"
@@ -308,10 +319,59 @@
{
"fieldname": "column_break_ocfq",
"fieldtype": "Column Break"
},
{
"fieldname": "report",
"fieldtype": "Select",
"label": "Report",
"options": "General Ledger\nAccounts Receivable",
"reqd": 1
},
{
"default": "Today",
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
},
{
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
"options": "Payment Terms Template"
},
{
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
"fieldname": "sales_partner",
"fieldtype": "Link",
"label": "Sales Partner",
"options": "Sales Partner"
},
{
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
"fieldname": "sales_person",
"fieldtype": "Link",
"label": "Sales Person",
"options": "Sales Person"
},
{
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
"fieldname": "territory",
"fieldtype": "Link",
"label": "Territory",
"options": "Territory"
},
{
"default": "0",
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
"fieldname": "based_on_payment_terms",
"fieldtype": "Check",
"label": "Based On Payment Terms"
}
],
"links": [],
"modified": "2023-04-26 12:46:43.645455",
"modified": "2023-06-23 10:13:15.051950",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -14,6 +14,7 @@ from frappe.www.printview import get_print_style
from erpnext import get_company_currency
from erpnext.accounts.party import get_party_account_currency
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute as get_ar_soa
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import (
execute as get_ageing,
)
@@ -42,29 +43,10 @@ class ProcessStatementOfAccounts(Document):
def get_report_pdf(doc, consolidated=True):
statement_dict = {}
ageing = ""
base_template_path = "frappe/www/printview.html"
template_path = (
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
)
for entry in doc.customers:
if doc.include_ageing:
ageing_filters = frappe._dict(
{
"company": doc.company,
"report_date": doc.to_date,
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
"customer": entry.customer,
}
)
col1, ageing = get_ageing(ageing_filters)
if ageing:
ageing[0]["ageing_based_on"] = doc.ageing_based_on
ageing = set_ageing(doc, entry)
tax_id = frappe.get_doc("Customer", entry.customer).tax_id
presentation_currency = (
@@ -72,59 +54,25 @@ def get_report_pdf(doc, consolidated=True):
or doc.currency
or get_company_currency(doc.company)
)
if doc.letter_head:
from frappe.www.printview import get_letter_head
letter_head = get_letter_head(doc, 0)
filters = get_common_filters(doc)
filters = frappe._dict(
{
"from_date": doc.from_date,
"to_date": doc.to_date,
"company": doc.company,
"finance_book": doc.finance_book if doc.finance_book else None,
"account": [doc.account] if doc.account else None,
"party_type": "Customer",
"party": [entry.customer],
"party_name": [entry.customer_name] if entry.customer_name else None,
"presentation_currency": presentation_currency,
"group_by": doc.group_by,
"currency": doc.currency,
"cost_center": [cc.cost_center_name for cc in doc.cost_center],
"project": [p.project_name for p in doc.project],
"show_opening_entries": 0,
"include_default_book_entries": 0,
"tax_id": tax_id if tax_id else None,
}
)
col, res = get_soa(filters)
if doc.report == "General Ledger":
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
else:
filters.update(get_ar_filters(doc, entry))
for x in [0, -2, -1]:
res[x]["account"] = res[x]["account"].replace("'", "")
if doc.report == "General Ledger":
col, res = get_soa(filters)
for x in [0, -2, -1]:
res[x]["account"] = res[x]["account"].replace("'", "")
if len(res) == 3:
continue
else:
ar_res = get_ar_soa(filters)
col, res = ar_res[0], ar_res[1]
if len(res) == 3:
continue
html = frappe.render_template(
template_path,
{
"filters": filters,
"data": res,
"ageing": ageing[0] if (doc.include_ageing and ageing) else None,
"letter_head": letter_head if doc.letter_head else None,
"terms_and_conditions": frappe.db.get_value(
"Terms and Conditions", doc.terms_and_conditions, "terms"
)
if doc.terms_and_conditions
else None,
},
)
html = frappe.render_template(
base_template_path,
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
)
statement_dict[entry.customer] = html
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
if not bool(statement_dict):
return False
@@ -137,6 +85,110 @@ def get_report_pdf(doc, consolidated=True):
return statement_dict
def set_ageing(doc, entry):
ageing_filters = frappe._dict(
{
"company": doc.company,
"report_date": doc.to_date,
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
"customer": entry.customer,
}
)
col1, ageing = get_ageing(ageing_filters)
if ageing:
ageing[0]["ageing_based_on"] = doc.ageing_based_on
return ageing
def get_common_filters(doc):
return frappe._dict(
{
"company": doc.company,
"finance_book": doc.finance_book if doc.finance_book else None,
"account": [doc.account] if doc.account else None,
"cost_center": [cc.cost_center_name for cc in doc.cost_center],
}
)
def get_gl_filters(doc, entry, tax_id, presentation_currency):
return {
"from_date": doc.from_date,
"to_date": doc.to_date,
"party_type": "Customer",
"party": [entry.customer],
"party_name": [entry.customer_name] if entry.customer_name else None,
"presentation_currency": presentation_currency,
"group_by": doc.group_by,
"currency": doc.currency,
"project": [p.project_name for p in doc.project],
"show_opening_entries": 0,
"include_default_book_entries": 0,
"tax_id": tax_id if tax_id else None,
"show_net_values_in_party_account": doc.show_net_values_in_party_account,
}
def get_ar_filters(doc, entry):
return {
"report_date": doc.posting_date if doc.posting_date else None,
"customer_name": entry.customer,
"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,
"sales_person": doc.sales_person if doc.sales_person else None,
"territory": doc.territory if doc.territory else None,
"based_on_payment_terms": doc.based_on_payment_terms,
"report_name": "Accounts Receivable",
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
def get_html(doc, filters, entry, col, res, ageing):
base_template_path = "frappe/www/printview.html"
template_path = (
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
if doc.report == "General Ledger"
else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
)
if doc.letter_head:
from frappe.www.printview import get_letter_head
letter_head = get_letter_head(doc, 0)
html = frappe.render_template(
template_path,
{
"filters": filters,
"data": res,
"report": {"report_name": doc.report, "columns": col},
"ageing": ageing[0] if (doc.include_ageing and ageing) else None,
"letter_head": letter_head if doc.letter_head else None,
"terms_and_conditions": frappe.db.get_value(
"Terms and Conditions", doc.terms_and_conditions, "terms"
)
if doc.terms_and_conditions
else None,
},
)
html = frappe.render_template(
base_template_path,
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
)
return html
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
fields_dict = {
"Customer Group": "customer_group",

View File

@@ -0,0 +1,348 @@
<style>
.print-format {
padding: 4mm;
font-size: 8.0pt !important;
}
.print-format td {
vertical-align:middle !important;
}
</style>
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
<h4 class="text-center">
{% if (filters.customer_name) %}
{{ filters.customer_name }}
{% else %}
{{ filters.customer ~ filters.supplier }}
{% endif %}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) %}
{{ _("Tax Id: ") }}{{ filters.tax_id }}
{% endif %}
</h6>
<h5 class="text-center">
{{ _(filters.ageing_based_on) }}
{{ _("Until") }}
{{ frappe.format(filters.report_date, 'Date') }}
</h5>
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) %}
<strong>{{ _("Payment Terms") }}:</strong> {{ filters.payment_terms }}
{% endif %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) %}
<strong>{{ _("Credit Limit") }}:</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
{% endif %}
</div>
</div>
{% if(filters.show_future_payments) %}
{% set balance_row = data.slice(-1).pop() %}
{% for i in report.columns %}
{% if i.fieldname == 'age' %}
{% set elem = i %}
{% endif %}
{% endfor %}
{% set start = report.columns.findIndex(elem) %}
{% set range1 = report.columns[start].label %}
{% set range2 = report.columns[start+1].label %}
{% set range3 = report.columns[start+2].label %}
{% set range4 = report.columns[start+3].label %}
{% set range5 = report.columns[start+4].label %}
{% set range6 = report.columns[start+5].label %}
{% if(balance_row) %}
<table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {{ data[0]["currency"] ~ "" }})</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
<thead>
<tr>
<th>{{ _(" ") }}</th>
<th>{{ _(range1) }}</th>
<th>{{ _(range2) }}</th>
<th>{{ _(range3) }}</th>
<th>{{ _(range4) }}</th>
<th>{{ _(range5) }}</th>
<th>{{ _(range6) }}</th>
<th>{{ _("Total") }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ _("Total Outstanding") }}</td>
<td class="text-right">
{{ format_number(balance_row["age"], null, 2) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
</td>
</tr>
<td>{{ _("Future Payments") }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
</td>
<tr class="cvs-footer">
<th class="text-left">{{ _("Cheques Required") }}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
</tr>
</tbody>
</table>
{% endif %}
{% endif %}
<table class="table table-bordered">
<thead>
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
<th style="width: 10%">{{ _("Date") }}</th>
<th style="width: 4%">{{ _("Age (Days)") }}</th>
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<th style="width: 14%">{{ _("Reference") }}</th>
<th style="width: 10%">{{ _("Sales Person") }}</th>
{% else %}
<th style="width: 24%">{{ _("Reference") }}</th>
{% endif %}
{% if not(filters.show_future_payments) %}
<th style="width: 20%">
{% if (filters.customer or filters.supplier or filters.customer_name) %}
{{ _("Remarks") }}
{% else %}
{{ _("Party") }}
{% endif %}
</th>
{% endif %}
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
{% if not(filters.show_future_payments) %}
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
<th style="width: 10%; text-align: right">
{% if report.report_name == "Accounts Receivable" %}
{{ _('Credit Note') }}
{% else %}
{{ _('Debit Note') }}
{% endif %}
</th>
{% endif %}
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
{% endif %}
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
{% endif %}
{% else %}
<th style="width: 40%">
{% if (filters.customer or filters.supplier or filters.customer_name) %}
{{ _("Remarks")}}
{% else %}
{{ _("Party") }}
{% endif %}
</th>
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
<th style="width: 15%">
{% if report.report_name == "Accounts Receivable Summary" %}
{{ _('Credit Note Amount') }}
{% else %}
{{ _('Debit Note Amount') }}
{% endif %}
</th>
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for i in range(data|length) %}
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
{% if(data[i]["party"]) %}
<td>{{ (data[i]["posting_date"]) }}</td>
<td style="text-align: right">{{ data[i]["age"] }}</td>
<td>
{% if not(filters.show_future_payments) %}
{{ data[i]["voucher_type"] }}
<br>
{% endif %}
{{ data[i]["voucher_no"] }}
</td>
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<td>{{ data[i]["sales_person"] }}</td>
{% endif %}
{% if not (filters.show_future_payments) %}
<td>
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
{{ data[i]["party"] }}
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
<br> {{ data[i]["customer_name"] }}
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
<br> {{ data[i]["supplier_name"] }}
{% endif %}
{% endif %}
<div>
{% if data[i]["remarks"] %}
{{ _("Remarks") }}:
{{ data[i]["remarks"] }}
{% endif %}
</div>
</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
{% if not(filters.show_future_payments) %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<td style="text-align: right">
{{ data[i]["po_no"] }}</td>
{% endif %}
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% else %}
<td></td>
{% if not(filters.show_future_payments) %}
<td></td>
{% endif %}
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<td></td>
{% endif %}
<td></td>
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
{% if not(filters.show_future_payments) %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<td style="text-align: right">
{{ data[i]["po_no"] }}</td>
{% endif %}
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% endif %}
{% else %}
{% if(data[i]["party"] or "&nbsp;") %}
{% if not(data[i]["is_total_row"]) %}
<td>
{% if(not(filters.customer | filters.supplier)) %}
{{ data[i]["party"] }}
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
<br> {{ data[i]["customer_name"] }}
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
<br> {{ data[i]["supplier_name"] }}
{% endif %}
{% endif %}
<br>{{ _("Remarks") }}:
{{ data[i]["remarks"] }}
</td>
{% else %}
<td><b>{{ _("Total") }}</b></td>
{% endif %}
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% endif %}
</tr>
{% endfor %}
<td></td>
<td></td>
<td></td>
<td></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
</tbody>
</table>
<br>
{% if ageing %}
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
{{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}}
</h4>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">30 Days</th>
<th style="width: 25%">60 Days</th>
<th style="width: 25%">90 Days</th>
<th style="width: 25%">120 Days</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
</tr>
</tbody>
</table>
{% endif %}
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>

View File

@@ -547,6 +547,7 @@
"depends_on": "update_stock",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -1573,7 +1574,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-04-29 12:57:50.832598",
"modified": "2023-07-04 17:23:59.145031",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -637,13 +637,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
gle_filters={"account": "Stock In Hand - TCP1"},
)
# assert loss booked in COGS
self.assertGLEs(
return_pi,
[{"credit": 0, "debit": 200}],
gle_filters={"account": "Cost of Goods Sold - TCP1"},
)
def test_return_with_lcv(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
@@ -1662,6 +1655,21 @@ 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)
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
# override the rate with valuation rate
sle = frappe.get_all(
"Stock Ledger Entry",
fields=["stock_value_difference", "actual_qty"],
filters={"voucher_no": returned_inv.name},
)[0]
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -176,6 +176,7 @@
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
"no_copy": 1,
"read_only": 1
},
{
@@ -420,6 +421,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"options": "Warehouse"
},
@@ -880,7 +882,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-11-29 13:01:20.438217",
"modified": "2023-07-04 17:22:21.501152",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -890,4 +892,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -670,19 +670,6 @@ frappe.ui.form.on('Sales Invoice', {
}
}
// expense account
frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) {
if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
return {
filters: {
'report_type': 'Profit and Loss',
'company': doc.company,
"is_group": 0
}
}
}
}
// discount account
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {

View File

@@ -320,6 +320,7 @@
},
{
"default": "0",
"depends_on": "eval: !doc.is_debit_note",
"fieldname": "is_return",
"fieldtype": "Check",
"hide_days": 1,
@@ -1959,6 +1960,7 @@
},
{
"default": "0",
"depends_on": "eval: !doc.is_return",
"description": "Issue a debit note with 0 qty against an existing Sales Invoice",
"fieldname": "is_debit_note",
"fieldtype": "Check",
@@ -2153,7 +2155,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2023-04-28 14:15:59.901154",
"modified": "2023-06-19 16:02:05.309332",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -1012,10 +1012,16 @@ class SalesInvoice(SellingController):
def check_prev_docstatus(self):
for d in self.get("items"):
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
if (
d.sales_order
and frappe.db.get_value("Sales Order", d.sales_order, "docstatus", cache=True) != 1
):
frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
if (
d.delivery_note
and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus", cache=True) != 1
):
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None, from_repost=False):

View File

@@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "naming_series:",
"creation": "2017-12-25 16:50:53.878430",
"doctype": "DocType",
@@ -111,11 +112,12 @@
"read_only": 1
}
],
"modified": "2019-11-17 23:24:11.395882",
"links": [],
"modified": "2023-04-10 22:02:20.406087",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shareholder",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -158,6 +160,7 @@
"search_fields": "folio_no",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

@@ -3,8 +3,10 @@
import frappe
from frappe import _
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 import cint, flt, getdate
@@ -346,26 +348,33 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
def get_advance_vouchers(
parties, company=None, from_date=None, to_date=None, party_type="Supplier"
):
# for advance vouchers, debit and credit is reversed
dr_or_cr = "debit" if party_type == "Supplier" else "credit"
"""
Use Payment Ledger to fetch unallocated Advance Payments
"""
filters = {
dr_or_cr: [">", 0],
"is_opening": "No",
"is_cancelled": 0,
"party_type": party_type,
"party": ["in", parties],
}
ple = qb.DocType("Payment Ledger Entry")
if party_type == "Customer":
filters.update({"against_voucher": ["is", "not set"]})
conditions = []
conditions.append(ple.amount.lt(0))
conditions.append(ple.delinked == 0)
conditions.append(ple.party_type == party_type)
conditions.append(ple.party.isin(parties))
conditions.append(ple.voucher_no == ple.against_voucher_no)
if company:
filters["company"] = company
if from_date and to_date:
filters["posting_date"] = ["between", (from_date, to_date)]
conditions.append(ple.company == company)
return frappe.get_all("GL Entry", filters=filters, distinct=1, pluck="voucher_no") or [""]
if from_date and to_date:
conditions.append(ple.posting_date[from_date:to_date])
advances = (
qb.from_(ple).select(ple.voucher_no).distinct().where(Criterion.all(conditions)).run(as_list=1)
)
if advances:
advances = [x[0] for x in advances]
return advances
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
@@ -499,6 +508,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
tcs_amount = 0
ple = qb.DocType("Payment Ledger Entry")
# sum of debit entries made from sales invoices
invoiced_amt = (
@@ -516,18 +526,20 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
)
# sum of credit entries made from PE / JV with unset 'against voucher'
conditions = []
conditions.append(ple.amount.lt(0))
conditions.append(ple.delinked == 0)
conditions.append(ple.party.isin(parties))
conditions.append(ple.voucher_no == ple.against_voucher_no)
conditions.append(ple.company == inv.company)
advances = (
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)
)
advance_amt = (
frappe.db.get_value(
"GL Entry",
{
"is_cancelled": 0,
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", adv_vouchers],
},
"sum(credit)",
)
or 0.0
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
)
# sum of credit entries made from sales invoice
@@ -573,7 +585,9 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
"supplier": ("in", parties),
"apply_tds": 1,
"docstatus": 1,
"tax_withholding_category": ldc.tax_withholding_category,
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
"company": ldc.company,
},
"sum(tax_withholding_net_total)",
)
@@ -603,7 +617,7 @@ def is_valid_certificate(
):
valid = False
available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount)
available_amount = flt(certificate_limit) - flt(deducted_amount)
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
valid = True

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe.tests.utils import change_settings
from frappe.utils import today
from erpnext.accounts.utils import get_fiscal_year
@@ -152,6 +153,64 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices):
d.cancel()
@change_settings(
"Accounts Settings",
{"unlink_payment_on_cancellation_of_invoice": 1},
)
def test_tcs_on_unallocated_advance_payments(self):
frappe.db.set_value(
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"
)
vouchers = []
# create advance payment
pe = create_payment_entry(
payment_type="Receive", party_type="Customer", party="Test TCS Customer", paid_amount=20000
)
pe.paid_from = "Debtors - _TC"
pe.paid_to = "Cash - _TC"
pe.submit()
vouchers.append(pe)
# create invoice
si1 = create_sales_invoice(customer="Test TCS Customer", rate=5000)
si1.submit()
vouchers.append(si1)
# reconcile
pr = frappe.get_doc("Payment Reconciliation")
pr.company = "_Test Company"
pr.party_type = "Customer"
pr.party = "Test TCS Customer"
pr.receivable_payable_account = "Debtors - _TC"
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# make another invoice
# sum of unallocated amount from payment entry and this sales invoice will breach cumulative threashold
# TDS should be calculated
si2 = create_sales_invoice(customer="Test TCS Customer", rate=15000)
si2.submit()
vouchers.append(si2)
si3 = create_sales_invoice(customer="Test TCS Customer", rate=10000)
si3.submit()
vouchers.append(si3)
# assert tax collection on total invoice amount created until now
tcs_charged = sum([d.base_tax_amount for d in si2.taxes if d.account_head == "TCS - _TC"])
tcs_charged += sum([d.base_tax_amount for d in si3.taxes if d.account_head == "TCS - _TC"])
self.assertEqual(tcs_charged, 1500)
# cancel invoice and payments to avoid clashing
for d in reversed(vouchers):
d.reload()
d.cancel()
def test_tds_calculation_on_net_total(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"

View File

@@ -300,6 +300,9 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
if gl_map[0]["voucher_type"] != "Period Closing Voucher":
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -521,6 +524,9 @@ def make_reverse_gl_entries(
)
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries:
@@ -568,6 +574,28 @@ def check_freezing_date(posting_date, adv_adj=False):
)
def validate_against_pcv(is_opening, posting_date, company):
if is_opening and frappe.db.exists(
"Period Closing Voucher", {"docstatus": 1, "company": company}
):
frappe.throw(
_("Opening Entry can not be created after Period Closing Voucher is created."),
title=_("Invalid Opening Entry"),
)
last_pcv_date = frappe.db.get_value(
"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
)
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
message = _("Books have been closed till the period ending on {0}").format(
formatdate(last_pcv_date)
)
message += "</br >"
message += _("You cannot create/amend any accounting entries till this date.")
frappe.throw(message, title=_("Period Closed"))
def set_as_cancel(voucher_type, voucher_no):
"""
Set is_cancelled=1 in all original gl entries for the voucher

View File

@@ -284,4 +284,4 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>

View File

@@ -25,6 +25,8 @@ def execute(filters=None):
company=filters.company,
)
filters.period_start_date = period_list[0]["year_start_date"]
currency = filters.presentation_currency or frappe.get_cached_value(
"Company", filters.company, "default_currency"
)
@@ -96,7 +98,7 @@ def execute(filters=None):
chart = get_chart_data(filters, columns, asset, liability, equity)
report_summary = get_report_summary(
period_list, asset, liability, equity, provisional_profit_loss, total_credit, currency, filters
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
)
return columns, data, message, chart, report_summary
@@ -174,7 +176,6 @@ def get_report_summary(
liability,
equity,
provisional_profit_loss,
total_credit,
currency,
filters,
consolidated=False,

View File

@@ -152,5 +152,5 @@ def get_entries(filters):
return sorted(
journal_entries + payment_entries + loan_disbursements + loan_repayments,
key=lambda k: k[2] or getdate(nowdate()),
key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()),
)

View File

@@ -118,7 +118,6 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
liability,
equity,
provisional_profit_loss,
total_credit,
company_currency,
filters,
True,
@@ -656,7 +655,7 @@ def set_gl_entries_by_account(
if filters and filters.get("presentation_currency") != d.default_currency:
currency_info["company"] = d.name
currency_info["company_currency"] = d.default_currency
convert_to_presentation_currency(gl_entries, currency_info, filters.get("company"))
convert_to_presentation_currency(gl_entries, currency_info)
for entry in gl_entries:
if entry.account_number:

View File

@@ -418,50 +418,51 @@ def set_gl_entries_by_account(
ignore_closing_entries=False,
):
"""Returns a dict like { "account": [gl entries], ... }"""
gl_entries = []
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
accounts = frappe.db.sql_list(
"""select name from `tabAccount`
where lft >= %s and rgt <= %s and company = %s""",
(root_lft, root_rgt, company),
accounts_list = frappe.db.get_all(
"Account",
filters={"company": company, "is_group": 0, "lft": (">=", root_lft), "rgt": ("<=", root_rgt)},
pluck="name",
)
if accounts:
additional_conditions += " and account in ({})".format(
", ".join(frappe.db.escape(d) for d in accounts)
)
ignore_opening_entries = False
if accounts_list:
# For balance sheet
if not from_date:
from_date = filters["period_start_date"]
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", from_date)},
fields=["posting_date", "name"],
order_by="posting_date desc",
limit=1,
)
if last_period_closing_voucher:
gl_entries += get_accounting_entries(
"Account Closing Balance",
from_date,
to_date,
accounts_list,
filters,
ignore_closing_entries,
last_period_closing_voucher[0].name,
)
from_date = add_days(last_period_closing_voucher[0].posting_date, 1)
ignore_opening_entries = True
gl_filters = {
"company": company,
"from_date": from_date,
"to_date": to_date,
"finance_book": cstr(filters.get("finance_book")),
}
if filters.get("include_default_book_entries"):
gl_filters["company_fb"] = frappe.db.get_value("Company", company, "default_finance_book")
for key, value in filters.items():
if value:
gl_filters.update({key: value})
gl_entries = frappe.db.sql(
"""
select posting_date, account, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and is_cancelled = 0""".format(
additional_conditions=additional_conditions
),
gl_filters,
as_dict=True,
gl_entries += get_accounting_entries(
"GL Entry",
from_date,
to_date,
accounts_list,
filters,
ignore_closing_entries,
ignore_opening_entries=ignore_opening_entries,
)
if filters and filters.get("presentation_currency"):
convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get("company"))
convert_to_presentation_currency(gl_entries, get_currency(filters))
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
@@ -469,49 +470,90 @@ def set_gl_entries_by_account(
return gl_entries_by_account
def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = []
def get_accounting_entries(
doctype,
from_date,
to_date,
accounts,
filters,
ignore_closing_entries,
period_closing_voucher=None,
ignore_opening_entries=False,
):
gl_entry = frappe.qb.DocType(doctype)
query = (
frappe.qb.from_(gl_entry)
.select(
gl_entry.account,
gl_entry.debit,
gl_entry.credit,
gl_entry.debit_in_account_currency,
gl_entry.credit_in_account_currency,
gl_entry.account_currency,
)
.where(gl_entry.company == filters.company)
)
if doctype == "GL Entry":
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
query = query.where(gl_entry.is_cancelled == 0)
query = query.where(gl_entry.posting_date <= to_date)
if ignore_opening_entries:
query = query.where(gl_entry.is_opening == "No")
else:
query = query.select(gl_entry.closing_date.as_("posting_date"))
query = query.where(gl_entry.period_closing_voucher == period_closing_voucher)
query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters)
query = query.where(gl_entry.account.isin(accounts))
entries = query.run(as_dict=True)
return entries
def apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters):
gl_entry = frappe.qb.DocType(doctype)
accounting_dimensions = get_accounting_dimensions(as_list=False)
if ignore_closing_entries:
additional_conditions.append("ifnull(voucher_type, '')!='Period Closing Voucher'")
if doctype == "GL Entry":
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
else:
query = query.where(gl_entry.is_period_closing_voucher_entry == 0)
if from_date:
additional_conditions.append("posting_date >= %(from_date)s")
if from_date and doctype == "GL Entry":
query = query.where(gl_entry.posting_date >= from_date)
if filters:
if filters.get("project"):
if not isinstance(filters.get("project"), list):
filters.project = frappe.parse_json(filters.get("project"))
additional_conditions.append("project in %(project)s")
query = query.where(gl_entry.project.isin(filters.project))
if filters.get("cost_center"):
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
additional_conditions.append("cost_center in %(cost_center)s")
query = query.where(gl_entry.cost_center.isin(filters.cost_center))
if filters.get("include_default_book_entries"):
if filters.get("finance_book"):
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
filters.get("company_fb")
):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
additional_conditions.append(
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
else:
if filters.get("finance_book"):
additional_conditions.append(
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
additional_conditions.append("(finance_book in ('') OR finance_book IS NULL)")
query = query.where(
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (gl_entry.finance_book.isnull())
)
else:
query = query.where(
(gl_entry.finance_book.isin([cstr(filters.finance_book), ""]))
| (gl_entry.finance_book.isnull())
)
if accounting_dimensions:
for dimension in accounting_dimensions:
@@ -520,11 +562,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, filters.get(dimension.fieldname)
)
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
else:
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
query = query.where(gl_entry[dimension.fieldname].isin(filters[dimension.fieldname]))
return query
def get_cost_centers_with_children(cost_centers):

View File

@@ -204,7 +204,7 @@ def get_gl_entries(filters, accounting_dimensions):
)
if filters.get("presentation_currency"):
return convert_to_presentation_currency(gl_entries, currency_map, filters.get("company"))
return convert_to_presentation_currency(gl_entries, currency_map)
else:
return gl_entries

View File

@@ -15,21 +15,21 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
get_group_by_conditions,
get_tax_accounts,
)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None):
return _execute(filters)
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
def _execute(filters=None, additional_table_columns=None):
if not filters:
filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company)
item_list = get_items(filters, additional_query_columns)
item_list = get_items(filters, get_query_columns(additional_table_columns))
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(
@@ -40,6 +40,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
tax_doctype="Purchase Taxes and Charges",
)
scrubbed_tax_fields = {}
for tax in tax_columns:
scrubbed_tax_fields.update(
{
tax + " Rate": frappe.scrub(tax + " Rate"),
tax + " Amount": frappe.scrub(tax + " Amount"),
}
)
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
data = []
@@ -50,11 +60,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get("group_by"):
grand_total = get_grand_total(filters, "Purchase Invoice")
item_details = get_item_details()
for d in item_list:
item_record = item_details.get(d.item_code)
purchase_receipt = None
if d.purchase_receipt:
purchase_receipt = d.purchase_receipt
@@ -67,42 +73,34 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
"item_code": d.item_code,
"item_name": item_record.item_name if item_record else d.item_name,
"item_group": item_record.item_group if item_record else d.item_group,
"item_name": d.pi_item_name if d.pi_item_name else d.i_item_name,
"item_group": d.pi_item_group if d.pi_item_group else d.i_item_group,
"description": d.description,
"invoice": d.parent,
"posting_date": d.posting_date,
"supplier": d.supplier,
"supplier_name": d.supplier_name,
**get_values_for_columns(additional_table_columns, d),
"credit_to": d.credit_to,
"mode_of_payment": d.mode_of_payment,
"project": d.project,
"company": d.company,
"purchase_order": d.purchase_order,
"purchase_receipt": purchase_receipt,
"expense_account": expense_account,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
"rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
"amount": d.base_net_amount,
}
if additional_query_columns:
for col in additional_query_columns:
row.update({col: d.get(col)})
row.update(
{
"credit_to": d.credit_to,
"mode_of_payment": d.mode_of_payment,
"project": d.project,
"company": d.company,
"purchase_order": d.purchase_order,
"purchase_receipt": d.purchase_receipt,
"expense_account": expense_account,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
"rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
"amount": d.base_net_amount,
}
)
total_tax = 0
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
{
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
}
)
total_tax += flt(item_tax.get("tax_amount"))
@@ -241,7 +239,7 @@ def get_columns(additional_table_columns, filters):
},
{
"label": _("Purchase Receipt"),
"fieldname": "Purchase Receipt",
"fieldname": "purchase_receipt",
"fieldtype": "Link",
"options": "Purchase Receipt",
"width": 100,
@@ -312,11 +310,6 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
else:
additional_query_columns = ""
return frappe.db.sql(
"""
select
@@ -325,19 +318,20 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
`tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`,
`tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {0}
from `tabPurchase Invoice`, `tabPurchase Invoice Item`
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
`tabPurchase Invoice`.docstatus = 1 {1}
""".format(
additional_query_columns
)
% (conditions),
additional_query_columns, conditions
),
filters,
as_dict=1,
)

View File

@@ -9,9 +9,9 @@ from frappe.utils import cstr, flt
from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import (
get_customer_details,
get_item_details,
)
@@ -19,22 +19,27 @@ def execute(filters=None):
return _execute(filters)
def _execute(
filters=None,
additional_table_columns=None,
additional_query_columns=None,
additional_conditions=None,
):
def _execute(filters=None, additional_table_columns=None, additional_conditions=None):
if not filters:
filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns, additional_conditions)
item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
scrubbed_tax_fields = {}
for tax in tax_columns:
scrubbed_tax_fields.update(
{
tax + " Rate": frappe.scrub(tax + " Rate"),
tax + " Amount": frappe.scrub(tax + " Amount"),
}
)
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
so_dn_map = get_delivery_notes_against_sales_order(item_list)
@@ -47,11 +52,9 @@ def _execute(
grand_total = get_grand_total(filters, "Sales Invoice")
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None
if d.delivery_note:
@@ -64,38 +67,30 @@ def _execute(
row = {
"item_code": d.item_code,
"item_name": item_record.item_name if item_record else d.item_name,
"item_group": item_record.item_group if item_record else d.item_group,
"item_name": d.si_item_name if d.si_item_name else d.i_item_name,
"item_group": d.si_item_group if d.si_item_group else d.i_item_group,
"description": d.description,
"invoice": d.parent,
"posting_date": d.posting_date,
"customer": d.customer,
"customer_name": customer_record.customer_name,
"customer_group": customer_record.customer_group,
**get_values_for_columns(additional_table_columns, d),
"debit_to": d.debit_to,
"mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
"territory": d.territory,
"project": d.project,
"company": d.company,
"sales_order": d.sales_order,
"delivery_note": d.delivery_note,
"income_account": d.unrealized_profit_loss_account
if d.is_internal_customer == 1
else d.income_account,
"cost_center": d.cost_center,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
}
if additional_query_columns:
for col in additional_query_columns:
row.update({col: d.get(col)})
row.update(
{
"debit_to": d.debit_to,
"mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
"territory": d.territory,
"project": d.project,
"company": d.company,
"sales_order": d.sales_order,
"delivery_note": d.delivery_note,
"income_account": d.unrealized_profit_loss_account
if d.is_internal_customer == 1
else d.income_account,
"cost_center": d.cost_center,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
}
)
if d.stock_uom != d.uom and d.stock_qty:
row.update({"rate": (d.base_net_rate * d.qty) / d.stock_qty, "amount": d.base_net_amount})
else:
@@ -107,8 +102,8 @@ def _execute(
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
{
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
}
)
if item_tax.get("is_other_charges"):
@@ -387,11 +382,6 @@ def get_group_by_conditions(filters, doctype):
def get_items(filters, additional_query_columns, additional_conditions=None):
conditions = get_conditions(filters, additional_conditions)
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
else:
additional_query_columns = ""
return frappe.db.sql(
"""
select
@@ -404,17 +394,20 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabSales Invoice Item`.project,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
`tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 {1}
from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and
`tabItem`.name = `tabSales Invoice Item`.`item_code` and
`tabSales Invoice`.docstatus = 1 {1}
""".format(
additional_query_columns or "", conditions
additional_query_columns, conditions
),
filters,
as_dict=1,

View File

@@ -14,8 +14,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Project"),
"fieldtype": "MultiSelectList",
get_data: function(txt) {
return frappe.db.get_link_options('Project', txt);
}
return frappe.db.get_link_options('Project', txt, {
company: frappe.query_report.get_filter_value("company")
});
},
},
{
"fieldname": "include_default_book_entries",

View File

@@ -10,17 +10,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None):
return _execute(filters)
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
def _execute(filters=None, additional_table_columns=None):
if not filters:
filters = {}
invoice_list = get_invoices(filters, additional_query_columns)
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
invoice_list, additional_table_columns
)
@@ -47,13 +48,12 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
row = [inv.name, inv.posting_date, inv.supplier, inv.supplier_name]
if additional_query_columns:
for col in additional_query_columns:
row.append(inv.get(col))
row += [
row = [
inv.name,
inv.posting_date,
inv.supplier,
inv.supplier_name,
*get_values_for_columns(additional_table_columns, inv).values(),
supplier_details.get(inv.supplier), # supplier_group
inv.tax_id,
inv.credit_to,
@@ -244,9 +244,6 @@ def get_conditions(filters):
def get_invoices(filters, additional_query_columns):
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
conditions = get_conditions(filters)
return frappe.db.sql(
"""
@@ -255,11 +252,10 @@ def get_invoices(filters, additional_query_columns):
remarks, base_net_total, base_grand_total, outstanding_amount,
mode_of_payment {0}
from `tabPurchase Invoice`
where docstatus = 1 %s
where docstatus = 1 {1}
order by posting_date desc, name desc""".format(
additional_query_columns or ""
)
% conditions,
additional_query_columns, conditions
),
filters,
as_dict=1,
)

View File

@@ -11,17 +11,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None):
return _execute(filters)
def _execute(filters, additional_table_columns=None, additional_query_columns=None):
def _execute(filters, additional_table_columns=None):
if not filters:
filters = frappe._dict({})
invoice_list = get_invoices(filters, additional_query_columns)
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
invoice_list, additional_table_columns
)
@@ -54,30 +55,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
"posting_date": inv.posting_date,
"customer": inv.customer,
"customer_name": inv.customer_name,
**get_values_for_columns(additional_table_columns, inv),
"customer_group": inv.get("customer_group"),
"territory": inv.get("territory"),
"tax_id": inv.get("tax_id"),
"receivable_account": inv.debit_to,
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
"project": inv.project,
"owner": inv.owner,
"remarks": inv.remarks,
"sales_order": ", ".join(sales_order),
"delivery_note": ", ".join(delivery_note),
"cost_center": ", ".join(cost_center),
"warehouse": ", ".join(warehouse),
"currency": company_currency,
}
if additional_query_columns:
for col in additional_query_columns:
row.update({col: inv.get(col)})
row.update(
{
"customer_group": inv.get("customer_group"),
"territory": inv.get("territory"),
"tax_id": inv.get("tax_id"),
"receivable_account": inv.debit_to,
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
"project": inv.project,
"owner": inv.owner,
"remarks": inv.remarks,
"sales_order": ", ".join(sales_order),
"delivery_note": ", ".join(delivery_note),
"cost_center": ", ".join(cost_center),
"warehouse": ", ".join(warehouse),
"currency": company_currency,
}
)
# map income values
base_net_total = 0
for income_acc in income_accounts:
@@ -402,9 +395,6 @@ def get_conditions(filters):
def get_invoices(filters, additional_query_columns):
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
conditions = get_conditions(filters)
return frappe.db.sql(
"""
@@ -413,10 +403,10 @@ def get_invoices(filters, additional_query_columns):
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
is_internal_customer, represents_company, company {0}
from `tabSales Invoice`
where docstatus = 1 %s order by posting_date desc, name desc""".format(
additional_query_columns or ""
)
% conditions,
where docstatus = 1 {1}
order by posting_date desc, name desc""".format(
additional_query_columns, conditions
),
filters,
as_dict=1,
)

View File

@@ -67,8 +67,9 @@ def get_all_transfers(date, shareholder):
# condition = 'AND company = %(company)s '
return frappe.db.sql(
"""SELECT * FROM `tabShare Transfer`
WHERE (DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition})
WHERE ((DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition}))
AND docstatus = 1
ORDER BY date""".format(
condition=condition
),

View File

@@ -4,7 +4,8 @@
import frappe
from frappe import _
from frappe.utils import cstr, flt, formatdate, getdate
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, cstr, flt, formatdate, getdate
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -16,6 +17,7 @@ from erpnext.accounts.report.financial_statements import (
filter_out_zero_value_rows,
set_gl_entries_by_account,
)
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
value_fields = (
"opening_debit",
@@ -38,7 +40,7 @@ def validate_filters(filters):
if not filters.fiscal_year:
frappe.throw(_("Fiscal Year {0} is required").format(filters.fiscal_year))
fiscal_year = frappe.db.get_value(
fiscal_year = frappe.get_cached_value(
"Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
)
if not fiscal_year:
@@ -137,58 +139,140 @@ def get_opening_balances(filters):
def get_rootwise_opening_balances(filters, report_type):
additional_conditions = ""
if not filters.show_unclosed_fy_pl_balances:
additional_conditions = (
" and posting_date >= %(year_start_date)s" if report_type == "Profit and Loss" else ""
)
gle = []
if not flt(filters.with_period_closing_entry):
additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'"
if filters.cost_center:
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (
lft,
rgt,
)
if filters.project:
additional_conditions += " and project = %(project)s"
company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book")
if filters.get("include_default_book_entries"):
if filters.get("finance_book"):
if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
additional_conditions += (
" AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions += " AND (finance_book in (%(company_fb)s, '') OR finance_book IS NULL)"
else:
if filters.get("finance_book"):
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
else:
additional_conditions += " AND (finance_book in ('') OR finance_book IS NULL)"
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
fields=["posting_date", "name"],
order_by="posting_date desc",
limit=1,
)
accounting_dimensions = get_accounting_dimensions(as_list=False)
query_filters = {
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
"report_type": report_type,
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
"company_fb": company_fb,
}
if last_period_closing_voucher:
gle = get_opening_balance(
"Account Closing Balance",
filters,
report_type,
accounting_dimensions,
period_closing_voucher=last_period_closing_voucher[0].name,
)
# Report getting generate from the mid of a fiscal year
if getdate(last_period_closing_voucher[0].posting_date) < getdate(
add_days(filters.from_date, -1)
):
start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
gle += get_opening_balance(
"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
)
else:
gle = get_opening_balance("GL Entry", filters, report_type, accounting_dimensions)
opening = frappe._dict()
for d in gle:
opening.setdefault(
d.account,
{
"account": d.account,
"opening_debit": 0.0,
"opening_credit": 0.0,
},
)
opening[d.account]["opening_debit"] += flt(d.debit)
opening[d.account]["opening_credit"] += flt(d.credit)
return opening
def get_opening_balance(
doctype, filters, report_type, accounting_dimensions, period_closing_voucher=None, start_date=None
):
closing_balance = frappe.qb.DocType(doctype)
account = frappe.qb.DocType("Account")
opening_balance = (
frappe.qb.from_(closing_balance)
.select(
closing_balance.account,
closing_balance.account_currency,
Sum(closing_balance.debit).as_("debit"),
Sum(closing_balance.credit).as_("credit"),
Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
)
.where(
(closing_balance.company == filters.company)
& (
closing_balance.account.isin(
frappe.qb.from_(account).select("name").where(account.report_type == report_type)
)
)
)
.groupby(closing_balance.account)
)
if period_closing_voucher:
opening_balance = opening_balance.where(
closing_balance.period_closing_voucher == period_closing_voucher
)
else:
if start_date:
opening_balance = opening_balance.where(closing_balance.posting_date >= start_date)
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
else:
opening_balance = opening_balance.where(
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
)
if (
not filters.show_unclosed_fy_pl_balances
and report_type == "Profit and Loss"
and doctype == "GL Entry"
):
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
if not flt(filters.with_period_closing_entry):
if doctype == "Account Closing Balance":
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
else:
opening_balance = opening_balance.where(
closing_balance.voucher_type != "Period Closing Voucher"
)
if filters.cost_center:
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
cost_center = frappe.qb.DocType("Cost Center")
opening_balance = opening_balance.where(
closing_balance.cost_center.in_(
frappe.qb.from_(cost_center)
.select("name")
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
)
)
if filters.project:
opening_balance = opening_balance.where(closing_balance.project == filters.project)
if filters.get("include_default_book_entries"):
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
| (closing_balance.finance_book.isnull())
)
if accounting_dimensions:
for dimension in accounting_dimensions:
@@ -197,35 +281,20 @@ def get_rootwise_opening_balances(filters, report_type):
filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, filters.get(dimension.fieldname)
)
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
opening_balance = opening_balance.where(
closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
)
else:
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
opening_balance = opening_balance.where(
closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
)
query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
gle = opening_balance.run(as_dict=1)
gle = frappe.db.sql(
"""
select
account, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
where
company=%(company)s
{additional_conditions}
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
and account in (select name from `tabAccount` where report_type=%(report_type)s)
and is_cancelled = 0
group by account""".format(
additional_conditions=additional_conditions
),
query_filters,
as_dict=True,
)
if filters and filters.get("presentation_currency"):
convert_to_presentation_currency(gle, get_currency(filters))
opening = frappe._dict()
for d in gle:
opening.setdefault(d.account, d)
return opening
return gle
def calculate_values(accounts, gl_entries_by_account, opening_balances):

View File

@@ -1,5 +1,5 @@
import frappe
from frappe.utils import flt, formatdate, get_datetime_str
from frappe.utils import flt, formatdate, get_datetime_str, get_table_name
from erpnext import get_company_currency, get_default_company
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date
@@ -78,7 +78,7 @@ def get_rate_as_at(date, from_currency, to_currency):
return rate
def convert_to_presentation_currency(gl_entries, currency_info, company):
def convert_to_presentation_currency(gl_entries, currency_info):
"""
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
in `currency_info`.
@@ -93,7 +93,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
for entry in gl_entries:
account = entry["account"]
debit = flt(entry["debit"])
credit = flt(entry["credit"])
debit_in_account_currency = flt(entry["debit_in_account_currency"])
@@ -151,3 +150,32 @@ def get_invoiced_item_gross_margin(
result = sum(d.gross_profit for d in result)
return result
def get_query_columns(report_columns):
if not report_columns:
return ""
columns = []
for column in report_columns:
fieldname = column["fieldname"]
if doctype := column.get("_doctype"):
columns.append(f"`{get_table_name(doctype)}`.`{fieldname}`")
else:
columns.append(fieldname)
return ", " + ", ".join(columns)
def get_values_for_columns(report_columns, report_row):
values = {}
if not report_columns:
return values
for column in report_columns:
fieldname = column["fieldname"]
values[fieldname] = report_row.get(fieldname)
return values

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Voucher-wise Balance"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company"
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
"width": "60px"
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today(),
"width": "60px"
},
]
};

View File

@@ -0,0 +1,33 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2023-06-27 16:40:15.109554",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"letter_head": "LetterHead",
"modified": "2023-06-27 16:40:32.493725",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Voucher-wise Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "GL Entry",
"report_name": "Voucher-wise Balance",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
}

View File

@@ -0,0 +1,66 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
def execute(filters=None):
columns = get_columns()
data = get_data(filters)
return columns, data
def get_columns():
return [
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 300},
{
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
"width": 300,
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"options": "currency",
"width": 300,
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"options": "currency",
"width": 300,
},
]
def get_data(filters):
gle = frappe.qb.DocType("GL Entry")
query = (
frappe.qb.from_(gle)
.select(
gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")
)
.groupby(gle.voucher_no)
)
query = apply_filters(query, filters, gle)
gl_entries = query.run(as_dict=True)
unmatched = [entry for entry in gl_entries if entry.debit != entry.credit]
return unmatched
def apply_filters(query, filters, gle):
if filters.get("company"):
query = query.where(gle.company == filters.company)
if filters.get("voucher_type"):
query = query.where(gle.voucher_type == filters.voucher_type)
if filters.get("from_date"):
query = query.where(gle.posting_date >= filters.from_date)
if filters.get("to_date"):
query = query.where(gle.posting_date <= filters.to_date)
return query

View File

@@ -51,13 +51,25 @@ GL_REPOSTING_CHUNK = 100
@frappe.whitelist()
def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
):
return get_fiscal_years(date, fiscal_year, label, verbose, company, as_dict=as_dict)[0]
fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
)
if boolean:
return fiscal_years
else:
return fiscal_years[0]
def get_fiscal_years(
transaction_date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
transaction_date=None,
fiscal_year=None,
label="Date",
verbose=1,
company=None,
as_dict=False,
boolean=False,
):
fiscal_years = frappe.cache().hget("fiscal_years", company) or []
@@ -121,8 +133,12 @@ def get_fiscal_years(
if company:
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
if boolean:
return False
if verbose == 1:
frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)
@@ -221,11 +237,6 @@ def get_balance_on(
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
acc.check_permission("read")
if report_type == "Profit and Loss":
# for pl accounts, get balance within a fiscal year
cond.append(
"posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" % year_start_date
)
# different filter for group and ledger - improved performance
if acc.is_group:
cond.append(
@@ -836,7 +847,7 @@ def get_held_invoices(party_type, party):
if party_type == "Supplier":
held_invoices = frappe.db.sql(
"select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
"select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()",
as_dict=1,
)
held_invoices = set(d["name"] for d in held_invoices)
@@ -1393,6 +1404,50 @@ def check_and_delete_linked_reports(report):
frappe.delete_doc("Desktop Icon", icon)
def create_err_and_its_journals(companies: list = None) -> None:
if companies:
for company in companies:
err = frappe.new_doc("Exchange Rate Revaluation")
err.company = company.name
err.posting_date = nowdate()
err.rounding_loss_allowance = 0.0
err.fetch_and_calculate_accounts_data()
if err.accounts:
err.save().submit()
response = err.make_jv_entries()
if company.submit_err_jv:
jv = response.get("revaluation_jv", None)
jv and frappe.get_doc("Journal Entry", jv).submit()
jv = response.get("zero_balance_jv", None)
jv and frappe.get_doc("Journal Entry", jv).submit()
def auto_create_exchange_rate_revaluation_daily() -> None:
"""
Executed by background job
"""
companies = frappe.db.get_all(
"Company",
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
fields=["name", "submit_err_jv"],
)
create_err_and_its_journals(companies)
def auto_create_exchange_rate_revaluation_weekly() -> None:
"""
Executed by background job
"""
companies = frappe.db.get_all(
"Company",
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
fields=["name", "submit_err_jv"],
)
create_err_and_its_journals(companies)
def get_payment_ledger_entries(gl_entries, cancel=0):
ple_map = []
if gl_entries:

View File

@@ -34,6 +34,7 @@ def post_depreciation_entries(date=None):
date = today()
failed_asset_names = []
error_log_names = []
for asset_name in get_depreciable_assets(date):
try:
@@ -42,10 +43,12 @@ def post_depreciation_entries(date=None):
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
error_log = frappe.log_error(e)
error_log_names.append(error_log.name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names, error_log_names)
frappe.db.commit()
@@ -137,15 +140,15 @@ def make_depreciation_entry(asset_name, date=None):
je.flags.ignore_permissions = True
je.flags.planned_depr_entry = True
je.save()
if not je.meta.get_workflow():
je.submit()
d.db_set("journal_entry", je.name)
idx = cint(d.finance_book_id)
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
if not je.meta.get_workflow():
je.submit()
idx = cint(d.finance_book_id)
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
asset.db_set("depr_entry_posting_status", "Successful")
@@ -217,7 +220,7 @@ def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
def notify_depr_entry_posting_error(failed_asset_names, error_log_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
@@ -225,7 +228,8 @@ def notify_depr_entry_posting_error(failed_asset_names):
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
asset_links = get_comma_separated_links(failed_asset_names, "Asset")
error_log_links = get_comma_separated_links(error_log_names, "Error Log")
message = (
_("Hello,")
@@ -235,23 +239,26 @@ def notify_depr_entry_posting_error(failed_asset_names):
)
+ "."
+ "<br><br>"
+ _(
"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
+ _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
error_log_links
)
+ "."
+ "<br><br>"
+ _("Please share this email with your support team so that they can find and fix the issue.")
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
def get_comma_separated_links(names, doctype):
links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
for name in names:
links.append(get_link_to_form(doctype, name))
asset_links = ", ".join(asset_links)
links = ", ".join(links)
return asset_links
return links
@frappe.whitelist()
@@ -343,6 +350,9 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
def reverse_depreciation_entry_made_after_disposal(asset, date):
if not asset.calculate_depreciation:
return
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
@@ -483,18 +493,22 @@ def get_gl_entries_on_asset_disposal(
},
item=asset,
),
asset.get_gl_dict(
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
item=asset,
),
]
if accumulated_depr_amount:
gl_entries.append(
asset.get_gl_dict(
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
item=asset,
),
)
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
get_profit_gl_entries(

View File

@@ -14,7 +14,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
}
refresh() {
erpnext.hide_company();
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();
@@ -105,10 +104,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
return this.get_target_item_details();
}
target_asset() {
return this.get_target_asset_details();
}
item_code(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Stock Item") {
@@ -223,26 +218,6 @@ 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

@@ -11,13 +11,14 @@
"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",
"target_asset_name",
"target_asset_location",
"target_warehouse",
"target_qty",
"target_stock_uom",
@@ -85,14 +86,13 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"fieldname": "target_asset",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Asset",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
"no_copy": 1,
"options": "Asset"
"options": "Asset",
"read_only": 1
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
@@ -108,11 +108,11 @@
"fieldtype": "Column Break"
},
{
"fetch_from": "asset.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
@@ -158,7 +158,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length))",
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"label": "Consumed Stock Items"
@@ -189,7 +189,7 @@
"fieldname": "target_qty",
"fieldtype": "Float",
"label": "Target Qty",
"read_only_depends_on": "target_is_fixed_asset"
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
},
{
"fetch_from": "target_item_code.stock_uom",
@@ -227,7 +227,7 @@
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"label": "Consumed Asset Items"
"label": "Consumed Assets"
},
{
"fieldname": "asset_items",
@@ -266,7 +266,7 @@
"options": "Finance Book"
},
{
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.service_items && doc.service_items.length))",
"fieldname": "service_expenses_section",
"fieldtype": "Section Break",
"label": "Service Expenses"
@@ -329,12 +329,20 @@
"label": "Target Fixed Asset Account",
"options": "Account",
"read_only": 1
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"fieldname": "target_asset_location",
"fieldtype": "Link",
"label": "Target Asset Location",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
"options": "Location"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-09-12 15:09:40.771332",
"modified": "2023-06-22 14:17:07.995120",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization",

View File

@@ -7,7 +7,7 @@ import frappe
# import erpnext
from frappe import _
from frappe.utils import cint, flt
from frappe.utils import cint, flt, get_link_to_form
from six import string_types
import erpnext
@@ -43,7 +43,6 @@ force_fields = [
"target_has_batch_no",
"target_stock_uom",
"stock_uom",
"target_fixed_asset_account",
"fixed_asset_account",
"valuation_rate",
]
@@ -54,7 +53,6 @@ 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()
@@ -65,17 +63,18 @@ class AssetCapitalization(StockController):
def before_submit(self):
self.validate_source_mandatory()
if self.entry_type == "Capitalization":
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")
self.update_stock_ledger()
self.make_gl_entries()
self.update_target_asset()
self.restore_consumed_asset_items()
def set_title(self):
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
@@ -86,15 +85,6 @@ class AssetCapitalization(StockController):
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
self.set(k, v)
# Remove asset if item not a fixed asset
if not self.target_is_fixed_asset:
self.target_asset = None
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,9 +136,6 @@ class AssetCapitalization(StockController):
if not target_item.is_stock_item:
self.target_warehouse = None
if not target_item.is_fixed_asset:
self.target_asset = None
self.target_fixed_asset_account = None
if not target_item.has_batch_no:
self.target_batch_no = None
if not target_item.has_serial_no:
@@ -159,17 +146,6 @@ 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 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)
)
self.validate_asset(target_asset)
def validate_consumed_stock_item(self):
for d in self.stock_items:
if d.item_code:
@@ -379,7 +355,11 @@ class AssetCapitalization(StockController):
gl_entries, target_account, target_against, precision
)
if not self.stock_items and not self.service_items and self.are_all_asset_items_non_depreciable:
return []
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
return gl_entries
def get_target_account(self):
@@ -422,11 +402,14 @@ class AssetCapitalization(StockController):
def get_gl_entries_for_consumed_asset_items(
self, gl_entries, target_account, target_against, precision
):
self.are_all_asset_items_non_depreciable = True
# Consumed Assets
for item in self.asset_items:
asset = self.get_asset(item)
asset = frappe.get_doc("Asset", item.asset)
if asset.calculate_depreciation:
self.are_all_asset_items_non_depreciable = False
depreciate_asset(asset, self.posting_date)
asset.reload()
@@ -507,30 +490,41 @@ class AssetCapitalization(StockController):
)
)
def update_target_asset(self):
def create_target_asset(self):
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
if self.docstatus == 1 and self.entry_type == "Capitalization":
asset_doc = frappe.get_doc("Asset", self.target_asset)
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.prepare_depreciation_data()
asset_doc.flags.ignore_validate_update_after_submit = True
asset_doc.save()
elif self.docstatus == 2:
for item in self.asset_items:
asset = self.get_asset(item)
asset.db_set("disposal_date", None)
self.set_consumed_asset_status(asset)
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.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.flags.ignore_validate = True
asset_doc.insert()
if asset.calculate_depreciation:
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
reset_depreciation_schedule(asset, self.posting_date)
self.target_asset = asset_doc.name
def get_asset(self, item):
asset = frappe.get_doc("Asset", item.asset)
self.check_finance_books(item, asset)
return asset
self.target_fixed_asset_account = get_asset_category_account(
"fixed_asset_account", item=self.target_item_code, company=asset_doc.company
)
frappe.msgprint(
_(
"Asset {0} has been created. 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)
asset.db_set("disposal_date", None)
self.set_consumed_asset_status(asset)
if asset.calculate_depreciation:
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
reset_depreciation_schedule(asset, self.posting_date)
def set_consumed_asset_status(self, asset):
if self.docstatus == 1:
@@ -580,33 +574,6 @@ 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):

View File

@@ -39,13 +39,6 @@ class TestAssetCapitalization(unittest.TestCase):
total_amount = 103000
# Create assets
target_asset = create_asset(
asset_name="Asset Capitalization Target Asset",
submit=1,
warehouse="Stores - TCP1",
company=company,
)
consumed_asset = create_asset(
asset_name="Asset Capitalization Consumable Asset",
asset_value=consumed_asset_value,
@@ -57,7 +50,8 @@ class TestAssetCapitalization(unittest.TestCase):
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="Capitalization",
target_asset=target_asset.name,
target_item_code="Macbook Pro",
target_asset_location="Test Location",
stock_qty=stock_qty,
stock_rate=stock_rate,
consumed_asset=consumed_asset.name,
@@ -86,7 +80,7 @@ class TestAssetCapitalization(unittest.TestCase):
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
# Test Target Asset values
target_asset.reload()
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)
@@ -134,13 +128,6 @@ class TestAssetCapitalization(unittest.TestCase):
total_amount = 103000
# Create assets
target_asset = create_asset(
asset_name="Asset Capitalization Target Asset",
submit=1,
warehouse="Stores - _TC",
company=company,
)
consumed_asset = create_asset(
asset_name="Asset Capitalization Consumable Asset",
asset_value=consumed_asset_value,
@@ -152,7 +139,8 @@ class TestAssetCapitalization(unittest.TestCase):
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="Capitalization",
target_asset=target_asset.name,
target_item_code="Macbook Pro",
target_asset_location="Test Location",
stock_qty=stock_qty,
stock_rate=stock_rate,
consumed_asset=consumed_asset.name,
@@ -181,7 +169,7 @@ class TestAssetCapitalization(unittest.TestCase):
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
# Test Target Asset values
target_asset.reload()
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)
@@ -343,6 +331,7 @@ def create_asset_capitalization(**args):
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
"target_item_code": target_item_code,
"target_asset": target_asset.name,
"target_asset_location": "Test Location",
"target_warehouse": target_warehouse,
"target_qty": flt(args.target_qty) or 1,
"target_batch_no": args.target_batch_no,

View File

@@ -70,19 +70,21 @@ frappe.ui.form.on('Asset Movement', {
else if (frm.doc.purpose === 'Issue') {
fieldnames_to_be_altered = {
target_location: { read_only: 1, reqd: 0 },
source_location: { read_only: 1, reqd: 1 },
source_location: { read_only: 1, reqd: 0 },
from_employee: { read_only: 1, reqd: 0 },
to_employee: { read_only: 0, reqd: 1 }
};
}
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
Object.keys(property_to_be_altered).forEach(property => {
let value = property_to_be_altered[property];
frm.set_df_property(fieldname, property, value, cdn, 'assets');
if (fieldnames_to_be_altered) {
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
Object.keys(property_to_be_altered).forEach(property => {
let value = property_to_be_altered[property];
frm.fields_dict['assets'].grid.update_docfield_property(fieldname, property, value);
});
});
});
frm.refresh_field('assets');
frm.refresh_field('assets');
}
}
});

View File

@@ -37,6 +37,7 @@
"reqd": 1
},
{
"default": "Now",
"fieldname": "transaction_date",
"fieldtype": "Datetime",
"in_list_view": 1,
@@ -95,10 +96,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-01-22 12:30:55.295670",
"modified": "2023-06-28 16:54:26.571083",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Movement",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
@@ -148,5 +150,6 @@
}
],
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -28,25 +28,20 @@ class AssetMovement(Document):
def validate_location(self):
for d in self.assets:
if self.purpose in ["Transfer", "Issue"]:
if not d.source_location:
d.source_location = frappe.db.get_value("Asset", d.asset, "location")
if not d.source_location:
frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset))
current_location = frappe.db.get_value("Asset", d.asset, "location")
if d.source_location:
current_location = frappe.db.get_value("Asset", d.asset, "location")
if current_location != d.source_location:
frappe.throw(
_("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location)
)
else:
d.source_location = current_location
if self.purpose == "Issue":
if d.target_location:
frappe.throw(
_(
"Issuing cannot be done to a location. Please enter employee who has issued Asset {0}"
"Issuing cannot be done to a location. Please enter employee to issue the Asset {0} to"
).format(d.asset),
title=_("Incorrect Movement Purpose"),
)
@@ -107,12 +102,12 @@ class AssetMovement(Document):
)
def on_submit(self):
self.set_latest_location_in_asset()
self.set_latest_location_and_custodian_in_asset()
def on_cancel(self):
self.set_latest_location_in_asset()
self.set_latest_location_and_custodian_in_asset()
def set_latest_location_in_asset(self):
def set_latest_location_and_custodian_in_asset(self):
current_location, current_employee = "", ""
cond = "1=1"

View File

@@ -47,7 +47,7 @@ class TestAssetMovement(unittest.TestCase):
if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
movement1 = create_asset_movement(
create_asset_movement(
purpose="Transfer",
company=asset.company,
assets=[
@@ -58,7 +58,7 @@ class TestAssetMovement(unittest.TestCase):
)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
create_asset_movement(
movement1 = create_asset_movement(
purpose="Transfer",
company=asset.company,
assets=[
@@ -70,21 +70,32 @@ class TestAssetMovement(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
employee = make_employee("testassetmovemp@example.com", company="_Test Company")
create_asset_movement(
purpose="Issue",
company=asset.company,
assets=[{"asset": asset.name, "source_location": "Test Location", "to_employee": employee}],
assets=[{"asset": asset.name, "source_location": "Test Location 2", "to_employee": employee}],
reference_doctype="Purchase Receipt",
reference_name=pr.name,
)
# after issuing asset should belong to an employee not at a location
# after issuing, asset should belong to an employee not at a location
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
create_asset_movement(
purpose="Receipt",
company=asset.company,
assets=[{"asset": asset.name, "from_employee": employee, "target_location": "Test Location"}],
reference_doctype="Purchase Receipt",
reference_name=pr.name,
)
# after receiving, asset should belong to a location not at an employee
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
def test_last_movement_cancellation(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"

View File

@@ -19,56 +19,6 @@ frappe.query_reports["Fixed Asset Register"] = {
options: "\nIn Location\nDisposed",
default: 'In Location'
},
{
"fieldname":"filter_based_on",
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["Fiscal Year", "Date Range"],
"default": "Fiscal Year",
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("Start Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("End Date"),
"fieldtype": "Date",
"default": frappe.datetime.nowdate(),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
"reqd": 1
},
{
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
"reqd": 1
},
{
"fieldname":"to_fiscal_year",
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
"reqd": 1
},
{
"fieldname":"date_based_on",
"label": __("Date Based On"),
"fieldtype": "Select",
"options": ["Purchase Date", "Available For Use Date"],
"default": "Purchase Date",
"reqd": 1
},
{
fieldname:"asset_category",
label: __("Asset Category"),
@@ -89,22 +39,67 @@ frappe.query_reports["Fixed Asset Register"] = {
default: "--Select a group--",
reqd: 1
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book",
depends_on: "eval: doc.filter_by_finance_book == 1",
},
{
fieldname:"filter_by_finance_book",
label: __("Filter by Finance Book"),
fieldtype: "Check"
},
{
fieldname:"only_existing_assets",
label: __("Only existing assets"),
fieldtype: "Check"
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book",
},
{
"fieldname": "include_default_book_assets",
"label": __("Include Default Book Assets"),
"fieldtype": "Check",
"default": 1
},
{
"fieldname":"filter_based_on",
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["--Select a period--", "Fiscal Year", "Date Range"],
"default": "--Select a period--",
},
{
"fieldname":"from_date",
"label": __("Start Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
},
{
"fieldname":"to_date",
"label": __("End Date"),
"fieldtype": "Date",
"default": frappe.datetime.nowdate(),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
},
{
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
"fieldname":"to_fiscal_year",
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
"fieldname":"date_based_on",
"label": __("Date Based On"),
"fieldtype": "Select",
"options": ["Purchase Date", "Available For Use Date"],
"default": "Purchase Date",
"depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'",
},
]
};

View File

@@ -2,9 +2,11 @@
# For license information, please see license.txt
from itertools import chain
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cstr, flt, formatdate, getdate
from erpnext.accounts.report.financial_statements import (
@@ -13,7 +15,6 @@ from erpnext.accounts.report.financial_statements import (
validate_fiscal_year,
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
def execute(filters=None):
@@ -64,11 +65,9 @@ def get_conditions(filters):
def get_data(filters):
data = []
conditions = get_conditions(filters)
depreciation_amount_map = get_finance_book_value_map(filters)
pr_supplier_map = get_purchase_receipt_supplier_map()
pi_supplier_map = get_purchase_invoice_supplier_map()
@@ -102,20 +101,31 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = None
assets_linked_to_fb = get_assets_linked_to_fb(filters)
if filters.filter_by_finance_book:
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.include_default_book_assets and company_fb:
finance_book = company_fb
elif filters.finance_book:
finance_book = filters.finance_book
else:
finance_book = None
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
for asset in assets_record:
if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb:
if (
assets_linked_to_fb
and asset.calculate_depreciation
and asset.asset_id not in assets_linked_to_fb
):
continue
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
asset_value = get_asset_value_after_depreciation(
asset.asset_id, finance_book
) or get_asset_value_after_depreciation(asset.asset_id)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
@@ -126,7 +136,7 @@ def get_data(filters):
or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map),
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
@@ -140,14 +150,23 @@ def get_data(filters):
def prepare_chart_data(data, filters):
labels_values_map = {}
date_field = frappe.scrub(filters.date_based_on)
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
filters_filter_based_on = "Date Range"
date_field = "purchase_date"
filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
else:
filters_filter_based_on = filters.filter_based_on
date_field = frappe.scrub(filters.date_based_on)
filters_from_date = filters.from_date
filters_to_date = filters.to_date
period_list = get_period_list(
filters.from_fiscal_year,
filters.to_fiscal_year,
filters.from_date,
filters.to_date,
filters.filter_based_on,
filters_from_date,
filters_to_date,
filters_filter_based_on,
"Monthly",
company=filters.company,
ignore_fiscal_year=True,
@@ -184,57 +203,76 @@ def prepare_chart_data(data, filters):
}
def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
if asset.calculate_depreciation:
depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
else:
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
def get_assets_linked_to_fb(filters):
afb = frappe.qb.DocType("Asset Finance Book")
return flt(depr_amount, 2)
def get_finance_book_value_map(filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
return frappe._dict(
frappe.db.sql(
""" Select
parent, SUM(depreciation_amount)
FROM `tabDepreciation Schedule`
WHERE
parentfield='schedules'
AND schedule_date<=%s
AND journal_entry IS NOT NULL
AND ifnull(finance_book, '')=%s
GROUP BY parent""",
(date, cstr(filters.finance_book or "")),
)
query = frappe.qb.from_(afb).select(
afb.parent,
)
if filters.include_default_book_assets:
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
def get_manual_depreciation_amount_of_asset(asset, filters):
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'"))
query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (afb.finance_book.isnull())
)
else:
query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull())
)
assets_linked_to_fb = list(chain(*query.run(as_list=1)))
return assets_linked_to_fb
def get_depreciation_amount_of_asset(asset, depreciation_amount_map):
return depreciation_amount_map.get(asset.asset_id) or 0.0
def get_asset_depreciation_amount_map(filters, finance_book):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
asset = frappe.qb.DocType("Asset")
gle = frappe.qb.DocType("GL Entry")
aca = frappe.qb.DocType("Asset Category Account")
company = frappe.qb.DocType("Company")
result = (
query = (
frappe.qb.from_(gle)
.select(Sum(gle.debit))
.where(gle.against_voucher == asset.asset_id)
.where(gle.account == depreciation_expense_account)
.join(asset)
.on(gle.against_voucher == asset.name)
.join(aca)
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
.join(company)
.on(company.name == asset.company)
.select(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount"))
.where(
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(gle.posting_date <= date)
).run()
.where(asset.docstatus == 1)
.groupby(asset.name)
)
if result and result[0] and result[0][0]:
depr_amount = result[0][0]
if finance_book:
query = query.where(
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
)
else:
depr_amount = 0
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
return depr_amount
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
query = query.where(gle.posting_date <= date)
asset_depr_amount_map = query.run()
return dict(asset_depr_amount_map)
def get_purchase_receipt_supplier_map():

View File

@@ -286,7 +286,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
source_name: this.frm.doc.supplier,
target: this.frm,
setters: {
company: me.frm.doc.company
company: this.frm.doc.company
},
get_query_filters: {
docstatus: ["!=", 2],

View File

@@ -457,7 +457,7 @@
"link_fieldname": "party"
}
],
"modified": "2022-11-09 18:02:59.075203",
"modified": "2023-05-09 15:34:13.408932",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -329,6 +329,11 @@ def make_default_records():
"variable_label": "Total Shipments",
"path": "get_total_shipments",
},
{
"param_name": "total_ordered",
"variable_label": "Total Ordered",
"path": "get_ordered_qty",
},
]
install_standing_docs = [
{

View File

@@ -7,6 +7,7 @@ import sys
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import getdate
@@ -422,6 +423,23 @@ def get_total_shipments(scorecard):
return data
def get_ordered_qty(scorecard):
"""Returns the total number of ordered quantity (based on Purchase Orders)"""
po = frappe.qb.DocType("Purchase Order")
return (
frappe.qb.from_(po)
.select(Sum(po.total_qty))
.where(
(po.supplier == scorecard.supplier)
& (po.docstatus == 1)
& (po.transaction_date >= scorecard.get("start_date"))
& (po.transaction_date <= scorecard.get("end_date"))
)
).run(as_list=True)[0][0] or 0
def get_rfq_total_number(scorecard):
"""Gets the total number of RFQs sent to supplier"""
supplier = frappe.get_doc("Supplier", scorecard.supplier)

View File

@@ -30,6 +30,8 @@ class BuyingController(SubcontractingController):
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
def validate(self):
self.set_rate_for_standalone_debit_note()
super(BuyingController, self).validate()
if getattr(self, "supplier", None) and not self.supplier_name:
self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name")
@@ -72,6 +74,30 @@ 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:
# 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
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@@ -384,18 +410,23 @@ class BuyingController(SubcontractingController):
# validate rate with ref PR
def validate_rejected_warehouse(self):
for d in self.get("items"):
if flt(d.rejected_qty) and not d.rejected_warehouse:
for item in self.get("items"):
if flt(item.rejected_qty) and not item.rejected_warehouse:
if self.rejected_warehouse:
d.rejected_warehouse = self.rejected_warehouse
item.rejected_warehouse = self.rejected_warehouse
if not d.rejected_warehouse:
if not item.rejected_warehouse:
frappe.throw(
_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
d.idx, d.item_code
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
item.idx, item.item_code
)
)
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
frappe.throw(
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
)
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):
for d in self.get("items"):
@@ -445,7 +476,7 @@ class BuyingController(SubcontractingController):
continue
if d.warehouse:
pr_qty = flt(d.qty) * flt(d.conversion_factor)
pr_qty = flt(flt(d.qty) * flt(d.conversion_factor), d.precision("stock_qty"))
if pr_qty:
@@ -507,7 +538,7 @@ class BuyingController(SubcontractingController):
d,
{
"warehouse": d.rejected_warehouse,
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
"actual_qty": flt(flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")),
"serial_no": cstr(d.rejected_serial_no).strip(),
"incoming_rate": 0.0,
},
@@ -683,6 +714,7 @@ class BuyingController(SubcontractingController):
"asset_quantity": row.qty if is_grouped_asset else 0,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
"cost_center": row.cost_center,
}
)

View File

@@ -617,6 +617,13 @@ def get_filters(
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
if (
voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and item_row
and item_row.get("warehouse")
):
filters["warehouse"] = item_row.get("warehouse")
return filters

View File

@@ -201,6 +201,12 @@ class StockController(AccountsController):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
if not expense_account:
frappe.throw(
_(
"Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
).format(frappe.bold(self.company))
)
gl_list.append(
self.get_gl_dict(

View File

@@ -169,45 +169,50 @@ class SubcontractingController(StockController):
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
def __get_transferred_items(self):
fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"]
alias_dict = {
"item_code": "rm_item_code",
"subcontracted_item": "main_item_code",
"basic_rate": "rate",
}
se = frappe.qb.DocType("Stock Entry")
se_detail = frappe.qb.DocType("Stock Entry Detail")
child_table_fields = [
"item_code",
"item_name",
"description",
"qty",
"basic_rate",
"amount",
"serial_no",
"uom",
"subcontracted_item",
"stock_uom",
"batch_no",
"conversion_factor",
"s_warehouse",
"t_warehouse",
"item_group",
self.subcontract_data.rm_detail_field,
]
query = (
frappe.qb.from_(se)
.inner_join(se_detail)
.on(se.name == se_detail.parent)
.select(
se[self.subcontract_data.order_field],
se_detail.item_code.as_("rm_item_code"),
se_detail.item_name,
se_detail.description,
(
frappe.qb.terms.Case()
.when(((se.purpose == "Material Transfer") & (se.is_return == 1)), -1 * se_detail.qty)
.else_(se_detail.qty)
).as_("qty"),
se_detail.basic_rate.as_("rate"),
se_detail.amount,
se_detail.serial_no,
se_detail.uom,
se_detail.subcontracted_item.as_("main_item_code"),
se_detail.stock_uom,
se_detail.batch_no,
se_detail.conversion_factor,
se_detail.s_warehouse,
se_detail.t_warehouse,
se_detail.item_group,
se_detail[self.subcontract_data.rm_detail_field],
)
.where(
(se.docstatus == 1)
& (se[self.subcontract_data.order_field].isin(self.subcontract_orders))
& (
(se.purpose == "Send to Subcontractor")
| ((se.purpose == "Material Transfer") & (se.is_return == 1))
)
)
)
if self.backflush_based_on == "BOM":
child_table_fields.append("original_item")
query = query.select(se_detail.original_item)
for field in child_table_fields:
fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}")
filters = [
["Stock Entry", "docstatus", "=", 1],
["Stock Entry", "purpose", "=", "Send to Subcontractor"],
["Stock Entry", self.subcontract_data.order_field, "in", self.subcontract_orders],
]
return frappe.get_all("Stock Entry", fields=fields, filters=filters)
return query.run(as_dict=True)
def __set_alternative_item_details(self, row):
if row.get("original_item"):

View File

@@ -3,7 +3,7 @@
import frappe
import frappe.defaults
from frappe import _, throw
from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.contacts.doctype.contact.contact import get_contact_name
from frappe.utils import cint, cstr, flt, get_fullname
@@ -201,6 +201,11 @@ def get_shopping_cart_menu(context=None):
@frappe.whitelist()
def add_new_address(doc):
doc = frappe.parse_json(doc)
address_title = doc.get("address_title")
if frappe.db.exists("Address", {"address_title": address_title}):
msg = f"The address with the title {bold(address_title)} already exists. Please change the title accordingly."
frappe.throw(_(msg), title=_("Address Already Exists"))
doc.update({"doctype": "Address"})
address = frappe.get_doc(doc)
address.save(ignore_permissions=True)

View File

@@ -162,6 +162,7 @@ def get_next_attribute_and_values(item_code, selected_attributes):
product_info = get_item_variant_price_dict(exact_match[0], cart_settings)
if product_info:
product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item")
product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock)
else:
product_info = None

View File

@@ -420,6 +420,10 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
],
"weekly": [
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",
@@ -523,6 +527,7 @@ accounting_dimension_doctypes = [
"Subcontracting Order Item",
"Subcontracting Receipt",
"Subcontracting Receipt Item",
"Account Closing Balance",
]
# get matching queries for Bank Reconciliation

View File

@@ -290,8 +290,8 @@ def get_last_accrual_date(loan, posting_date):
# interest for last interest accrual date is already booked, so add 1 day
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
if last_disbursement_date and getdate(last_disbursement_date) > getdate(
last_interest_accrual_date
if last_disbursement_date and getdate(last_disbursement_date) > add_days(
getdate(last_interest_accrual_date), 1
):
last_interest_accrual_date = last_disbursement_date

View File

@@ -99,7 +99,7 @@ frappe.ui.form.on('Production Plan', {
}, __('Create'));
}
if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
if (frm.doc.mr_items && frm.doc.mr_items.length && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
frm.add_custom_button(__("Material Request"), ()=> {
frm.trigger("make_material_request");
}, __('Create'));

View File

@@ -515,6 +515,9 @@ class ProductionPlan(Document):
self.show_list_created_message("Work Order", wo_list)
self.show_list_created_message("Purchase Order", po_list)
if not wo_list:
frappe.msgprint(_("No Work Orders were created"))
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
items_data = self.get_production_items()
@@ -618,6 +621,9 @@ class ProductionPlan(Document):
def create_work_order(self, item):
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
if item.get("qty") <= 0:
return
wo = frappe.new_doc("Work Order")
wo.update(item)
wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date")
@@ -691,10 +697,9 @@ class ProductionPlan(Document):
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")
material_request.save()
if self.get("submit_material_request"):
material_request.submit()
else:
material_request.save()
frappe.flags.mute_messages = False

View File

@@ -76,6 +76,13 @@ class TestProductionPlan(FrappeTestCase):
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
)
pln.make_work_order()
nwork_orders = frappe.get_all(
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
)
self.assertTrue(len(work_orders), len(nwork_orders))
self.assertTrue(len(work_orders), len(pln.po_items))
for name in material_requests:

View File

@@ -1657,6 +1657,61 @@ class TestWorkOrder(FrappeTestCase):
job_card2.time_logs = []
job_card2.save()
def test_make_serial_no_batch_from_work_order_for_serial_no(self):
item_code = "Test Serial No Item For Work Order"
warehouse = "_Test Warehouse - _TC"
raw_materials = [
"Test RM Item 1 for Serial No Item In Work Order",
]
make_item(
item_code,
{
"has_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "TSNIFWO-.#####",
},
)
for rm_item in raw_materials:
make_item(
rm_item,
{
"has_stock_item": 1,
},
)
test_stock_entry.make_stock_entry(item_code=rm_item, target=warehouse, qty=10, basic_rate=100)
bom = make_bom(item=item_code, raw_materials=raw_materials)
frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 1)
wo_order = make_wo_order_test_record(
item=item_code,
bom_no=bom.name,
qty=5,
skip_transfer=1,
from_wip_warehouse=1,
)
serial_nos = frappe.get_all(
"Serial No",
filters={"item_code": item_code, "work_order": wo_order.name},
)
serial_nos = [d.name for d in serial_nos]
self.assertEqual(len(serial_nos), 5)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
stock_entry.submit()
for row in stock_entry.items:
if row.is_finished_item:
self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 0)
def prepare_data_for_workstation_type_check():
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
@@ -1886,6 +1941,7 @@ def make_wo_order_test_record(**args):
wo_order.sales_order = args.sales_order or None
wo_order.planned_start_date = args.planned_start_date or now()
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
wo_order.from_wip_warehouse = args.from_wip_warehouse or None
if args.source_warehouse:
for item in wo_order.get("required_items"):

View File

@@ -33,10 +33,9 @@ def get_data(filters: Filters) -> Data:
wo.name,
wo.status,
wo.production_item,
wo.qty,
wo.produced_qty,
wo.process_loss_qty,
(wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"),
wo.qty.as_("qty_to_manufacture"),
Sum(se.total_incoming_value).as_("total_fg_value"),
Sum(se.total_outgoing_value).as_("total_rm_value"),
)
@@ -44,6 +43,7 @@ def get_data(filters: Filters) -> Data:
(wo.process_loss_qty > 0)
& (wo.company == filters.company)
& (se.docstatus == 1)
& (se.purpose == "Manufacture")
& (se.posting_date.between(filters.from_date, filters.to_date))
)
.groupby(se.work_order)
@@ -79,20 +79,30 @@ def get_columns() -> Columns:
"width": "100",
},
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"},
{
"label": _("Qty To Manufacture"),
"fieldname": "qty_to_manufacture",
"fieldtype": "Float",
"width": "150",
},
{
"label": _("Manufactured Qty"),
"fieldname": "produced_qty",
"fieldtype": "Float",
"width": "150",
},
{"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"},
{
"label": _("Actual Manufactured Qty"),
"fieldname": "actual_produced_qty",
"label": _("Process Loss Qty"),
"fieldname": "process_loss_qty",
"fieldtype": "Float",
"width": "150",
},
{
"label": _("Process Loss Value"),
"fieldname": "total_pl_value",
"fieldtype": "Float",
"width": "150",
},
{"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"},
{"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"},
{
"label": _("Raw Material Value"),
@@ -105,5 +115,5 @@ def get_columns() -> Columns:
def update_data_with_total_pl_value(data: Data) -> None:
for row in data:
value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
value_per_unit_fg = row["total_fg_value"] / row["qty_to_manufacture"]
row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg

View File

@@ -333,3 +333,7 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.update_company_in_ldc
erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes
erpnext.patches.v14_0.cleanup_workspaces
erpnext.patches.v14_0.enable_allow_existing_serial_no
erpnext.patches.v14_0.set_report_in_process_SOA
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
erpnext.patches.v14_0.update_closing_balances #15-07-2023

View File

@@ -0,0 +1,31 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
doctype = "Account Closing Balance"
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

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
def execute():
frappe.reload_doc("stock", "doctype", frappe.scrub("Stock Settings"))
frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1, update_modified=False)

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
def execute():
process_soa = frappe.qb.DocType("Process Statement Of Accounts")
q = frappe.qb.update(process_soa).set(process_soa.report, "General Ledger")
q.run()

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