Compare commits

..

191 Commits

Author SHA1 Message Date
Frappe PR Bot
c910b8ab03 chore(release): Bumped to Version 14.70.2
## [14.70.2](https://github.com/frappe/erpnext/compare/v14.70.1...v14.70.2) (2024-05-23)

### Bug Fixes

* Fetch outstanding and total amount for reference journal entry ([94c3ee6](94c3ee645d))
2024-05-23 11:01:54 +00:00
ruthra kumar
b9ebb50a02 Merge pull request #41600 from frappe/mergify/bp/version-14/pr-40957
fix: Fetch correct outstanding and total amount for reference journal entry (backport #40920) (backport #40957)
2024-05-23 16:30:31 +05:30
ruthra kumar
38cc28a4c3 chore: resolve conflicts
(cherry picked from commit 5230d411bf)
2024-05-23 10:39:16 +00:00
ruthra kumar
bbb9b9e3b6 chore: remove unused imports
(cherry picked from commit a6bf7c1ebd)
2024-05-23 10:39:15 +00:00
Nabin Hait
94c3ee645d fix: Fetch outstanding and total amount for reference journal entry
(cherry picked from commit f331f9b15c)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
(cherry picked from commit 50f6afd588)
2024-05-23 10:39:15 +00:00
Frappe PR Bot
b7d6a54bed chore(release): Bumped to Version 14.70.1
## [14.70.1](https://github.com/frappe/erpnext/compare/v14.70.0...v14.70.1) (2024-05-22)

### Bug Fixes

* minor Dr and Cr between Purchase Receipt and Purchase Invoice ([82d206b](82d206b709))
* possible sql error on General Ledger ([dfb4c47](dfb4c47089))
* print format bold for field "total" ([89d507e](89d507e07e))
* priority not working for multiple pricing rules (backport [#41516](https://github.com/frappe/erpnext/issues/41516)) ([#41524](https://github.com/frappe/erpnext/issues/41524)) ([97fdda8](97fdda8a7c))
* typerror on hide_fields ([331a743](331a743d69))
* validate reorder group warehouse (backport [#41478](https://github.com/frappe/erpnext/issues/41478)) ([#41479](https://github.com/frappe/erpnext/issues/41479)) ([2659535](26595351cc))
2024-05-22 08:46:30 +00:00
ruthra kumar
05e4dae1b8 Merge pull request #41575 from frappe/version-14-hotfix
chore: release v14
2024-05-22 14:15:11 +05:30
mergify[bot]
97fdda8a7c fix: priority not working for multiple pricing rules (backport #41516) (#41524)
* fix: priority not working for multiple pricing rules (#41516)

(cherry picked from commit 5cf5b18aea)

# Conflicts:
#	erpnext/accounts/doctype/pricing_rule/pricing_rule.json
#	erpnext/accounts/doctype/pricing_rule/pricing_rule.py
#	erpnext/patches.txt

* chore: fix conflicts

* chore: fix conflicts

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-20 13:39:39 +05:30
ruthra kumar
9802333397 Merge pull request #41527 from frappe/mergify/bp/version-14-hotfix/pr-41523
fix: minor Dr and Cr between Purchase Receipt and Purchase Invoice (backport #41523)
2024-05-20 08:32:24 +05:30
ruthra kumar
82d206b709 fix: minor Dr and Cr between Purchase Receipt and Purchase Invoice
This applies for Provisional Accounting for Non-stock items

(cherry picked from commit 1c0a24424a)
2024-05-17 12:21:28 +00:00
ruthra kumar
80810c2ebb Merge pull request #41518 from frappe/mergify/bp/version-14-hotfix/pr-41517
fix: typerror on hide_fields (backport #41517)
2024-05-17 14:54:06 +05:30
ruthra kumar
331a743d69 fix: typerror on hide_fields
(cherry picked from commit deb9766f2a)
2024-05-17 14:51:57 +05:30
ruthra kumar
32a1fea8f0 Merge pull request #41512 from frappe/mergify/bp/version-14-hotfix/pr-41511
fix: possible sql error on General Ledger (backport #41511)
2024-05-17 11:17:24 +05:30
ruthra kumar
dfb4c47089 fix: possible sql error on General Ledger
(cherry picked from commit 76131f8e10)
2024-05-17 05:26:54 +00:00
mergify[bot]
0cbf049608 chore(BOM Explorer): display items in the same order as in the BOM (backport #41496) (#41510)
chore(BOM Explorer): display items in the same order as in the BOM (#41496)

(cherry picked from commit bd381cc0c6)

Co-authored-by: Samuel Danieli <23150094+scdanieli@users.noreply.github.com>
2024-05-17 09:37:03 +05:30
ruthra kumar
3f0cb47464 Merge pull request #41500 from frappe/mergify/bp/version-14-hotfix/pr-40072
fix: print format bold for field "total" (backport #40072)
2024-05-16 15:35:48 +05:30
Nihantra C. Patel
89d507e07e fix: print format bold for field "total"
(cherry picked from commit 3c9640df27)
2024-05-16 10:04:03 +00:00
mergify[bot]
26595351cc fix: validate reorder group warehouse (backport #41478) (#41479)
fix: validate reorder group warehouse (#41478)

(cherry picked from commit 0363afcfd0)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-15 15:15:41 +05:30
Frappe PR Bot
dfaca93292 chore(release): Bumped to Version 14.70.0
# [14.70.0](https://github.com/frappe/erpnext/compare/v14.69.1...v14.70.0) (2024-05-15)

### Bug Fixes

* "Based on" field always has the value "Not applicable" ([1078a98](1078a98cce))
* address filter and quotation to for prospect ([c9e7f45](c9e7f450c5))
* address filter and quotation to for prospect ([6902780](690278042d))
* address filter and quotation to for prospect ([754e193](754e193c76))
* consistent use of "Address & Contact" (backport [#41386](https://github.com/frappe/erpnext/issues/41386)) ([#41387](https://github.com/frappe/erpnext/issues/41387)) ([256d6a4](256d6a47ac))
* data getting override in delivery trip (backport [#41431](https://github.com/frappe/erpnext/issues/41431)) ([#41432](https://github.com/frappe/erpnext/issues/41432)) ([5366356](5366356400))
* Default dates in report ([c3244f0](c3244f009b))
* default fiscal year ([7a380f5](7a380f584d))
* Duplicate party name column in AR/AP report ([a8be5f0](a8be5f0789))
* PSOA ageing ([e69e540](e69e5404d3))

### Features

* allow to pick manually qty / batches / serial nos (backport [#40723](https://github.com/frappe/erpnext/issues/40723)) ([#41435](https://github.com/frappe/erpnext/issues/41435)) ([7b28d7d](7b28d7d2b8))
2024-05-15 05:12:14 +00:00
Deepesh Garg
40de3f3481 Merge pull request #41458 from frappe/version-14-hotfix
chore: release v14
2024-05-15 10:40:58 +05:30
Deepesh Garg
fc2614612b Merge pull request #41466 from frappe/mergify/bp/version-14-hotfix/pr-41258
fix: PSOA ageing (#41258)
2024-05-14 21:19:17 +05:30
Deepesh Garg
e69e5404d3 fix: PSOA ageing
(cherry picked from commit fed2d11905)
2024-05-14 14:40:36 +00:00
Nabin Hait
14ec6351ae Merge pull request #41448 from frappe/mergify/bp/version-14-hotfix/pr-41412
fix: Duplicate party name column in AR/AP report (backport #41412)
2024-05-14 20:02:56 +05:30
Deepesh Garg
a8be5f0789 fix: Duplicate party name column in AR/AP report
(cherry picked from commit 7501fe8ebd)
2024-05-14 02:53:49 +00:00
Deepesh Garg
42312c5bba Merge pull request #41170 from deepeshgarg007/default_dates_in_reports
fix: Default dates in report
2024-05-14 08:21:51 +05:30
mergify[bot]
7b28d7d2b8 feat: allow to pick manually qty / batches / serial nos (backport #40723) (#41435)
* feat: allow to pick manually qty / batches / serial nos

(cherry picked from commit 50dd9fa8a3)

# Conflicts:
#	erpnext/stock/doctype/pick_list/pick_list.json
#	erpnext/stock/doctype/pick_list/pick_list.py

* chore: fix conflicts

* chore: fix conflicts

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-05-13 21:37:13 +05:30
mergify[bot]
5366356400 fix: data getting override in delivery trip (backport #41431) (#41432)
fix: data getting override in delivery trip (#41431)

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

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-13 16:31:34 +05:30
Deepesh Garg
7a380f584d fix: default fiscal year 2024-05-11 17:51:53 +05:30
mergify[bot]
256d6a47ac fix: consistent use of "Address & Contact" (backport #41386) (#41387)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: consistent use of "Address & Contact" (#41386)
2024-05-10 17:42:27 +02:00
Akhil Narang
67e06615a3 Merge pull request #41406 from frappe/mergify/bp/version-14-hotfix/pr-37031
fix: "Based on" field always has the value "Not applicable" (backport #37031)
2024-05-10 16:44:33 +05:30
HarryPaulo
1078a98cce fix: "Based on" field always has the value "Not applicable"
(cherry picked from commit fae640c56f)
2024-05-10 11:13:03 +00:00
ruthra kumar
d87ffae03f Merge pull request #41394 from frappe/mergify/bp/version-14-hotfix/pr-41040
fix: address filter and quotation to for prospect (backport #41040)
2024-05-09 12:48:48 +05:30
ruthra kumar
0404941fb2 chore: resolve conflict 2024-05-09 12:21:29 +05:30
ruthra kumar
c4dfcbec96 refactor: make use of doc.quotation_to
(cherry picked from commit 754c7f6d1c)

# Conflicts:
#	erpnext/selling/doctype/quotation/quotation.js
2024-05-09 06:45:53 +00:00
Nihantra Patel
c9e7f450c5 fix: address filter and quotation to for prospect
(cherry picked from commit 2896e3666c)
2024-05-09 06:45:53 +00:00
Nihantra Patel
690278042d fix: address filter and quotation to for prospect
(cherry picked from commit 24a68a79df)
2024-05-09 06:45:52 +00:00
Nihantra Patel
754e193c76 fix: address filter and quotation to for prospect
(cherry picked from commit fe5b88522e)

# Conflicts:
#	erpnext/selling/doctype/quotation/quotation.js
2024-05-09 06:45:52 +00:00
Frappe PR Bot
9d5e4b3b3a chore(release): Bumped to Version 14.69.1
## [14.69.1](https://github.com/frappe/erpnext/compare/v14.69.0...v14.69.1) (2024-05-09)

### Bug Fixes

* added brand column in Warehouse wise Item Balance Age and Value … (backport [#41280](https://github.com/frappe/erpnext/issues/41280)) ([#41281](https://github.com/frappe/erpnext/issues/41281)) ([d727c52](d727c52421))
* Cost center not getting saved in PSOA ([e82ea12](e82ea12cbc))
* filter validation for batch-wise balance history report (backport [#41356](https://github.com/frappe/erpnext/issues/41356)) ([#41360](https://github.com/frappe/erpnext/issues/41360)) ([339256b](339256bc71))
* incorrect query for Purchase Invoice rate in GP ([93b30d9](93b30d9f11))
* missing Item Name on Save for Quotation created from Item (backport [#41233](https://github.com/frappe/erpnext/issues/41233)) ([#41303](https://github.com/frappe/erpnext/issues/41303)) ([a26ae64](a26ae64385))
* pricing rule rounding ([d2ce927](d2ce927891))
* reset rate for serial batch supplied items ([#41293](https://github.com/frappe/erpnext/issues/41293)) ([cd33199](cd33199da2))

### Performance Improvements

* index on item code for the Pick List Item doctype (backport [#41357](https://github.com/frappe/erpnext/issues/41357)) ([#41362](https://github.com/frappe/erpnext/issues/41362)) ([4647ec8](4647ec8892))
2024-05-09 05:30:06 +00:00
ruthra kumar
d7709cf4e4 Merge pull request #41354 from frappe/version-14-hotfix
chore: release v14
2024-05-09 10:58:39 +05:30
mergify[bot]
4647ec8892 perf: index on item code for the Pick List Item doctype (backport #41357) (#41362)
* perf: index on item code for the Pick List Item doctype (#41357)

(cherry picked from commit 0887161f2a)

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

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-08 11:38:02 +05:30
mergify[bot]
339256bc71 fix: filter validation for batch-wise balance history report (backport #41356) (#41360)
fix: filter validation for batch-wise balance history report (#41356)

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

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

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

(cherry picked from commit 9bf37426c1)
2024-05-07 04:32:03 +00:00
mergify[bot]
a26ae64385 fix: missing Item Name on Save for Quotation created from Item (backport #41233) (#41303)
fix: missing Item Name on Save for Quotation created from Item (#41233)

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

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

(cherry picked from commit c8e92cb1b2)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2024-05-06 21:46:18 +05:30
s-aga-r
cd33199da2 fix: reset rate for serial batch supplied items (#41293) 2024-05-06 21:37:27 +05:30
ruthra kumar
27100401aa Merge pull request #41336 from frappe/mergify/bp/version-14-hotfix/pr-41334
fix: incorrect query for Purchase Invoice rate in GP (backport #41334)
2024-05-06 13:17:58 +05:30
ruthra kumar
93b30d9f11 fix: incorrect query for Purchase Invoice rate in GP
(cherry picked from commit bd8382c592)
2024-05-06 07:24:49 +00:00
Deepesh Garg
904f369e99 Merge pull request #41324 from frappe/mergify/bp/version-14-hotfix/pr-41318
fix: Cost center not getting saved in PSOA (#41318)
2024-05-06 12:25:14 +05:30
Deepesh Garg
7b9c22775c chore: resolve conflicts 2024-05-06 11:52:26 +05:30
Deepesh Garg
6aa8d5fb4b chore: resolve conflicts 2024-05-06 11:29:53 +05:30
Deepesh Garg
e82ea12cbc fix: Cost center not getting saved in PSOA
(cherry picked from commit 58f7039630)

# Conflicts:
#	erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json
#	erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py
2024-05-04 08:26:55 +00:00
mergify[bot]
d727c52421 fix: added brand column in Warehouse wise Item Balance Age and Value … (backport #41280) (#41281)
fix: added brand column in Warehouse wise Item Balance Age and Value … (#41280)

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

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-05-03 17:53:52 +05:30
Frappe PR Bot
5ae29655f9 chore(release): Bumped to Version 14.69.0
# [14.69.0](https://github.com/frappe/erpnext/compare/v14.68.2...v14.69.0) (2024-05-02)

### Bug Fixes

* 'NoneType' object has no attribute '_read_rowdata_packet_unbuffered' ([949aa93](949aa9346c))
* args when get the delivery note in delivery trip ([abe64aa](abe64aa1ab))
* args when get the delivery note in delivery trip ([1a7b3c4](1a7b3c437d))
* display term name for single term invoices ([58b68b7](58b68b7597))
* duplicate column in the stock ledger report ([3fcdcef](3fcdcef178))
* handle stock balance unbuffered_cursor error (backport [#41186](https://github.com/frappe/erpnext/issues/41186)) ([#41187](https://github.com/frappe/erpnext/issues/41187)) ([59010c9](59010c9a61))
* incorrectly applying TDS when Advance is in previous FY ([eb22fb9](eb22fb9326))
* mode of payment has precedance ([33d38ba](33d38ba3a7))
* multiple pricing rules with discount amount and discount percentage not working ([#41211](https://github.com/frappe/erpnext/issues/41211)) ([54313b5](54313b5db9))
* negative stock qty error for stock reconciliation ([#41283](https://github.com/frappe/erpnext/issues/41283)) ([9aa054c](9aa054c400))
* permission issue when user permission restricts on company ([45c4167](45c4167c86))
* validation to prevent overallocation ([8318286](8318286865))

### Features

* allow to do reposting for all stock transactions (audit) (backport [#41165](https://github.com/frappe/erpnext/issues/41165)) ([#41205](https://github.com/frappe/erpnext/issues/41205)) ([1e13193](1e1319351d))

### Performance Improvements

* timeout issue while submitting purchase receipt (v14) ([113351e](113351e850))
2024-05-02 04:31:43 +00:00
ruthra kumar
81a99309d8 Merge pull request #41264 from frappe/version-14-hotfix
chore: release v14
2024-05-02 09:59:30 +05:30
rohitwaghchaure
9aa054c400 fix: negative stock qty error for stock reconciliation (#41283)
fix: negative stock qty error for stock reco
2024-05-02 09:38:33 +05:30
ruthra kumar
82b2675aa8 Merge pull request #41269 from frappe/mergify/bp/version-14-hotfix/pr-41268
fix: validation to prevent overallocation (backport #41268)
2024-04-30 18:40:47 +05:30
ruthra kumar
8318286865 fix: validation to prevent overallocation
(cherry picked from commit bf755fab55)
2024-04-30 17:48:55 +05:30
ruthra kumar
f87411f40d Merge pull request #41254 from frappe/mergify/bp/version-14-hotfix/pr-41252
fix: permission issue when user permission restricts on company (backport #41252)
2024-04-30 14:19:39 +05:30
ruthra kumar
45c4167c86 fix: permission issue when user permission restricts on company 2024-04-30 12:24:32 +05:30
ruthra kumar
e0d1f2f6eb Merge pull request #41248 from frappe/mergify/bp/version-14-hotfix/pr-41240
fix: display term name for single term invoices in AR/AP (backport #41240)
2024-04-30 07:42:33 +05:30
ruthra kumar
ba45ea42f8 Merge pull request #41246 from frappe/mergify/bp/version-14-hotfix/pr-41194
fix: TDS incorrectly applied when Advance is in previous FY (backport #41194)
2024-04-30 07:42:23 +05:30
ruthra kumar
58b68b7597 fix: display term name for single term invoices
(cherry picked from commit 5fa4cfee04)
2024-04-30 01:40:17 +00:00
ruthra kumar
4d56c46446 test: TDS deduction across fiscal year
(cherry picked from commit 2f9a144023)
2024-04-30 07:04:21 +05:30
ruthra kumar
eb22fb9326 fix: incorrectly applying TDS when Advance is in previous FY
(cherry picked from commit b195f519e2)
2024-04-30 01:27:56 +00:00
rohitwaghchaure
54313b5db9 fix: multiple pricing rules with discount amount and discount percentage not working (#41211) 2024-04-29 20:53:10 +05:30
mergify[bot]
866b0c6ac7 chore: delete invalid translations (backport #41227) (#41228)
chore: delete invalid translations (#41227)

(cherry picked from commit 067419b7cd)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2024-04-29 10:41:54 +05:30
mergify[bot]
1e1319351d feat: allow to do reposting for all stock transactions (audit) (backport #41165) (#41205)
* feat: allow to do reposting for all transactions (audit)

(cherry picked from commit aefbe21b46)

# Conflicts:
#	erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json
#	erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py

* chore: fix conflicts

* chore: fix conflicts

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-04-26 14:25:51 +05:30
rohitwaghchaure
6b2874694e Merge pull request #41204 from rohitwaghchaure/fixed-unbuffered_cursor
fix: 'NoneType' object has no attribute '_read_rowdata_packet_unbuffe…
2024-04-26 13:50:07 +05:30
rohitwaghchaure
ff37706bef Merge pull request #41200 from frappe/mergify/bp/version-14-hotfix/pr-41185
fix: args when get the delivery note in delivery trip (backport #41185)
2024-04-26 13:36:14 +05:30
Rohit Waghchaure
949aa9346c fix: 'NoneType' object has no attribute '_read_rowdata_packet_unbuffered' 2024-04-26 13:25:46 +05:30
rohitwaghchaure
2f6fee9877 chore: fix conflicts 2024-04-26 13:15:32 +05:30
Nihantra Patel
abe64aa1ab fix: args when get the delivery note in delivery trip
(cherry picked from commit ca577f7aaa)
2024-04-26 07:40:32 +00:00
Nihantra Patel
1a7b3c437d fix: args when get the delivery note in delivery trip
(cherry picked from commit 2f359e201d)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.py
2024-04-26 07:40:31 +00:00
rohitwaghchaure
da69b1e71b Merge pull request #41196 from frappe/mergify/bp/version-14-hotfix/pr-41192
fix: duplicate column in the stock ledger report (backport #41192)
2024-04-26 12:49:35 +05:30
Frappe PR Bot
d160f5b61a chore(release): Bumped to Version 14.68.2
## [14.68.2](https://github.com/frappe/erpnext/compare/v14.68.1...v14.68.2) (2024-04-26)

### Performance Improvements

* timeout issue while submitting purchase receipt (v14) ([c93840e](c93840eb56))
2024-04-26 06:53:19 +00:00
Rohit Waghchaure
3fcdcef178 fix: duplicate column in the stock ledger report
(cherry picked from commit be7fd6bfb4)
2024-04-26 06:53:08 +00:00
rohitwaghchaure
40ece3f5da Merge pull request #41193 from frappe/mergify/bp/version-14/pr-41174
perf: timeout issue while submitting purchase receipt (v14) (backport #41174)
2024-04-26 12:21:59 +05:30
Rohit Waghchaure
c93840eb56 perf: timeout issue while submitting purchase receipt (v14)
(cherry picked from commit 113351e850)
2024-04-26 05:27:47 +00:00
rohitwaghchaure
7656fe4bfc Merge pull request #41174 from rohitwaghchaure/fixed-performance-issue-for-serial-no-creation
perf: timeout issue while submitting purchase receipt (v14)
2024-04-26 10:56:28 +05:30
mergify[bot]
59010c9a61 fix: handle stock balance unbuffered_cursor error (backport #41186) (#41187)
fix: handle stock balance unbuffered_cursor error (#41186)

(cherry picked from commit 341fb6d8f3)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2024-04-25 17:26:29 +05:30
Rohit Waghchaure
113351e850 perf: timeout issue while submitting purchase receipt (v14) 2024-04-25 12:10:29 +05:30
Frappe PR Bot
2026c986ba chore(release): Bumped to Version 14.68.1
## [14.68.1](https://github.com/frappe/erpnext/compare/v14.68.0...v14.68.1) (2024-04-25)

### Bug Fixes

* mode of payment has precedance ([7e52f72](7e52f72bed))
2024-04-25 03:43:28 +00:00
ruthra kumar
91c202f172 Merge pull request #41179 from frappe/mergify/bp/version-14/pr-41142
fix: mode of payment has precedance in Payment Entry (backport #41142)
2024-04-25 09:12:14 +05:30
ruthra kumar
7e52f72bed fix: mode of payment has precedance
Mode of Payment is given precedence over company/party bank account

(cherry picked from commit 4aef969879)
2024-04-25 09:06:04 +05:30
ruthra kumar
b9d28fc1ad Merge pull request #41178 from frappe/mergify/bp/version-14-hotfix/pr-41142
fix: mode of payment has precedance in Payment Entry (backport #41142)
2024-04-25 09:02:37 +05:30
ruthra kumar
33d38ba3a7 fix: mode of payment has precedance
Mode of Payment is given precedence over company/party bank account

(cherry picked from commit 4aef969879)
2024-04-25 08:58:01 +05:30
Deepesh Garg
ff8dba1cb7 Merge branch 'version-14-hotfix' of https://github.com/frappe/erpnext into default_dates_in_reports 2024-04-24 17:20:55 +05:30
Deepesh Garg
c3244f009b fix: Default dates in report 2024-04-24 16:59:51 +05:30
Frappe PR Bot
30b2cac423 chore(release): Bumped to Version 14.68.0
# [14.68.0](https://github.com/frappe/erpnext/compare/v14.67.2...v14.68.0) (2024-04-24)

### Bug Fixes

* account and stock manager read perm ([03ce9ee](03ce9ee321))
* allow Employee role to select cost center & project (accounting dimensions) (backport [#41160](https://github.com/frappe/erpnext/issues/41160)) ([#41161](https://github.com/frappe/erpnext/issues/41161)) ([bb48440](bb48440591))
* do not add actual expense twice for validating budget ([ec4f07f](ec4f07fd60))
* don't attempt to set gender from salutation (backport [#40997](https://github.com/frappe/erpnext/issues/40997)) ([#41072](https://github.com/frappe/erpnext/issues/41072)) ([5d05bf8](5d05bf8d4e))
* Missing args while fetching items from delivery note ([0df80ad](0df80ad923))
* Multiple partial payment requests against Purchase Invoice ([f1b75e8](f1b75e8c54))
* Party type in Payment Order ([a2e1d13](a2e1d132df))
* Permission for lower dedcution certificate ([a22be6f](a22be6f9b9))
* Test case ([5aef9d2](5aef9d2ef2))
* validate uom is integer for PR item (backport [#41074](https://github.com/frappe/erpnext/issues/41074)) ([#41076](https://github.com/frappe/erpnext/issues/41076)) ([9d0c1dc](9d0c1dc46f))

### Features

* Available batches report as on specific date ([8868cb1](8868cb147d))
* show expense breakup ([f087ec8](f087ec8df5))

### Performance Improvements

* stock ageing and batch-wise balance history report ([6017e7a](6017e7ac3e))
2024-04-24 07:32:43 +00:00
Deepesh Garg
67be2ba9dc Merge pull request #41151 from frappe/version-14-hotfix
chore: release v14
2024-04-24 13:01:19 +05:30
Deepesh Garg
3e81f0f578 Merge pull request #41100 from frappe/mergify/bp/version-14-hotfix/pr-41085
fix: Permission for lower deduction certificate (#41085)
2024-04-24 12:00:13 +05:30
mergify[bot]
bb48440591 fix: allow Employee role to select cost center & project (accounting dimensions) (backport #41160) (#41161)
* fix: allow Employee role to select cost center & project (accounting dimensions)

(cherry picked from commit d0d496a515)

# Conflicts:
#	erpnext/accounts/doctype/cost_center/cost_center.json
#	erpnext/projects/doctype/project/project.json

* chore: fix conflicts

---------

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2024-04-24 11:43:33 +05:30
Deepesh Garg
35230a9692 chore: Resolve conflicts 2024-04-24 11:33:34 +05:30
Nabin Hait
df3ff1097c Merge pull request #41140 from frappe/mergify/bp/version-14-hotfix/pr-41136
fix: Missing args while fetching items from delivery note in Installation Note (backport #41136)
2024-04-24 10:59:16 +05:30
rohitwaghchaure
521bf31fe3 Merge pull request #41154 from rohitwaghchaure/feat-new-batch-report-v14
feat: Available batches report as on specific date
2024-04-23 17:17:26 +05:30
Rohit Waghchaure
8868cb147d feat: Available batches report as on specific date 2024-04-23 17:10:05 +05:30
Deepesh Garg
0dfae12f14 Merge pull request #41130 from frappe/mergify/bp/version-14-hotfix/pr-40797
fix: Party type in Payment Order (#40797)
2024-04-23 12:05:25 +05:30
Nabin Hait
0df80ad923 fix: Missing args while fetching items from delivery note
(cherry picked from commit bbe323fbb4)
2024-04-23 04:55:02 +00:00
rohitwaghchaure
c71d1118a9 Merge pull request #41138 from rohitwaghchaure/fixed-performance-for-stock-reports
perf: stock ageing and batch-wise balance history report
2024-04-23 10:12:19 +05:30
Rohit Waghchaure
6017e7ac3e perf: stock ageing and batch-wise balance history report 2024-04-23 06:56:41 +05:30
Deepesh Garg
d5784cc629 Merge pull request #41126 from frappe/mergify/bp/version-14-hotfix/pr-40812
fix: Multiple partial payment requests against Purchase Invoice (#40812)
2024-04-22 11:36:30 +05:30
Deepesh Garg
a2e1d132df fix: Party type in Payment Order
(cherry picked from commit 91fa41c9ec)
2024-04-22 06:05:12 +00:00
Gursheen Kaur Anand
3e912993e1 Merge pull request #41123 from frappe/mergify/bp/version-14-hotfix/pr-40769
fix: budget validation for purchase orders (backport #40769)
2024-04-21 18:57:52 +05:30
Deepesh Garg
5aef9d2ef2 fix: Test case
(cherry picked from commit 071e5ed648)
2024-04-21 11:50:38 +00:00
Deepesh Garg
f1b75e8c54 fix: Multiple partial payment requests against Purchase Invoice
(cherry picked from commit 45d5f6e00a)
2024-04-21 11:50:38 +00:00
Gursheen Anand
5b1c5e3fea refactor: show list for expense breakup
(cherry picked from commit 9a12376e29)
2024-04-21 11:26:19 +00:00
Gursheen Anand
f087ec8df5 feat: show expense breakup
(cherry picked from commit 59292a09c4)
2024-04-21 11:26:19 +00:00
Gursheen Anand
ec4f07fd60 fix: do not add actual expense twice for validating budget
(cherry picked from commit af26ac96e9)
2024-04-21 11:26:19 +00:00
Deepesh Garg
a22be6f9b9 fix: Permission for lower dedcution certificate
(cherry picked from commit f6f118855b)

# Conflicts:
#	erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json
2024-04-19 10:11:41 +00:00
Gursheen Kaur Anand
e8accd0256 Merge pull request #41094 from frappe/mergify/bp/version-14-hotfix/pr-41093
fix: accounts manager perm for FY (backport #41093)
2024-04-19 14:08:19 +05:30
Gursheen Kaur Anand
de3a423618 chore: resolve conflicts 2024-04-19 13:32:39 +05:30
Gursheen Anand
03ce9ee321 fix: account and stock manager read perm
(cherry picked from commit 572e844a91)

# Conflicts:
#	erpnext/accounts/doctype/fiscal_year/fiscal_year.json
2024-04-19 08:00:48 +00:00
mergify[bot]
9d0c1dc46f fix: validate uom is integer for PR item (backport #41074) (#41076)
* fix: validate uom is integer for PR item

(cherry picked from commit 9a290fdfc9)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-04-18 07:53:33 +05:30
mergify[bot]
5d05bf8d4e fix: don't attempt to set gender from salutation (backport #40997) (#41072)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: don't attempt to set gender from salutation (#40997)
2024-04-17 17:54:03 +02:00
Frappe PR Bot
84789b7407 chore(release): Bumped to Version 14.67.2
## [14.67.2](https://github.com/frappe/erpnext/compare/v14.67.1...v14.67.2) (2024-04-17)

### Bug Fixes

* barcode not fetched on selection of item (backport [#40814](https://github.com/frappe/erpnext/issues/40814)) ([#41027](https://github.com/frappe/erpnext/issues/41027)) ([0bee921](0bee921d40))
* Delayed Order Report not working (backport [#41037](https://github.com/frappe/erpnext/issues/41037)) ([#41038](https://github.com/frappe/erpnext/issues/41038)) ([e956dbb](e956dbbf68))
* Don't call get_fiscal_year if setup is not done yet ([59cea9f](59cea9f4dd))
* Don't set delivery date as today while making SO from Quotation ([b47e224](b47e224a1c))
* exclude some query builder lines from ruff rules ([2425119](2425119b5e))
* expense account set as COGS for stock entry Material Issue (backport [#41026](https://github.com/frappe/erpnext/issues/41026)) ([#41028](https://github.com/frappe/erpnext/issues/41028)) ([153e0ba](153e0ba81b))
* get address if multiple companies ([c2b6b64](c2b6b64e2e))
* get address if multiple companies ([0493872](04938726d9))
* **gp:** SLEs not fetched for correct warehouse ([97e7b3f](97e7b3f3d3))
* incorrect exc gain/loss for PE against JE for payable accounts ([50a74ee](50a74ee7fe))
* Resolve merge conflicts ([cc925ae](cc925ae938))
* Subcontracting Receipt GL Entries (backport [#40773](https://github.com/frappe/erpnext/issues/40773)) ([#40978](https://github.com/frappe/erpnext/issues/40978)) ([c2c4548](c2c4548cc0))
* test cases ([79e23da](79e23dad2c))
* **treewide:** manual ruff fixes ([b087fb3](b087fb3d54))
* use 'eq' and isnull() on qb conditions ([b4ed2d2](b4ed2d2c16))
2024-04-17 06:12:30 +00:00
rohitwaghchaure
cd70c6c1b2 Merge pull request #41032 from frappe/version-14-hotfix
chore: release v14
2024-04-17 11:41:15 +05:30
ruthra kumar
4cc0a5851b Merge pull request #40854 from frappe/mergify/bp/version-14-hotfix/pr-40786
fix(gp): SLEs not fetched for correct warehouse (backport #40786)
2024-04-17 10:36:52 +05:30
ruthra kumar
6e9da0a729 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-40786 2024-04-17 10:16:55 +05:30
Dany Robert
97e7b3f3d3 fix(gp): SLEs not fetched for correct warehouse
(cherry picked from commit f958e8be06)
2024-04-17 10:15:29 +05:30
mergify[bot]
e956dbbf68 fix: Delayed Order Report not working (backport #41037) (#41038)
fix: Delayed Order Report not working (#41037)

(cherry picked from commit d69a18b826)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-04-16 18:24:45 +05:30
rohitwaghchaure
ebf5f13447 Merge branch 'version-14' into version-14-hotfix 2024-04-16 17:59:35 +05:30
ruthra kumar
9404dea97e Merge pull request #41034 from frappe/mergify/bp/version-14-hotfix/pr-40373
fix: get address if multiple companies (backport #40373)
2024-04-16 16:41:26 +05:30
ruthra kumar
ee06448f0d chore: resolve conflict 2024-04-16 16:38:32 +05:30
Nihantra C. Patel
c2b6b64e2e fix: get address if multiple companies
(cherry picked from commit 655a1797be)
2024-04-16 11:01:59 +00:00
Nihantra Patel
04938726d9 fix: get address if multiple companies
(cherry picked from commit c6cf1bec76)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.js
2024-04-16 11:01:58 +00:00
mergify[bot]
153e0ba81b fix: expense account set as COGS for stock entry Material Issue (backport #41026) (#41028)
fix: expense account set as COGS for stock entry Material Issue (#41026)

(cherry picked from commit 03231e99ef)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-04-16 14:22:53 +05:30
mergify[bot]
0bee921d40 fix: barcode not fetched on selection of item (backport #40814) (#41027)
* fix: barcode not fetched on selection of item

(cherry picked from commit b0730293e2)

# Conflicts:
#	erpnext/stock/get_item_details.py

* chore: fix conflicts

* chore: fix test case

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-04-16 14:22:32 +05:30
ruthra kumar
9d3c861766 Merge pull request #41022 from frappe/mergify/bp/version-14-hotfix/pr-41020
fix: incorrect exc gain/loss for PE against JE for payable accounts (backport #41020)
2024-04-16 11:31:40 +05:30
ruthra kumar
e8d05517c5 chore: resolve conflict 2024-04-16 11:07:39 +05:30
ruthra kumar
2ebdd93a83 test: exc gain/loss journals booking in Payable accounts
(cherry picked from commit 8821c98625)
2024-04-16 05:29:49 +00:00
ruthra kumar
50a74ee7fe fix: incorrect exc gain/loss for PE against JE for payable accounts
(cherry picked from commit 81b574053f)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2024-04-16 05:29:49 +00:00
Nabin Hait
da1c317c6c Merge pull request #41006 from frappe/mergify/bp/version-14-hotfix/pr-40858
fix: Don't set delivery date as today while making SO from Quotation (backport #40858)
2024-04-15 13:05:21 +05:30
Nabin Hait
79e23dad2c fix: test cases
(cherry picked from commit 65c74fa3c7)
2024-04-15 07:03:42 +00:00
Nabin Hait
b47e224a1c fix: Don't set delivery date as today while making SO from Quotation
(cherry picked from commit fec20decc1)
2024-04-15 07:03:42 +00:00
Deepesh Garg
3310358d36 Merge pull request #40947 from frappe/mergify/bp/version-14-hotfix/pr-40931
fix: Don't call get_fiscal_year if setup is not done yet (backport #40931)
2024-04-15 11:18:06 +05:30
Frappe PR Bot
7a6c0e5283 chore(release): Bumped to Version 14.67.1
## [14.67.1](https://github.com/frappe/erpnext/compare/v14.67.0...v14.67.1) (2024-04-12)

### Bug Fixes

* Subcontracting Receipt GL Entries (backport [#40773](https://github.com/frappe/erpnext/issues/40773)) (backport [#40978](https://github.com/frappe/erpnext/issues/40978)) ([#40982](https://github.com/frappe/erpnext/issues/40982)) ([4672f59](4672f59599))
2024-04-12 10:01:51 +00:00
mergify[bot]
4672f59599 fix: Subcontracting Receipt GL Entries (backport #40773) (backport #40978) (#40982)
* fix: Subcontracting Receipt GL Entries (backport #40773) (#40978)

* fix: Subcontracting Receipt GL Entries

(cherry picked from commit 9808ae92a4)

* chore: linter

---------

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

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py

* chore: `conflicts`

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-04-12 15:30:27 +05:30
mergify[bot]
c2c4548cc0 fix: Subcontracting Receipt GL Entries (backport #40773) (#40978)
* fix: Subcontracting Receipt GL Entries

(cherry picked from commit 9808ae92a4)

* chore: linter

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-04-12 14:50:12 +05:30
Deepesh Garg
cc925ae938 fix: Resolve merge conflicts 2024-04-12 12:10:55 +05:30
Akhil Narang
f0ecc627fb Merge pull request #40943 from akhilnarang/v14-ruff
chore: backport ruff linting/formatting
2024-04-12 11:01:43 +05:30
Akhil Narang
cf3c26d80c chore: gitignore ruff format
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-12 11:01:32 +05:30
Corentin Flr
59cea9f4dd fix: Don't call get_fiscal_year if setup is not done yet
(cherry picked from commit c203fafb1b)

# Conflicts:
#	erpnext/public/js/utils.js
2024-04-10 15:17:44 +00:00
Frappe PR Bot
cc6bacb190 chore(release): Bumped to Version 14.67.0
# [14.67.0](https://github.com/frappe/erpnext/compare/v14.66.4...v14.67.0) (2024-04-10)

### Bug Fixes

* Get pro-rata depr amount based on correct days ([10d7600](10d760089e))
* group warehouse added in the stock reconciliation ([9dea6d3](9dea6d3393))
* ignore dimension validation for cancelled entries ([9745724](9745724d41))
* incorrect currency symbol in General Ledger print ([5896e75](5896e755bf))
* incorrect operator causing incorrect validation ([7e1ab75](7e1ab75b38))
* query_report.trigger_refresh is not a function ([a6145fa](a6145fa13c))
* translatable web footer ([#40834](https://github.com/frappe/erpnext/issues/40834)) ([b55d859](b55d8597e8))
* use reference type name to update exc rate ([09cda60](09cda60bdf))

### Features

* ledger health doctype ([b667a02](b667a02470))
* new hook `fields_for_group_similar_items` to group additional fields for print formats ([#40831](https://github.com/frappe/erpnext/issues/40831)) ([0172880](0172880fd3))

### Performance Improvements

* memory consumption for the Batch-Wise Balance History report ([3165682](3165682d7a))
2024-04-10 14:22:01 +00:00
Deepesh Garg
cebde2662d Merge pull request #40919 from frappe/version-14-hotfix
chore: release v14
2024-04-10 19:50:19 +05:30
ruthra kumar
b4ed2d2c16 fix: use 'eq' and isnull() on qb conditions
(cherry picked from commit eee86d2e4b)
(cherry picked from commit 22689958da)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 17:13:31 +05:30
Akhil Narang
2425119b5e fix: exclude some query builder lines from ruff rules
`== None` and `== True` are intentional here

(cherry picked from commit ac69513f60)
(cherry picked from commit 41d7c03cbb)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 17:13:31 +05:30
Akhil Narang
b087fb3d54 fix(treewide): manual ruff fixes
(cherry picked from commit f63396ef47)
(cherry picked from commit 7828eee014)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 17:13:31 +05:30
Akhil Narang
4d34b1ead7 refactor(treewide): formatting and ruff fixes, + manually enabled F401
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 17:13:31 +05:30
barredterra
c28d19cf7f chore: switch to ruff for python formatting/linting
(cherry picked from commit 8afb7790de)
(cherry picked from commit 9eeedd8515)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 17:07:51 +05:30
ruthra kumar
73d2595aa6 Merge pull request #40937 from frappe/mergify/bp/version-14-hotfix/pr-40936
fix: ignore dimension validation for cancelled entries (backport #40936)
2024-04-10 15:28:00 +05:30
ruthra kumar
9745724d41 fix: ignore dimension validation for cancelled entries
(cherry picked from commit 971c867f29)
2024-04-10 08:28:35 +00:00
ruthra kumar
a31b2e17f5 Merge pull request #40929 from frappe/mergify/bp/version-14-hotfix/pr-40695
feat: Periodically monitor ledger health (backport #40695)
2024-04-10 07:17:29 +05:30
ruthra kumar
46c76b166d chore: resolve conflicts 2024-04-09 19:59:09 +05:30
ruthra kumar
6079c5636e Merge pull request #40927 from frappe/mergify/bp/version-14-hotfix/pr-40926
fix: incorrect currency symbol in General Ledger print (backport #40926)
2024-04-09 19:54:29 +05:30
ruthra kumar
5808feaccf chore: use super() instead of super(__class__, self)
(cherry picked from commit 24d37d22a3)
2024-04-09 12:25:55 +00:00
ruthra kumar
98ad034b21 chore: make ledger health doctype read_only
(cherry picked from commit dc79213bb3)
2024-04-09 12:25:55 +00:00
ruthra kumar
277717a2e0 test: ledger monitoring function
(cherry picked from commit 4776d660b5)
2024-04-09 12:25:55 +00:00
ruthra kumar
0591f72e01 chore: schedule job to run daily
(cherry picked from commit f96cf111ed)
2024-04-09 12:25:54 +00:00
ruthra kumar
1af6b4256f chore: permission and UI changes
(cherry picked from commit 1a43ed763b)
2024-04-09 12:25:53 +00:00
ruthra kumar
6273a31b8c refactor: only run checks on specified companies
(cherry picked from commit 00eeacd06a)

# Conflicts:
#	erpnext/accounts/utils.py
2024-04-09 12:25:53 +00:00
ruthra kumar
f86c035f88 refactor: make health check configurable for companies
(cherry picked from commit 704925549b)
2024-04-09 12:25:52 +00:00
ruthra kumar
9981a900b2 refactor: control monitoring through settings page
(cherry picked from commit a42482ce35)

# Conflicts:
#	erpnext/accounts/utils.py
2024-04-09 12:25:52 +00:00
ruthra kumar
7a6ffccecc chore: settings page for health monitor
(cherry picked from commit b2fb7843d1)
2024-04-09 12:25:47 +00:00
ruthra kumar
58698c9aa4 refactor: barebones method to run checks
(cherry picked from commit 8c8d9be810)

# Conflicts:
#	erpnext/accounts/utils.py
2024-04-09 12:25:47 +00:00
ruthra kumar
bbe4ca7d74 refactor: flag for general and payment ledger mismatch
(cherry picked from commit d620b9eae8)
2024-04-09 12:25:46 +00:00
ruthra kumar
153fc91478 refactor: date on which vouchers was reported
(cherry picked from commit 402ffc6d27)
2024-04-09 12:25:45 +00:00
ruthra kumar
b667a02470 feat: ledger health doctype
(cherry picked from commit 9ed74dd8cc)
2024-04-09 12:25:45 +00:00
ruthra kumar
5896e755bf fix: incorrect currency symbol in General Ledger print
(cherry picked from commit 429e036e8c)
2024-04-09 12:16:41 +00:00
rohitwaghchaure
dd6c53df96 Merge pull request #40910 from rohitwaghchaure/fixed-perf-issue-for-batchwise-balance-history
perf: memory consumption for the Batch-Wise Balance History report
2024-04-09 17:36:43 +05:30
Rohit Waghchaure
3165682d7a perf: memory consumption for the Batch-Wise Balance History report 2024-04-08 19:03:48 +05:30
ruthra kumar
aff71970e5 Merge pull request #40904 from frappe/mergify/bp/version-14-hotfix/pr-40878
refactor: merge taxes from delivery note to Sales Invoice (backport #40878)
2024-04-08 16:18:48 +05:30
Sagar Vora
2b5ae1dbae Merge pull request #40906 from frappe/mergify/bp/version-14-hotfix/pr-40831 2024-04-08 10:22:43 +00:00
Sagar Vora
24709ab400 chore: fix conflict 2024-04-08 15:50:58 +05:30
Smit Vora
0172880fd3 feat: new hook fields_for_group_similar_items to group additional fields for print formats (#40831)
(cherry picked from commit f7c9e1538b)

# Conflicts:
#	erpnext/hooks.py
2024-04-08 10:18:15 +00:00
ruthra kumar
b76c00de68 chore: resolve conflict 2024-04-08 15:44:13 +05:30
ruthra kumar
ae1858f465 test: tax merging from 2 Delivery Note to Sales Invoice
(cherry picked from commit 39a48a2e2a)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2024-04-08 09:09:36 +00:00
ruthra kumar
22b16a6b0e refactor: merge taxes from delivery note to Sales Invoice
(cherry picked from commit 550cbbd91c)
2024-04-08 09:09:34 +00:00
Nabin Hait
1eb720a7b0 Merge pull request #40897 from nabinhait/pro-rata-depr-fix
fix: Get pro-rata depr amount based on correct days
2024-04-07 15:23:42 +05:30
rohitwaghchaure
c4e950f6b5 Merge pull request #40835 from frappe/mergify/bp/version-14-hotfix/pr-40834
fix: translatable web footer (backport #40834)
2024-04-07 14:35:45 +05:30
rohitwaghchaure
d4313f7109 Merge pull request #40859 from frappe/mergify/bp/version-14-hotfix/pr-40848
fix: group warehouse added in the stock reconciliation (backport #40848)
2024-04-07 14:34:33 +05:30
rohitwaghchaure
26d7354973 Merge pull request #40893 from frappe/mergify/bp/version-14-hotfix/pr-40883
fix: incorrect operator causing incorrect validation (backport #40883)
2024-04-07 14:33:10 +05:30
Nabin Hait
10d760089e fix: Get pro-rata depr amount based on correct days 2024-04-07 14:08:45 +05:30
Rohit Waghchaure
7e1ab75b38 fix: incorrect operator causing incorrect validation
(cherry picked from commit 6b317b0c0d)
2024-04-07 06:34:07 +00:00
rohitwaghchaure
0f9af4b82c Merge pull request #40888 from frappe/mergify/bp/version-14-hotfix/pr-40887
fix: query_report.trigger_refresh is not a function (backport #40887)
2024-04-07 12:02:16 +05:30
Rohit Waghchaure
a6145fa13c fix: query_report.trigger_refresh is not a function
(cherry picked from commit 30bbb58ca1)
2024-04-07 06:27:49 +00:00
ruthra kumar
cc2f861bca Merge pull request #40868 from frappe/mergify/bp/version-14-hotfix/pr-40856
fix: unwanted Exc Gain/Loss journals on Payment against Journal entry (backport #40856)
2024-04-05 11:01:48 +05:30
ruthra kumar
8f2fd5dec9 test: payment against JE reconciliation with different rates
(cherry picked from commit fe84558b77)
2024-04-05 05:03:56 +00:00
ruthra kumar
09cda60bdf fix: use reference type name to update exc rate
(cherry picked from commit c15690e475)
2024-04-05 05:03:56 +00:00
Rohit Waghchaure
9dea6d3393 fix: group warehouse added in the stock reconciliation
(cherry picked from commit 8f53bc0096)
2024-04-04 10:09:55 +00:00
Raffael Meyer
b55d8597e8 fix: translatable web footer (#40834)
(cherry picked from commit f3bcdbe5bd)
2024-04-02 15:16:18 +00:00
695 changed files with 6807 additions and 6980 deletions

View File

@@ -28,4 +28,7 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
494bd9ef78313436f0424b918f200dab8fc7c20b
# bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d
baec607ff5905b1c67531096a9cf50ec7ff00a5d
# ruff
4d34b1ead73baf4c5430a2ecbe44b9e8468d7626

View File

@@ -65,27 +65,15 @@ repos:
.*/loan_write_off.js
)$
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.0
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- id: ruff
name: "Run ruff linter and apply fixes"
args: ["--fix"]
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
- id: ruff-format
name: "Format Python code"
ci:

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.66.4"
__version__ = "14.70.2"
def get_default_company(user=None):
@@ -36,10 +36,8 @@ def get_default_cost_center(company):
if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {}
if not company in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center"
)
if company not in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value("Company", company, "cost_center")
return frappe.flags.company_cost_center[company]
@@ -47,7 +45,7 @@ def get_company_currency(company):
"""Returns the default company currency"""
if not frappe.flags.company_currency:
frappe.flags.company_currency = {}
if not company in frappe.flags.company_currency:
if company not in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True
)
@@ -81,7 +79,7 @@ def is_perpetual_inventory_enabled(company):
if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory:
if company not in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
)
@@ -96,7 +94,7 @@ def get_default_finance_book(company=None):
if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book:
if company not in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book"
)
@@ -108,7 +106,7 @@ def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {}
if not party_type in frappe.local.party_account_types:
if party_type not in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or ""
)

View File

@@ -11,14 +11,14 @@ class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
self.update_compnay_address()
super(ERPNextAddress, self).validate()
super().validate()
def link_address(self):
"""Link address based on owner"""
if self.is_your_company_address:
return
return super(ERPNextAddress, self).link_address()
return super().link_address()
def update_compnay_address(self):
for link in self.get("links"):
@@ -26,11 +26,11 @@ class ERPNextAddress(Address):
self.is_your_company_address = 1
def validate_reference(self):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
if self.is_your_company_address and not [row for row in self.links if row.link_doctype == "Company"]:
frappe.throw(
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
_(
"Address needs to be linked to a Company. Please add a row for Company in the Links table."
),
title=_("Company Not Linked"),
)

View File

@@ -37,7 +37,7 @@ def get(
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
account = filters.get("account")
company = filters.get("company")
filters.get("company")
if not account and chart_name:
frappe.throw(
@@ -83,7 +83,6 @@ def build_result(account, dates, gl_entries):
# get balances in debit
for entry in gl_entries:
# entry date is after the current pointer, so move the pointer forward
while getdate(entry.posting_date) > result[date_index][0]:
date_index += 1
@@ -133,8 +132,6 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date):
date = get_period_ending(
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
)
date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
dates.append(date)
return dates

View File

@@ -24,14 +24,10 @@ from erpnext.accounts.utils import get_account_currency
def validate_service_stop_date(doc):
"""Validates service_stop_date for Purchase Invoice and Sales Invoice"""
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
old_stop_dates = {}
old_doc = frappe.db.get_all(
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
)
old_doc = frappe.db.get_all(f"{doc.doctype} Item", {"parent": doc.name}, ["name", "service_stop_date"])
for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or ""
@@ -62,16 +58,14 @@ def build_conditions(process_type, account, company):
)
if account:
conditions += "AND %s='%s'" % (deferred_account, account)
conditions += f"AND {deferred_account}='{account}'"
elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions
def convert_deferred_expense_to_expense(
deferred_process, start_date=None, end_date=None, conditions=""
):
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -81,16 +75,14 @@ def convert_deferred_expense_to_expense(
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list(
"""
f"""
select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
""".format(
conditions
),
{conditions}
""",
(end_date, start_date),
) # nosec
@@ -103,9 +95,7 @@ def convert_deferred_expense_to_expense(
send_mail(deferred_process)
def convert_deferred_revenue_to_income(
deferred_process, start_date=None, end_date=None, conditions=""
):
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -115,16 +105,14 @@ def convert_deferred_revenue_to_income(
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list(
"""
f"""
select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
""".format(
conditions
),
{conditions}
""",
(end_date, start_date),
) # nosec
@@ -243,9 +231,7 @@ def calculate_monthly_amount(
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency == doc.company_currency:
amount = base_amount
else:
@@ -265,17 +251,13 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
amount = flt(item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount"))
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency == doc.company_currency:
amount = base_amount
else:
@@ -296,26 +278,22 @@ def get_already_booked_amount(doc, item):
gl_entries_details = frappe.db.sql(
"""
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no
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
group by voucher_detail_no
""".format(
total_credit_debit, total_credit_debit_currency
),
""".format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
journal_entry_details = frappe.db.sql(
"""
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no
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 p.docstatus < 2 group by reference_detail_no
""".format(
total_credit_debit, total_credit_debit_currency
),
""".format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
@@ -337,9 +315,7 @@ def get_already_booked_amount(doc, item):
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
@@ -440,9 +416,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
via_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
)
submit_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
submit_journal_entry = cint(frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries"))
book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on"
)
@@ -462,9 +436,7 @@ def process_deferred_accounting(posting_date=None):
posting_date = today()
if not cint(
frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
frappe.db.get_singles_value("Accounts Settings", "automatically_process_deferred_accounting_entry")
):
return
@@ -587,16 +559,13 @@ def book_revenue_via_journal_entry(
deferred_process=None,
submit="No",
):
if amount == 0:
return
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.voucher_type = "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
@@ -645,7 +614,6 @@ def book_revenue_via_journal_entry(
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item",

View File

@@ -29,7 +29,7 @@ class Account(NestedSet):
if frappe.local.flags.ignore_update_nsm:
return
else:
super(Account, self).on_update()
super().on_update()
def onload(self):
frozen_accounts_modifier = frappe.db.get_value(
@@ -87,9 +87,7 @@ class Account(NestedSet):
def set_root_and_report_type(self):
if self.parent_account:
par = frappe.db.get_value(
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1
)
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1)
if par.report_type:
self.report_type = par.report_type
@@ -144,9 +142,7 @@ class Account(NestedSet):
def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies
if (
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
):
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
return
ancestors = get_root_company(self.company)
if ancestors:
@@ -341,7 +337,7 @@ class Account(NestedSet):
if self.check_gle_exists():
throw(_("Account with existing transaction can not be deleted"))
super(Account, self).on_trash(True)
super().on_trash(True)
@frappe.whitelist()
@@ -349,9 +345,8 @@ class Account(NestedSet):
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s
and %s like %s order by name limit %s offset %s"""
% ("%s", searchfield, "%s", "%s", "%s"),
where is_group = 1 and docstatus != 2 and company = {}
and {} like {} order by name limit {} offset {}""".format("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, page_len, start),
as_list=1,
)
@@ -409,9 +404,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if not account:
return
old_acc_name, old_acc_number = frappe.db.get_value(
"Account", name, ["account_name", "account_number"]
)
old_acc_name, old_acc_number = frappe.db.get_value("Account", name, ["account_name", "account_number"])
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
@@ -519,7 +512,5 @@ def sync_update_account_number_in_child(
if old_acc_number:
filters["account_number"] = old_acc_number
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
for d in frappe.db.get_values("Account", filters=filters, fieldname=["company", "name"], as_dict=True):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -31,7 +31,6 @@ def create_charts(
"tax_rate",
"account_currency",
]:
account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate(
account_name, account_number, accounts
@@ -39,7 +38,9 @@ def create_charts(
is_group = identify_is_group(child)
report_type = (
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
"Balance Sheet"
if root_type in ["Asset", "Liability", "Equity"]
else "Profit and Loss"
)
account = frappe.get_doc(
@@ -141,7 +142,7 @@ def get_chart(chart_template, existing_company=None):
for fname in os.listdir(path):
fname = frappe.as_unicode(fname)
if fname.endswith(".json"):
with open(os.path.join(path, fname), "r") as f:
with open(os.path.join(path, fname)) as f:
chart = f.read()
if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree")
@@ -173,7 +174,7 @@ def get_charts_for_country(country, with_standard=False):
for fname in os.listdir(path):
fname = frappe.as_unicode(fname)
if (fname.startswith(country_code) or fname.startswith(country)) and fname.endswith(".json"):
with open(os.path.join(path, fname), "r") as f:
with open(os.path.join(path, fname)) as f:
_get_chart_name(f.read())
# if more than one charts, returned then add the standard
@@ -247,7 +248,13 @@ def validate_bank_account(coa, bank_account):
def _get_account_names(account_master):
for account_name, child in account_master.items():
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
if account_name not in [
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
accounts.append(account_name)
_get_account_names(child)

View File

@@ -26,7 +26,7 @@ def go():
default_account_types = get_default_account_types()
country_dirs = []
for basepath, folders, files in os.walk(path):
for basepath, _folders, _files in os.walk(path):
basename = os.path.basename(basepath)
if basename.startswith("l10n_"):
country_dirs.append(basename)
@@ -35,9 +35,7 @@ def go():
accounts, charts = {}, {}
country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
data_files = (
manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
)
data_files = manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path)
@@ -90,10 +88,10 @@ def get_csv_contents(files_path):
fname = os.path.basename(filepath)
for file_type in ["account.account.template", "account.account.type", "account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath, "r") as csvfile:
with open(filepath) as csvfile:
try:
csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read()))
except Exception as e:
except Exception:
continue
return csv_content
@@ -138,7 +136,7 @@ def get_account_types(root_list, csv_content, prefix=None):
if csv_content and csv_content[0][0] == "id":
for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row))
row_dict = dict(zip(csv_content[0], row, strict=False))
data = {}
if row_dict.get("code") and account_type_map.get(row_dict["code"]):
data["account_type"] = account_type_map[row_dict["code"]]
@@ -150,7 +148,7 @@ def get_account_types(root_list, csv_content, prefix=None):
def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`"""
for model, root_list in xml_roots.items():
for _model, root_list in xml_roots.items():
for root in root_list:
for node in root[0].findall("record"):
if node.get("model") == "account.account.template":
@@ -186,7 +184,7 @@ def make_maps_for_xml(xml_roots, account_types, country_dir):
def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []):
for row in content[1:]:
data = dict(zip(content[0], row))
data = dict(zip(content[0], row, strict=False))
account = {
"name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
@@ -206,7 +204,7 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.chart.template", []):
for row in content[1:]:
if row:
data = dict(zip(content[0], row))
data = dict(zip(content[0], row, strict=False))
charts.setdefault(data.get("id"), {}).update(
{
"account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"),
@@ -241,7 +239,7 @@ def make_charts():
if not src.get("name") or not src.get("account_root_id"):
continue
if not src["account_root_id"] in accounts:
if src["account_root_id"] not in accounts:
continue
filename = src["id"][5:] + "_" + chart_id
@@ -255,14 +253,20 @@ def make_charts():
for key, val in chart["tree"].items():
if key in ["name", "parent_id"]:
chart["tree"].pop(key)
if type(val) == dict:
if isinstance(val, dict):
val["root_type"] = ""
if chart:
fpath = os.path.join(
"erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json"
"erpnext",
"erpnext",
"accounts",
"doctype",
"account",
"chart_of_accounts",
filename + ".json",
)
with open(fpath, "r") as chartfile:
with open(fpath) as chartfile:
old_content = chartfile.read()
if not old_content or (
json.loads(old_content).get("is_active", "No") == "No"

View File

@@ -260,28 +260,20 @@ class TestAccount(unittest.TestCase):
acc.insert()
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
)
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 4"})
)
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 5"})
)
# Try renaming child company account
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
self.assertRaises(
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
)
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
# Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue(
@@ -290,9 +282,7 @@ class TestAccount(unittest.TestCase):
)
)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
to_delete = [
"Test Group Account - _TC3",
@@ -317,9 +307,7 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc.account_currency, "INR")
# Make a JV against this account
make_journal_entry(
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
)
make_journal_entry("Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True)
acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save)

View File

@@ -17,16 +17,12 @@ class AccountClosingBalance(Document):
def make_closing_entries(closing_entries, voucher_name, company, closing_date):
accounting_dimensions = get_accounting_dimensions()
previous_closing_entries = get_previous_closing_entries(
company, closing_date, accounting_dimensions
)
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
)
merged_entries = aggregate_with_last_account_closing_balance(combined_entries, accounting_dimensions)
for key, value in merged_entries.items():
for _key, value in merged_entries.items():
cle = frappe.new_doc("Account Closing Balance")
cle.update(value)
cle.update(value["dimensions"])

View File

@@ -17,7 +17,8 @@ class AccountingDimension(Document):
self.set_fieldname_and_label()
def validate(self):
if self.document_type in core_doctypes_list + (
if self.document_type in (
*core_doctypes_list,
"Accounting Dimension",
"Project",
"Cost Center",
@@ -25,13 +26,10 @@ class AccountingDimension(Document):
"Company",
"Account",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
exists = frappe.db.get_value(
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
exists = frappe.db.get_value("Accounting Dimension", {"document_type": self.document_type}, ["name"])
if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension"))
@@ -89,7 +87,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
count = 0
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break"
else:
@@ -123,7 +120,7 @@ def add_dimension_to_budget_doctype(df, doc):
df.update(
{
"insert_after": "cost_center",
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
"depends_on": f"eval:doc.budget_against == '{doc.document_type}'",
}
)
@@ -157,19 +154,17 @@ def delete_accounting_dimension(doc):
frappe.db.sql(
"""
DELETE FROM `tabCustom Field`
WHERE fieldname = %s
AND dt IN (%s)"""
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
WHERE fieldname = {}
AND dt IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname, *doclist]),
)
frappe.db.sql(
"""
DELETE FROM `tabProperty Setter`
WHERE field_name = %s
AND doc_type IN (%s)"""
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
WHERE field_name = {}
AND doc_type IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname, *doclist]),
)
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
@@ -218,7 +213,6 @@ def get_doctypes_with_dimensions():
def get_accounting_dimensions(as_list=True, filters=None):
if not filters:
filters = {"disabled": 0}
@@ -249,7 +243,6 @@ def get_checks_for_pl_and_bs_accounts():
def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str):
dimensions = [dimensions]
@@ -257,9 +250,7 @@ def get_dimension_with_children(doctype, dimensions):
for dimension in dimensions:
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all(
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
all_dimensions += [c.name for c in children]
return all_dimensions

View File

@@ -57,9 +57,7 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def create_accounting_dimension_filter():
if not frappe.db.get_value(
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",

View File

@@ -67,7 +67,10 @@ class AccountingPeriod(Document):
for doctype_for_closing in self.get_doctypes_for_closing():
self.append(
"closed_documents",
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
{
"document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed,
},
)

View File

@@ -34,9 +34,7 @@ class TestAccountingPeriod(unittest.TestCase):
ap1 = create_accounting_period(period_name="Test Accounting Period 2")
ap1.save()
doc = create_sales_invoice(
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
self.assertRaises(ClosedAccountingPeriod, doc.save)
def tearDown(self):

View File

@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try:
bank_account.validate_iban()
except ValidationError:
msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban)
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()

View File

@@ -27,7 +27,7 @@ class BankClearance(Document):
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql(
"""
f"""
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
@@ -41,9 +41,7 @@ class BankClearance(Document):
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""".format(
condition=condition
),
""",
{"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
)
@@ -52,7 +50,7 @@ class BankClearance(Document):
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
"""
f"""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
@@ -67,9 +65,7 @@ class BankClearance(Document):
{condition}
order by
posting_date ASC, name DESC
""".format(
condition=condition
),
""",
{
"account": self.account,
"from": self.from_date,
@@ -132,11 +128,9 @@ class BankClearance(Document):
query = query.where(loan_repayment.clearance_date.isnull())
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))
query = query.where(loan_repayment.repay_from_salary == 0)
query = query.orderby(loan_repayment.posting_date).orderby(
loan_repayment.name, order=frappe.qb.desc
)
query = query.orderby(loan_repayment.posting_date).orderby(loan_repayment.name, order=frappe.qb.desc)
loan_repayments = query.run(as_dict=True)

View File

@@ -61,9 +61,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -76,10 +74,7 @@ def get_account_balance(bank_account, till_date):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = (
flt(balance_as_per_system)
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
)
return bank_bal
@@ -377,12 +372,13 @@ def auto_reconcile_vouchers(
)
transaction = frappe.get_doc("Bank Transaction", transaction.name)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
matched_trans = 0
for voucher in vouchers:
gl_entry = frappe.db.get_value(
"GL Entry",
dict(
account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
account=account,
voucher_type=voucher["payment_doctype"],
voucher_no=voucher["payment_name"],
),
["credit", "debit"],
as_dict=1,
@@ -731,7 +727,7 @@ def get_lr_matching_query(bank_account, exact_match, filters):
)
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))
query = query.where(loan_repayment.repay_from_salary == 0)
if exact_match:
query.where(loan_repayment.amount_paid == filters.get("amount"))
@@ -764,7 +760,7 @@ def get_pe_matching_query(
if cint(filter_by_reference_date):
filter_by_date = f"AND reference_date between '{from_reference_date}' and '{to_reference_date}'"
order_by = " reference_date"
if frappe.flags.auto_reconcile_vouchers == True:
if frappe.flags.auto_reconcile_vouchers is True:
filter_by_reference_no = f"AND reference_no = '{transaction.reference_number}'"
return f"""
SELECT
@@ -815,7 +811,7 @@ def get_je_matching_query(
if cint(filter_by_reference_date):
filter_by_date = f"AND je.cheque_date between '{from_reference_date}' and '{to_reference_date}'"
order_by = " je.cheque_date"
if frappe.flags.auto_reconcile_vouchers == True:
if frappe.flags.auto_reconcile_vouchers is True:
filter_by_reference_no = f"AND je.cheque_no = '{transaction.reference_number}'"
return f"""
SELECT

View File

@@ -21,7 +21,7 @@ INVALID_VALUES = ("", None)
class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs):
super(BankStatementImport, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def validate(self):
doc_before_save = self.get_doc_before_save()
@@ -30,7 +30,6 @@ class BankStatementImport(DataImport):
or (doc_before_save and doc_before_save.import_file != self.import_file)
or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url)
):
template_options_dict = {}
column_to_field_map = {}
bank = frappe.get_doc("Bank", self.bank)
@@ -45,7 +44,6 @@ class BankStatementImport(DataImport):
self.validate_google_sheets_url()
def start_import(self):
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
self.import_file, self.google_sheets_url
)
@@ -102,7 +100,7 @@ def download_errored_template(data_import_name):
def parse_data_from_template(raw_data):
data = []
for i, row in enumerate(raw_data):
for _i, row in enumerate(raw_data):
if all(v in INVALID_VALUES for v in row):
# empty row
continue
@@ -112,9 +110,7 @@ def parse_data_from_template(raw_data):
return data
def start_import(
data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
):
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
"""This method runs in background job"""
update_mapping_db(bank, template_options)

View File

@@ -1,5 +1,3 @@
from typing import Tuple, Union
import frappe
from frappe.utils import flt
from rapidfuzz import fuzz, process
@@ -19,7 +17,7 @@ class AutoMatchParty:
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> Union[Tuple, None]:
def match(self) -> tuple | None:
result = None
result = AutoMatchbyAccountIBAN(
bank_party_account_number=self.bank_party_account_number,
@@ -50,7 +48,7 @@ class AutoMatchbyAccountIBAN:
result = self.match_account_in_party()
return result
def match_account_in_party(self) -> Union[Tuple, None]:
def match_account_in_party(self) -> tuple | None:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None
parties = get_parties_in_order(self.deposit)
@@ -97,7 +95,7 @@ class AutoMatchbyPartyNameDescription:
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> Union[Tuple, None]:
def match(self) -> tuple | None:
# fuzzy search by customer/supplier & employee
if not (self.bank_party_name or self.description):
return None
@@ -105,7 +103,7 @@ class AutoMatchbyPartyNameDescription:
result = self.match_party_name_desc_in_party()
return result
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
def match_party_name_desc_in_party(self) -> tuple | None:
"""Fuzzy search party name and/or description against parties in the system"""
result = None
parties = get_parties_in_order(self.deposit)
@@ -130,7 +128,7 @@ class AutoMatchbyPartyNameDescription:
return result
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
def fuzzy_search_and_return_result(self, party, names, field) -> tuple | None:
skip = False
result = process.extract(
query=self.get(field),
@@ -147,7 +145,7 @@ class AutoMatchbyPartyNameDescription:
party_name,
), skip
def process_fuzzy_result(self, result: Union[list, None]):
def process_fuzzy_result(self, result: 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.

View File

@@ -186,9 +186,7 @@ def get_clearance_details(transaction, payment_entry):
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(
payment_entry.payment_document, payment_entry.payment_entry
)
bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry)
unallocated_amount = min(
transaction.unallocated_amount,
@@ -286,7 +284,6 @@ def get_total_allocated_amount(doctype, docname):
def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
@@ -325,9 +322,7 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
)
elif payment_entry.payment_document == "Loan Repayment":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
)
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value(
@@ -337,9 +332,7 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
else:
frappe.throw(
"Please reconcile {0}: {1} manually".format(
payment_entry.payment_document, payment_entry.payment_entry
)
f"Please reconcile {payment_entry.payment_document}: {payment_entry.payment_entry} manually"
)

View File

@@ -18,12 +18,12 @@ def upload_bank_statement():
fcontent = frappe.local.uploaded_file
fname = frappe.local.uploaded_filename
if frappe.safe_encode(fname).lower().endswith("csv".encode("utf-8")):
if frappe.safe_encode(fname).lower().endswith(b"csv"):
from frappe.utils.csvutils import read_csv_content
rows = read_csv_content(fcontent, False)
elif frappe.safe_encode(fname).lower().endswith("xlsx".encode("utf-8")):
elif frappe.safe_encode(fname).lower().endswith(b"xlsx"):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(fcontent=fcontent)

View File

@@ -430,9 +430,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
mode_of_payment.save()

View File

@@ -14,26 +14,6 @@ from frappe.utils.data import guess_date_format
class BisectAccountingStatements(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
algorithm: DF.Literal["BFS", "DFS"]
b_s_summary: DF.Float
company: DF.Link | None
current_from_date: DF.Datetime | None
current_node: DF.Link | None
current_to_date: DF.Datetime | None
difference: DF.Float
from_date: DF.Datetime | None
p_l_summary: DF.Float
to_date: DF.Datetime | None
# end: auto-generated types
def validate(self):
self.validate_dates()

View File

@@ -40,10 +40,11 @@ class Budget(Document):
select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where
ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and
b.fiscal_year=%s and b.name != %s and ba.account in (%s) """
% ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))),
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts),
ba.parent = b.name and b.docstatus < 2 and b.company = {} and {}={} and
b.fiscal_year={} and b.name != {} and ba.account in ({}) """.format(
"%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))
),
(self.company, budget_against, self.fiscal_year, self.name, *tuple(accounts)),
as_dict=1,
)
@@ -66,12 +67,14 @@ class Budget(Document):
if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company:
frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
frappe.throw(
_("Account {0} does not belongs to company {1}").format(d.account, self.company)
)
elif account_details.report_type != "Profit and Loss":
frappe.throw(
_("Budget cannot be assigned against {0}, as it's not an Income or Expense account").format(
d.account
)
_(
"Budget cannot be assigned against {0}, as it's not an Income or Expense account"
).format(d.account)
)
if d.account in account_list:
@@ -118,9 +121,7 @@ def validate_expense_against_budget(args, expense_amount=0):
"Company", args.get("company"), "exception_budget_approver_role"
)
if not frappe.get_cached_value(
"Budget", {"fiscal_year": args.fiscal_year, "company": args.company}
): # nosec
if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec
return
if not args.account:
@@ -151,30 +152,24 @@ def validate_expense_against_budget(args, expense_amount=0):
and args.account
and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense")
):
doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.%s)""" % (
doctype,
lft,
rgt,
budget_against,
) # nosec
condition = f"""and exists(select name from `tab{doctype}`
where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec
args.is_tree = True
else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}"
args.is_tree = False
args.budget_against_field = budget_against
args.budget_against_doctype = doctype
budget_records = frappe.db.sql(
"""
f"""
select
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
b.{budget_against} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request,
ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
@@ -187,9 +182,7 @@ def validate_expense_against_budget(args, expense_amount=0):
b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1
{condition}
""".format(
condition=condition, budget_against_field=budget_against
),
""",
(args.fiscal_year, args.account),
as_dict=True,
) # nosec
@@ -201,12 +194,18 @@ def validate_expense_against_budget(args, expense_amount=0):
def validate_budget_records(args, budget_records, expense_amount):
for budget in budget_records:
if flt(budget.budget_amount):
amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
args["for_material_request"] = budget.for_material_request
args["for_purchase_order"] = budget.for_purchase_order
if yearly_action in ("Stop", "Warn"):
compare_expense_with_budget(
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
args,
flt(budget.budget_amount),
_("Annual"),
yearly_action,
budget.budget_against,
expense_amount,
)
if monthly_action in ["Stop", "Warn"]:
@@ -217,18 +216,32 @@ def validate_budget_records(args, budget_records, expense_amount):
args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget(
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
args,
budget_amount,
_("Accumulated Monthly"),
monthly_action,
budget.budget_against,
expense_amount,
)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
actual_expense = get_actual_expense(args)
total_expense = actual_expense + amount
args.actual_expense, args.requested_amount, args.ordered_amount = get_actual_expense(args), 0, 0
if not amount:
args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args)
if args.get("doctype") == "Material Request" and args.for_material_request:
amount = args.requested_amount + args.ordered_amount
elif args.get("doctype") == "Purchase Order" and args.for_purchase_order:
amount = args.ordered_amount
total_expense = args.actual_expense + amount
if total_expense > budget_amount:
if actual_expense > budget_amount:
if args.actual_expense > budget_amount:
error_tense = _("is already")
diff = actual_expense - budget_amount
diff = args.actual_expense - budget_amount
else:
error_tense = _("will be")
diff = total_expense - budget_amount
@@ -245,9 +258,10 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.bold(fmt_money(diff, currency=currency)),
)
if (
frappe.flags.exception_approver_role
and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
msg += get_expense_breakup(args, currency, budget_against)
if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles(
frappe.session.user
):
action = "Warn"
@@ -257,6 +271,83 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
def get_expense_breakup(args, currency, budget_against):
msg = "<hr>Total Expenses booked through - <ul>"
common_filters = frappe._dict(
{
args.budget_against_field: budget_against,
"account": args.account,
"company": args.company,
}
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"General Ledger",
label="Actual Expenses",
filters=common_filters.copy().update(
{
"from_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_start_date"),
"to_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_end_date"),
"is_cancelled": 0,
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.actual_expense, currency=currency))
+ "</li>"
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"Material Request",
label="Material Requests",
report_type="Report Builder",
doctype="Material Request",
filters=common_filters.copy().update(
{
"status": [["!=", "Stopped"]],
"docstatus": 1,
"material_request_type": "Purchase",
"schedule_date": [["fiscal year", "2023-2024"]],
"item_code": args.item_code,
"per_ordered": [["<", 100]],
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.requested_amount, currency=currency))
+ "</li>"
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"Purchase Order",
label="Unbilled Orders",
report_type="Report Builder",
doctype="Purchase Order",
filters=common_filters.copy().update(
{
"status": [["!=", "Closed"]],
"docstatus": 1,
"transaction_date": [["fiscal year", "2023-2024"]],
"item_code": args.item_code,
"per_billed": [["<", 100]],
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.ordered_amount, currency=currency))
+ "</li></ul>"
)
return msg
def get_actions(args, budget):
yearly_action = budget.action_if_annual_budget_exceeded
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
@@ -272,31 +363,15 @@ def get_actions(args, budget):
return yearly_action, monthly_action
def get_amount(args, budget):
amount = 0
if args.get("doctype") == "Material Request" and budget.for_material_request:
amount = (
get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
)
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
return amount
def get_requested_amount(args, budget):
def get_requested_amount(args):
item_code = args.get("item_code")
condition = get_other_condition(args, budget, "Material Request")
condition = get_other_condition(args, "Material Request")
data = frappe.db.sql(
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(
condition
),
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {} and
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition),
item_code,
as_list=1,
)
@@ -304,17 +379,15 @@ def get_requested_amount(args, budget):
return data[0][0] if data else 0
def get_ordered_amount(args, budget):
def get_ordered_amount(args):
item_code = args.get("item_code")
condition = get_other_condition(args, budget, "Purchase Order")
condition = get_other_condition(args, "Purchase Order")
data = frappe.db.sql(
""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` child, `tabPurchase Order` parent where
parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
and parent.status != 'Closed' and {0}""".format(
condition
),
and parent.status != 'Closed' and {condition}""",
item_code,
as_list=1,
)
@@ -322,12 +395,12 @@ def get_ordered_amount(args, budget):
return data[0][0] if data else 0
def get_other_condition(args, budget, for_doc):
def get_other_condition(args, for_doc):
condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field):
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
condition += f" and child.{budget_against_field} = '{args.get(budget_against_field)}'"
if args.get("fiscal_year"):
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
@@ -335,12 +408,8 @@ def get_other_condition(args, budget, for_doc):
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
)
condition += """ and parent.%s
between '%s' and '%s' """ % (
date_field,
start_date,
end_date,
)
condition += f""" and parent.{date_field}
between '{start_date}' and '{end_date}' """
return condition
@@ -359,21 +428,17 @@ def get_actual_expense(args):
args.update(lft_rgt)
condition2 = """and exists(select name from `tab{doctype}`
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""".format(
doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
)
and name=gle.{budget_against_field})"""
else:
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
gle.{budget_against} = %({budget_against})s)""".format(
doctype=args.budget_against_doctype, budget_against=budget_against_field
)
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
where name=gle.{budget_against_field} and
gle.{budget_against_field} = %({budget_against_field})s)"""
amount = flt(
frappe.db.sql(
"""
f"""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where
@@ -384,9 +449,7 @@ def get_actual_expense(args):
and gle.company=%(company)s
and gle.docstatus=1
{condition2}
""".format(
condition1=condition1, condition2=condition2
),
""",
(args),
)[0][0]
) # nosec

View File

@@ -41,9 +41,7 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -63,9 +61,7 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -97,9 +93,7 @@ class TestBudget(unittest.TestCase):
)
fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
mr = frappe.get_doc(
@@ -138,9 +132,7 @@ class TestBudget(unittest.TestCase):
)
fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
@@ -158,9 +150,7 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
@@ -223,7 +213,7 @@ class TestBudget(unittest.TestCase):
if month > 9:
month = 9
for i in range(month + 1):
for _i in range(month + 1):
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
@@ -237,9 +227,7 @@ class TestBudget(unittest.TestCase):
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv.cancel)
@@ -255,7 +243,7 @@ class TestBudget(unittest.TestCase):
month = 9
project = frappe.get_value("Project", {"project_name": "_Test Project"})
for i in range(month + 1):
for _i in range(month + 1):
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
@@ -270,9 +258,7 @@ class TestBudget(unittest.TestCase):
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
)
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv.cancel)
@@ -284,9 +270,7 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -316,9 +300,7 @@ class TestBudget(unittest.TestCase):
).insert(ignore_permissions=True)
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -423,13 +405,11 @@ def make_budget(**args):
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project":
project_name = "{0}%".format("_Test Project/" + fiscal_year)
project_name = "{}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", project_name)})
else:
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all(
"Budget", fields=["name"], filters={"name": ("like", cost_center_name)}
)
cost_center_name = "{}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", cost_center_name)})
for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
@@ -451,24 +431,18 @@ def make_budget(**args):
budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
budget.budget_against = budget_against
budget.append(
"accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}
)
budget.append("accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000})
if args.applicable_on_material_request:
budget.applicable_on_material_request = 1
budget.action_if_annual_budget_exceeded_on_mr = (
args.action_if_annual_budget_exceeded_on_mr or "Warn"
)
budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or "Warn"
budget.action_if_accumulated_monthly_budget_exceeded_on_mr = (
args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn"
)
if args.applicable_on_purchase_order:
budget.applicable_on_purchase_order = 1
budget.action_if_annual_budget_exceeded_on_po = (
args.action_if_annual_budget_exceeded_on_po or "Warn"
)
budget.action_if_annual_budget_exceeded_on_po = args.action_if_annual_budget_exceeded_on_po or "Warn"
budget.action_if_accumulated_monthly_budget_exceeded_on_po = (
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
)

View File

@@ -26,9 +26,7 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document):
def validate(self):
if self.import_file:
get_coa(
"Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1
)
get_coa("Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1)
def validate_columns(data):
@@ -104,7 +102,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
file_path = file_doc.get_full_path()
data = []
with open(file_path, "r") as in_file:
with open(file_path) as in_file:
csv_reader = list(csv.reader(in_file))
headers = csv_reader[0]
del csv_reader[0] # delete top row and headers row
@@ -203,10 +201,10 @@ def build_forest(data):
for row in data:
account_name, parent_account, account_number, parent_account_number = row[0:4]
if account_number:
account_name = "{} - {}".format(account_number, account_name)
account_name = f"{account_number} - {account_name}"
if parent_account_number:
parent_account_number = cstr(parent_account_number).strip()
parent_account = "{} - {}".format(parent_account_number, parent_account)
parent_account = f"{parent_account_number} - {parent_account}"
if parent_account == account_name == child:
return [parent_account]
@@ -218,7 +216,7 @@ def build_forest(data):
frappe.bold(parent_account)
)
)
return [child] + parent_account_list
return [child, *parent_account_list]
charts_map, paths = {}, []
@@ -238,12 +236,12 @@ def build_forest(data):
) = i
if not account_name:
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
error_messages.append(f"Row {line_no}: Please enter Account Name")
name = account_name
if account_number:
account_number = cstr(account_number).strip()
account_name = "{} - {}".format(account_number, account_name)
account_name = f"{account_number} - {account_name}"
charts_map[account_name] = {}
charts_map[account_name]["account_name"] = name
@@ -340,9 +338,9 @@ def get_template(template_type, company):
def get_sample_template(writer, company):
currency = frappe.db.get_value("Company", company, "default_currency")
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv")) as f:
for row in f:
row = row.strip().split(",") + [currency]
row = [*row.strip().split(","), currency]
writer.writerow(row)
return writer
@@ -451,7 +449,7 @@ def unset_existing_data(company):
"Purchase Taxes and Charges Template",
]:
frappe.db.sql(
'''delete from `tab{0}` where `company`="%s"'''.format(doctype) % (company) # nosec
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
)

View File

@@ -31,71 +31,71 @@ def create_or_update_cheque_print_format(template_name):
cheque_print.html = """
<style>
.print-format {
.print-format {{
padding: 0px;
}
@media screen {
.print-format {
}}
@media screen {{
.print-format {{
padding: 0in;
}
}
}}
}}
</style>
<div style="position: relative; top:%(starting_position_from_top_edge)scm">
<div style="width:%(cheque_width)scm;height:%(cheque_height)scm;">
<span style="top:%(acc_pay_dist_from_top_edge)scm; left:%(acc_pay_dist_from_left_edge)scm;
<div style="position: relative; top:{starting_position_from_top_edge}cm">
<div style="width:{cheque_width}cm;height:{cheque_height}cm;">
<span style="top:{acc_pay_dist_from_top_edge}cm; left:{acc_pay_dist_from_left_edge}cm;
border-bottom: solid 1px;border-top:solid 1px; width:2cm;text-align: center; position: absolute;">
%(message_to_show)s
{message_to_show}
</span>
<span style="top:%(date_dist_from_top_edge)scm; left:%(date_dist_from_left_edge)scm;
<span style="top:{date_dist_from_top_edge}cm; left:{date_dist_from_left_edge}cm;
position: absolute;">
{{ frappe.utils.formatdate(doc.reference_date) or '' }}
{{{{ frappe.utils.formatdate(doc.reference_date) or '' }}}}
</span>
<span style="top:%(acc_no_dist_from_top_edge)scm;left:%(acc_no_dist_from_left_edge)scm;
<span style="top:{acc_no_dist_from_top_edge}cm;left:{acc_no_dist_from_left_edge}cm;
position: absolute; min-width: 6cm;">
{{ doc.account_no or '' }}
{{{{ doc.account_no or '' }}}}
</span>
<span style="top:%(payer_name_from_top_edge)scm;left: %(payer_name_from_left_edge)scm;
<span style="top:{payer_name_from_top_edge}cm;left: {payer_name_from_left_edge}cm;
position: absolute; min-width: 6cm;">
{{doc.party_name}}
{{{{doc.party_name}}}}
</span>
<span style="top:%(amt_in_words_from_top_edge)scm; left:%(amt_in_words_from_left_edge)scm;
position: absolute; display: block; width: %(amt_in_word_width)scm;
line-height:%(amt_in_words_line_spacing)scm; word-wrap: break-word;">
{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}
<span style="top:{amt_in_words_from_top_edge}cm; left:{amt_in_words_from_left_edge}cm;
position: absolute; display: block; width: {amt_in_word_width}cm;
line-height:{amt_in_words_line_spacing}cm; word-wrap: break-word;">
{{{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}}}
</span>
<span style="top:%(amt_in_figures_from_top_edge)scm;left: %(amt_in_figures_from_left_edge)scm;
<span style="top:{amt_in_figures_from_top_edge}cm;left: {amt_in_figures_from_left_edge}cm;
position: absolute; min-width: 4cm;">
{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}
{{{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}}}
</span>
<span style="top:%(signatory_from_top_edge)scm;left: %(signatory_from_left_edge)scm;
<span style="top:{signatory_from_top_edge}cm;left: {signatory_from_left_edge}cm;
position: absolute; min-width: 6cm;">
{{doc.company}}
{{{{doc.company}}}}
</span>
</div>
</div>""" % {
"starting_position_from_top_edge": doc.starting_position_from_top_edge
</div>""".format(
starting_position_from_top_edge=doc.starting_position_from_top_edge
if doc.cheque_size == "A4"
else 0.0,
"cheque_width": doc.cheque_width,
"cheque_height": doc.cheque_height,
"acc_pay_dist_from_top_edge": doc.acc_pay_dist_from_top_edge,
"acc_pay_dist_from_left_edge": doc.acc_pay_dist_from_left_edge,
"message_to_show": doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
"date_dist_from_top_edge": doc.date_dist_from_top_edge,
"date_dist_from_left_edge": doc.date_dist_from_left_edge,
"acc_no_dist_from_top_edge": doc.acc_no_dist_from_top_edge,
"acc_no_dist_from_left_edge": doc.acc_no_dist_from_left_edge,
"payer_name_from_top_edge": doc.payer_name_from_top_edge,
"payer_name_from_left_edge": doc.payer_name_from_left_edge,
"amt_in_words_from_top_edge": doc.amt_in_words_from_top_edge,
"amt_in_words_from_left_edge": doc.amt_in_words_from_left_edge,
"amt_in_word_width": doc.amt_in_word_width,
"amt_in_words_line_spacing": doc.amt_in_words_line_spacing,
"amt_in_figures_from_top_edge": doc.amt_in_figures_from_top_edge,
"amt_in_figures_from_left_edge": doc.amt_in_figures_from_left_edge,
"signatory_from_top_edge": doc.signatory_from_top_edge,
"signatory_from_left_edge": doc.signatory_from_left_edge,
}
cheque_width=doc.cheque_width,
cheque_height=doc.cheque_height,
acc_pay_dist_from_top_edge=doc.acc_pay_dist_from_top_edge,
acc_pay_dist_from_left_edge=doc.acc_pay_dist_from_left_edge,
message_to_show=doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
date_dist_from_top_edge=doc.date_dist_from_top_edge,
date_dist_from_left_edge=doc.date_dist_from_left_edge,
acc_no_dist_from_top_edge=doc.acc_no_dist_from_top_edge,
acc_no_dist_from_left_edge=doc.acc_no_dist_from_left_edge,
payer_name_from_top_edge=doc.payer_name_from_top_edge,
payer_name_from_left_edge=doc.payer_name_from_left_edge,
amt_in_words_from_top_edge=doc.amt_in_words_from_top_edge,
amt_in_words_from_left_edge=doc.amt_in_words_from_left_edge,
amt_in_word_width=doc.amt_in_word_width,
amt_in_words_line_spacing=doc.amt_in_words_line_spacing,
amt_in_figures_from_top_edge=doc.amt_in_figures_from_top_edge,
amt_in_figures_from_left_edge=doc.amt_in_figures_from_left_edge,
signatory_from_top_edge=doc.signatory_from_top_edge,
signatory_from_left_edge=doc.signatory_from_left_edge,
)
cheque_print.save(ignore_permissions=True)

View File

@@ -125,7 +125,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2022-01-31 13:22:58.916273",
"modified": "2024-04-24 10:55:54.083042",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
@@ -163,6 +163,15 @@
{
"read": 1,
"role": "Purchase User"
},
{
"email": 1,
"export": 1,
"print": 1,
"report": 1,
"role": "Employee",
"select": 1,
"share": 1
}
],
"search_fields": "parent_cost_center, is_group",

View File

@@ -15,9 +15,7 @@ class CostCenter(NestedSet):
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(
self.cost_center_number, self.cost_center_name, self.company
)
self.name = get_autoname_with_number(self.cost_center_number, self.cost_center_name, self.company)
def validate(self):
self.validate_mandatory()
@@ -90,14 +88,14 @@ class CostCenter(NestedSet):
new_cost_center = get_name_with_abbr(newdn, self.company)
# Validate properties before merging
super(CostCenter, self).before_rename(olddn, new_cost_center, merge, "is_group")
super().before_rename(olddn, new_cost_center, merge, "is_group")
if not merge:
new_cost_center = get_name_with_number(new_cost_center, self.cost_center_number)
return new_cost_center
def after_rename(self, olddn, newdn, merge=False):
super(CostCenter, self).after_rename(olddn, newdn, merge)
super().after_rename(olddn, newdn, merge)
if not merge:
new_cost_center = frappe.db.get_value(

View File

@@ -10,7 +10,6 @@ test_records = frappe.get_test_records("Cost Center")
class TestCostCenter(unittest.TestCase):
def test_cost_center_creation_against_child_node(self):
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
frappe.get_doc(test_records[1]).insert()

View File

@@ -29,7 +29,7 @@ class InvalidDateError(frappe.ValidationError):
class CostCenterAllocation(Document):
def __init__(self, *args, **kwargs):
super(CostCenterAllocation, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._skip_from_date_validation = False
def validate(self):
@@ -44,9 +44,7 @@ class CostCenterAllocation(Document):
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
if total_percentage != 100:
frappe.throw(
_("Total percentage against cost centers should be 100"), WrongPercentageAllocation
)
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
def validate_from_date_based_on_existing_gle(self):
# Check if GLE exists against the main cost center

View File

@@ -18,7 +18,6 @@ class CurrencyExchangeSettings(Document):
def set_parameters_and_result(self):
if self.service_provider == "exchangerate.host":
if not self.access_key:
frappe.throw(
_("Access Key is required for Service Provider: {0}").format(
@@ -53,9 +52,7 @@ class CurrencyExchangeSettings(Document):
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
api_url = self.api_endpoint.format(
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
api_url = self.api_endpoint.format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
try:
response = requests.get(api_url, params=params)
@@ -75,14 +72,14 @@ class CurrencyExchangeSettings(Document):
]
except Exception:
frappe.throw(_("Invalid result key. Response:") + " " + response.text)
if not isinstance(value, (int, float)):
if not isinstance(value, int | float):
frappe.throw(_("Returned exchange rate is neither integer not float."))
self.url = response.url
@frappe.whitelist()
def get_api_endpoint(service_provider: str = None, use_http: bool = False):
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
if service_provider == "exchangerate.host":
api = "api.exchangerate.host/convert"

View File

@@ -246,7 +246,6 @@ class ExchangeRateRevaluation(Document):
# Handle Accounts with '0' balance in Account/Base Currency
for d in [x for x in account_details if x.zero_balance]:
if d.balance != 0:
current_exchange_rate = new_exchange_rate = 0
@@ -259,7 +258,8 @@ class ExchangeRateRevaluation(Document):
new_balance_in_account_currency = 0
current_exchange_rate = (
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party)
or 0.0
)
gain_loss = new_balance_in_account_currency - (
@@ -313,9 +313,7 @@ class ExchangeRateRevaluation(Document):
revaluation_jv = self.make_jv_for_revaluation()
if revaluation_jv:
frappe.msgprint(
f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}"
)
frappe.msgprint(f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}")
return {
"revaluation_jv": revaluation_jv.name if revaluation_jv else None,
@@ -372,7 +370,8 @@ class ExchangeRateRevaluation(Document):
journal_account.update(
{
dr_or_cr: flt(
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
abs(d.get("balance_in_account_currency")),
d.precision("balance_in_account_currency"),
),
reverse_dr_or_cr: 0,
"debit": 0,
@@ -498,7 +497,9 @@ class ExchangeRateRevaluation(Document):
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
),
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
"exchange_rate": flt(
d.get("current_exchange_rate"), d.precision("current_exchange_rate")
),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
@@ -576,7 +577,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
@frappe.whitelist()
def get_account_details(
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float | None = None
):
if not (company and posting_date):
frappe.throw(_("Company and Posting Date is mandatory"))
@@ -589,7 +590,7 @@ def get_account_details(
frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type))
account_details = {}
company_currency = erpnext.get_company_currency(company)
erpnext.get_company_currency(company)
account_details = {
"account_currency": account_currency,
@@ -603,9 +604,7 @@ def get_account_details(
rounding_loss_allowance=rounding_loss_allowance,
)
if account_balance and (
account_balance[0].balance or account_balance[0].balance_in_account_currency
):
if account_balance and (account_balance[0].balance or account_balance[0].balance_in_account_currency):
if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance(
company, posting_date, account_balance
):

View File

@@ -1,21 +1,14 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, today
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.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.stock.doctype.item.test_item import create_item
class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
@@ -73,9 +66,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
err.extend("accounts", accounts)
row = err.accounts[0]
row.new_exchange_rate = 85
row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.new_balance_in_base_currency = flt(row.new_exchange_rate * flt(row.balance_in_account_currency))
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
err.set_total_gain_loss()
err = err.save().submit()
@@ -127,9 +118,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
pe.save().submit()
# Cancel the auto created gain/loss JE to simulate balance only in base currency
je = frappe.db.get_all(
"Journal Entry Account", filters={"reference_name": si.name}, pluck="parent"
)[0]
je = frappe.db.get_all("Journal Entry Account", filters={"reference_name": si.name}, pluck="parent")[
0
]
frappe.get_doc("Journal Entry", je).cancel()
err = frappe.new_doc("Exchange Rate Revaluation")
@@ -235,9 +226,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
self.assertEqual(flt(acc.debit, precision), 0.0)
self.assertEqual(flt(acc.credit, precision), 0.0)
row = [x for x in je.accounts if x.account == self.debtors_usd][0]
row = next(x for x in je.accounts if x.account == self.debtors_usd)
self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD
row = [x for x in je.accounts if x.account != self.debtors_usd][0]
row = next(x for x in je.accounts if x.account != self.debtors_usd)
self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR
# total_debit and total_credit will be 0.0, as JV is posting only to account currency fields
@@ -294,5 +285,5 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
"new_balance_in_account_currency": 100.0,
}
for key, val in expected_data.items():
for key, _val in expected_data.items():
self.assertEqual(expected_data.get(key), account_details.get(key))

View File

@@ -118,9 +118,17 @@
{
"read": 1,
"role": "Employee"
},
{
"read": 1,
"role": "Accounts Manager"
},
{
"read": 1,
"role": "Stock Manager"
}
],
"show_name_in_global_search": 1,
"sort_field": "name",
"sort_order": "DESC"
}
}

View File

@@ -98,9 +98,9 @@ class FiscalYear(Document):
if overlap:
frappe.throw(
_("Year start date or end date is overlapping with {0}. To avoid please set company").format(
existing.name
),
_(
"Year start date or end date is overlapping with {0}. To avoid please set company"
).format(existing.name),
frappe.NameError,
)
@@ -116,9 +116,9 @@ def check_duplicate_fiscal_year(doc):
not frappe.flags.in_test
):
frappe.throw(
_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(
fiscal_year
)
_(
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
).format(fiscal_year)
)

View File

@@ -63,13 +63,18 @@ class GLEntry(Document):
]:
# Update outstanding amt on against voucher
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
self.against_voucher_type
in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
self.account,
self.party_type,
self.party,
self.against_voucher_type,
self.against_voucher,
)
def check_mandatory(self):
@@ -135,12 +140,13 @@ class GLEntry(Document):
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
and not self.is_cancelled
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
_(
"Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}."
).format(dimension.label, self.account)
)
if (
@@ -148,12 +154,13 @@ class GLEntry(Document):
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
and not self.is_cancelled
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
_(
"Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}."
).format(dimension.label, self.account)
)
def check_pl_account(self):
@@ -201,9 +208,7 @@ class GLEntry(Document):
if not self.cost_center:
return
is_group, company = frappe.get_cached_value(
"Cost Center", self.cost_center, ["is_group", "company"]
)
is_group, company = frappe.get_cached_value("Cost Center", self.cost_center, ["is_group", "company"])
if company != self.company:
frappe.throw(
@@ -272,7 +277,7 @@ def update_outstanding_amt(
account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
):
if party_type and party:
party_condition = " and party_type={0} and party={1}".format(
party_condition = " and party_type={} and party={}".format(
frappe.db.escape(party_type), frappe.db.escape(party)
)
else:
@@ -280,23 +285,19 @@ def update_outstanding_amt(
if against_voucher_type == "Sales Invoice":
party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
account_condition = "and account in ({0}, {1})".format(
frappe.db.escape(account), frappe.db.escape(party_account)
)
account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})"
else:
account_condition = " and account = {0}".format(frappe.db.escape(account))
account_condition = f" and account = {frappe.db.escape(account)}"
# get final outstanding amt
bal = flt(
frappe.db.sql(
"""
f"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where against_voucher_type=%s and against_voucher=%s
and voucher_type != 'Invoice Discounting'
{0} {1}""".format(
party_condition, account_condition
),
{party_condition} {account_condition}""",
(against_voucher_type, against_voucher),
)[0][0]
or 0.0
@@ -307,12 +308,10 @@ def update_outstanding_amt(
elif against_voucher_type == "Journal Entry":
against_voucher_amount = flt(
frappe.db.sql(
"""
f"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
and account = %s and (against_voucher is null or against_voucher='') {0}""".format(
party_condition
),
and account = %s and (against_voucher is null or against_voucher='') {party_condition}""",
(against_voucher, account),
)[0][0]
)
@@ -331,7 +330,9 @@ def update_outstanding_amt(
# Validation : Outstanding can not be negative for JV
if bal < 0 and not on_cancel:
frappe.throw(
_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))
_("Outstanding for {0} cannot be less than zero ({1})").format(
against_voucher, fmt_money(bal)
)
)
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
@@ -404,7 +405,7 @@ def rename_temporarily_named_docs(doctype):
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
newname = doc.name
frappe.db.sql(
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0 where name = %s",
(newname, oldname),
auto_commit=True,
)

View File

@@ -14,9 +14,7 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestGLEntry(unittest.TestCase):
def test_round_off_entry(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
frappe.db.set_value(
"Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC"
)
frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
@@ -73,7 +71,9 @@ class TestGLEntry(unittest.TestCase):
)
self.assertTrue(all(entry.to_rename == 0 for entry in new_gl_entries))
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
self.assertTrue(
all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries, strict=False))
)
new_naming_series_current_value = frappe.db.sql(
"SELECT current from tabSeries where name = %s", naming_series

View File

@@ -55,9 +55,7 @@ class InvoiceDiscounting(AccountsController):
frappe.throw(
_(
"Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}"
).format(
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)
)
).format(record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice))
)
def calculate_total_amount(self):
@@ -77,7 +75,9 @@ class InvoiceDiscounting(AccountsController):
self.status = status
self.db_set("status", status)
for d in self.invoices:
frappe.get_doc("Sales Invoice", d.sales_invoice).set_status(update=True, update_modified=False)
frappe.get_doc("Sales Invoice", d.sales_invoice).set_status(
update=True, update_modified=False
)
else:
self.status = "Draft"
if self.docstatus == 1:

View File

@@ -75,8 +75,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
gle = get_gl_entries("Invoice Discounting", inv_disc.name)
expected_gle = {inv.debit_to: [0.0, 200], self.ar_credit: [200, 0.0]}
for i, gle in enumerate(gle):
self.assertEqual([gle.debit, gle.credit], expected_gle.get(gle.account))
for _i, gle_value in enumerate(gle):
self.assertEqual([gle_value.debit, gle_value.credit], expected_gle.get(gle_value.account))
def test_loan_on_submit(self):
inv = create_sales_invoice(rate=300)
@@ -92,9 +92,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
period=60,
)
self.assertEqual(inv_disc.status, "Sanctioned")
self.assertEqual(
inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period)
)
self.assertEqual(inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period))
def test_on_disbursed(self):
inv = create_sales_invoice(rate=500)
@@ -262,13 +260,9 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_discounted)
self.assertEqual(
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
self.assertEqual(
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)
@@ -304,13 +298,9 @@ class TestInvoiceDiscounting(unittest.TestCase):
je_on_payment.submit()
self.assertEqual(je_on_payment.accounts[0].account, self.ar_unpaid)
self.assertEqual(
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
self.assertEqual(
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
)
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
inv.reload()
self.assertEqual(inv.outstanding_amount, 0)

View File

@@ -14,7 +14,7 @@ class ItemTaxTemplate(Document):
def autoname(self):
if self.company and self.title:
abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = "{0} - {1}".format(self.title, abbr)
self.name = f"{self.title} - {abbr}"
def validate_tax_accounts(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""

View File

@@ -33,7 +33,7 @@ class StockAccountInvalidTransaction(frappe.ValidationError):
class JournalEntry(AccountsController):
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def get_feed(self):
return self.voucher_type
@@ -102,7 +102,7 @@ class JournalEntry(AccountsController):
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller
super(JournalEntry, self).on_cancel()
super().on_cancel()
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
@@ -137,10 +137,7 @@ class JournalEntry(AccountsController):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def validate_inter_company_accounts(self):
if (
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
@@ -286,10 +283,7 @@ class JournalEntry(AccountsController):
asset.set_status()
def update_inter_company_jv(self):
if (
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
frappe.db.set_value(
"Journal Entry",
self.inter_company_journal_entry_reference,
@@ -317,17 +311,25 @@ class JournalEntry(AccountsController):
if d.account == inv_disc_doc.short_term_loan and d.reference_name == inv_disc:
if self.docstatus == 1:
if d.credit > 0:
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Sanctioned", d.idx)
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Sanctioned", d.idx
)
status = "Disbursed"
elif d.debit > 0:
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Disbursed", d.idx)
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Disbursed", d.idx
)
status = "Settled"
else:
if d.credit > 0:
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Disbursed", d.idx)
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Disbursed", d.idx
)
status = "Sanctioned"
elif d.debit > 0:
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Settled", d.idx)
_validate_invoice_discounting_status(
inv_disc, inv_disc_doc.status, "Settled", d.idx
)
status = "Disbursed"
break
if status:
@@ -384,10 +386,7 @@ class JournalEntry(AccountsController):
)
def unlink_inter_company_jv(self):
if (
self.voucher_type == "Inter Company Journal Entry"
and self.inter_company_journal_entry_reference
):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
frappe.db.set_value(
"Journal Entry",
self.inter_company_journal_entry_reference,
@@ -409,9 +408,9 @@ class JournalEntry(AccountsController):
if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party):
frappe.throw(
_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(
d.idx, d.account
)
_(
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
).format(d.idx, d.account)
)
elif (
d.party_type
@@ -476,16 +475,18 @@ class JournalEntry(AccountsController):
def system_generated_gain_loss(self):
return (
self.voucher_type == "Exchange Gain Or Loss"
and self.multi_currency
and self.is_system_generated
self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency and self.is_system_generated
)
def validate_against_jv(self):
for d in self.get("accounts"):
if d.reference_type == "Journal Entry":
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0 and not self.system_generated_gain_loss():
if (
account_root_type == "Asset"
and flt(d.debit) > 0
and not self.system_generated_gain_loss()
):
frappe.throw(
_(
"Row #{0}: For {1}, you can select reference document only if account gets credited"
@@ -567,11 +568,13 @@ class JournalEntry(AccountsController):
if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
frappe.throw(
_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type)
_("Row {0}: Credit entry can not be linked with a {1}").format(
d.idx, d.reference_type
)
)
# set totals
if not d.reference_name in self.reference_totals:
if d.reference_name not in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
@@ -589,7 +592,10 @@ class JournalEntry(AccountsController):
# check if party and account match
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
if self.voucher_type in ("Deferred Revenue", "Deferred Expense") and d.reference_detail_no:
if (
self.voucher_type in ("Deferred Revenue", "Deferred Expense")
and d.reference_detail_no
):
debit_or_credit = "Debit" if d.debit else "Credit"
party_account = get_deferred_booking_accounts(
d.reference_type, d.reference_detail_no, debit_or_credit
@@ -598,7 +604,8 @@ class JournalEntry(AccountsController):
else:
if d.reference_type == "Sales Invoice":
party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
get_party_account_based_on_invoice_discounting(d.reference_name)
or against_voucher[1]
)
else:
party_account = against_voucher[1]
@@ -722,7 +729,9 @@ class JournalEntry(AccountsController):
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
if self.difference:
frappe.throw(
_("Total Debit must be equal to Total Credit. The difference is {0}").format(self.difference)
_("Total Debit must be equal to Total Credit. The difference is {0}").format(
self.difference
)
)
def set_total_debit_credit(self):
@@ -786,7 +795,6 @@ class JournalEntry(AccountsController):
and self.posting_date
)
):
ignore_exchange_rate = False
if self.get("flags") and self.flags.get("ignore_exchange_rate"):
ignore_exchange_rate = True
@@ -1032,27 +1040,21 @@ class JournalEntry(AccountsController):
self.validate_total_debit_and_credit()
def get_values(self):
cond = (
" and outstanding_amount <= {0}".format(self.write_off_amount)
if flt(self.write_off_amount) > 0
else ""
)
cond = f" and outstanding_amount <= {self.write_off_amount}" if flt(self.write_off_amount) > 0 else ""
if self.write_off_based_on == "Accounts Receivable":
return frappe.db.sql(
"""select name, debit_to as account, customer as party, outstanding_amount
from `tabSales Invoice` where docstatus = 1 and company = %s
and outstanding_amount > 0 %s"""
% ("%s", cond),
from `tabSales Invoice` where docstatus = 1 and company = {}
and outstanding_amount > 0 {}""".format("%s", cond),
self.company,
as_dict=True,
)
elif self.write_off_based_on == "Accounts Payable":
return frappe.db.sql(
"""select name, credit_to as account, supplier as party, outstanding_amount
from `tabPurchase Invoice` where docstatus = 1 and company = %s
and outstanding_amount > 0 %s"""
% ("%s", cond),
from `tabPurchase Invoice` where docstatus = 1 and company = {}
and outstanding_amount > 0 {}""".format("%s", cond),
self.company,
as_dict=True,
)
@@ -1161,7 +1163,7 @@ def get_payment_entry_against_order(
"amount_field_bank": amount_field_bank,
"amount": amount,
"debit_in_account_currency": debit_in_account_currency,
"remarks": "Advance Payment received against {0} {1}".format(dt, dn),
"remarks": f"Advance Payment received against {dt} {dn}",
"is_advance": "Yes",
"bank_account": bank_account,
"journal_entry": journal_entry,
@@ -1200,7 +1202,7 @@ def get_payment_entry_against_invoice(
"amount_field_bank": amount_field_bank,
"amount": amount if amount else abs(ref_doc.outstanding_amount),
"debit_in_account_currency": debit_in_account_currency,
"remarks": "Payment received against {0} {1}. {2}".format(dt, dn, ref_doc.remarks),
"remarks": f"Payment received against {dt} {dn}. {ref_doc.remarks}",
"is_advance": "No",
"bank_account": bank_account,
"journal_entry": journal_entry,
@@ -1226,9 +1228,7 @@ def get_payment_entry(ref_doc, args):
)
je = frappe.new_doc("Journal Entry")
je.update(
{"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")}
)
je.update({"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")})
party_row = je.append(
"accounts",
@@ -1251,9 +1251,7 @@ def get_payment_entry(ref_doc, args):
bank_row = je.append("accounts")
# Make it bank_details
bank_account = get_default_bank_cash_account(
ref_doc.company, "Bank", account=args.get("bank_account")
)
bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
if bank_account:
bank_row.update(bank_account)
# Modified to include the posting date for which the exchange rate is required.
@@ -1293,7 +1291,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return []
return frappe.db.sql(
"""
f"""
SELECT jv.name, jv.posting_date, jv.user_remark
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name
@@ -1304,16 +1302,14 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
OR jv_detail.reference_type = ''
)
AND jv.docstatus = 1
AND jv.`{0}` LIKE %(txt)s
AND jv.`{searchfield}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(limit)s offset %(offset)s
""".format(
searchfield
),
""",
dict(
account=filters.get("account"),
party=cstr(filters.get("party")),
txt="%{0}%".format(txt),
txt=f"%{txt}%",
offset=start,
limit=page_len,
),
@@ -1335,19 +1331,15 @@ def get_outstanding(args):
condition = " and party=%(party)s" if args.get("party") else ""
against_jv_amount = frappe.db.sql(
"""
f"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0}
and (reference_type is null or reference_type = '')""".format(
condition
),
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {condition}
and (reference_type is null or reference_type = '')""",
args,
)
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
amount_field = (
"credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
)
amount_field = "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
return {amount_field: abs(against_jv_amount)}
elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"):
party_type = "Customer" if args.get("doctype") == "Sales Invoice" else "Supplier"
@@ -1360,9 +1352,7 @@ def get_outstanding(args):
due_date = invoice.get("due_date")
exchange_rate = (
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
)
exchange_rate = invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
if args["doctype"] == "Sales Invoice":
amount_field = (
@@ -1400,17 +1390,13 @@ def get_party_account_and_currency(company, party_type, party):
@frappe.whitelist()
def get_account_details_and_party_type(
account, date, company, debit=None, credit=None, exchange_rate=None
):
def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
company_currency = erpnext.get_company_currency(company)
account_details = frappe.db.get_value(
"Account", account, ["account_type", "account_currency"], as_dict=1
)
account_details = frappe.db.get_value("Account", account, ["account_type", "account_currency"], as_dict=1)
if not account_details:
return

View File

@@ -69,10 +69,8 @@ class TestJournalEntry(unittest.TestCase):
self.assertTrue(
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s and {0}=400""".format(
dr_or_cr
),
f"""select name from `tabJournal Entry Account`
where reference_type = %s and reference_name = %s and {dr_or_cr}=400""",
(submitted_voucher.doctype, submitted_voucher.name),
)
)
@@ -84,9 +82,8 @@ class TestJournalEntry(unittest.TestCase):
def advance_paid_testcase(self, base_jv, test_voucher, dr_or_cr):
# Test advance paid field
advance_paid = frappe.db.sql(
"""select advance_paid from `tab%s`
where name=%s"""
% (test_voucher.doctype, "%s"),
"""select advance_paid from `tab{}`
where name={}""".format(test_voucher.doctype, "%s"),
(test_voucher.name),
)
payment_against_order = base_jv.get("accounts")[0].get(dr_or_cr)
@@ -159,9 +156,7 @@ class TestJournalEntry(unittest.TestCase):
jv.cancel()
def test_multi_currency(self):
jv = make_journal_entry(
"_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False
)
jv = make_journal_entry("_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
@@ -201,7 +196,7 @@ class TestJournalEntry(unittest.TestCase):
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# cancel
@@ -263,7 +258,7 @@ class TestJournalEntry(unittest.TestCase):
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self):

View File

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

View File

@@ -0,0 +1,70 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2024-03-26 17:01:47.443986",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"voucher_type",
"voucher_no",
"checked_on",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch"
],
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Data",
"label": "Voucher Type"
},
{
"fieldname": "voucher_no",
"fieldtype": "Data",
"label": "Voucher No"
},
{
"default": "0",
"fieldname": "debit_credit_mismatch",
"fieldtype": "Check",
"label": "Debit-Credit mismatch"
},
{
"fieldname": "checked_on",
"fieldtype": "Datetime",
"label": "Checked On"
},
{
"default": "0",
"fieldname": "general_and_payment_ledger_mismatch",
"fieldtype": "Check",
"label": "General and Payment Ledger mismatch"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-04-09 11:16:07.044484",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Ledger Health",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,25 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LedgerHealth(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
checked_on: DF.Datetime | None
debit_credit_mismatch: DF.Check
general_and_payment_ledger_mismatch: DF.Check
name: DF.Int | None
voucher_no: DF.Data | None
voucher_type: DF.Data | None
# end: auto-generated types
pass

View File

@@ -0,0 +1,109 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import run_ledger_health_checks
class TestLedgerHealth(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
self.configure_monitoring_tool()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def configure_monitoring_tool(self):
monitor_settings = frappe.get_doc("Ledger Health Monitor")
monitor_settings.enable_health_monitor = True
monitor_settings.enable_for_last_x_days = 60
monitor_settings.debit_credit_mismatch = True
monitor_settings.general_and_payment_ledger_mismatch = True
exists = [x for x in monitor_settings.companies if x.company == self.company]
if not exists:
monitor_settings.append("companies", {"company": self.company})
monitor_settings.save()
def clear_old_entries(self):
super().clear_old_entries()
lh = qb.DocType("Ledger Health")
qb.from_(lh).delete().run()
def create_journal(self):
je = frappe.new_doc("Journal Entry")
je.company = self.company
je.voucher_type = "Journal Entry"
je.posting_date = nowdate()
je.append(
"accounts",
{
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 10000,
},
)
je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000})
je.save().submit()
self.je = je
def test_debit_credit_mismatch(self):
self.create_journal()
# manually cause debit-credit mismatch
gle = frappe.db.get_all(
"GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account}
)[0]
frappe.db.set_value("GL Entry", gle.name, "credit", 8000)
run_ledger_health_checks()
expected = {
"voucher_type": self.je.doctype,
"voucher_no": self.je.name,
"debit_credit_mismatch": True,
"general_and_payment_ledger_mismatch": False,
}
actual = frappe.db.get_all(
"Ledger Health",
fields=[
"voucher_type",
"voucher_no",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch",
],
)
self.assertEqual(len(actual), 1)
self.assertEqual(expected, actual[0])
def test_gl_and_pl_mismatch(self):
self.create_journal()
# manually cause GL and PL discrepancy
ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0]
frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000)
run_ledger_health_checks()
expected = {
"voucher_type": self.je.doctype,
"voucher_no": self.je.name,
"debit_credit_mismatch": False,
"general_and_payment_ledger_mismatch": True,
}
actual = frappe.db.get_all(
"Ledger Health",
fields=[
"voucher_type",
"voucher_no",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch",
],
)
self.assertEqual(len(actual), 1)
self.assertEqual(expected, actual[0])

View File

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

View File

@@ -0,0 +1,104 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-03-27 09:38:07.427997",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"enable_health_monitor",
"monitor_section",
"monitor_for_last_x_days",
"debit_credit_mismatch",
"general_and_payment_ledger_mismatch",
"section_break_xdsp",
"companies"
],
"fields": [
{
"default": "0",
"fieldname": "enable_health_monitor",
"fieldtype": "Check",
"label": "Enable Health Monitor"
},
{
"fieldname": "monitor_section",
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"default": "0",
"fieldname": "debit_credit_mismatch",
"fieldtype": "Check",
"label": "Debit-Credit Mismatch"
},
{
"default": "0",
"fieldname": "general_and_payment_ledger_mismatch",
"fieldtype": "Check",
"label": "Discrepancy between General and Payment Ledger"
},
{
"default": "60",
"fieldname": "monitor_for_last_x_days",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Monitor for Last 'X' days",
"reqd": 1
},
{
"fieldname": "section_break_xdsp",
"fieldtype": "Section Break",
"label": "Companies"
},
{
"fieldname": "companies",
"fieldtype": "Table",
"options": "Ledger Health Monitor Company"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-27 10:14:16.511681",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Ledger Health Monitor",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts User",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,28 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LedgerHealthMonitor(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.ledger_health_monitor_company.ledger_health_monitor_company import (
LedgerHealthMonitorCompany,
)
companies: DF.Table[LedgerHealthMonitorCompany]
debit_credit_mismatch: DF.Check
enable_health_monitor: DF.Check
general_and_payment_ledger_mismatch: DF.Check
monitor_for_last_x_days: DF.Int
# end: auto-generated types
pass

View File

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

View File

@@ -0,0 +1,32 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-03-27 10:04:45.727054",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 10:06:22.806155",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Ledger Health Monitor Company",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,23 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LedgerHealthMonitorCompany(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass

View File

@@ -83,7 +83,10 @@ class TestLedgerMerge(unittest.TestCase):
"account": "Indirect Income - _TC",
"merge_accounts": [
{"account": "Indirect Test Income - _TC", "account_name": "Indirect Test Income"},
{"account": "Administrative Test Income - _TC", "account_name": "Administrative Test Income"},
{
"account": "Administrative Test Income - _TC",
"account_name": "Administrative Test Income",
},
],
}
).insert(ignore_permissions=True)

View File

@@ -25,13 +25,11 @@ def get_loyalty_details(
condition += " and expiry_date>='%s' " % expiry_date
loyalty_point_details = frappe.db.sql(
"""select sum(loyalty_points) as loyalty_points,
f"""select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
group by customer""".format(
condition=condition
),
group by customer""",
(customer, loyalty_program, expiry_date),
as_dict=1,
)
@@ -52,9 +50,7 @@ def get_loyalty_program_details_with_points(
include_expired_entry=False,
current_transaction_amount=0,
):
lp_details = get_loyalty_program_details(
customer, loyalty_program, company=company, silent=silent
)
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update(
get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)

View File

@@ -19,9 +19,7 @@ class TestLoyaltyProgram(unittest.TestCase):
create_records()
def test_loyalty_points_earned_single_tier(self):
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
@@ -69,9 +67,7 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel()
def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty"
)
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
# assign multiple tier program to the customer
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
customer.loyalty_program = frappe.get_doc(
@@ -128,9 +124,7 @@ class TestLoyaltyProgram(unittest.TestCase):
def test_cancel_sales_invoice(self):
"""cancelling the sales invoice should cancel the earned points"""
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
# create a new sales invoice
si = create_sales_invoice_record()
si.insert()
@@ -140,7 +134,7 @@ class TestLoyaltyProgram(unittest.TestCase):
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
)
self.assertEqual(True, not (lpe is None))
self.assertEqual(True, lpe is not None)
# cancelling sales invoice
si.cancel()
@@ -148,9 +142,7 @@ class TestLoyaltyProgram(unittest.TestCase):
self.assertEqual(True, (lpe is None))
def test_sales_invoice_return(self):
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
# create a new sales invoice
si_original = create_sales_invoice_record(2)
si_original.conversion_rate = flt(1)
@@ -346,9 +338,7 @@ def create_records():
).insert()
# create item price
if not frappe.db.exists(
"Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}
):
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
frappe.get_doc(
{
"doctype": "Item Price",

View File

@@ -37,9 +37,7 @@ class MonthlyDistribution(Document):
total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
if flt(total, 2) != 100.0:
frappe.throw(
_("Percentage Allocation should be equal to 100%") + " ({0}%)".format(str(flt(total, 2)))
)
frappe.throw(_("Percentage Allocation should be equal to 100%") + f" ({flt(total, 2)!s}%)")
def get_periodwise_distribution_data(distribution_id, period_list, periodicity):

View File

@@ -83,9 +83,7 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value(
"Company", company, "default_receivable_account"
)
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
@@ -121,9 +119,7 @@ class TestOpeningInvoiceCreationTool(FrappeTestCase):
self.assertTrue(error_log)
# teardown
frappe.db.set_value(
"Company", company, "default_receivable_account", old_default_receivable_account
)
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
def test_renaming_of_invoice_using_invoice_number_field(self):
company = "_Test Opening Invoice Company"
@@ -169,7 +165,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 1.0,
"outstanding_amount": 300,
"party": args.get("party_1") or "_Test {0}".format(party),
"party": args.get("party_1") or f"_Test {party}",
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@@ -179,7 +175,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 2.0,
"outstanding_amount": 250,
"party": args.get("party_2") or "_Test {0} 1".format(party),
"party": args.get("party_2") or f"_Test {party} 1",
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",

View File

@@ -24,7 +24,10 @@ class PartyLink(Document):
if existing_party_link:
frappe.throw(
_("{} {} is already linked with {} {}").format(
self.primary_role, bold(self.primary_party), self.secondary_role, bold(self.secondary_party)
self.primary_role,
bold(self.primary_party),
self.secondary_role,
bold(self.secondary_party),
)
)

View File

@@ -1088,7 +1088,9 @@ frappe.ui.form.on('Payment Entry', {
},
callback: function(r) {
if (r.message) {
frm.set_value(field, r.message.account);
if (!frm.doc.mode_of_payment) {
frm.set_value(field, r.message.account);
}
frm.set_value('bank', r.message.bank);
frm.set_value('bank_account_no', r.message.bank_account_no);
}
@@ -1393,8 +1395,9 @@ frappe.ui.form.on('Payment Entry Reference', {
args: {
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
party_account_currency: frm.doc.payment_type=="Receive" ?
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency
party_account_currency: frm.doc.payment_type == "Receive" ? frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency,
party_type: frm.doc.party_type,
party: frm.doc.party,
},
callback: function(r, rt) {
if(r.message) {

View File

@@ -9,8 +9,7 @@ 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
from pypika import Case
from pypika.functions import Coalesce, Sum
from pypika.functions import Sum
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
@@ -47,7 +46,7 @@ class InvalidPaymentEntry(ValidationError):
class PaymentEntry(AccountsController):
def __init__(self, *args, **kwargs):
super(PaymentEntry, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if not self.is_new():
self.setup_party_account_field()
@@ -113,7 +112,7 @@ class PaymentEntry(AccountsController):
"Unreconcile Payment",
"Unreconcile Payment Entries",
)
super(PaymentEntry, self).on_cancel()
super().on_cancel()
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()
@@ -221,9 +220,7 @@ class PaymentEntry(AccountsController):
# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
):
) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
@@ -235,7 +232,9 @@ class PaymentEntry(AccountsController):
# 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 (
@@ -259,14 +258,14 @@ class PaymentEntry(AccountsController):
and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
)
and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
)
):
frappe.throw(
_(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format(
d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
)
).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
)
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
@@ -338,22 +337,30 @@ class PaymentEntry(AccountsController):
self,
force: bool = False,
update_ref_details_only_for: list | None = None,
ref_exchange_rate: float | None = None,
reference_exchange_details: dict | None = None,
) -> None:
for d in self.get("references"):
if d.allocated_amount:
if update_ref_details_only_for and (
not (d.reference_doctype, d.reference_name) in update_ref_details_only_for
(d.reference_doctype, d.reference_name) not in update_ref_details_only_for
):
continue
ref_details = get_reference_details(
d.reference_doctype, d.reference_name, self.party_account_currency
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
)
# Only update exchange rate when the reference is Journal Entry
if ref_exchange_rate and d.reference_doctype == "Journal Entry":
ref_details.update({"exchange_rate": ref_exchange_rate})
if (
reference_exchange_details
and d.reference_doctype == reference_exchange_details.reference_doctype
and d.reference_name == reference_exchange_details.reference_name
):
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
@@ -386,7 +393,9 @@ class PaymentEntry(AccountsController):
else:
if ref_doc:
if self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get(
"conversion_rate"
)
if not self.source_exchange_rate:
self.source_exchange_rate = get_exchange_rate(
@@ -423,7 +432,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
_("Reference Doctype must be one of {0}").format(
comma_or((_(d) for d in valid_reference_doctypes))
comma_or(_(d) for d in valid_reference_doctypes)
)
)
@@ -446,7 +455,8 @@ class PaymentEntry(AccountsController):
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
get_party_account_based_on_invoice_discounting(d.reference_name)
or ref_doc.debit_to
)
elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
@@ -456,7 +466,10 @@ 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,
)
)
@@ -467,7 +480,9 @@ class PaymentEntry(AccountsController):
)
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":
@@ -638,9 +653,7 @@ class PaymentEntry(AccountsController):
if not (is_single_currency and reference_is_multi_currency):
return allocated_amount
allocated_amount = flt(
allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
)
allocated_amount = flt(allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount"))
return allocated_amount
@@ -688,7 +701,6 @@ class PaymentEntry(AccountsController):
accounts = []
for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"):
# Preserve user updated included in paid amount
if d.included_in_paid_amount:
tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount})
@@ -808,7 +820,6 @@ class PaymentEntry(AccountsController):
flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
)
else:
# Use source/target exchange rate, so no difference amount is calculated.
# then update exchange gain/loss amount in reference table
# if there is an exchange gain/loss amount in reference table, submit a JE for that
@@ -925,7 +936,9 @@ class PaymentEntry(AccountsController):
total_negative_outstanding = flt(
sum(
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
abs(flt(d.outstanding_amount))
for d in self.get("references")
if flt(d.outstanding_amount) < 0
),
self.references[0].precision("outstanding_amount") if self.references else None,
)
@@ -978,7 +991,6 @@ class PaymentEntry(AccountsController):
)
]
else:
remarks = [
_("Amount {0} {1} {2} {3}").format(
self.party_account_currency,
@@ -998,14 +1010,19 @@ class PaymentEntry(AccountsController):
if d.allocated_amount:
remarks.append(
_("Amount {0} {1} against {2} {3}").format(
self.party_account_currency, d.allocated_amount, d.reference_doctype, d.reference_name
self.party_account_currency,
d.allocated_amount,
d.reference_doctype,
d.reference_name,
)
)
for d in self.get("deductions"):
if d.amount:
remarks.append(
_("Amount {0} {1} deducted against {2}").format(self.company_currency, d.amount, d.account)
_("Amount {0} {1} deducted against {2}").format(
self.company_currency, d.amount, d.account
)
)
self.set("remarks", "\n".join(remarks))
@@ -1440,7 +1457,8 @@ def get_outstanding_reference_documents(args):
return []
elif supplier_status["hold_type"] == "Payments":
if (
not supplier_status["release_date"] or getdate(nowdate()) <= supplier_status["release_date"]
not supplier_status["release_date"]
or getdate(nowdate()) <= supplier_status["release_date"]
):
return []
@@ -1450,7 +1468,7 @@ def get_outstanding_reference_documents(args):
# Get positive outstanding sales /purchase invoices
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={0} and voucher_no={1}".format(
condition = " and voucher_type={} and voucher_no={}".format(
frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
)
common_filter.append(ple.voucher_type == args["voucher_type"])
@@ -1465,7 +1483,7 @@ def get_outstanding_reference_documents(args):
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if args.get(dim.fieldname):
condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname))
condition += f" and {dim.fieldname}='{args.get(dim.fieldname)}'"
accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
date_fields_dict = {
@@ -1475,21 +1493,21 @@ def get_outstanding_reference_documents(args):
for fieldname, date_fields in date_fields_dict.items():
if args.get(date_fields[0]) and args.get(date_fields[1]):
condition += " and {0} between '{1}' and '{2}'".format(
condition += " and {} between '{}' and '{}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
elif args.get(date_fields[0]):
# if only from date is supplied
condition += " and {0} >= '{1}'".format(fieldname, args.get(date_fields[0]))
condition += f" and {fieldname} >= '{args.get(date_fields[0])}'"
posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
elif args.get(date_fields[1]):
# if only to date is supplied
condition += " and {0} <= '{1}'".format(fieldname, args.get(date_fields[1]))
condition += f" and {fieldname} <= '{args.get(date_fields[1])}'"
posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
condition += " and company = {}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company"))
outstanding_invoices = []
@@ -1561,9 +1579,7 @@ def get_outstanding_reference_documents(args):
frappe.msgprint(
_(
"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"))
)
).format(_(ref_document_type), _(args.get("party_type")).lower(), frappe.bold(args.get("party")))
)
return data
@@ -1599,12 +1615,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list
return outstanding_invoices_after_split
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
def get_currency_data(outstanding_invoices: list, company: str | None = None) -> dict:
"""Get currency and conversion data for a list of invoices."""
exc_rates = frappe._dict()
company_currency = (
frappe.db.get_value("Company", company, "default_currency") if company else None
)
company_currency = frappe.db.get_value("Company", company, "default_currency") if company else None
for doctype in ["Sales Invoice", "Purchase Invoice"]:
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
@@ -1699,7 +1713,7 @@ def get_orders_to_be_billed(
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if filters.get(dim.fieldname):
condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname))
condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
@@ -1848,18 +1862,14 @@ def get_account_details(account, date, cost_center=None):
frappe.has_permission("Payment Entry", throw=True)
# to check if the passed account is accessible under reference doctype Payment Entry
account_list = frappe.get_list(
"Account", {"name": account}, reference_doctype="Payment Entry", limit=1
)
account_list = frappe.get_list("Account", {"name": account}, reference_doctype="Payment Entry", limit=1)
# There might be some user permissions which will allow account under certain doctypes
# except for Payment Entry, only in such case we should throw permission error
if not account_list:
frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account))
account_balance = get_balance_on(
account, date, cost_center=cost_center, ignore_account_permission=True
)
account_balance = get_balance_on(account, date, cost_center=cost_center, ignore_account_permission=True)
return frappe._dict(
{
@@ -1876,53 +1886,59 @@ def get_company_defaults(company):
return frappe.get_cached_value("Company", company, fields, as_dict=1)
def get_outstanding_on_journal_entry(name):
gl = frappe.qb.DocType("GL Entry")
res = (
frappe.qb.from_(gl)
.select(
Case()
.when(
gl.party_type == "Customer",
Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
)
.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
.as_("outstanding_amount")
)
def get_outstanding_on_journal_entry(voucher_no, party_type, party):
ple = frappe.qb.DocType("Payment Ledger Entry")
outstanding = (
frappe.qb.from_(ple)
.select(Sum(ple.amount_in_account_currency))
.where(
(Coalesce(gl.party_type, "") != "")
& (gl.is_cancelled == 0)
& ((gl.voucher_no == name) | (gl.against_voucher == name))
(ple.against_voucher_no == voucher_no)
& (ple.party_type == party_type)
& (ple.party == party)
& (ple.delinked == 0)
)
).run(as_dict=True)
).run()
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
outstanding_amount = outstanding[0][0] if outstanding else 0
return outstanding_amount
total = (
frappe.qb.from_(ple)
.select(Sum(ple.amount_in_account_currency))
.where(
(ple.voucher_no == voucher_no)
& (ple.party_type == party_type)
& (ple.party == party)
& (ple.delinked == 0)
)
).run()
total_amount = total[0][0] if total else 0
return outstanding_amount, total_amount
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
def get_reference_details(
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
):
total_amount = outstanding_amount = exchange_rate = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
ref_doc.company
)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
if reference_doctype == "Dunning":
total_amount = outstanding_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
exchange_rate = get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
outstanding_amount, total_amount = get_outstanding_on_journal_entry(
reference_name, party_type, party
)
elif reference_doctype != "Journal Entry":
if not total_amount:
@@ -1977,9 +1993,7 @@ def get_payment_entry(
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance
):
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (100.0 + over_billing_allowance):
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
if not party_type:
@@ -2032,9 +2046,7 @@ def get_payment_entry(
pe.paid_from_account_currency = (
party_account_currency if payment_type == "Receive" else bank.account_currency
)
pe.paid_to_account_currency = (
party_account_currency if payment_type == "Pay" else bank.account_currency
)
pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
@@ -2063,7 +2075,6 @@ def get_payment_entry(
{"name": doc.payment_terms_template},
"allocate_payment_based_on_payment_terms",
):
for reference in get_reference_as_per_payment_terms(
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
@@ -2184,9 +2195,9 @@ def set_party_account_currency(dt, party_account, doc):
def set_payment_type(dt, doc):
if (
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)) or (
dt == "Purchase Invoice" and doc.outstanding_amount < 0
):
payment_type = "Receive"
else:
payment_type = "Pay"
@@ -2246,9 +2257,7 @@ def set_paid_amount_and_received_amount(
return paid_amount, received_amount
def apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
):
def apply_early_payment_discount(paid_amount, received_amount, doc, party_account_currency, reference_date):
total_discount = 0
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
@@ -2258,7 +2267,6 @@ def apply_early_payment_discount(
if doc.doctype in eligible_for_payments and has_payment_schedule:
for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
if term.discount_type == "Percentage":
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(grand_total) * (term.discount / 100)
@@ -2287,9 +2295,7 @@ def apply_early_payment_discount(
return paid_amount, received_amount, total_discount, valid_discounts
def set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
):
def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss, party_account_currency):
# If multi-currency, get base discount amount to adjust with base currency deductions/losses
if party_account_currency != doc.company_currency:
discount_amount = discount_amount * doc.get("conversion_rate", 1)
@@ -2309,7 +2315,8 @@ def set_pending_discount_loss(
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, account_type),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * positive_negative,
}
)
@@ -2332,9 +2339,7 @@ def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
def get_total_discount_percent(doc, valid_discounts) -> float:
"""Get total percentage and amount discount applied as a percentage."""
total_discount_percent = (
sum(
discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
)
sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage")
or 0.0
)
@@ -2377,9 +2382,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
# The same account head could be used more than once
for tax in doc.get("taxes", []):
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
total_discount_percentage / 100
)
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (total_discount_percentage / 100)
account = tax.get("account_head")
if not tax_discount_loss.get(account):
@@ -2396,7 +2399,8 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"deductions",
{
"account": account,
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
},
)
@@ -2419,7 +2423,8 @@ def get_reference_as_per_payment_terms(
if not is_multi_currency_acc:
# If accounting is done in company currency for multi-currency transaction
payment_term_outstanding = flt(
payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
payment_term_outstanding * doc.get("conversion_rate"),
payment_term.precision("payment_amount"),
)
if payment_term_outstanding:
@@ -2447,7 +2452,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
paid_amount = frappe.db.sql(
"""
f"""
select ifnull(sum({dr_or_cr}), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = %s
@@ -2457,9 +2462,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
and account = %s
and due_date = %s
and {dr_or_cr} > 0
""".format(
dr_or_cr=dr_or_cr
),
""",
(dt, dn, party_type, party, account, due_date),
)

View File

@@ -2,7 +2,6 @@
# See license.txt
import json
import unittest
import frappe
from frappe import qb
@@ -163,7 +162,7 @@ class TestPaymentEntry(FrappeTestCase):
supplier.on_hold = 0
supplier.save()
except:
except Exception:
pass
else:
raise Exception
@@ -470,9 +469,7 @@ class TestPaymentEntry(FrappeTestCase):
si.save()
si.submit()
pe = get_payment_entry(
"Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700
)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700)
pe.reference_no = si.name
pe.reference_date = nowdate()
@@ -639,9 +636,7 @@ class TestPaymentEntry(FrappeTestCase):
pe.set_exchange_rate()
pe.set_amounts()
self.assertEqual(
pe.source_exchange_rate, 65.1, "{0} is not equal to {1}".format(pe.source_exchange_rate, 65.1)
)
self.assertEqual(pe.source_exchange_rate, 65.1, f"{pe.source_exchange_rate} is not equal to {65.1}")
def test_internal_transfer_usd_to_inr(self):
pe = frappe.new_doc("Payment Entry")
@@ -910,9 +905,7 @@ class TestPaymentEntry(FrappeTestCase):
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
pi = make_purchase_invoice_against_cost_center(
cost_center=cost_center, credit_to="Creditors - _TC"
)
pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
self.assertEqual(pe.cost_center, pi.cost_center)
@@ -953,9 +946,7 @@ class TestPaymentEntry(FrappeTestCase):
si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=si.cost_center)
party_balance = get_balance_on(
party_type="Customer", party=si.customer, cost_center=si.cost_center
)
party_balance = get_balance_on(party_type="Customer", party=si.customer, cost_center=si.cost_center)
party_account_balance = get_balance_on(si.debit_to, cost_center=si.cost_center)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
@@ -1096,7 +1087,9 @@ class TestPaymentEntry(FrappeTestCase):
pe.source_exchange_rate = 50
pe.save()
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
ref_details = get_reference_details(
so.doctype, so.name, pe.paid_from_account_currency, "Customer", so.customer
)
expected_response = {
"total_amount": 5000.0,
"outstanding_amount": 5000.0,
@@ -1214,7 +1207,7 @@ class TestPaymentEntry(FrappeTestCase):
Overallocation validation shouldn't fire for Template without "Allocate Payment based on Payment Terms" enabled
"""
customer = create_customer()
create_customer()
create_payment_terms_template()
template = frappe.get_doc("Payment Terms Template", "Test Receivable Template")
@@ -1273,9 +1266,7 @@ class TestPaymentEntry(FrappeTestCase):
create_payment_terms_template()
# SI has an earlier due date and SI2 has a later due date
si = create_sales_invoice(
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
)
si = create_sales_invoice(qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4))
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
si2.payment_terms_template = "Test Receivable Template"
si2.submit()
@@ -1401,12 +1392,11 @@ def create_payment_entry(**args):
def create_payment_terms_template():
create_payment_term("Basic Amount Receivable")
create_payment_term("Tax Receivable")
if not frappe.db.exists("Payment Terms Template", "Test Receivable Template"):
payment_term_template = frappe.get_doc(
frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "Test Receivable Template",

View File

@@ -108,9 +108,9 @@ class PaymentLedgerEntry(Document):
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
_(
"Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}."
).format(dimension.label, self.account)
)
if (
@@ -121,9 +121,9 @@ class PaymentLedgerEntry(Document):
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
_(
"Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}."
).format(dimension.label, self.account)
)
def validate(self):

View File

@@ -84,11 +84,14 @@ class TestPaymentLedgerEntry(FrappeTestCase):
self.customer = customer.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
if posting_date is None:
posting_date = nowdate()
sinv = create_sales_invoice(
qty=qty,
rate=rate,
@@ -112,10 +115,12 @@ class TestPaymentLedgerEntry(FrappeTestCase):
)
return sinv
def create_payment_entry(self, amount=100, posting_date=nowdate()):
def create_payment_entry(self, amount=100, posting_date=None):
"""
Helper function to populate default values in payment entry
"""
if posting_date is None:
posting_date = nowdate()
payment = create_payment_entry(
company=self.company,
payment_type="Receive",
@@ -128,9 +133,10 @@ class TestPaymentLedgerEntry(FrappeTestCase):
payment.posting_date = posting_date
return payment
def create_sales_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
def create_sales_order(self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False):
if posting_date is None:
posting_date = nowdate()
so = make_sales_order(
company=self.company,
transaction_date=posting_date,
@@ -159,9 +165,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_journal_entry(
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
):
def create_journal_entry(self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None):
je = frappe.new_doc("Journal Entry")
je.posting_date = posting_date or nowdate()
je.company = self.company
@@ -319,9 +323,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
ple.amount,
ple.delinked,
)
.where(
(ple.against_voucher_type == cr_note1.doctype) & (ple.against_voucher_no == cr_note1.name)
)
.where((ple.against_voucher_type == cr_note1.doctype) & (ple.against_voucher_no == cr_note1.name))
.orderby(ple.creation)
.run(as_dict=True)
)
@@ -362,9 +364,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
)
cr_note2.is_return = 1
cr_note2 = cr_note2.save().submit()
je1 = self.create_journal_entry(
self.debit_to, self.debit_to, amount, posting_date=transaction_date
)
je1 = self.create_journal_entry(self.debit_to, self.debit_to, amount, posting_date=transaction_date)
je1.get("accounts")[0].party_type = je1.get("accounts")[1].party_type = "Customer"
je1.get("accounts")[0].party = je1.get("accounts")[1].party = self.customer
je1.get("accounts")[0].reference_type = cr_note2.doctype
@@ -419,9 +419,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
ple.amount,
ple.delinked,
)
.where(
(ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name)
)
.where((ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name))
.orderby(ple.creation)
.run(as_dict=True)
)
@@ -518,7 +516,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
amount = 100
so = self.create_sales_order(qty=1, rate=amount, posting_date=transaction_date).save().submit()
pe = get_payment_entry(so.doctype, so.name).save().submit()
get_payment_entry(so.doctype, so.name).save().submit()
so.reload()
so.cancel()

View File

@@ -71,6 +71,7 @@ frappe.ui.form.on("Payment Order", {
target: frm,
date_field: "posting_date",
setters: {
party_type: "Supplier",
party: frm.doc.supplier || "",
},
get_query_filters: {
@@ -91,6 +92,7 @@ frappe.ui.form.on("Payment Order", {
source_doctype: "Payment Request",
target: frm,
setters: {
party_type: "Supplier",
party: frm.doc.supplier || "",
},
get_query_filters: {

View File

@@ -66,9 +66,7 @@ def make_journal_entry(doc, supplier, mode_of_payment=None):
je = frappe.new_doc("Journal Entry")
je.payment_order = doc.name
je.posting_date = nowdate()
mode_of_payment_type = frappe._dict(
frappe.get_all("Mode of Payment", fields=["name", "type"], as_list=1)
)
mode_of_payment_type = frappe._dict(frappe.get_all("Mode of Payment", fields=["name", "type"], as_list=1))
je.voucher_type = "Bank Entry"
if mode_of_payment and mode_of_payment_type.get(mode_of_payment) == "Cash":

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -41,9 +40,7 @@ class TestPaymentOrder(FrappeTestCase):
payment_entry.insert()
payment_entry.submit()
doc = create_payment_order_against_payment_entry(
payment_entry, "Payment Entry", self.bank_account
)
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry", self.bank_account)
reference_doc = doc.get("references")[0]
self.assertEqual(reference_doc.reference_name, payment_entry.name)
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")

View File

@@ -25,7 +25,7 @@ from erpnext.controllers.accounts_controller import get_advance_payment_entries_
class PaymentReconciliation(Document):
def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.common_filter_conditions = []
self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
@@ -219,7 +219,6 @@ class PaymentReconciliation(Document):
self.return_invoices = self.return_invoices_query.run(as_dict=True)
def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry")
@@ -340,9 +339,7 @@ class PaymentReconciliation(Document):
payment_entry[0].get("reference_name")
)
new_difference_amount = self.get_difference_amount(
payment_entry[0], invoice[0], allocated_amount
)
new_difference_amount = self.get_difference_amount(payment_entry[0], invoice[0], allocated_amount)
return new_difference_amount
@frappe.whitelist()
@@ -460,9 +457,9 @@ class PaymentReconciliation(Document):
if running_doc:
frappe.throw(
_("A Reconciliation Job {0} is running for the same filters. Cannot reconcile now").format(
get_link_to_form("Auto Reconcile", running_doc)
)
_(
"A Reconciliation Job {0} is running for the same filters. Cannot reconcile now"
).format(get_link_to_form("Auto Reconcile", running_doc))
)
return
@@ -555,9 +552,7 @@ class PaymentReconciliation(Document):
invoice_exchange_map.update(purchase_invoice_map)
journals = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
]
journals = [d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"]
journals.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
)
@@ -677,7 +672,7 @@ class PaymentReconciliation(Document):
def get_journal_filter_conditions(self):
conditions = []
je = qb.DocType("Journal Entry")
jea = qb.DocType("Journal Entry Account")
qb.DocType("Journal Entry Account")
conditions.append(je.company == self.company)
if self.from_payment_date:
@@ -797,7 +792,7 @@ def adjust_allocations_for_taxes(doc):
@frappe.whitelist()
def get_queries_for_dimension_filters(company: str = None):
def get_queries_for_dimension_filters(company: str | None = None):
dimensions_with_filters = []
for d in get_dimensions()[0]:
filters = {}

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe import qb
@@ -127,11 +126,14 @@ class TestPaymentReconciliation(FrappeTestCase):
setattr(self, x.attribute, acc.name)
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
if posting_date is None:
posting_date = nowdate()
sinv = create_sales_invoice(
qty=qty,
rate=rate,
@@ -155,10 +157,13 @@ class TestPaymentReconciliation(FrappeTestCase):
)
return sinv
def create_payment_entry(self, amount=100, posting_date=nowdate(), customer=None):
def create_payment_entry(self, amount=100, posting_date=None, customer=None):
"""
Helper function to populate default values in payment entry
"""
if posting_date is None:
posting_date = nowdate()
payment = create_payment_entry(
company=self.company,
payment_type="Receive",
@@ -172,11 +177,14 @@ class TestPaymentReconciliation(FrappeTestCase):
return payment
def create_purchase_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
if posting_date is None:
posting_date = nowdate()
pinv = make_purchase_invoice(
qty=qty,
rate=rate,
@@ -201,11 +209,14 @@ class TestPaymentReconciliation(FrappeTestCase):
return pinv
def create_purchase_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
if posting_date is None:
posting_date = nowdate()
pord = create_purchase_order(
qty=qty,
rate=rate,
@@ -250,9 +261,7 @@ class TestPaymentReconciliation(FrappeTestCase):
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
return pr
def create_journal_entry(
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
):
def create_journal_entry(self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None):
je = frappe.new_doc("Journal Entry")
je.posting_date = posting_date or nowdate()
je.company = self.company
@@ -402,7 +411,7 @@ class TestPaymentReconciliation(FrappeTestCase):
rate = 100
invoices = []
payments = []
for i in range(5):
for _i in range(5):
invoices.append(self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date))
pe = self.create_payment_entry(amount=rate, posting_date=transaction_date).save().submit()
payments.append(pe)
@@ -821,9 +830,7 @@ class TestPaymentReconciliation(FrappeTestCase):
cr_note.cancel()
pay = self.create_payment_entry(
amount=amount, posting_date=transaction_date, customer=self.customer3
)
pay = self.create_payment_entry(amount=amount, posting_date=transaction_date, customer=self.customer3)
pay.paid_from = self.debtors_eur
pay.paid_from_account_currency = "EUR"
pay.source_exchange_rate = exchange_rate
@@ -1025,9 +1032,7 @@ class TestPaymentReconciliation(FrappeTestCase):
rate = 100
# 'Main - PR' Cost Center
si1 = self.create_sales_invoice(
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si1 = self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True)
si1.cost_center = self.main_cc.name
si1.submit()
@@ -1043,9 +1048,7 @@ class TestPaymentReconciliation(FrappeTestCase):
je1 = je1.save().submit()
# 'Sub - PR' Cost Center
si2 = self.create_sales_invoice(
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si2 = self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True)
si2.cost_center = self.sub_cc.name
si2.submit()

View File

@@ -37,7 +37,7 @@ class PaymentRequest(Document):
self.status = "Draft"
self.validate_reference_document()
self.validate_payment_request_amount()
self.validate_currency()
# self.validate_currency()
self.validate_subscription_details()
def validate_reference_document(self):
@@ -50,7 +50,7 @@ class PaymentRequest(Document):
)
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
@@ -103,7 +103,7 @@ class PaymentRequest(Document):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if (
hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart"
hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart"
) or self.flags.mute_email:
send_mail = False
@@ -155,7 +155,7 @@ class PaymentRequest(Document):
def make_invoice(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart":
if hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart":
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
si = make_sales_invoice(self.reference_name, ignore_permissions=True)
@@ -241,14 +241,10 @@ class PaymentRequest(Document):
else:
party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company)
party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(
party_account
)
party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(party_account)
bank_amount = self.grand_total
if (
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
):
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
else:
party_amount = self.grand_total
@@ -266,7 +262,7 @@ class PaymentRequest(Document):
"mode_of_payment": self.mode_of_payment,
"reference_no": self.name,
"reference_date": nowdate(),
"remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(
"remarks": "Payment Entry against {} {} via Payment Request {}".format(
self.reference_doctype, self.reference_name, self.name
),
}
@@ -280,21 +276,17 @@ class PaymentRequest(Document):
}
)
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
amount = payment_entry.base_paid_amount
else:
amount = self.grand_total
payment_entry.received_amount = amount
payment_entry.get("references")[0].allocated_amount = amount
for dimension in get_accounting_dimensions():
payment_entry.update({dimension: self.get(dimension)})
if payment_entry.difference_amount:
company_details = get_company_defaults(ref_doc.company)
payment_entry.append(
"deductions",
{
"account": company_details.exchange_gain_loss_account,
"cost_center": company_details.cost_center,
"amount": payment_entry.difference_amount,
},
)
if submit:
payment_entry.insert(ignore_permissions=True)
payment_entry.submit()
@@ -380,14 +372,13 @@ class PaymentRequest(Document):
and hasattr(frappe.local, "session")
and frappe.local.session.user != "Guest"
) and self.payment_channel != "Phone":
success_url = shopping_cart_settings.payment_success_url
if success_url:
redirect_to = ({"Orders": "/orders", "Invoices": "/invoices", "My Account": "/me"}).get(
success_url, "/me"
)
else:
redirect_to = get_url("/orders/{0}".format(self.reference_name))
redirect_to = get_url(f"/orders/{self.reference_name}")
return redirect_to
@@ -413,15 +404,11 @@ def make_payment_request(**args):
frappe.db.set_value(
"Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False
)
frappe.db.set_value(
"Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False
)
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
grand_total = grand_total - loyalty_amount
bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party"))
if args.get("party_type")
else ""
get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else ""
)
draft_payment_request = frappe.db.get_value(
@@ -441,6 +428,12 @@ def make_payment_request(**args):
pr = frappe.get_doc("Payment Request", draft_payment_request)
else:
pr = frappe.new_doc("Payment Request")
if not args.get("payment_request_type"):
args["payment_request_type"] = (
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
)
pr.update(
{
"payment_gateway_account": gateway_account.get("name"),
@@ -499,9 +492,9 @@ def get_amount(ref_doc, payment_account=None):
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.outstanding_amount)
grand_total = flt(ref_doc.grand_total)
else:
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate
elif dt == "Sales Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
@@ -603,7 +596,11 @@ def update_payment_req_status(doc, method):
if payment_request_name:
ref_details = get_reference_details(
ref.reference_doctype, ref.reference_name, doc.party_account_currency
ref.reference_doctype,
ref.reference_name,
doc.party_account_currency,
doc.party_type,
doc.party,
)
pay_req_doc = frappe.get_doc("Payment Request", payment_request_name)
status = pay_req_doc.status

View File

@@ -86,6 +86,8 @@ class TestPaymentRequest(unittest.TestCase):
pr = make_payment_request(
dt="Purchase Invoice",
dn=si_usd.name,
party_type="Supplier",
party="_Test Supplier USD",
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
@@ -93,11 +95,56 @@ class TestPaymentRequest(unittest.TestCase):
return_doc=1,
)
pe = pr.create_payment_entry()
pr.create_payment_entry()
pr.load_from_db()
self.assertEqual(pr.status, "Paid")
def test_multiple_payment_entry_against_purchase_invoice(self):
purchase_invoice = make_purchase_invoice(
customer="_Test Supplier USD",
debit_to="_Test Payable USD - _TC",
currency="USD",
conversion_rate=50,
)
pr = make_payment_request(
dt="Purchase Invoice",
party_type="Supplier",
party="_Test Supplier USD",
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
return_doc=1,
)
pr.grand_total = pr.grand_total / 2
pr.submit()
pr.create_payment_entry()
purchase_invoice.load_from_db()
self.assertEqual(purchase_invoice.status, "Partly Paid")
pr = make_payment_request(
dt="Purchase Invoice",
party_type="Supplier",
party="_Test Supplier USD",
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
return_doc=1,
)
pr.save()
pr.submit()
pr.create_payment_entry()
purchase_invoice.load_from_db()
self.assertEqual(purchase_invoice.status, "Paid")
def test_payment_entry(self):
frappe.db.set_value(
"Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
@@ -158,7 +205,7 @@ class TestPaymentRequest(unittest.TestCase):
self.assertTrue(gl_entries)
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[gle.account][0], gle.account)
self.assertEqual(expected_gle[gle.account][1], gle.debit)
self.assertEqual(expected_gle[gle.account][2], gle.credit)

View File

@@ -19,9 +19,7 @@ class PaymentTermsTemplate(Document):
total_portion += flt(term.get("invoice_portion", 0))
if flt(total_portion, 2) != 100.00:
frappe.msgprint(
_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red"
)
frappe.msgprint(_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red")
def validate_terms(self):
terms = []

View File

@@ -47,7 +47,8 @@ class PeriodClosingVoucher(AccountsController):
enqueue_after_commit=True,
)
frappe.msgprint(
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
_("The GL Entries will be cancelled in the background, it can take a few minutes."),
alert=True,
)
else:
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
@@ -89,9 +90,7 @@ class PeriodClosingVoucher(AccountsController):
self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
)
self.year_start_date = get_fiscal_year(
self.posting_date, self.fiscal_year, company=self.company
)[1]
self.year_start_date = get_fiscal_year(self.posting_date, self.fiscal_year, company=self.company)[1]
self.check_if_previous_year_closed()
@@ -205,7 +204,9 @@ class PeriodClosingVoucher(AccountsController):
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
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,
"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,
@@ -229,7 +230,9 @@ class PeriodClosingVoucher(AccountsController):
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
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,
"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,

View File

@@ -33,7 +33,7 @@ class POSClosingEntry(StatusUpdater):
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
_(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
)
if error_list:
@@ -128,9 +128,7 @@ def get_pos_invoices(start, end, pos_profile, user):
as_dict=1,
)
data = list(
filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data)
)
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
# need to get taxes and payments so can't avoid get_doc
data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data]
@@ -201,7 +199,11 @@ def make_closing_entry_from_opening(opening_entry):
else:
payments.append(
frappe._dict(
{"mode_of_payment": p.mode_of_payment, "opening_amount": 0, "expected_amount": p.amount}
{
"mode_of_payment": p.mode_of_payment,
"opening_amount": 0,
"expected_amount": p.amount,
}
)
)

View File

@@ -27,7 +27,7 @@ from erpnext.stock.doctype.serial_no.serial_no import (
class POSInvoice(SalesInvoice):
def __init__(self, *args, **kwargs):
super(POSInvoice, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def validate(self):
if not cint(self.is_pos):
@@ -129,7 +129,9 @@ class POSInvoice(SalesInvoice):
)
if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
return frappe.throw(
_("Payment related to {0} is not completed").format(pay.mode_of_payment)
)
def validate_pos_reserved_serial_nos(self, item):
serial_nos = get_serial_nos(item.serial_no)
@@ -164,7 +166,7 @@ class POSInvoice(SalesInvoice):
serial_nos = row.serial_no.split("\n")
if serial_nos:
for key, value in collections.Counter(serial_nos).items():
for _key, value in collections.Counter(serial_nos).items():
if value > 1:
frappe.throw(_("Duplicate Serial No {0} found").format("key"))
@@ -191,9 +193,7 @@ class POSInvoice(SalesInvoice):
frappe.throw(
_(
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
).format(
item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed
),
).format(item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed),
title=_("Item Unavailable"),
)
@@ -249,7 +249,7 @@ class POSInvoice(SalesInvoice):
available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse)
item_code, warehouse, qty = (
item_code, warehouse, _qty = (
frappe.bold(d.item_code),
frappe.bold(d.warehouse),
frappe.bold(d.qty),

View File

@@ -338,9 +338,7 @@ class TestPOSInvoice(unittest.TestCase):
)
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50})
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60, "default": 1}
)
@@ -594,9 +592,7 @@ class TestPOSInvoice(unittest.TestCase):
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
create_records()
frappe.db.set_value(
"Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty"
)
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
before_lp_details = get_loyalty_program_details_with_points(
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
)
@@ -670,9 +666,7 @@ class TestPOSInvoice(unittest.TestCase):
consolidate_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value(
"Sales Invoice", pos_inv.consolidated_invoice, "rounded_total"
)
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 3470)
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
@@ -719,9 +713,7 @@ class TestPOSInvoice(unittest.TestCase):
consolidate_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value(
"Sales Invoice", pos_inv.consolidated_invoice, "rounded_total"
)
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 840)
def test_merging_with_validate_selling_price(self):
@@ -773,9 +765,7 @@ class TestPOSInvoice(unittest.TestCase):
consolidate_pos_invoices()
pos_inv2.load_from_db()
rounded_total = frappe.db.get_value(
"Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total"
)
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 400)
def test_pos_batch_item_qty_validation(self):
@@ -839,19 +829,19 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv = create_pos_invoice(qty=1, do_not_submit=1)
pos_inv.items[0].rate = 300
pos_inv.save()
self.assertEquals(pos_inv.items[0].discount_percentage, 10)
self.assertEqual(pos_inv.items[0].discount_percentage, 10)
# rate shouldn't change
self.assertEquals(pos_inv.items[0].rate, 405)
self.assertEqual(pos_inv.items[0].rate, 405)
pos_inv.ignore_pricing_rule = 1
pos_inv.save()
self.assertEquals(pos_inv.ignore_pricing_rule, 1)
self.assertEqual(pos_inv.ignore_pricing_rule, 1)
# rate should reset since pricing rules are ignored
self.assertEquals(pos_inv.items[0].rate, 450)
self.assertEqual(pos_inv.items[0].rate, 450)
pos_inv.items[0].rate = 300
pos_inv.save()
self.assertEquals(pos_inv.items[0].rate, 300)
self.assertEqual(pos_inv.items[0].rate, 300)
finally:
item_price.delete()
@@ -874,7 +864,7 @@ class TestPOSInvoice(unittest.TestCase):
dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=serial_no)
delivery_document_no = frappe.db.get_value("Serial No", serial_no, "delivery_document_no")
self.assertEquals(delivery_document_no, dn.name)
self.assertEqual(delivery_document_no, dn.name)
init_user_and_profile()

View File

@@ -29,7 +29,7 @@ class POSInvoiceMergeLog(Document):
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
_(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
)
if error_list:
@@ -56,7 +56,9 @@ class POSInvoiceMergeLog(Document):
bold_pos_invoice = frappe.bold(d.pos_invoice)
bold_status = frappe.bold(status)
if docstatus != 1:
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice))
frappe.throw(
_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice)
)
if status == "Consolidated":
frappe.throw(
_("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status)
@@ -75,15 +77,17 @@ class POSInvoiceMergeLog(Document):
d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated
)
msg += " "
msg += _("Original invoice should be consolidated before or along with the return invoice.")
msg += _(
"Original invoice should be consolidated before or along with the return invoice."
)
msg += "<br><br>"
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
msg += _("You can add original invoice {} manually to proceed.").format(
bold_return_against
)
frappe.throw(msg)
def on_submit(self):
pos_invoice_docs = [
frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices
]
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
returns = [d for d in pos_invoice_docs if d.get("is_return") == 1]
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
@@ -100,9 +104,7 @@ class POSInvoiceMergeLog(Document):
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
def on_cancel(self):
pos_invoice_docs = [
frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices
]
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
self.update_pos_invoices(pos_invoice_docs)
self.cancel_linked_invoices()
@@ -192,7 +194,9 @@ class POSInvoiceMergeLog(Document):
for t in taxes:
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
t.base_tax_amount = flt(t.base_tax_amount) + flt(
tax.base_tax_amount_after_discount_amount
)
update_item_wise_tax_detail(t, tax)
found = True
if not found:
@@ -292,9 +296,7 @@ def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
else:
consolidated_tax_detail.update({item_code: [tax_data[0], tax_data[1]]})
consolidate_tax_row.item_wise_tax_detail = json.dumps(
consolidated_tax_detail, separators=(",", ":")
)
consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(",", ":"))
def get_all_unconsolidated_invoices():
@@ -339,9 +341,7 @@ def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
if len(invoices) >= 10 and closing_entry:
closing_entry.set_status(update=True, status="Queued")
enqueue_job(
create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry
)
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
else:
create_merge_logs(invoice_by_customer, closing_entry)
@@ -389,9 +389,7 @@ def split_invoices(invoices):
if not item.serial_no:
continue
return_against_is_added = any(
d for d in _invoices if d.pos_invoice == pos_invoice.return_against
)
return_against_is_added = any(d for d in _invoices if d.pos_invoice == pos_invoice.return_against)
if return_against_is_added:
break
@@ -442,7 +440,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry:
closing_entry.set_status(update=True, status="Failed")
if type(error_message) == list:
if isinstance(error_message, list):
error_message = frappe.json.dumps(error_message)
closing_entry.db_set("error_message", error_message)
raise
@@ -493,7 +491,7 @@ def enqueue_job(job, **kwargs):
timeout=10000,
event="processing_merge_logs",
job_name=job_name,
now=frappe.conf.developer_mode or frappe.flags.in_test
now=frappe.conf.developer_mode or frappe.flags.in_test,
)
if job == create_merge_logs:

View File

@@ -28,15 +28,11 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}
)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv3.submit()
consolidate_pos_invoices()
@@ -65,15 +61,11 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}
)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv3.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
@@ -309,7 +301,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
init_user_and_profile()
item_rates = [69, 59, 29]
for i in [1, 2]:
for _i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:

View File

@@ -114,10 +114,8 @@ class POSProfile(Document):
condition = " where pfu.default = 1 "
pos_view_users = frappe.db.sql_list(
"""select pfu.user
from `tabPOS Profile User` as pfu {0}""".format(
condition
)
f"""select pfu.user
from `tabPOS Profile User` as pfu {condition}"""
)
for user in pos_view_users:
@@ -144,10 +142,8 @@ def get_item_groups(pos_profile):
def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql(
""" Select name, lft, rgt from `tab{tab}` where
lft >= {lft} and rgt <= {rgt} order by lft""".format(
tab=group_type, lft=lft, rgt=rgt
),
f""" Select name, lft, rgt from `tab{group_type}` where
lft >= {lft} and rgt <= {rgt} order by lft""",
as_dict=1,
)

View File

@@ -52,11 +52,9 @@ def get_customers_list(pos_profile=None):
return (
frappe.db.sql(
""" select name, customer_name, customer_group,
f""" select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0
and {cond}""".format(
cond=cond
),
and {cond}""",
tuple(customer_groups),
as_dict=1,
)
@@ -75,7 +73,7 @@ def get_items_list(pos_profile, company):
cond = "and i.item_group in (%s)" % (", ".join(["%s"] * len(args_list)))
return frappe.db.sql(
"""
f"""
select
i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no,
i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image,
@@ -88,10 +86,8 @@ def get_items_list(pos_profile, company):
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0
{cond}
""".format(
cond=cond
),
tuple([company] + args_list),
""",
tuple([company, *args_list]),
as_dict=1,
)

View File

@@ -74,15 +74,21 @@
"discount_amount",
"discount_percentage",
"for_price_list",
"section_break_13",
"threshold_percentage",
"priority",
"dynamic_condition_tab",
"condition",
"column_break_66",
"section_break_13",
"apply_multiple_pricing_rules",
"apply_discount_on_rate",
"column_break_66",
"threshold_percentage",
"validate_pricing_rule_section",
"validate_applied_rule",
"column_break_texp",
"rule_description",
"priority_section",
"has_priority",
"column_break_sayg",
"priority",
"help_section",
"pricing_rule_help",
"reference_section",
@@ -477,7 +483,7 @@
{
"collapsible": 1,
"fieldname": "section_break_13",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Advanced Settings"
},
{
@@ -487,6 +493,7 @@
"label": "Threshold for Suggestion (In Percentage)"
},
{
"depends_on": "has_priority",
"description": "Higher the number, higher the priority",
"fieldname": "priority",
"fieldtype": "Select",
@@ -513,6 +520,7 @@
{
"default": "0",
"depends_on": "eval:doc.price_or_product_discount == 'Price'",
"description": "If enabled, then system will only validate the pricing rule and not apply automatically. User has to manually set the discount percentage / margin / free items to validate the pricing rule",
"fieldname": "validate_applied_rule",
"fieldtype": "Check",
"label": "Validate Applied Rule"
@@ -525,7 +533,8 @@
},
{
"fieldname": "help_section",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Help Article",
"options": "Simple"
},
{
@@ -603,12 +612,42 @@
"fieldname": "apply_recursion_over",
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)"
},
{
"fieldname": "priority_section",
"fieldtype": "Section Break",
"label": "Priority"
},
{
"fieldname": "dynamic_condition_tab",
"fieldtype": "Tab Break",
"label": "Dynamic Condition"
},
{
"fieldname": "validate_pricing_rule_section",
"fieldtype": "Section Break",
"label": "Validate Pricing Rule"
},
{
"fieldname": "column_break_texp",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_sayg",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Enable this checkbox even if you want to set the zero priority",
"fieldname": "has_priority",
"fieldtype": "Check",
"label": "Has Priority"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2023-02-14 04:53:34.887358",
"modified": "2024-05-17 13:16:34.496704",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -47,6 +47,12 @@ class PricingRule(Document):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
def validate_mandatory(self):
if self.has_priority and not self.priority:
throw(_("Priority is mandatory"), frappe.MandatoryError, _("Please Set Priority"))
if self.priority and not self.has_priority:
self.has_priority = 1
for apply_on, field in apply_on_dict.items():
if self.apply_on == apply_on and len(self.get(field) or []) < 1:
throw(_("{0} is not added in the table").format(apply_on), frappe.MandatoryError)
@@ -77,9 +83,9 @@ class PricingRule(Document):
if self.priority and cint(self.priority) == 1:
throw(
_("As the field {0} is enabled, the value of the field {1} should be more than 1.").format(
frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")
)
_(
"As the field {0} is enabled, the value of the field {1} should be more than 1."
).format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority"))
)
def validate_applicable_for_selling_or_buying(self):
@@ -273,9 +279,7 @@ def apply_pricing_rule(args, doc=None):
def get_serial_no_for_item(args):
from erpnext.stock.get_item_details import get_serial_no
item_details = frappe._dict(
{"doctype": args.doctype, "name": args.name, "serial_no": args.serial_no}
)
item_details = frappe._dict({"doctype": args.doctype, "name": args.name, "serial_no": args.serial_no})
if args.get("parenttype") in ("Sales Invoice", "Delivery Note") and flt(args.stock_qty) > 0:
item_details.serial_no = get_serial_no(args)
return item_details
@@ -373,9 +377,11 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
)
if pricing_rule.apply_rule_on_other_items:
item_details["apply_rule_on_other_items"] = json.dumps(pricing_rule.apply_rule_on_other_items)
item_details["apply_rule_on_other_items"] = json.dumps(
pricing_rule.apply_rule_on_other_items
)
if pricing_rule.coupon_code_based == 1 and args.coupon_code == None:
if pricing_rule.coupon_code_based == 1 and args.coupon_code is None:
return item_details
if not pricing_rule.validate_applied_rule:
@@ -419,7 +425,6 @@ def update_args_for_pricing_rule(args):
if args.transaction_type == "selling":
if args.customer and not (args.customer_group and args.territory):
if args.quotation_to and args.quotation_to != "Customer":
customer = frappe._dict()
else:
@@ -450,9 +455,9 @@ def get_pricing_rule_details(args, pricing_rule):
def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
if (
pricing_rule.margin_type in ["Amount", "Percentage"] and pricing_rule.currency == args.currency
) or (pricing_rule.margin_type == "Percentage"):
if (pricing_rule.margin_type in ["Amount", "Percentage"] and pricing_rule.currency == args.currency) or (
pricing_rule.margin_type == "Percentage"
):
item_details.margin_type = pricing_rule.margin_type
item_details.has_margin = True
@@ -487,6 +492,22 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
# Apply discount on discounted rate
item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
elif args.price_list_rate:
value = pricing_rule.get(field, 0)
calculate_discount_percentage = False
if field == "discount_percentage":
field = "discount_amount"
value = args.price_list_rate * (value / 100)
calculate_discount_percentage = True
if field not in item_details:
item_details.setdefault(field, 0)
item_details[field] += value if pricing_rule else args.get(field, 0)
if calculate_discount_percentage and args.price_list_rate and item_details.discount_amount:
item_details.discount_percentage = flt(
(flt(item_details.discount_amount) / flt(args.price_list_rate)) * 100
)
else:
if field not in item_details:
item_details.setdefault(field, 0)
@@ -595,7 +616,7 @@ def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all(
"UOM Conversion Detail",
filters={"parent": ("in", items), "uom": ("like", "{0}%".format(txt))},
filters={"parent": ("in", items), "uom": ("like", f"{txt}%")},
fields=["distinct uom"],
as_list=1,
)

View File

@@ -103,8 +103,6 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(details.get("discount_percentage"), 15)
def test_pricing_rule_for_margin(self):
from frappe import MandatoryError
from erpnext.stock.get_item_details import get_item_details
test_record = {
@@ -205,8 +203,6 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(details.get("discount_percentage"), 10)
def test_pricing_rule_for_variants(self):
from frappe import MandatoryError
from erpnext.stock.get_item_details import get_item_details
if not frappe.db.exists("Item", "Test Variant PRT"):
@@ -980,7 +976,116 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4)
self.assertEqual(so.items[1].qty, 3)
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 1",
"name": "_Test Pricing Rule 1",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"apply_multiple_pricing_rules": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 2",
"name": "_Test Pricing Rule 2",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Amount",
"discount_amount": 100,
"apply_multiple_pricing_rules": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
self.assertEqual(so.items[0].discount_amount, 200)
self.assertEqual(so.items[0].rate, 800)
frappe.delete_doc_if_exists("Sales Order", so.name)
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_priority_of_multiple_pricing_rules(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 1",
"name": "_Test Pricing Rule 1",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"has_priority": 1,
"priority": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 2",
"name": "_Test Pricing Rule 2",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 20,
"has_priority": 1,
"priority": 3,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
self.assertEqual(so.items[0].discount_percentage, 20)
self.assertEqual(so.items[0].rate, 800)
frappe.delete_doc_if_exists("Sales Order", so.name)
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_dependencies = ["Campaign"]
@@ -1010,6 +1115,7 @@ def make_pricing_rule(**args):
"priority": args.priority or 1,
"discount_amount": args.discount_amount or 0.0,
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
"has_priority": args.has_priority or 0,
}
)
@@ -1055,8 +1161,7 @@ def delete_existing_pricing_rules():
"Pricing Rule Item Group",
"Pricing Rule Brand",
]:
frappe.db.sql("delete from `tab{0}`".format(doctype))
frappe.db.sql(f"delete from `tab{doctype}`")
def make_item_price(item, price_list_name, item_price):

View File

@@ -6,6 +6,7 @@
import copy
import json
import math
import frappe
from frappe import _, bold
@@ -32,6 +33,9 @@ def get_pricing_rules(args, doc=None):
for apply_on in ["Item Code", "Item Group", "Brand"]:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and pricing_rules[0].has_priority:
continue
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
break
@@ -101,14 +105,12 @@ def _get_pricing_rules(apply_on, args, values):
if not args.get(apply_on_field):
return []
child_doc = "`tabPricing Rule {0}`".format(apply_on)
child_doc = f"`tabPricing Rule {apply_on}`"
conditions = item_variant_condition = item_conditions = ""
values[apply_on_field] = args.get(apply_on_field)
if apply_on_field in ["item_code", "brand"]:
item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(
child_doc=child_doc, apply_on_field=apply_on_field
)
item_conditions = f"{child_doc}.{apply_on_field}= %({apply_on_field})s"
if apply_on_field == "item_code":
if args.get("uom", None):
@@ -121,9 +123,7 @@ def _get_pricing_rules(apply_on, args, values):
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
if args.variant_of:
item_variant_condition = " or {child_doc}.item_code=%(variant_of)s ".format(
child_doc=child_doc
)
item_variant_condition = f" or {child_doc}.item_code=%(variant_of)s "
values["variant_of"] = args.variant_of
elif apply_on_field == "item_group":
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
@@ -131,7 +131,7 @@ def _get_pricing_rules(apply_on, args, values):
conditions += get_other_conditions(conditions, values, args)
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
if warehouse_conditions:
warehouse_conditions = " and {0}".format(warehouse_conditions)
warehouse_conditions = f" and {warehouse_conditions}"
if not args.price_list:
args.price_list = None
@@ -157,7 +157,7 @@ def _get_pricing_rules(apply_on, args, values):
item_variant_condition=item_variant_condition,
transaction_type=args.transaction_type,
warehouse_cond=warehouse_conditions,
apply_on_other_field="other_{0}".format(apply_on_field),
apply_on_other_field=f"other_{apply_on_field}",
conditions=conditions,
),
values,
@@ -196,14 +196,13 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
frappe.throw(_("Invalid {0}").format(args.get(field)))
parent_groups = frappe.db.sql_list(
"""select name from `tab%s`
where lft<=%s and rgt>=%s"""
% (parenttype, "%s", "%s"),
"""select name from `tab{}`
where lft<={} and rgt>={}""".format(parenttype, "%s", "%s"),
(lft, rgt),
)
if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
parent_field = f"parent_{frappe.scrub(parenttype)}"
root_name = frappe.db.get_list(
parenttype,
{"is_group": 1, parent_field: ("is", "not set")},
@@ -229,10 +228,10 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
def get_other_conditions(conditions, values, args):
for field in ["company", "customer", "supplier", "campaign", "sales_partner"]:
if args.get(field):
conditions += " and ifnull(`tabPricing Rule`.{0}, '') in (%({1})s, '')".format(field, field)
conditions += f" and ifnull(`tabPricing Rule`.{field}, '') in (%({field})s, '')"
values[field] = args.get(field)
else:
conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field)
conditions += f" and ifnull(`tabPricing Rule`.{field}, '') = ''"
for parenttype in ["Customer Group", "Territory", "Supplier Group"]:
group_condition = _get_tree_conditions(args, parenttype, "`tabPricing Rule`")
@@ -504,7 +503,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
"transaction_date" if frappe.get_meta(doctype).has_field("transaction_date") else "posting_date"
)
child_doctype = "{0} Item".format(doctype)
child_doctype = f"{doctype} Item"
apply_on = frappe.scrub(pr_doc.get("apply_on"))
values = [pr_doc.valid_from, pr_doc.valid_upto]
@@ -514,9 +513,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
warehouses = get_child_warehouses(pr_doc.warehouse)
condition += """ and `tab{child_doc}`.warehouse in ({warehouses})
""".format(
child_doc=child_doctype, warehouses=",".join(["%s"] * len(warehouses))
)
""".format(child_doc=child_doctype, warehouses=",".join(["%s"] * len(warehouses)))
values.extend(warehouses)
@@ -528,16 +525,14 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
values.extend(items)
data_set = frappe.db.sql(
""" SELECT `tab{child_doc}`.stock_qty,
`tab{child_doc}`.amount
FROM `tab{child_doc}`, `tab{parent_doc}`
f""" SELECT `tab{child_doctype}`.stock_qty,
`tab{child_doctype}`.amount
FROM `tab{child_doctype}`, `tab{doctype}`
WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name and `tab{parent_doc}`.{date_field}
between %s and %s and `tab{parent_doc}`.docstatus = 1
{condition} group by `tab{child_doc}`.name
""".format(
parent_doc=doctype, child_doc=child_doctype, condition=condition, date_field=date_field
),
`tab{child_doctype}`.parent = `tab{doctype}`.name and `tab{doctype}`.{date_field}
between %s and %s and `tab{doctype}`.docstatus = 1
{condition} group by `tab{child_doctype}`.name
""",
tuple(values),
as_dict=1,
)
@@ -556,11 +551,9 @@ def apply_pricing_rule_on_transaction(doc):
conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(
""" Select `tabPricing Rule`.* from `tabPricing Rule`
f""" Select `tabPricing Rule`.* from `tabPricing Rule`
where {conditions} and `tabPricing Rule`.disable = 0
""".format(
conditions=conditions
),
""",
values,
as_dict=1,
)
@@ -583,7 +576,9 @@ def apply_pricing_rule_on_transaction(doc):
continue
if (
d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field)
d.validate_applied_rule
and doc.get(field) is not None
and doc.get(field) < d.get(pr_field)
):
frappe.msgprint(_("User has not applied rule on the invoice {0}").format(doc.name))
else:
@@ -643,13 +638,11 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive:
transaction_qty = (
args.get("qty") if args else doc.total_qty
) - pricing_rule.apply_recursion_over
transaction_qty = (args.get("qty") if args else doc.total_qty) - pricing_rule.apply_recursion_over
if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = round(qty)
qty = math.floor(qty)
free_item_data_args = {
"item_code": free_item,

View File

@@ -40,7 +40,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
si.save()
si.submit()
process_deferred_accounting = doc = frappe.get_doc(
process_deferred_accounting = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
posting_date="2023-07-01",

View File

@@ -41,9 +41,7 @@ class ProcessPaymentReconciliation(Document):
def on_cancel(self):
self.db_set("status", "Cancelled")
log = frappe.db.get_value(
"Process Payment Reconciliation Log", filters={"process_pr": self.name}
)
log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": self.name})
if log:
frappe.db.set_value("Process Payment Reconciliation Log", log, "status", "Cancelled")
@@ -129,7 +127,7 @@ def trigger_job_for_doc(docname: str | None = None):
frappe.db.set_value("Process Payment Reconciliation", docname, "status", "Running")
job_name = f"start_processing_{docname}"
if not is_job_running(job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile_based_on_filters",
queue="long",
is_async=True,
@@ -147,7 +145,7 @@ def trigger_job_for_doc(docname: str | None = None):
# Resume tasks for running doc
job_name = f"start_processing_{docname}"
if not is_job_running(job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile_based_on_filters",
queue="long",
is_async=True,
@@ -224,7 +222,7 @@ def reconcile_based_on_filters(doc: None | str = None) -> None:
job_name = f"process_{doc}_fetch_and_allocate"
if not is_job_running(job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.fetch_and_allocate",
queue="long",
timeout="3600",
@@ -245,7 +243,7 @@ def reconcile_based_on_filters(doc: None | str = None) -> None:
if not allocated:
job_name = f"process__{doc}_fetch_and_allocate"
if not is_job_running(job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.fetch_and_allocate",
queue="long",
timeout="3600",
@@ -263,7 +261,7 @@ def reconcile_based_on_filters(doc: None | str = None) -> None:
else:
reconcile_job_name = f"process_{doc}_reconcile"
if not is_job_running(reconcile_job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile",
queue="long",
timeout="3600",
@@ -350,7 +348,7 @@ def fetch_and_allocate(doc: str) -> None:
reconcile_job_name = f"process_{doc}_reconcile"
if not is_job_running(reconcile_job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile",
queue="long",
timeout="3600",
@@ -391,7 +389,6 @@ def reconcile(doc: None | str = None) -> None:
# If Payment Entry, update details only for newly linked references
# This is for performance
if allocations[0].reference_type == "Payment Entry":
references = [(x.invoice_type, x.invoice_number) for x in allocations]
pe = frappe.get_doc(allocations[0].reference_type, allocations[0].reference_name)
pe.flags.ignore_validate_update_after_submit = True
@@ -405,13 +402,14 @@ def reconcile(doc: None | str = None) -> None:
# Update reconciled count
reconciled_count = frappe.db.count(
"Process Payment Reconciliation Log Allocations", filters={"parent": log, "reconciled": True}
"Process Payment Reconciliation Log Allocations",
filters={"parent": log, "reconciled": True},
)
frappe.db.set_value(
"Process Payment Reconciliation Log", log, "reconciled_entries", reconciled_count
)
except Exception as err:
except Exception:
# Update the parent doc about the exception
frappe.db.rollback()
@@ -449,20 +447,19 @@ def reconcile(doc: None | str = None) -> None:
frappe.db.set_value("Process Payment Reconciliation Log", log, "reconciled", True)
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
else:
if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"):
if not (
frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"
):
# trigger next batch in job
# generate reconcile job name
allocation = get_next_allocation(log)
if allocation:
reconcile_job_name = (
f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}"
)
reconcile_job_name = f"process_{doc}_reconcile_allocation_{allocation[0].idx}_{allocation[-1].idx}"
else:
reconcile_job_name = f"process_{doc}_reconcile"
if not is_job_running(reconcile_job_name):
job = frappe.enqueue(
frappe.enqueue(
method="erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.reconcile",
queue="long",
timeout="3600",
@@ -481,7 +478,7 @@ def reconcile(doc: None | str = None) -> None:
def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None
if for_filter:
if type(for_filter) == str:
if isinstance(for_filter, str):
for_filter = frappe.json.loads(for_filter)
running_doc = frappe.db.get_value(

View File

@@ -104,7 +104,7 @@ def set_ageing(doc, entry):
ageing_filters = frappe._dict(
{
"company": doc.company,
"report_date": doc.to_date,
"report_date": doc.posting_date,
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,
@@ -423,9 +423,7 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
else:
new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3)
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
doc.add_comment(
"Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())
)
doc.add_comment("Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now()))
if doc.report == "General Ledger":
doc.db_set("to_date", new_to_date, commit=True)
doc.db_set("from_date", new_from_date, commit=True)

View File

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

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase

View File

@@ -100,9 +100,7 @@ class PromotionalScheme(Document):
docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name})
for docname in docnames:
if frappe.db.exists(
"Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}
):
if frappe.db.exists("Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}):
raise_for_transaction_exists(self.name)
if docnames and not transaction_exists:
@@ -177,7 +175,7 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
args = get_args_for_pricing_rule(doc)
applicable_for = frappe.scrub(doc.get("applicable_for"))
for idx, d in enumerate(doc.get(child_doc)):
for _idx, d in enumerate(doc.get(child_doc)):
if d.name in rules:
if not args.get(applicable_for):
docname = get_pricing_rule_docname(d)
@@ -187,7 +185,14 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
for applicable_for_value in args.get(applicable_for):
docname = get_pricing_rule_docname(d, applicable_for, applicable_for_value)
pr = prepare_pricing_rule(
args, doc, child_doc, discount_fields, d, docname, applicable_for, applicable_for_value
args,
doc,
child_doc,
discount_fields,
d,
docname,
applicable_for,
applicable_for_value,
)
new_doc.append(pr)
@@ -213,7 +218,7 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
def get_pricing_rule_docname(
row: dict, applicable_for: str = None, applicable_for_value: str = None
row: dict, applicable_for: str | None = None, applicable_for_value: str | None = None
) -> str:
fields = ["promotional_scheme_id", "name"]
filters = {"promotional_scheme_id": row.name}

View File

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

View File

@@ -440,8 +440,12 @@ function hide_fields(doc) {
var item_fields_stock = ['warehouse_section', 'received_qty', 'rejected_qty'];
cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
if (cur_frm.fields_dict["items"]) {
cur_frm.fields_dict["items"].grid.set_column_disp(
item_fields_stock,
cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false
);
}
cur_frm.refresh_fields();
}

View File

@@ -55,7 +55,7 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
class PurchaseInvoice(BuyingController):
def __init__(self, *args, **kwargs):
super(PurchaseInvoice, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.status_updater = [
{
"source_dt": "Purchase Invoice Item",
@@ -72,7 +72,7 @@ class PurchaseInvoice(BuyingController):
]
def onload(self):
super(PurchaseInvoice, self).onload()
super().onload()
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
self.set_onload("supplier_tds", supplier_tds)
@@ -92,7 +92,7 @@ class PurchaseInvoice(BuyingController):
self.validate_posting_time()
super(PurchaseInvoice, self).validate()
super().validate()
if not self.is_return:
self.po_required()
@@ -155,7 +155,6 @@ class PurchaseInvoice(BuyingController):
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(
self.get("rounded_total") or self.grand_total
) > 1 / (10 ** (self.precision("base_grand_total") + 1)):
frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total"""))
def create_remarks(self):
@@ -184,7 +183,7 @@ class PurchaseInvoice(BuyingController):
self.tax_withholding_category = tds_category
self.set_onload("supplier_tds", tds_category)
super(PurchaseInvoice, self).set_missing_values(for_validate)
super().set_missing_values(for_validate)
def validate_credit_to_acc(self):
if not self.credit_to:
@@ -218,12 +217,12 @@ class PurchaseInvoice(BuyingController):
check_list = []
for d in self.get("items"):
if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt:
if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
check_list.append(d.purchase_order)
check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
def validate_with_previous_doc(self):
super(PurchaseInvoice, self).validate_with_previous_doc(
super().validate_with_previous_doc(
{
"Purchase Order": {
"ref_dn_field": "purchase_order",
@@ -271,7 +270,7 @@ class PurchaseInvoice(BuyingController):
exc=WarehouseMissingError,
)
super(PurchaseInvoice, self).validate_warehouse()
super().validate_warehouse()
def validate_item_code(self):
for d in self.get("items"):
@@ -307,7 +306,6 @@ class PurchaseInvoice(BuyingController):
or not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")
)
):
if self.update_stock and item.warehouse and (not item.from_warehouse):
if (
for_validate
@@ -335,12 +333,16 @@ class PurchaseInvoice(BuyingController):
if negative_expense_booked_in_pr:
if (
for_validate and item.expense_account and item.expense_account != stock_not_billed_account
for_validate
and item.expense_account
and item.expense_account != stock_not_billed_account
):
msg = _(
"Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}"
).format(
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt)
item.idx,
frappe.bold(stock_not_billed_account),
frappe.bold(item.purchase_receipt),
)
frappe.msgprint(msg, title=_("Expense Head Changed"))
@@ -349,7 +351,9 @@ class PurchaseInvoice(BuyingController):
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
# This is done in cases when Purchase Invoice is created before Purchase Receipt
if (
for_validate and item.expense_account and item.expense_account != stock_not_billed_account
for_validate
and item.expense_account
and item.expense_account != stock_not_billed_account
):
msg = _(
"Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}."
@@ -400,7 +404,6 @@ class PurchaseInvoice(BuyingController):
def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes":
if frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
):
@@ -410,7 +413,9 @@ class PurchaseInvoice(BuyingController):
if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
msg += "<br><br>"
msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
msg += _(
"To submit the invoice without purchase order please set {0} as {1} in {2}"
).format(
frappe.bold(_("Purchase Order Required")),
frappe.bold("No"),
get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
@@ -420,7 +425,6 @@ class PurchaseInvoice(BuyingController):
def pr_required(self):
stock_items = self.get_stock_items()
if frappe.db.get_value("Buying Settings", None, "pr_required") == "Yes":
if frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt"
):
@@ -453,7 +457,8 @@ class PurchaseInvoice(BuyingController):
frappe.throw(_("Purchase Order {0} is not submitted").format(d.purchase_order))
if d.purchase_receipt:
submitted = frappe.db.sql(
"select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt
"select name from `tabPurchase Receipt` where docstatus = 1 and name = %s",
d.purchase_receipt,
)
if not submitted:
frappe.throw(_("Purchase Receipt {0} is not submitted").format(d.purchase_receipt))
@@ -501,7 +506,9 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"):
if item.purchase_receipt:
frappe.throw(
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
_("Stock cannot be updated against Purchase Receipt {0}").format(
item.purchase_receipt
)
)
def validate_for_repost(self):
@@ -511,7 +518,7 @@ class PurchaseInvoice(BuyingController):
validate_docs_for_deferred_accounting([], [self.name])
def on_submit(self):
super(PurchaseInvoice, self).on_submit()
super().on_submit()
self.check_prev_docstatus()
@@ -551,9 +558,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock == 1:
self.repost_future_sle_and_gle()
if (
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
if frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction":
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -733,9 +738,7 @@ class PurchaseInvoice(BuyingController):
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
frappe.db.get_value("Company", self.company, "enable_provisional_accounting_for_non_stock_items")
)
if provisional_accounting_for_non_stock_items:
self.get_provisional_accounts()
@@ -744,7 +747,7 @@ class PurchaseInvoice(BuyingController):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
frappe.get_cached_value("Item", item.item_code, "asset_category")
if (
self.update_stock
@@ -849,7 +852,9 @@ class PurchaseInvoice(BuyingController):
if flt(item.rm_supp_cost):
supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"]
if not supplier_warehouse_account:
frappe.throw(_("Please set account in Warehouse {0}").format(self.supplier_warehouse))
frappe.throw(
_("Please set account in Warehouse {0}").format(self.supplier_warehouse)
)
gl_entries.append(
self.get_gl_dict(
{
@@ -899,10 +904,9 @@ class PurchaseInvoice(BuyingController):
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
and item.net_rate == net_rate_map[item.pr_detail]
):
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * (
exchange_rate_map[item.purchase_receipt] - self.conversion_rate
)
discrepancy_caused_by_exchange_rate_difference = (
item.qty * item.net_rate
) * (exchange_rate_map[item.purchase_receipt] - self.conversion_rate)
gl_entries.append(
self.get_gl_dict(
@@ -978,12 +982,14 @@ class PurchaseInvoice(BuyingController):
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
fields=["name", "provisional_expense_account", "qty", "base_rate"],
fields=["name", "provisional_expense_account", "qty", "base_rate", "rate"],
)
default_provisional_account = self.get_company_default("default_provisional_account")
provisional_accounts = set(
[
d.provisional_expense_account if d.provisional_expense_account else default_provisional_account
d.provisional_expense_account
if d.provisional_expense_account
else default_provisional_account
for d in pr_items
]
)
@@ -1004,6 +1010,7 @@ class PurchaseInvoice(BuyingController):
"provisional_account": item.provisional_expense_account or default_provisional_account,
"qty": item.qty,
"base_rate": item.base_rate,
"rate": item.rate,
"has_provisional_entry": item.name in rows_with_provisional_entries,
}
@@ -1020,7 +1027,10 @@ class PurchaseInvoice(BuyingController):
self.posting_date,
pr_item.get("provisional_account"),
reverse=1,
item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
item_amount=(
(min(item.qty, pr_item.get("qty")) * pr_item.get("rate"))
* purchase_receipt_doc.get("conversion_rate")
),
)
def update_gross_purchase_amount_for_linked_assets(self, item):
@@ -1040,9 +1050,7 @@ class PurchaseInvoice(BuyingController):
},
)
def make_stock_adjustment_entry(
self, gl_entries, item, voucher_wise_stock_value, account_currency
):
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
net_amt_precision = item.precision("base_net_amount")
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
@@ -1058,7 +1066,6 @@ class PurchaseInvoice(BuyingController):
and warehouse_debit_amount
!= flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount
@@ -1281,9 +1288,7 @@ class PurchaseInvoice(BuyingController):
# base_rounding_adjustment may become zero due to small precision
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry
if (
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
):
if not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
)
@@ -1306,7 +1311,7 @@ class PurchaseInvoice(BuyingController):
def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)
super(PurchaseInvoice, self).on_cancel()
super().on_cancel()
self.check_on_hold_or_closed_status()
@@ -1337,9 +1342,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock == 1:
self.repost_future_sle_and_gle()
if (
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
if frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction":
self.update_project()
self.db_set("status", "Cancelled")
@@ -1370,9 +1373,7 @@ class PurchaseInvoice(BuyingController):
pj = frappe.qb.DocType("Project")
for proj, value in projects.items():
res = (
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
)
res = frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
current_purchase_cost = res and res[0][0] or 0
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
@@ -1640,9 +1641,7 @@ def get_purchase_document_details(doc):
)
net_rate_map = frappe._dict(
frappe.get_all(
child_doctype, filters={"name": ("in", items)}, fields=["name", "net_rate"], as_list=1
)
frappe.get_all(child_doctype, filters={"name": ("in", items)}, fields=["name", "net_rate"], as_list=1)
)
return exchange_rate_map, net_rate_map

View File

@@ -2,8 +2,6 @@
# License: GNU General Public License v3. See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
@@ -218,7 +216,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
supplier.on_hold = 0
supplier.save()
except:
except Exception:
pass
else:
raise Exception
@@ -252,7 +250,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(pi.on_hold, 0)
def test_gl_entries_with_perpetual_inventory_against_pr(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
supplier_warehouse="Work In Progress - TCP1",
@@ -303,7 +300,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
]
)
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
@@ -327,9 +324,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi.submit()
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value(
"Company", pi.company, "exchange_gain_loss_account"
)
exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
# fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value(
@@ -545,12 +540,10 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
existing_purchase_cost = frappe.db.sql(
"""select sum(base_net_amount)
f"""select sum(base_net_amount)
from `tabPurchase Invoice Item`
where project = '{0}'
and docstatus=1""".format(
project.name
)
where project = '{project.name}'
and docstatus=1"""
)
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
@@ -725,7 +718,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# Check for valid currency
@@ -747,7 +740,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertFalse(gle)
def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self):
pi = make_purchase_invoice(
update_stock=1,
posting_date=frappe.utils.nowdate(),
@@ -776,13 +768,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
(d[0], d) for d in [[pi.credit_to, 0.0, 250.0], [stock_in_hand_account, 250.0, 0.0]]
)
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_gl_entries[gle.account][0], gle.account)
self.assertEqual(expected_gl_entries[gle.account][1], gle.debit)
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self):
pi = make_purchase_invoice(
update_stock=1,
posting_date=frappe.utils.nowdate(),
@@ -817,7 +808,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
]
)
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_gl_entries[gle.account][0], gle.account)
self.assertEqual(expected_gl_entries[gle.account][1], gle.debit)
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
@@ -1015,12 +1006,8 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def test_duplicate_due_date_in_terms(self):
pi = make_purchase_invoice(do_not_save=1)
pi.append(
"payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50)
)
pi.append(
"payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50)
)
pi.append("payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50))
pi.append("payment_schedule", dict(due_date="2017-01-01", invoice_portion=50.00, payment_amount=50))
self.assertRaises(frappe.ValidationError, pi.insert)
@@ -1058,9 +1045,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
pi = make_purchase_invoice_against_cost_center(
cost_center=cost_center, credit_to="Creditors - _TC"
)
pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
self.assertEqual(pi.cost_center, cost_center)
expected_values = {
@@ -1522,9 +1507,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def test_provisional_accounting_entry(self):
setup_provisional_accounting()
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2)
)
pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
@@ -1533,7 +1516,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi.save()
pi.submit()
self.assertEquals(pr.items[0].provisional_expense_account, "Provision Account - _TC")
self.assertEqual(pr.items[0].provisional_expense_account, "Provision Account - _TC")
# Check GLE for Purchase Invoice
expected_gle = [
@@ -1560,9 +1543,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date],
]
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date)
toggle_provisional_accounting_setting()
@@ -1611,9 +1592,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
]
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date)
toggle_provisional_accounting_setting()
@@ -1659,9 +1638,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def test_adjust_incoming_rate(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1
)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
# Increase the cost of the item
@@ -1713,9 +1690,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
)
self.assertEqual(stock_value_difference, 50)
frappe.db.set_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0
)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
# Don't adjust incoming rate
@@ -1745,7 +1720,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_item_less_defaults(self):
pi = frappe.new_doc("Purchase Invoice")
pi.supplier = "_Test Supplier"
pi.company = "_Test Company"
@@ -2178,9 +2152,7 @@ def setup_provisional_accounting(**args):
parent_account=args.parent_account or "Current Liabilities - _TC",
company=company,
)
toggle_provisional_accounting_setting(
enable=1, company=company, provisional_account=provisional_account
)
toggle_provisional_accounting_setting(enable=1, company=company, provisional_account=provisional_account)
def toggle_provisional_accounting_setting(**args):

View File

@@ -17,4 +17,4 @@ class PurchaseTaxesandChargesTemplate(Document):
def autoname(self):
if self.company and self.title:
abbr = frappe.get_cached_value("Company", self.company, "abbr")
self.name = "{0} - {1}".format(self.title, abbr)
self.name = f"{self.title} - {abbr}"

View File

@@ -9,7 +9,7 @@ from frappe.utils.data import comma_and
class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._allowed_types = get_allowed_types_from_settings()
def validate(self):
@@ -136,7 +136,9 @@ def start_repost(account_repost_doc=str) -> None:
doc = frappe.get_doc(x.voucher_type, x.voucher_no)
if repost_doc.delete_cancelled_entries:
frappe.db.delete("GL Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name})
frappe.db.delete(
"GL Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
)
frappe.db.delete(
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
)
@@ -182,7 +184,9 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
if docs_with_deferred_revenue or docs_with_deferred_expense:
frappe.throw(
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
frappe.bold(
comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
)
)
)

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