Compare commits

..

439 Commits

Author SHA1 Message Date
Frappe PR Bot
5ecb022abc chore(release): Bumped to Version 15.20.5
## [15.20.5](https://github.com/frappe/erpnext/compare/v15.20.4...v15.20.5) (2024-04-17)

### Bug Fixes

* duplicate serial and batch bundle in stock entry and stock reco ([a1db5f0](a1db5f0f0a))
2024-04-17 09:25:54 +00:00
rohitwaghchaure
12ab3972de Merge pull request #41058 from frappe/mergify/bp/version-15/pr-41056
fix: duplicate serial and batch bundle in stock entry and stock reco (backport #41053) (backport #41056)
2024-04-17 14:54:34 +05:30
Rohit Waghchaure
a1db5f0f0a fix: duplicate serial and batch bundle in stock entry and stock reco
(cherry picked from commit 732b6e1417)
(cherry picked from commit c10c21157d)
2024-04-17 09:00:05 +00:00
Frappe PR Bot
00d45c34ba chore(release): Bumped to Version 15.20.4
## [15.20.4](https://github.com/frappe/erpnext/compare/v15.20.3...v15.20.4) (2024-04-17)

### Bug Fixes

* Delayed Order Report not working (backport [#41037](https://github.com/frappe/erpnext/issues/41037)) ([#41039](https://github.com/frappe/erpnext/issues/41039)) ([40d059c](40d059c7c1))
* do not validate batch qty for LCV ([5de9b6a](5de9b6ac75))
* Don't set delivery date as today while making SO from Quotation ([fea906b](fea906b883))
* expense account set as COGS for stock entry Material Issue (backport [#41026](https://github.com/frappe/erpnext/issues/41026)) ([#41029](https://github.com/frappe/erpnext/issues/41029)) ([f59e433](f59e43320b))
* get address if multiple companies ([dcfc768](dcfc768d33))
* get address if multiple companies ([ccdbad9](ccdbad9f90))
* **gp:** SLEs not fetched for correct warehouse ([881dc02](881dc02349))
* incorrect exc gain/loss for PE against JE for payable accounts ([361d7f1](361d7f1ba5))
* landed cost voucher for legacy pr with batch ([4e215c6](4e215c6b7b))
* Link Validation Error on Dunning cancellation ([0053d57](0053d57ec4))
* Multiple partial payment requests against Purchase Invoice ([e287376](e287376cc8))
* not able to submit subcontracting receipt (backport [#41041](https://github.com/frappe/erpnext/issues/41041)) ([#41045](https://github.com/frappe/erpnext/issues/41045)) ([b27ad76](b27ad76557))
* Subcontracting Receipt GL Entries (backport [#40773](https://github.com/frappe/erpnext/issues/40773)) ([#40979](https://github.com/frappe/erpnext/issues/40979)) ([9abc71f](9abc71f9c8))
* Test case ([641b2a4](641b2a4705))
* test cases ([0ee91a2](0ee91a2e54))
* type of transaction validation for the stock entry (backport [#40986](https://github.com/frappe/erpnext/issues/40986)) ([#40992](https://github.com/frappe/erpnext/issues/40992)) ([4342b89](4342b891eb))
* **ux:** Sales Order Stock Reservation Dialog (backport [#40707](https://github.com/frappe/erpnext/issues/40707)) ([#40980](https://github.com/frappe/erpnext/issues/40980)) ([14a1a18](14a1a18243))
* voucher no. is link field for non english user interface ([1c28ed4](1c28ed4d5b))
* zero division error ([e9c6c5a](e9c6c5a8eb))
2024-04-17 06:13:12 +00:00
rohitwaghchaure
44610621ab Merge pull request #41031 from frappe/version-15-hotfix
chore: release v15
2024-04-17 11:41:42 +05:30
ruthra kumar
088aa94fa1 Merge pull request #40789 from frappe/mergify/bp/version-15-hotfix/pr-40786
fix(gp): SLEs not fetched for correct warehouse (backport #40786)
2024-04-17 10:35:09 +05:30
ruthra kumar
c34b814c16 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-40786 2024-04-17 10:19:08 +05:30
Dany Robert
881dc02349 fix(gp): SLEs not fetched for correct warehouse
(cherry picked from commit f958e8be06)
2024-04-17 10:17:40 +05:30
mergify[bot]
b27ad76557 fix: not able to submit subcontracting receipt (backport #41041) (#41045)
fix: not able to submit subcontracting receipt (#41041)

(cherry picked from commit 5b1493b56c)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-04-17 06:37:38 +05:30
mergify[bot]
40d059c7c1 fix: Delayed Order Report not working (backport #41037) (#41039)
fix: Delayed Order Report not working (#41037)

(cherry picked from commit d69a18b826)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-04-16 18:24:38 +05:30
ruthra kumar
52b42dfc14 Merge pull request #41035 from frappe/mergify/bp/version-15-hotfix/pr-40373
fix: get address if multiple companies (backport #40373)
2024-04-16 16:39:39 +05:30
Nihantra C. Patel
dcfc768d33 fix: get address if multiple companies
(cherry picked from commit 655a1797be)
2024-04-16 11:01:58 +00:00
Nihantra Patel
ccdbad9f90 fix: get address if multiple companies
(cherry picked from commit c6cf1bec76)
2024-04-16 11:01:57 +00:00
mergify[bot]
f59e43320b fix: expense account set as COGS for stock entry Material Issue (backport #41026) (#41029)
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:43 +05:30
ruthra kumar
ec337d3465 Merge pull request #41023 from frappe/mergify/bp/version-15-hotfix/pr-41020
fix: incorrect exc gain/loss for PE against JE for payable accounts (backport #41020)
2024-04-16 11:22:26 +05:30
ruthra kumar
59950290a2 test: exc gain/loss journals booking in Payable accounts
(cherry picked from commit 8821c98625)
2024-04-16 05:29:53 +00:00
ruthra kumar
361d7f1ba5 fix: incorrect exc gain/loss for PE against JE for payable accounts
(cherry picked from commit 81b574053f)
2024-04-16 05:29:52 +00:00
Deepesh Garg
80adafc207 Merge pull request #41017 from frappe/mergify/bp/version-15-hotfix/pr-40812
fix: Multiple partial payment requests against Purchase Invoice (#40812)
2024-04-16 09:35:17 +05:30
Deepesh Garg
641b2a4705 fix: Test case
(cherry picked from commit 071e5ed648)
2024-04-15 16:21:41 +00:00
Deepesh Garg
e287376cc8 fix: Multiple partial payment requests against Purchase Invoice
(cherry picked from commit 45d5f6e00a)
2024-04-15 16:21:41 +00:00
ruthra kumar
21dfb68763 Merge pull request #41009 from frappe/mergify/bp/version-15-hotfix/pr-40714
fix: voucher no. is link field for non english user interface (backport #40714)
2024-04-15 14:26:54 +05:30
“Khushi
1c28ed4d5b fix: voucher no. is link field for non english user interface
(cherry picked from commit 2b8928cae6)
2024-04-15 14:02:33 +05:30
Nabin Hait
10059309da Merge pull request #41005 from frappe/mergify/bp/version-15-hotfix/pr-40858
fix: Don't set delivery date as today while making SO from Quotation (backport #40858)
2024-04-15 13:05:27 +05:30
Nabin Hait
0ee91a2e54 fix: test cases
(cherry picked from commit 65c74fa3c7)
2024-04-15 07:03:39 +00:00
Nabin Hait
fea906b883 fix: Don't set delivery date as today while making SO from Quotation
(cherry picked from commit fec20decc1)
2024-04-15 07:03:38 +00:00
Deepesh Garg
128391be0a Merge pull request #40990 from frappe/mergify/bp/version-15-hotfix/pr-40985
fix: Add payment request to dimension list (#40985)
2024-04-13 16:37:21 +05:30
Frappe PR Bot
2febb2965e chore(release): Bumped to Version 15.20.3
## [15.20.3](https://github.com/frappe/erpnext/compare/v15.20.2...v15.20.3) (2024-04-13)

### Bug Fixes

* do not validate batch qty for LCV (backport [#40975](https://github.com/frappe/erpnext/issues/40975)) (backport [#40981](https://github.com/frappe/erpnext/issues/40981)) ([#40991](https://github.com/frappe/erpnext/issues/40991)) ([7d12674](7d12674430))
* type of transaction validation for the stock entry (backport [#40986](https://github.com/frappe/erpnext/issues/40986)) (backport [#40992](https://github.com/frappe/erpnext/issues/40992)) ([#40994](https://github.com/frappe/erpnext/issues/40994)) ([e07557c](e07557c9c5))
2024-04-13 10:29:03 +00:00
mergify[bot]
e07557c9c5 fix: type of transaction validation for the stock entry (backport #40986) (backport #40992) (#40994)
fix: type of transaction validation for the stock entry (backport #40986) (#40992)

* fix: type of transaction validation for the stock entry

(cherry picked from commit 8ad0295f1b)

* chore: fix test case

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
(cherry picked from commit 4342b891eb)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-04-13 15:57:50 +05:30
mergify[bot]
7d12674430 fix: do not validate batch qty for LCV (backport #40975) (backport #40981) (#40991)
fix: do not validate batch qty for LCV

(cherry picked from commit baf0c83cc5)
(cherry picked from commit 5de9b6ac75)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-04-13 15:57:41 +05:30
mergify[bot]
4342b891eb fix: type of transaction validation for the stock entry (backport #40986) (#40992)
* fix: type of transaction validation for the stock entry

(cherry picked from commit 8ad0295f1b)

* chore: fix test case

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-04-13 15:30:58 +05:30
ruthra kumar
2d94ea074c chore: patch to setup exiting dimensions in Payment Request
(cherry picked from commit 3f8d785f02)
2024-04-13 04:52:23 +00:00
ruthra kumar
4dbeabc8ef refactor: add payment request to dimension list
(cherry picked from commit e93b4a1f2c)
2024-04-13 04:52:23 +00:00
Frappe PR Bot
7711b13766 chore(release): Bumped to Version 15.20.2
## [15.20.2](https://github.com/frappe/erpnext/compare/v15.20.1...v15.20.2) (2024-04-12)

### Bug Fixes

* Subcontracting Receipt GL Entries (backport [#40773](https://github.com/frappe/erpnext/issues/40773)) (backport [#40979](https://github.com/frappe/erpnext/issues/40979)) ([#40983](https://github.com/frappe/erpnext/issues/40983)) ([3d6ba6f](3d6ba6f21f))
2024-04-12 10:03:02 +00:00
mergify[bot]
3d6ba6f21f fix: Subcontracting Receipt GL Entries (backport #40773) (backport #40979) (#40983)
fix: Subcontracting Receipt GL Entries (backport #40773) (#40979)

* fix: Subcontracting Receipt GL Entries

(cherry picked from commit 9808ae92a4)

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

* chore: `conflicts`

---------

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

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-04-12 15:31:40 +05:30
mergify[bot]
9abc71f9c8 fix: Subcontracting Receipt GL Entries (backport #40773) (#40979)
* fix: Subcontracting Receipt GL Entries

(cherry picked from commit 9808ae92a4)

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

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-04-12 15:15:11 +05:30
rohitwaghchaure
52ee6dcaed Merge pull request #40981 from frappe/mergify/bp/version-15-hotfix/pr-40975
fix: do not validate batch qty for LCV (backport #40975)
2024-04-12 14:52:54 +05:30
Rohit Waghchaure
5de9b6ac75 fix: do not validate batch qty for LCV
(cherry picked from commit baf0c83cc5)
2024-04-12 08:36:12 +00:00
mergify[bot]
14a1a18243 fix(ux): Sales Order Stock Reservation Dialog (backport #40707) (#40980)
fix(ux): Sales Order Stock Reservation Dialog

(cherry picked from commit 5daf19da40)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-04-12 13:28:17 +05:30
ruthra kumar
3762259d3d Merge pull request #40974 from frappe/mergify/bp/version-15-hotfix/pr-40973
fix: Link Validation Error on Dunning cancellation (backport #40973)
2024-04-12 10:44:18 +05:30
ruthra kumar
0053d57ec4 fix: Link Validation Error on Dunning cancellation
(cherry picked from commit 205fd9888c)
2024-04-12 04:57:40 +00:00
Frappe PR Bot
c24d9675e8 chore(release): Bumped to Version 15.20.1
## [15.20.1](https://github.com/frappe/erpnext/compare/v15.20.0...v15.20.1) (2024-04-11)

### Bug Fixes

* landed cost voucher for legacy pr with batch ([83a5284](83a5284ba9))
* zero division error ([72fc083](72fc0836e1))
2024-04-11 13:23:28 +00:00
rohitwaghchaure
e90b60b5cb Merge pull request #40971 from frappe/mergify/bp/version-15/pr-40969
fix: zero division error (backport #40967) (backport #40969)
2024-04-11 18:52:09 +05:30
rohitwaghchaure
5d2ac5b3e9 Merge pull request #40970 from frappe/mergify/bp/version-15/pr-40968
fix: landed cost voucher for legacy pr with batch (backport #40966) (backport #40968)
2024-04-11 18:51:54 +05:30
Rohit Waghchaure
72fc0836e1 fix: zero division error
(cherry picked from commit f9e230e758)
(cherry picked from commit e9c6c5a8eb)
2024-04-11 12:47:35 +00:00
rohitwaghchaure
ea8c57263b Merge pull request #40969 from frappe/mergify/bp/version-15-hotfix/pr-40967
fix: zero division error (backport #40967)
2024-04-11 18:15:36 +05:30
Rohit Waghchaure
83a5284ba9 fix: landed cost voucher for legacy pr with batch
(cherry picked from commit fa91cda46c)
(cherry picked from commit 4e215c6b7b)
2024-04-11 12:31:50 +00:00
rohitwaghchaure
af0679076b Merge pull request #40968 from frappe/mergify/bp/version-15-hotfix/pr-40966
fix: landed cost voucher for legacy pr with batch (backport #40966)
2024-04-11 17:59:36 +05:30
Rohit Waghchaure
e9c6c5a8eb fix: zero division error
(cherry picked from commit f9e230e758)
2024-04-11 12:25:39 +00:00
Rohit Waghchaure
4e215c6b7b fix: landed cost voucher for legacy pr with batch
(cherry picked from commit fa91cda46c)
2024-04-11 12:12:52 +00:00
Frappe PR Bot
04ca25e199 chore(release): Bumped to Version 15.20.0
# [15.20.0](https://github.com/frappe/erpnext/compare/v15.19.2...v15.20.0) (2024-04-11)

### Bug Fixes

* check if there is any existing depreciation schedule ([dcb7e27](dcb7e27a5e))
* Default company bank account ([d8c5f15](d8c5f15bc0))
* depr amount pro rata and related tests ([350d53d](350d53dbe1))
* depr amount pro rata and related tests ([854d4a9](854d4a9c79))
* Don't call get_fiscal_year if setup is not done yet ([9beaf65](9beaf65d19))
* exclude some query builder lines from ruff rules ([41d7c03](41d7c03cbb))
* Fetch outstanding and total amount for reference journal entry ([c72f61e](c72f61ef2f))
* Get default party bank account and company bank account for a party ([1745673](174567350e))
* Get pro-rata amount based on correct days ([1276855](1276855f40))
* Get pro-rata amount based on correct days ([5bf7c79](5bf7c799e2))
* group warehouse added in the stock reconciliation ([e6d6172](e6d6172b4c))
* ignore dimension validation for cancelled entries ([b288c73](b288c73437))
* Ignore permissions while fetching module settings properties ([5e9f31c](5e9f31cf21))
* incorrect currency symbol in General Ledger print ([9f4663b](9f4663b4d0))
* incorrect operator causing incorrect validation ([80c2a33](80c2a33fd7))
* invalid ledger entries on payment against reverse payments ([f3eb559](f3eb559a71))
* linter issues ([8016b0a](8016b0aee7))
* notify update when update outstanding amount ([b10d3c4](b10d3c4da2))
* notify update when update outstanding amount - Linters ([f514819](f51481983d))
* pass empty string email content of pos invoice ([7a9291b](7a9291b3d1))
* pick list validation didn't consider existing draft pick list ([722abf1](722abf1b6b))
* pro-rata amount for straight line method ([16a5475](16a5475c0c))
* query_report.trigger_refresh is not a function ([4f48ede](4f48ede7a0))
* Recalculate sales commission on item update ([5117494](5117494a52))
* Receive payment entry from Employee ([0cee954](0cee954f84))
* resolved merge conflict ([edd143b](edd143b084))
* Show amount in transaction currency in general ledger report ([67bac41](67bac41df4))
* show currency symbol in base currency in fixed asset register report ([7904389](79043894b2))
* Show itemised TDS breakup based on Apply TDS checkbox on item level ([4e3c885](4e3c885808))
* **test:** Advance Received should be under liability account ([4f13f47](4f13f4779a))
* **test:** for reverse payments, only advance acc should have effect ([3668de7](3668de7cb7))
* translatable web footer ([#40834](https://github.com/frappe/erpnext/issues/40834)) ([e8497b2](e8497b2d80))
* **treewide:** manual ruff fixes ([7828eee](7828eee014))
* use 'eq' and isnull() on qb conditions ([2268995](22689958da))
* use reference type name to update exc rate ([5b62f20](5b62f2050b))
* Values in transaciton currency ([3b179ba](3b179bad89))
* workstation refresh after prompt ([9f12c96](9f12c964dc))
* wrong value for total amount in payments ([807def0](807def0f04))

### Features

* ledger health doctype ([5d978c2](5d978c2729))
* new hook `fields_for_group_similar_items` to group additional fields for print formats ([#40831](https://github.com/frappe/erpnext/issues/40831)) ([4ef4466](4ef4466e67))
2024-04-11 07:38:28 +00:00
Nabin Hait
205e7afb9d Merge pull request #40918 from frappe/version-15-hotfix
chore: release v15
2024-04-11 13:07:06 +05:30
Nabin Hait
3b166ea249 Merge pull request #40891 from frappe/mergify/bp/version-15-hotfix/pr-40698
fix: Get pro-rata amount based on correct days (backport #40698)
2024-04-11 11:56:32 +05:30
Nabin Hait
694187b853 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-40698 2024-04-11 11:53:20 +05:30
Nabin Hait
91fe25356e Merge pull request #40960 from nabinhait/itemised-tds-breakup-v15-hotfix
fix: Show itemised TDS breakup based on Apply TDS checkbox on item level
2024-04-11 11:49:34 +05:30
Nabin Hait
2e35065070 Merge pull request #40961 from nabinhait/permission-issue-v15
fix: Ignore permissions while fetching module settings properties
2024-04-11 11:49:21 +05:30
Nabin Hait
cca7f17b19 Merge pull request #40963 from frappe/revert-40630-mergify/bp/version-15-hotfix/pr-40514
Revert "fix: pass empty string email content of pos invoice (backport #40514)"
2024-04-11 11:48:49 +05:30
Nabin Hait
4591ba063c Revert "fix: pass empty string email content of pos invoice (backport #40514)" 2024-04-11 11:43:59 +05:30
Nabin Hait
5ba9fbcbdb Merge pull request #40953 from frappe/mergify/bp/version-15-hotfix/pr-40916
fix: check if there is any existing depreciation schedule (backport #40916)
2024-04-11 11:32:44 +05:30
Nabin Hait
23d4b01137 Merge pull request #40825 from frappe/mergify/bp/version-15-hotfix/pr-40584
fix: show currency symbol in base currency in fixed asset register report (backport #40584)
2024-04-11 11:30:51 +05:30
Nabin Hait
a93a9122bb Merge pull request #40955 from frappe/mergify/bp/version-15-hotfix/pr-40861
fix: Get default party bank account and company bank account for a party (backport #40861)
2024-04-11 11:29:30 +05:30
Nabin Hait
ac99d8543d Merge pull request #40958 from frappe/mergify/bp/version-15-hotfix/pr-40920
fix: Fetch correct outstanding and total amount for reference journal entry (backport #40920)
2024-04-11 11:28:53 +05:30
Nabin Hait
5e9f31cf21 fix: Ignore permissions while fetching module settings properties 2024-04-11 11:23:19 +05:30
Nabin Hait
4e3c885808 fix: Show itemised TDS breakup based on Apply TDS checkbox on item level 2024-04-11 11:20:38 +05:30
Nabin Hait
711bc3ff1c Merge pull request #40951 from frappe/mergify/bp/version-15-hotfix/pr-40833
fix: wrong value for total amount in payments (backport #40833)
2024-04-11 11:12:07 +05:30
Nabin Hait
c72f61ef2f fix: Fetch outstanding and total amount for reference journal entry
(cherry picked from commit f331f9b15c)
2024-04-11 05:35:43 +00:00
Nabin Hait
174567350e fix: Get default party bank account and company bank account for a party
(cherry picked from commit f87be1d96e)
2024-04-11 05:25:34 +00:00
Nabin Hait
dcb7e27a5e fix: check if there is any existing depreciation schedule
(cherry picked from commit 433c8c450c)
2024-04-11 05:24:08 +00:00
Nabin Hait
959d9c5fda Merge branch 'version-15' into version-15-hotfix 2024-04-11 10:51:32 +05:30
Deepesh Garg
955ffce775 Merge pull request #40630 from frappe/mergify/bp/version-15-hotfix/pr-40514
fix: pass empty string email content of pos invoice (#40514)
2024-04-10 21:36:57 +05:30
Deepesh Garg
6f3a70d35f Merge pull request #40697 from frappe/mergify/bp/version-15-hotfix/pr-40641
chore: Reduce usage of cur_frm (#40641)
2024-04-10 21:35:08 +05:30
mergify[bot]
0ec1f01b00 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-40584 2024-04-10 15:57:49 +00:00
HarryPaulo
807def0f04 fix: wrong value for total amount in payments
(cherry picked from commit bf88b8eb65)
2024-04-10 15:57:11 +00:00
Deepesh Garg
87e30a2981 Merge pull request #40950 from frappe/mergify/bp/version-15-hotfix/pr-40863
fix: Show amount in transaction currency in general ledger report (#40863)
2024-04-10 21:24:39 +05:30
Deepesh Garg
0e1c7e8788 Merge pull request #40946 from frappe/mergify/bp/version-15-hotfix/pr-40844
fix: notify update when update outstanding amount (#40844)
2024-04-10 21:22:42 +05:30
Deepesh Garg
21454e4dfb Merge pull request #40948 from frappe/mergify/bp/version-15-hotfix/pr-40931
fix: Don't call get_fiscal_year if setup is not done yet (backport #40931)
2024-04-10 21:22:22 +05:30
Deepesh Garg
18700c26f9 Merge pull request #40945 from frappe/mergify/bp/version-15-hotfix/pr-40849
fix: Values in transaction currency (backport #40849)
2024-04-10 21:22:06 +05:30
Nabin Hait
67bac41df4 fix: Show amount in transaction currency in general ledger report
(cherry picked from commit d868122f12)
2024-04-10 15:26:25 +00:00
Corentin Flr
9beaf65d19 fix: Don't call get_fiscal_year if setup is not done yet
(cherry picked from commit c203fafb1b)
2024-04-10 15:18:26 +00:00
HarryPaulo
f51481983d fix: notify update when update outstanding amount - Linters
(cherry picked from commit 198b90eb01)
2024-04-10 15:17:17 +00:00
HarryPaulo
b10d3c4da2 fix: notify update when update outstanding amount
(cherry picked from commit f67f163e7d)
2024-04-10 15:17:16 +00:00
Deepesh Garg
3b179bad89 fix: Values in transaciton currency
(cherry picked from commit a3c9fd6567)
2024-04-10 15:16:39 +00:00
Akhil Narang
29e3a400f9 Merge pull request #40935 from akhilnarang/v15-ruff
chore: backport ruff linting/formatting
2024-04-10 16:36:13 +05:30
Akhil Narang
3141a1702c chore: gitignore ruff format
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 16:11:12 +05:30
ruthra kumar
22689958da fix: use 'eq' and isnull() on qb conditions
(cherry picked from commit eee86d2e4b)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 16:11:12 +05:30
Akhil Narang
41d7c03cbb fix: exclude some query builder lines from ruff rules
`== None` and `== True` are intentional here

(cherry picked from commit ac69513f60)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 16:11:10 +05:30
Akhil Narang
7828eee014 fix(treewide): manual ruff fixes
(cherry picked from commit f63396ef47)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 16:11:10 +05:30
ruthra kumar
9b1e36ebf0 Merge pull request #40938 from frappe/mergify/bp/version-15-hotfix/pr-40936
fix: ignore dimension validation for cancelled entries (backport #40936)
2024-04-10 15:28:16 +05:30
Akhil Narang
960ef14b7a refactor(treewide): formatting and ruff fixes, + manually enabled F401
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 14:15:58 +05:30
ruthra kumar
b288c73437 fix: ignore dimension validation for cancelled entries
(cherry picked from commit 971c867f29)
2024-04-10 08:29:25 +00:00
barredterra
9eeedd8515 chore: switch to ruff for python formatting/linting
(cherry picked from commit 8afb7790de)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-10 13:10:19 +05:30
ruthra kumar
0f58a3918a Merge pull request #40930 from frappe/mergify/bp/version-15-hotfix/pr-40695
feat: Periodically monitor ledger health (backport #40695)
2024-04-09 20:00:23 +05:30
ruthra kumar
501c88ca3a Merge pull request #40928 from frappe/mergify/bp/version-15-hotfix/pr-40926
fix: incorrect currency symbol in General Ledger print (backport #40926)
2024-04-09 19:54:48 +05:30
ruthra kumar
b22784878f chore: use super() instead of super(__class__, self)
(cherry picked from commit 24d37d22a3)
2024-04-09 12:25:56 +00:00
ruthra kumar
d8f627a9d7 chore: make ledger health doctype read_only
(cherry picked from commit dc79213bb3)
2024-04-09 12:25:55 +00:00
ruthra kumar
711a6eaaaf test: ledger monitoring function
(cherry picked from commit 4776d660b5)
2024-04-09 12:25:55 +00:00
ruthra kumar
9834e3ab7f chore: schedule job to run daily
(cherry picked from commit f96cf111ed)
2024-04-09 12:25:55 +00:00
ruthra kumar
a331eadf29 chore: permission and UI changes
(cherry picked from commit 1a43ed763b)
2024-04-09 12:25:55 +00:00
ruthra kumar
bf0cfae9c9 refactor: only run checks on specified companies
(cherry picked from commit 00eeacd06a)
2024-04-09 12:25:54 +00:00
ruthra kumar
522cfd0c9a refactor: make health check configurable for companies
(cherry picked from commit 704925549b)
2024-04-09 12:25:54 +00:00
ruthra kumar
9bae5e37cd refactor: control monitoring through settings page
(cherry picked from commit a42482ce35)
2024-04-09 12:25:53 +00:00
ruthra kumar
9fbb4f676b chore: settings page for health monitor
(cherry picked from commit b2fb7843d1)
2024-04-09 12:25:53 +00:00
ruthra kumar
0f927f99a4 refactor: barebones method to run checks
(cherry picked from commit 8c8d9be810)
2024-04-09 12:25:53 +00:00
ruthra kumar
6bf997ee96 refactor: flag for general and payment ledger mismatch
(cherry picked from commit d620b9eae8)
2024-04-09 12:25:52 +00:00
ruthra kumar
b8bcae9252 refactor: date on which vouchers was reported
(cherry picked from commit 402ffc6d27)
2024-04-09 12:25:51 +00:00
ruthra kumar
5d978c2729 feat: ledger health doctype
(cherry picked from commit 9ed74dd8cc)
2024-04-09 12:25:51 +00:00
ruthra kumar
9f4663b4d0 fix: incorrect currency symbol in General Ledger print
(cherry picked from commit 429e036e8c)
2024-04-09 12:16:47 +00:00
rohitwaghchaure
8cc5b99986 Merge pull request #40922 from frappe/mergify/bp/version-15-hotfix/pr-40909
fix: pick list validation didn't consider existing draft pick list (backport #40909)
2024-04-09 17:28:18 +05:30
rohitwaghchaure
7a0e313895 chore: fix conflicts 2024-04-09 16:57:02 +05:30
Rohit Waghchaure
722abf1b6b fix: pick list validation didn't consider existing draft pick list
(cherry picked from commit 3bce4d92f6)

# Conflicts:
#	erpnext/stock/doctype/pick_list/pick_list.py
2024-04-09 11:25:22 +00:00
Sagar Vora
566d4fa76e Merge pull request #40907 from frappe/mergify/bp/version-15-hotfix/pr-40831 2024-04-08 10:19:31 +00:00
Smit Vora
4ef4466e67 feat: new hook fields_for_group_similar_items to group additional fields for print formats (#40831)
(cherry picked from commit f7c9e1538b)
2024-04-08 10:18:24 +00:00
ruthra kumar
6cefe45e5b Merge pull request #40905 from frappe/mergify/bp/version-15-hotfix/pr-40878
refactor: merge taxes from delivery note to Sales Invoice (backport #40878)
2024-04-08 15:47:11 +05:30
ruthra kumar
d4a74923c1 test: tax merging from 2 Delivery Note to Sales Invoice
(cherry picked from commit 39a48a2e2a)
2024-04-08 09:09:46 +00:00
ruthra kumar
e9b87fdd42 refactor: merge taxes from delivery note to Sales Invoice
(cherry picked from commit 550cbbd91c)
2024-04-08 09:09:46 +00:00
ruthra kumar
7f790ca908 Merge pull request #40903 from frappe/mergify/bp/version-15-hotfix/pr-40876
fix: ledger entries for Payment against reverse payment reconciliation (backport #40876)
2024-04-08 09:11:33 +05:30
ruthra kumar
2373dd9b95 chore: resolve conflicts 2024-04-08 08:52:41 +05:30
ruthra kumar
67df5ae0ec refactor(test): include new fields in assertion
(cherry picked from commit 64b7f624a5)
2024-04-08 01:59:13 +00:00
ruthra kumar
7c0d46409f test: advance payment against reverse payment reconciliation
(cherry picked from commit 141f462368)
2024-04-08 01:59:13 +00:00
ruthra kumar
4ad423b1b9 test: reverse payment against payment for supplier
(cherry picked from commit 248cc6105b)
2024-04-08 01:59:12 +00:00
ruthra kumar
3668de7cb7 fix(test): for reverse payments, only advance acc should have effect
This applies for Payments against Reverse-Payment reconcilition

(cherry picked from commit 74bc38e0af)
2024-04-08 01:59:12 +00:00
ruthra kumar
d9826e3dfc refactor: supplementary field to better handle reverse payments
(cherry picked from commit bdd36b0001)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
2024-04-08 01:59:12 +00:00
ruthra kumar
f3eb559a71 fix: invalid ledger entries on payment against reverse payments
On Advance payments booked on separate party account, reconciliation
against a reverse payment posted invalid ledger entries.

(cherry picked from commit 9fd2dddfde)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
2024-04-08 01:59:11 +00:00
ruthra kumar
4f13f4779a fix(test): Advance Received should be under liability account
(cherry picked from commit a75e095d00)
2024-04-08 01:59:10 +00:00
Frappe PR Bot
3566a8adc6 chore(release): Bumped to Version 15.19.2
## [15.19.2](https://github.com/frappe/erpnext/compare/v15.19.1...v15.19.2) (2024-04-07)

### Bug Fixes

* depr amount pro rata and related tests ([e41d23d](e41d23d26c))
* Get pro-rata amount based on correct days ([f229282](f229282754))
* pro-rata amount for straight line method ([e71f334](e71f33425c))
2024-04-07 10:40:27 +00:00
Nabin Hait
7fdc8f7c38 Merge pull request #40902 from frappe/mergify/bp/version-15/pr-40901
fix: Get pro-rata depreciation amount based on correct days (backport #40901)
2024-04-07 16:09:14 +05:30
Nabin Hait
e71f33425c fix: pro-rata amount for straight line method
(cherry picked from commit 16a5475c0c)
2024-04-07 10:14:39 +00:00
Nabin Hait
6ddd08d0b0 fix:linter issue
(cherry picked from commit 8247fd1727)
2024-04-07 10:14:39 +00:00
Nabin Hait
e41d23d26c fix: depr amount pro rata and related tests
(cherry picked from commit 350d53dbe1)
2024-04-07 10:14:39 +00:00
Nabin Hait
f229282754 fix: Get pro-rata amount based on correct days
(cherry picked from commit 1276855f40)
2024-04-07 10:14:38 +00:00
Nabin Hait
77fd0436a8 Merge pull request #40901 from nabinhait/pro-rata-depr-v15
fix: Get pro-rata depreciation amount based on correct days
2024-04-07 15:43:32 +05:30
Nabin Hait
16a5475c0c fix: pro-rata amount for straight line method 2024-04-07 15:21:46 +05:30
Nabin Hait
8247fd1727 fix:linter issue 2024-04-07 15:18:29 +05:30
rohitwaghchaure
159d633a62 Merge pull request #40836 from frappe/mergify/bp/version-15-hotfix/pr-40834
fix: translatable web footer (backport #40834)
2024-04-07 14:36:03 +05:30
rohitwaghchaure
99a57e689d Merge pull request #40860 from frappe/mergify/bp/version-15-hotfix/pr-40848
fix: group warehouse added in the stock reconciliation (backport #40848)
2024-04-07 14:34:58 +05:30
rohitwaghchaure
97bf4a829a Merge pull request #40894 from frappe/mergify/bp/version-15-hotfix/pr-40883
fix: incorrect operator causing incorrect validation (backport #40883)
2024-04-07 14:33:43 +05:30
Nabin Hait
350d53dbe1 fix: depr amount pro rata and related tests 2024-04-07 14:14:13 +05:30
Nabin Hait
1276855f40 fix: Get pro-rata amount based on correct days 2024-04-07 14:14:06 +05:30
Nabin Hait
edd143b084 fix: resolved merge conflict 2024-04-07 12:15:52 +05:30
Rohit Waghchaure
80c2a33fd7 fix: incorrect operator causing incorrect validation
(cherry picked from commit 6b317b0c0d)
2024-04-07 06:34:23 +00:00
rohitwaghchaure
25e0a808cd Merge pull request #40889 from frappe/mergify/bp/version-15-hotfix/pr-40887
fix: query_report.trigger_refresh is not a function (backport #40887)
2024-04-07 12:02:46 +05:30
Nabin Hait
0cd94b017f fix:linter issue
(cherry picked from commit be0cb5289f)

# Conflicts:
#	erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
2024-04-07 06:32:22 +00:00
Nabin Hait
854d4a9c79 fix: depr amount pro rata and related tests
(cherry picked from commit e8dcbe2625)
2024-04-07 06:32:22 +00:00
Nabin Hait
5bf7c799e2 fix: Get pro-rata amount based on correct days
(cherry picked from commit 6c1e9fab27)
2024-04-07 06:32:22 +00:00
Rohit Waghchaure
4f48ede7a0 fix: query_report.trigger_refresh is not a function
(cherry picked from commit 30bbb58ca1)
2024-04-07 06:27:51 +00:00
ruthra kumar
0532b1fd94 Merge pull request #40869 from frappe/mergify/bp/version-15-hotfix/pr-40856
fix: unwanted Exc Gain/Loss journals on Payment against Journal entry (backport #40856)
2024-04-05 10:52:43 +05:30
ruthra kumar
4b78eb7e76 test: payment against JE reconciliation with different rates
(cherry picked from commit fe84558b77)
2024-04-05 05:04:02 +00:00
ruthra kumar
5b62f2050b fix: use reference type name to update exc rate
(cherry picked from commit c15690e475)
2024-04-05 05:04:02 +00:00
Rohit Waghchaure
e6d6172b4c fix: group warehouse added in the stock reconciliation
(cherry picked from commit 8f53bc0096)
2024-04-04 10:10:24 +00:00
Deepesh Garg
91f689575e Merge pull request #40810 from frappe/mergify/bp/version-15-hotfix/pr-40796
fix: Default company bank account (#40796)
2024-04-04 14:28:07 +05:30
Frappe PR Bot
dc0c85a227 chore(release): Bumped to Version 15.19.1
## [15.19.1](https://github.com/frappe/erpnext/compare/v15.19.0...v15.19.1) (2024-04-04)

### Bug Fixes

* Receive payment entry from Employee ([3e7abf5](3e7abf5fe5))
2024-04-04 08:57:45 +00:00
Deepesh Garg
5c5a0c965d Merge pull request #40809 from frappe/mergify/bp/version-15-hotfix/pr-40770
fix: Recalculate sales commission on item update (#40770)
2024-04-04 14:27:40 +05:30
Deepesh Garg
162b6bf50b Merge pull request #40855 from frappe/mergify/bp/version-15/pr-40808
fix: Receive payment entry from Employee (#40755)
2024-04-04 14:26:36 +05:30
Deepesh Garg
3e7abf5fe5 fix: Receive payment entry from Employee
(cherry picked from commit 29e5c6807e)
(cherry picked from commit 0cee954f84)
2024-04-04 08:55:02 +00:00
Deepesh Garg
7131e7836d Merge pull request #40808 from frappe/mergify/bp/version-15-hotfix/pr-40755
fix: Receive payment entry from Employee (#40755)
2024-04-04 14:24:13 +05:30
rohitwaghchaure
806a802daa Merge pull request #40853 from frappe/mergify/bp/version-15-hotfix/pr-40852
fix: workstation refresh after prompt (backport #40852)
2024-04-04 12:07:47 +05:30
Nihantra C. Patel
9f12c964dc fix: workstation refresh after prompt
(cherry picked from commit bed1af26e3)
2024-04-04 06:21:36 +00:00
Frappe PR Bot
f78df8dccb chore(release): Bumped to Version 15.19.0
# [15.19.0](https://github.com/frappe/erpnext/compare/v15.18.3...v15.19.0) (2024-04-03)

### Bug Fixes

* added validation if parent id is missing in BOM Creator ([87ce9d8](87ce9d834f))
* barcode not fetched on selection of item ([2b1b51b](2b1b51b5bb))
* button to select serial / batch bundle in the subcontracting receipt ([d2a6509](d2a6509950))
* cost center shouldn't update debit/credit in Exc gain/loss JV ([71704be](71704beb6f))
* do not fetch received items in purchase receipt ([3761e74](3761e74436))
* enable list view for the issues on the portal ([b089eda](b089eda8bf))
* markdown to text editor set for supplier quotation ([5fb6428](5fb642831c))
* payment_order_status should be no_copy ([2dd697e](2dd697ee77))
* Priority not copied from project template ([a3a6bf6](a3a6bf6ae1))
* rate changed on changing of the qty ([41b3a59](41b3a59aae))
* sales / prchase return validation issue ([7a21997](7a21997701))
* show future payments allocated sales returns is considered as payment ([0561d0a](0561d0a81c))
* Show Stock Ageing Data filter not working in stock balance report ([2185a29](2185a29555))
* Warehouse linked company name in multicompany setup (backport [#40779](https://github.com/frappe/erpnext/issues/40779)) ([#40781](https://github.com/frappe/erpnext/issues/40781)) ([33b6244](33b6244ae6))

### Features

* allow to pick manually qty / batches / serial nos ([5ea11af](5ea11af36b))
2024-04-03 10:32:58 +00:00
Deepesh Garg
955078abb0 Merge pull request #40820 from frappe/version-15-hotfix
chore: release v15
2024-04-03 16:01:44 +05:30
rohitwaghchaure
405719cd3d Merge pull request #40838 from frappe/mergify/bp/version-15-hotfix/pr-40723
feat: allow to pick manually qty / batches / serial nos (backport #40723)
2024-04-02 22:55:34 +05:30
rohitwaghchaure
52c4157723 chore: fix conflicts 2024-04-02 22:23:22 +05:30
Rohit Waghchaure
5ea11af36b feat: allow to pick manually qty / batches / serial nos
(cherry picked from commit 50dd9fa8a3)

# Conflicts:
#	erpnext/stock/doctype/pick_list/pick_list.json
2024-04-02 16:45:23 +00:00
Raffael Meyer
e8497b2d80 fix: translatable web footer (#40834)
(cherry picked from commit f3bcdbe5bd)
2024-04-02 15:16:15 +00:00
rohitwaghchaure
d3044e635e Merge pull request #40832 from frappe/mergify/bp/version-15-hotfix/pr-40828
chore: remove rate of material based on Manual (backport #40828)
2024-04-02 19:41:04 +05:30
rohitwaghchaure
ca6caa86bc chore: fix conflicts 2024-04-02 19:18:02 +05:30
rohitwaghchaure
152921b82a chore: fix conflicts 2024-04-02 19:17:41 +05:30
Rohit Waghchaure
ecc317d690 chore: remove rate of material based on Manual
(cherry picked from commit 0b63dbf221)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/bom.json
#	erpnext/manufacturing/doctype/bom_creator/bom_creator.json
2024-04-02 12:42:22 +00:00
Nabin Hait
8016b0aee7 fix: linter issues
(cherry picked from commit 21a40a2236)
2024-04-02 10:27:55 +00:00
Nabin Hait
79043894b2 fix: show currency symbol in base currency in fixed asset register report
(cherry picked from commit 8b6220efd8)
2024-04-02 10:27:55 +00:00
rohitwaghchaure
6607e6b2c3 Merge pull request #40822 from frappe/mergify/bp/version-15-hotfix/pr-40816
fix: added validation if parent id is missing in BOM Creator (backport #40816)
2024-04-02 15:37:29 +05:30
Rohit Waghchaure
87ce9d834f fix: added validation if parent id is missing in BOM Creator
(cherry picked from commit 0a1fab762f)
2024-04-02 09:43:37 +00:00
rohitwaghchaure
204aea856d Merge pull request #40817 from frappe/mergify/bp/version-15-hotfix/pr-40814
fix: barcode not fetched on selection of item (backport #40814)
2024-04-02 14:22:58 +05:30
Rohit Waghchaure
2b1b51b5bb fix: barcode not fetched on selection of item
(cherry picked from commit b0730293e2)
2024-04-02 05:52:11 +00:00
Deepesh Garg
d8c5f15bc0 fix: Default company bank account
(cherry picked from commit 7ac91deb83)
2024-04-01 15:09:57 +00:00
Deepesh Garg
5117494a52 fix: Recalculate sales commission on item update
(cherry picked from commit f76da50b7e)
2024-04-01 15:09:05 +00:00
Deepesh Garg
0cee954f84 fix: Receive payment entry from Employee
(cherry picked from commit 29e5c6807e)
2024-04-01 15:08:35 +00:00
rohitwaghchaure
3971ff5f72 Merge pull request #40806 from frappe/mergify/bp/version-15-hotfix/pr-40735
fix: Show Stock Ageing Data filter not working in stock balance report (backport #40735)
2024-04-01 19:47:20 +05:30
rohitwaghchaure
f32de045e3 chore: fix conflicts 2024-04-01 18:53:04 +05:30
Rohit Waghchaure
2185a29555 fix: Show Stock Ageing Data filter not working in stock balance report
(cherry picked from commit 621421bda2)

# Conflicts:
#	erpnext/stock/report/stock_ageing/stock_ageing.py
2024-04-01 11:25:46 +00:00
ruthra kumar
d12f89cdb2 Merge pull request #40804 from frappe/mergify/bp/version-15-hotfix/pr-40181
fix: show future payments consider allocated sales returns (backport #40181)
2024-04-01 14:13:50 +05:30
Devin Slauenwhite
0561d0a81c fix: show future payments allocated sales returns is considered as payment
(cherry picked from commit 3381d0d945)
2024-04-01 07:34:45 +00:00
Devin Slauenwhite
ed4c6ef1a7 test: show future payments allocated sales returns is considered as payment
(cherry picked from commit daaa00bd4a)
2024-04-01 07:34:44 +00:00
rohitwaghchaure
ec91148672 Merge pull request #40738 from frappe/mergify/bp/version-15-hotfix/pr-40731
fix: markdown to text editor set for supplier quotation (backport #40731)
2024-03-31 21:14:15 +05:30
rohitwaghchaure
458d82aff2 Merge pull request #40784 from frappe/mergify/bp/version-15-hotfix/pr-40783
fix: button to select serial / batch bundle in the subcontracting receipt (backport #40783)
2024-03-31 21:13:45 +05:30
rohitwaghchaure
390b4986f9 chore: fix conflicts 2024-03-31 11:41:00 +05:30
rohitwaghchaure
6ecafe7606 chore: fix linter issue 2024-03-31 11:39:55 +05:30
rohitwaghchaure
04da610288 chore: fix conflicts 2024-03-30 20:05:21 +05:30
rohitwaghchaure
b397535392 chore: fix conflicts 2024-03-30 20:04:53 +05:30
Rohit Waghchaure
d2a6509950 fix: button to select serial / batch bundle in the subcontracting receipt
(cherry picked from commit d73b1f3e6a)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
#	erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
2024-03-30 05:54:14 +00:00
mergify[bot]
33b6244ae6 fix: Warehouse linked company name in multicompany setup (backport #40779) (#40781)
fix: Warehouse linked company name in multicompany setup (#40779)

(cherry picked from commit 679aea607b)

Co-authored-by: jeshani <56191568+jeshani@users.noreply.github.com>
2024-03-30 10:18:33 +05:30
Frappe PR Bot
8ab26cd76d chore(release): Bumped to Version 15.18.3
## [15.18.3](https://github.com/frappe/erpnext/compare/v15.18.2...v15.18.3) (2024-03-29)

### Bug Fixes

* sales / prchase return validation issue ([2eac2ef](2eac2ef69f))
2024-03-29 19:09:53 +00:00
rohitwaghchaure
3a380245c6 Merge pull request #40765 from frappe/mergify/bp/version-15/pr-40761
chore: show item name in the validation message (backport #40757) (backport #40761)
2024-03-30 00:38:40 +05:30
rohitwaghchaure
8cde784383 Merge pull request #40759 from frappe/mergify/bp/version-15/pr-40746
fix: sales / purchase return validation issue (backport #40741) (backport #40746)
2024-03-30 00:38:32 +05:30
ruthra kumar
8ef0a66d3e Merge pull request #40767 from frappe/mergify/bp/version-15-hotfix/pr-40758
fix: payment_order_status should be no_copy (backport #40758)
2024-03-29 13:49:16 +05:30
Ashish Shah
2dd697ee77 fix: payment_order_status should be no_copy
payment_order_status should be no_copy

(cherry picked from commit adee2ba541)
2024-03-29 07:57:04 +00:00
Rohit Waghchaure
a8ac196cde chore: show item name in the validation message
(cherry picked from commit d05412f96a)
(cherry picked from commit 0b4b0874f4)
2024-03-29 07:56:05 +00:00
rohitwaghchaure
bd7467a812 Merge pull request #40761 from frappe/mergify/bp/version-15-hotfix/pr-40757
chore: show item name in the validation message (backport #40757)
2024-03-29 13:24:18 +05:30
ruthra kumar
babcc420cc Merge pull request #40763 from frappe/mergify/bp/version-15-hotfix/pr-40760
fix: cost center shouldn't update debit/credit in Exc gain/loss JV (backport #40760)
2024-03-29 11:46:56 +05:30
ruthra kumar
71704beb6f fix: cost center shouldn't update debit/credit in Exc gain/loss JV
(cherry picked from commit 398d3022ef)
2024-03-29 06:13:51 +00:00
Rohit Waghchaure
0b4b0874f4 chore: show item name in the validation message
(cherry picked from commit d05412f96a)
2024-03-29 06:06:54 +00:00
Frappe PR Bot
6077cd8160 chore(release): Bumped to Version 15.18.2
## [15.18.2](https://github.com/frappe/erpnext/compare/v15.18.1...v15.18.2) (2024-03-29)

### Bug Fixes

* use sql to clear comments ([e2196ef](e2196efe1a))
2024-03-29 05:41:25 +00:00
ruthra kumar
aa4998a3b5 Merge pull request #40748 from frappe/mergify/bp/version-15/pr-40739
refactor: use sql to clear comments (backport #40739)
2024-03-29 11:10:07 +05:30
Rohit Waghchaure
2eac2ef69f fix: sales / prchase return validation issue
(cherry picked from commit 59dc4a96e1)
(cherry picked from commit 7a21997701)
2024-03-29 05:28:31 +00:00
ruthra kumar
e2196efe1a fix: use sql to clear comments
(cherry picked from commit 1f46c1530e)
2024-03-29 10:49:33 +05:30
rohitwaghchaure
0eb908e950 Merge pull request #40756 from frappe/mergify/bp/version-15-hotfix/pr-40752
chore: allow to override serial batch selector using custom app (backport #40752)
2024-03-29 09:15:37 +05:30
Rohit Waghchaure
8ca4f63572 chore: allow to override serial batch selector
(cherry picked from commit 0d9d42ae61)
2024-03-29 03:41:49 +00:00
Deepesh Garg
3c3b08f517 Merge pull request #40750 from frappe/mergify/bp/version-15/pr-40742
refactor: configurable exchange gain or loss posting date (#37630)
2024-03-28 22:12:29 +05:30
ruthra kumar
0247d5c87f chore: resolve conflicts
(cherry picked from commit 565bfa16f2)
2024-03-28 15:26:55 +00:00
ruthra kumar
ba4d1bc1a4 test: varying posting date for gain loss journal
(cherry picked from commit 514d5434a3)
(cherry picked from commit fd33437d7a)
2024-03-28 15:26:54 +00:00
ruthra kumar
22d352e875 refactor: pass gain loss posting date to controller
(cherry picked from commit 7e600a6494)

# Conflicts:
#	erpnext/accounts/utils.py
(cherry picked from commit 125722ae41)
2024-03-28 15:26:54 +00:00
ruthra kumar
63c3f51ffa refactor: introduce fields in popup
(cherry picked from commit 5323bb7bee)

# Conflicts:
#	erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
(cherry picked from commit 39571d828e)
2024-03-28 15:26:54 +00:00
ruthra kumar
efd40772ee refactor: gain_loss posting date fields in the allocation table
(cherry picked from commit 55dbcee36a)

# Conflicts:
#	erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
(cherry picked from commit 1fe80c2d02)
2024-03-28 15:26:53 +00:00
rohitwaghchaure
fb67648c68 Merge pull request #40746 from frappe/mergify/bp/version-15-hotfix/pr-40741
fix: sales / purchase return validation issue (backport #40741)
2024-03-28 18:10:42 +05:30
ruthra kumar
3d61086ac0 Merge pull request #40745 from frappe/mergify/bp/version-15-hotfix/pr-40739
refactor: use sql to clear comments (backport #40739)
2024-03-28 17:57:14 +05:30
Rohit Waghchaure
7a21997701 fix: sales / prchase return validation issue
(cherry picked from commit 59dc4a96e1)
2024-03-28 12:24:15 +00:00
Deepesh Garg
f2ecc1d2d5 Merge pull request #40742 from frappe/mergify/bp/version-15-hotfix/pr-37630
refactor: configurable exchange gain or loss posting date (#37630)
2024-03-28 17:46:30 +05:30
ruthra kumar
f50a2586b6 refactor: use sql to clear comments
(cherry picked from commit 1f46c1530e)
2024-03-28 12:12:17 +00:00
ruthra kumar
565bfa16f2 chore: resolve conflicts 2024-03-28 17:22:53 +05:30
ruthra kumar
fd33437d7a test: varying posting date for gain loss journal
(cherry picked from commit 514d5434a3)
2024-03-28 10:59:24 +00:00
ruthra kumar
125722ae41 refactor: pass gain loss posting date to controller
(cherry picked from commit 7e600a6494)

# Conflicts:
#	erpnext/accounts/utils.py
2024-03-28 10:59:24 +00:00
ruthra kumar
39571d828e refactor: introduce fields in popup
(cherry picked from commit 5323bb7bee)

# Conflicts:
#	erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
2024-03-28 10:59:23 +00:00
ruthra kumar
1fe80c2d02 refactor: gain_loss posting date fields in the allocation table
(cherry picked from commit 55dbcee36a)

# Conflicts:
#	erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
2024-03-28 10:59:22 +00:00
Nihantra Patel
5fb642831c fix: markdown to text editor set for supplier quotation
(cherry picked from commit 9828d34b19)

# Conflicts:
#	erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
2024-03-28 07:16:15 +00:00
rohitwaghchaure
beb8807b2e Merge pull request #40733 from frappe/mergify/bp/version-15-hotfix/pr-40713
fix: enable list view for the issues on the portal (backport #40713)
2024-03-28 12:11:21 +05:30
Rohit Waghchaure
b089eda8bf fix: enable list view for the issues on the portal
(cherry picked from commit bddc1e3974)
2024-03-28 05:38:12 +00:00
rohitwaghchaure
5f125c5e2f Merge pull request #40722 from frappe/mergify/bp/version-15-hotfix/pr-40718
fix: do not fetch received items in purchase receipt (backport #40718)
2024-03-27 22:59:38 +05:30
Rohit Waghchaure
3761e74436 fix: do not fetch received items in purchase receipt
(cherry picked from commit 5bff434902)
2024-03-27 17:11:25 +00:00
rohitwaghchaure
b7525263ff Merge pull request #40711 from frappe/mergify/bp/version-15-hotfix/pr-40708
fix: Priority not copied from project template (backport #40708)
2024-03-27 16:18:26 +05:30
Rohit Waghchaure
a3a6bf6ae1 fix: Priority not copied from project template
(cherry picked from commit 33fd7b8a1f)
2024-03-27 10:18:20 +00:00
Frappe PR Bot
ca3827efea chore(release): Bumped to Version 15.18.1
## [15.18.1](https://github.com/frappe/erpnext/compare/v15.18.0...v15.18.1) (2024-03-27)

### Bug Fixes

* rate changed on changing of the qty ([471f1c8](471f1c835c))
2024-03-27 08:37:31 +00:00
rohitwaghchaure
d2584dd3e8 Merge pull request #40704 from frappe/mergify/bp/version-15/pr-40703
fix: rate changed on changing of the qty (backport #40700) (backport #40703)
2024-03-27 14:06:20 +05:30
Rohit Waghchaure
471f1c835c fix: rate changed on changing of the qty
(cherry picked from commit cd36a1051f)
(cherry picked from commit 41b3a59aae)
2024-03-27 08:24:00 +00:00
rohitwaghchaure
ac4b7f20ca Merge pull request #40703 from frappe/mergify/bp/version-15-hotfix/pr-40700
fix: rate changed on changing of the qty (backport #40700)
2024-03-27 13:52:54 +05:30
Rohit Waghchaure
41b3a59aae fix: rate changed on changing of the qty
(cherry picked from commit cd36a1051f)
2024-03-27 08:07:18 +00:00
Deepesh Garg
c1b9ad2f3a chore: Reduce usage of cur_frm
(cherry picked from commit e44809bcc9)
2024-03-27 06:16:31 +00:00
Frappe PR Bot
70ce49e32c chore(release): Bumped to Version 15.18.0
# [15.18.0](https://github.com/frappe/erpnext/compare/v15.17.6...v15.18.0) (2024-03-27)

### Bug Fixes

* Add default finance book check in P&L statement ([f087159](f08715972a))
* Batch No is mandatory while making manufacture entry ([a638c9c](a638c9c8af))
* Book depreciation until the asset disposal date and removed unwanted commits ([2d7ff5d](2d7ff5d4cf))
* Cannot read properties of undefined (reading 'rate') ([d287ed5](d287ed5e47))
* existing batch from different warehouse not showing in the dropdown ([79d0478](79d047802a))
* incorrect total qty in job card ([68d57c3](68d57c3947))
* item not coming in quick entry ([008894d](008894d32b))
* permissions during bulk transaction logs ([6e6ca42](6e6ca42261))
* pick list not picked qty less than 1 ([5310ec3](5310ec38ca))
* rate not fetching from the item price ([fd02060](fd02060cd2))
* rate reset to zero ([4636ef0](4636ef0a29))
* remove faulty translation ([#40667](https://github.com/frappe/erpnext/issues/40667)) ([afa930f](afa930ff48))
* style for tax breakup ([501b09a](501b09a170))
* translatability of bom configurator labels ([cf49cec](cf49cec203))
* Turkish translations ([#40650](https://github.com/frappe/erpnext/issues/40650)) ([17a0876](17a087663b))
* use serial/batch fields for subcontracting receipt and in transit stock entry ([9ec24bc](9ec24bcc6c))
* use Text Editor for rendering tax breakup table ([a95bb27](a95bb27c65))
* validate gl for previous fiscal year ([4b973b2](4b973b2f86))
* validation error for the subcontracting receipt ([23ae82d](23ae82d282))
* wrong buying amount if delivered and billed qty varies ([c224240](c2242406f4))

### Features

* bisect doctype ([884a18f](884a18f8f1))
* nodes doctype ([bfccfa4](bfccfa4af1))

### Performance Improvements

* add in some indexes (backport [#40590](https://github.com/frappe/erpnext/issues/40590)) ([#40605](https://github.com/frappe/erpnext/issues/40605)) ([f66cf7d](f66cf7d838))
2024-03-27 03:27:37 +00:00
Deepesh Garg
d09821e1e1 Merge pull request #40678 from frappe/version-15-hotfix
chore: release v15
2024-03-27 08:56:26 +05:30
rohitwaghchaure
b6ac3e7d08 Merge pull request #40689 from frappe/mergify/bp/version-15-hotfix/pr-40685
fix: validation error for the subcontracting receipt (backport #40685)
2024-03-27 08:04:58 +05:30
Raffael Meyer
afa930ff48 fix: remove faulty translation (#40667) 2024-03-27 00:21:16 +01:00
Rohit Waghchaure
23ae82d282 fix: validation error for the subcontracting receipt
(cherry picked from commit 016585c248)
2024-03-26 14:27:36 +00:00
rohitwaghchaure
ad8961030e Merge pull request #40688 from frappe/mergify/bp/version-15-hotfix/pr-40684
fix: incorrect total qty in job card (backport #40684)
2024-03-26 19:54:46 +05:30
Rohit Waghchaure
68d57c3947 fix: incorrect total qty in job card
(cherry picked from commit c3546cf8e2)
2024-03-26 14:04:03 +00:00
rohitwaghchaure
9b9892da03 Merge pull request #40680 from frappe/mergify/bp/version-15-hotfix/pr-40674
fix: existing batch from different warehouse not showing in the dropdown (backport #40674)
2024-03-26 16:33:14 +05:30
Rohit Waghchaure
79d047802a fix: existing batch from different warehouse not showing in the dropdown
(cherry picked from commit 662cf21afb)
2024-03-26 10:35:51 +00:00
rohitwaghchaure
108e80419e Merge pull request #40673 from frappe/mergify/bp/version-15-hotfix/pr-40672
fix: item not coming in quick entry (backport #40672)
2024-03-26 14:33:46 +05:30
Rohit Waghchaure
008894d32b fix: item not coming in quick entry
(cherry picked from commit dcbb54c41f)
2024-03-26 06:13:17 +00:00
Deepesh Garg
634054c07b Merge pull request #40430 from frappe/mergify/bp/version-15-hotfix/pr-40385
fix: Book depreciation until the asset disposal date and remove unwanted commits (#40385)
2024-03-26 09:05:20 +05:30
SelenSoft
17a087663b fix: Turkish translations (#40650)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2024-03-25 23:27:51 +01:00
Deepesh Garg
b04cef9f3f Merge pull request #40653 from frappe/mergify/bp/version-15-hotfix/pr-40640
fix: Add default finance book check in P&L statement (backport #40640)
2024-03-25 17:08:40 +05:30
Deepesh Garg
f08715972a fix: Add default finance book check in P&L statement
(cherry picked from commit 5be3417fdf)
2024-03-25 09:14:45 +00:00
Frappe PR Bot
22f3935024 chore(release): Bumped to Version 15.17.6
## [15.17.6](https://github.com/frappe/erpnext/compare/v15.17.5...v15.17.6) (2024-03-25)

### Bug Fixes

* reset update outstanding flag for old records ([1753a13](1753a138e9))
2024-03-25 02:55:11 +00:00
ruthra kumar
0b0b3f0d9d Merge pull request #40645 from frappe/mergify/bp/version-15/pr-40622
fix: reset update_outstanding_for_self flag for old records (backport #40622)
2024-03-25 08:24:03 +05:30
ruthra kumar
0658285a3b refactor: hide on print formats
(cherry picked from commit fdcdc8a56e)
2024-03-25 07:44:37 +05:30
ruthra kumar
1753a138e9 fix: reset update outstanding flag for old records
(cherry picked from commit a88bf8419e)
2024-03-25 07:44:16 +05:30
rohitwaghchaure
4e4e6c669d Merge pull request #40639 from frappe/mergify/bp/version-15-hotfix/pr-40638
fix: Batch No is mandatory while making manufacture entry (backport #40638)
2024-03-24 21:19:51 +05:30
Rohit Waghchaure
a638c9c8af fix: Batch No is mandatory while making manufacture entry
(cherry picked from commit 43fd60a877)
2024-03-24 13:02:10 +00:00
rohitwaghchaure
a95295b9f9 Merge pull request #40637 from frappe/mergify/bp/version-15-hotfix/pr-40594
fix: use serial/batch fields for subcontracting receipt and in transit in stock entry (backport #40594)
2024-03-24 17:45:50 +05:30
ruthra kumar
e33c383345 Merge pull request #40624 from frappe/mergify/bp/version-15-hotfix/pr-40622
refactor: reset update_outstanding_for_self flag for old records (backport #40622)
2024-03-24 16:39:03 +05:30
ruthra kumar
996f7d30b9 chore: resolve conflict 2024-03-24 16:24:10 +05:30
Rohit Waghchaure
9ec24bcc6c fix: use serial/batch fields for subcontracting receipt and in transit stock entry
(cherry picked from commit 3423334e4f)
2024-03-24 10:34:52 +00:00
Danny
7a9291b3d1 fix: pass empty string email content of pos invoice
(cherry picked from commit ee7bd98878)
2024-03-23 09:10:55 +00:00
Deepesh Garg
bfa316ad09 Merge pull request #40591 from frappe/mergify/bp/version-15-hotfix/pr-40587
refactor(Item Price): validate dates (#40587)
2024-03-23 08:58:47 +05:30
ruthra kumar
d238cdbdd5 refactor: hide on print formats
(cherry picked from commit fdcdc8a56e)
2024-03-22 14:54:02 +00:00
ruthra kumar
f25b5bdfab refactor: reset flag for old records
(cherry picked from commit a88bf8419e)

# Conflicts:
#	erpnext/patches.txt
2024-03-22 14:54:02 +00:00
Frappe PR Bot
c39d02fa14 chore(release): Bumped to Version 15.17.5
## [15.17.5](https://github.com/frappe/erpnext/compare/v15.17.4...v15.17.5) (2024-03-22)

### Bug Fixes

* rate reset to zero ([1ed9133](1ed9133bb6))
2024-03-22 10:30:44 +00:00
rohitwaghchaure
4a1c56113e Merge pull request #40619 from frappe/mergify/bp/version-15/pr-40617
fix: rate reset to zero (backport #40616) (backport #40617)
2024-03-22 15:59:36 +05:30
Rohit Waghchaure
1ed9133bb6 fix: rate reset to zero
(cherry picked from commit 6821baa850)
(cherry picked from commit 4636ef0a29)
2024-03-22 10:08:05 +00:00
rohitwaghchaure
9f13b6ed2b Merge pull request #40617 from frappe/mergify/bp/version-15-hotfix/pr-40616
fix: rate reset to zero (backport #40616)
2024-03-22 15:36:51 +05:30
Rohit Waghchaure
4636ef0a29 fix: rate reset to zero
(cherry picked from commit 6821baa850)
2024-03-22 09:50:50 +00:00
ruthra kumar
fcbe2556d3 Merge pull request #40613 from frappe/mergify/bp/version-15-hotfix/pr-38496
feat: utility to debug financial reports - Bisect Accounting Statements (backport #38496)
2024-03-22 14:19:42 +05:30
ruthra kumar
345d5a657a chore: resolve JS linter issue 2024-03-22 14:03:43 +05:30
ruthra kumar
e7b3ef0c90 chore: resolve linter issues
(cherry picked from commit 0890b414b1)
2024-03-22 07:53:45 +00:00
ruthra kumar
b371369699 refactor: cache results
(cherry picked from commit 14c8c8c33d)
2024-03-22 07:53:45 +00:00
ruthra kumar
067f101c99 refactor: flag to differentiate generated and default values
(cherry picked from commit 0925706d5e)
2024-03-22 07:53:44 +00:00
ruthra kumar
0305aafff8 refactor: save results in node
(cherry picked from commit ca14ae8f1b)
2024-03-22 07:53:44 +00:00
ruthra kumar
a59dc8b40c chore: restrict only to administrator and type info
(cherry picked from commit 90c6d4dc85)
2024-03-22 07:53:44 +00:00
ruthra kumar
6b1b2e863a chore: change data type for summary fields
(cherry picked from commit 228aa1a244)
2024-03-22 07:53:44 +00:00
ruthra kumar
415be361b4 refactor: adding labels to important section
(cherry picked from commit 993e2bfbf9)
2024-03-22 07:53:44 +00:00
ruthra kumar
509d6e3ca9 chore: UI cleanup
(cherry picked from commit ea3071db66)
2024-03-22 07:53:44 +00:00
ruthra kumar
6ff311c4e9 chore: add screen freeze on wait
(cherry picked from commit 395299803f)
2024-03-22 07:53:43 +00:00
ruthra kumar
348359ad5a chore: code cleanup
(cherry picked from commit 5e2d21c033)
2024-03-22 07:53:43 +00:00
ruthra kumar
1a08482655 chore: hide internal variables section
(cherry picked from commit c4c3090f46)
2024-03-22 07:53:43 +00:00
ruthra kumar
b686e1f972 chore: rename btree and remove debugging statements
(cherry picked from commit 6492019383)
2024-03-22 07:53:42 +00:00
ruthra kumar
5674ea450d chore: hide internal fields and better painting logic for heatmap
(cherry picked from commit f6831fba13)
2024-03-22 07:53:42 +00:00
ruthra kumar
f376659785 refactor: working heatmap
(cherry picked from commit 16db6c2f47)
2024-03-22 07:53:42 +00:00
ruthra kumar
c678134de6 chore: hide some internal fields
(cherry picked from commit bd3dc6482e)
2024-03-22 07:53:41 +00:00
ruthra kumar
a25f6493de refactor: calculate summary on tree navigation
(cherry picked from commit f7b7b2b438)
2024-03-22 07:53:41 +00:00
ruthra kumar
59ce9ccc8e refactor: use DB to store tree and state
(cherry picked from commit 99fbd8ad18)
2024-03-22 07:53:41 +00:00
ruthra kumar
a14eceb41a chore: use doctype as btree
(cherry picked from commit 9d20256366)
2024-03-22 07:53:41 +00:00
ruthra kumar
bfccfa4af1 feat: nodes doctype
(cherry picked from commit 85f2a6dd54)
2024-03-22 07:53:41 +00:00
ruthra kumar
0bce5d6057 chore: remove unwanted code
(cherry picked from commit de2eba0d98)
2024-03-22 07:53:40 +00:00
ruthra kumar
fd566f134d refactor: add basic navigation
1. remove unnecessary columns
2. added basic tree navigation

(cherry picked from commit 705ef4f5a3)
2024-03-22 07:53:40 +00:00
ruthra kumar
40176a6b41 refactor: ability to build and load tree from DB
(cherry picked from commit b2dde55f2c)
2024-03-22 07:53:40 +00:00
ruthra kumar
9f859c2bd3 refactor: introduce node class
(cherry picked from commit d53b34c0ce)
2024-03-22 07:53:40 +00:00
ruthra kumar
62987372ba refactor: more buttons
(cherry picked from commit a427029151)
2024-03-22 07:53:40 +00:00
ruthra kumar
80b77ffc87 refactor: date validation
(cherry picked from commit 2de3e6ce6d)
2024-03-22 07:53:39 +00:00
ruthra kumar
ed64d691fc refactor: support for BFS and DFS
(cherry picked from commit 03a38ed025)
2024-03-22 07:53:39 +00:00
ruthra kumar
1f235a7ba3 refactor: simplify DFS
(cherry picked from commit 26503a205f)
2024-03-22 07:53:39 +00:00
ruthra kumar
ce039f279b refactor: Depth First Search(DFS)
(cherry picked from commit 5a25c80f2e)
2024-03-22 07:53:39 +00:00
ruthra kumar
b6c2dbe581 refactor: some logic
(cherry picked from commit 4c8a8c3bcd)
2024-03-22 07:53:39 +00:00
ruthra kumar
884a18f8f1 feat: bisect doctype
(cherry picked from commit decdbd2782)
2024-03-22 07:53:39 +00:00
ruthra kumar
0393855243 refactor: primitive summary for p&l and balance sheet
(cherry picked from commit eb4c476490)
2024-03-22 07:53:38 +00:00
ruthra kumar
0e0b8e5ab4 Merge pull request #40608 from frappe/mergify/bp/version-15-hotfix/pr-40520
fix: permissions during bulk transaction logs (backport #40520)
2024-03-22 10:33:28 +05:30
David
6e6ca42261 fix: permissions during bulk transaction logs
(cherry picked from commit 49dd4c1ef3)
2024-03-22 04:38:45 +00:00
ruthra kumar
7344296a39 Merge pull request #40597 from frappe/mergify/bp/version-15-hotfix/pr-40386
fix: wrong buying amount if delivered and billed qty varies (backport #40386)
2024-03-22 09:04:37 +05:30
mergify[bot]
f66cf7d838 perf: add in some indexes (backport #40590) (#40605)
perf: add in some indexes (#40590)

`Sales Invoice Item.purchase_order`
`Delivery Note Item.purchase_order`

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
(cherry picked from commit 1cd38c860a)

Co-authored-by: Akhil Narang <me@akhilnarang.dev>
2024-03-22 08:51:57 +05:30
Frappe PR Bot
2b80cb99be chore(release): Bumped to Version 15.17.4
## [15.17.4](https://github.com/frappe/erpnext/compare/v15.17.3...v15.17.4) (2024-03-21)

### Bug Fixes

* rate not fetching from the item price ([0d95ace](0d95acefa2))
2024-03-21 17:30:02 +00:00
rohitwaghchaure
57e0b4ea5f Merge pull request #40602 from frappe/mergify/bp/version-15/pr-40600
fix: rate not fetching from the item price (backport #40595) (backport #40600)
2024-03-21 22:58:48 +05:30
Rohit Waghchaure
0d95acefa2 fix: rate not fetching from the item price
(cherry picked from commit d893a465d7)
(cherry picked from commit fd02060cd2)
2024-03-21 17:13:04 +00:00
rohitwaghchaure
5b155e37aa Merge pull request #40600 from frappe/mergify/bp/version-15-hotfix/pr-40595
fix: rate not fetching from the item price (backport #40595)
2024-03-21 22:40:47 +05:30
Rohit Waghchaure
fd02060cd2 fix: rate not fetching from the item price
(cherry picked from commit d893a465d7)
2024-03-21 16:50:13 +00:00
ruthra kumar
f5008a1467 test: buying amt calculation if DN and SI differ in qty
(cherry picked from commit ccb51ded95)
2024-03-21 15:46:01 +00:00
Dany Robert
c2242406f4 fix: wrong buying amount if delivered and billed qty varies
(cherry picked from commit b8da0d9334)
2024-03-21 15:46:01 +00:00
barredterra
df6226f07f chore: resolve conflicts 2024-03-21 14:16:27 +01:00
barredterra
357a1c9752 refactor(Item Price): validate dates
(cherry picked from commit cbcc47b5c4)

# Conflicts:
#	erpnext/stock/doctype/item_price/item_price.py
2024-03-21 13:13:27 +00:00
ruthra kumar
9e5e244e7a Merge pull request #40582 from frappe/mergify/bp/version-15-hotfix/pr-40580
refactor: config changes in Transaction Deletion Record (backport #40580)
2024-03-21 11:02:39 +05:30
Gursheen Kaur Anand
3dcd7380ac Merge pull request #40576 from frappe/mergify/bp/version-15-hotfix/pr-40572
fix: PCV validation for previous fiscal year (backport #40572)
2024-03-21 10:57:26 +05:30
ruthra kumar
b7dc28fff9 refactor: config changes in Transaction Deletion Record
(cherry picked from commit 4ba67fb3ec)
2024-03-21 05:17:25 +00:00
rohitwaghchaure
fa95bd331a Merge pull request #40564 from frappe/mergify/bp/version-15-hotfix/pr-40545
fix: translatability of bom configurator labels (backport #40545)
2024-03-21 01:58:16 +05:30
Gursheen Anand
4b973b2f86 fix: validate gl for previous fiscal year
(cherry picked from commit a1d108c062)
2024-03-20 15:30:03 +00:00
ruthra kumar
5194b87dc4 Merge pull request #40575 from frappe/mergify/bp/version-15-hotfix/pr-39717
refactor: Transaction Deletion record for large volumes (backport #39717)
2024-03-20 20:40:27 +05:30
Frappe PR Bot
85aeb1f3b1 chore(release): Bumped to Version 15.17.3
## [15.17.3](https://github.com/frappe/erpnext/compare/v15.17.2...v15.17.3) (2024-03-20)

### Bug Fixes

* style for tax breakup ([07bb836](07bb8360d5))
* use Text Editor for rendering tax breakup table ([64672e6](64672e655a))
2024-03-20 14:58:46 +00:00
Gursheen Kaur Anand
03a40f491b Merge pull request #40568 from frappe/mergify/bp/version-15/pr-40563
fix: use `Text Editor` for rendering tax breakup table (backport #40563)
2024-03-20 20:27:31 +05:30
Gursheen Kaur Anand
36c6820096 Merge pull request #40570 from frappe/mergify/bp/version-15-hotfix/pr-40563
fix: use `Text Editor` for rendering tax breakup table (backport #40563)
2024-03-20 20:27:03 +05:30
Gursheen Kaur Anand
9c204b7106 chore: resolve conflicts 2024-03-20 20:04:44 +05:30
Gursheen Kaur Anand
8e5d481cba chore: resolve conflicts 2024-03-20 20:03:03 +05:30
Gursheen Kaur Anand
37eae8eb4e chore: resolve conflicts 2024-03-20 19:58:01 +05:30
Gursheen Kaur Anand
4ede3a9ba5 chore: resolve conflicts 2024-03-20 19:57:24 +05:30
ruthra kumar
e7d2451f60 chore: fix linting issue in JS
(cherry picked from commit 02c522b7cd)
2024-03-20 14:13:21 +00:00
ruthra kumar
5bbccf1fbd refactor(test): test cases modified to handle new approach
(cherry picked from commit 81309576b0)
2024-03-20 14:13:21 +00:00
ruthra kumar
40574f0d72 refactor: ability to process in single transaction
(cherry picked from commit a158b825d7)
2024-03-20 14:13:20 +00:00
ruthra kumar
1104deb58e refactor: link running doc validation to company master
(cherry picked from commit 5a3afea8c7)
2024-03-20 14:13:20 +00:00
ruthra kumar
29804f6a41 chore: rename entry point
(cherry picked from commit 5fe0b20be1)
2024-03-20 14:13:20 +00:00
ruthra kumar
ae6260f717 chore: move status and error log to their own section
(cherry picked from commit 3cec62d4f8)
2024-03-20 14:13:19 +00:00
ruthra kumar
aceee829d2 refactor: minor UI tweaks
(cherry picked from commit 0455d0c46c)
2024-03-20 14:13:19 +00:00
ruthra kumar
02c3303fee refactor: exception propogation
(cherry picked from commit 4a55240e63)
2024-03-20 14:13:18 +00:00
ruthra kumar
3f45f63ff8 chore: code cleanup
(cherry picked from commit eea260b9f9)
2024-03-20 14:13:17 +00:00
ruthra kumar
3de53999ce refactor: better method naming
(cherry picked from commit 30463657bf)
2024-03-20 14:13:17 +00:00
ruthra kumar
b31e43e3db refactor: barebones hook on all doctypes with 'company' field
(cherry picked from commit ec194ef076)
2024-03-20 14:13:16 +00:00
ruthra kumar
d4c2e6c77d refactor: make sure only one task is running for doc
(cherry picked from commit 78c9cc63b1)
2024-03-20 14:13:16 +00:00
ruthra kumar
049c54bb32 chore: hide docfield in list view
(cherry picked from commit 98afb4d468)
2024-03-20 14:13:15 +00:00
ruthra kumar
d35bef12b6 refactor: validations to prevent duplicate jobs
(cherry picked from commit 31a2da552b)
2024-03-20 14:13:15 +00:00
ruthra kumar
90ad3d3176 refactor: no copy on summary table and more validations
(cherry picked from commit 55e93b3fe1)
2024-03-20 14:13:15 +00:00
ruthra kumar
db9cc5350d refactor: reset all flags and remove unwanted code
(cherry picked from commit 2dbe68a09d)
2024-03-20 14:13:15 +00:00
ruthra kumar
f67c447402 chore: show correct status in list view
(cherry picked from commit 1014940953)
2024-03-20 14:13:14 +00:00
ruthra kumar
cbaee05740 refactor: validate status before running events
(cherry picked from commit 86b5e2e277)
2024-03-20 14:13:14 +00:00
ruthra kumar
c827be4345 refactor: make Excluded doctype table read only
(cherry picked from commit 7c4cff2649)
2024-03-20 14:13:14 +00:00
ruthra kumar
efebec3f95 chore: remove unwanted UI code
(cherry picked from commit b98a5e4edc)
2024-03-20 14:13:14 +00:00
ruthra kumar
c3dd68ecc3 refactor: use separate child table for summary
(cherry picked from commit 49d3bcbc8d)
2024-03-20 14:13:13 +00:00
ruthra kumar
2bb1bfc4a0 refactor: chained callback
(cherry picked from commit b12ca65fcc)
2024-03-20 14:13:13 +00:00
ruthra kumar
195bffdf44 refactor: reorder flags in Tasks section
(cherry picked from commit cccb2d5141)
2024-03-20 14:13:13 +00:00
ruthra kumar
17ed1bb12c refactor: use flags to decide on current stage
(cherry picked from commit 6a77d86a53)
2024-03-20 14:13:13 +00:00
ruthra kumar
f9321767a6 refactor: UI trigger
(cherry picked from commit 8944ab8b6a)
2024-03-20 14:13:13 +00:00
ruthra kumar
9c5108e492 refactor: tasks section and UI niceties
(cherry picked from commit d0dc2c6e77)
2024-03-20 14:13:12 +00:00
ruthra kumar
529abd619a refactor: set status and trigger job on submit
(cherry picked from commit 6fbb67b1d2)
2024-03-20 14:13:12 +00:00
ruthra kumar
03fc3cec70 refactor: more options for 'status' and move it to top
(cherry picked from commit 0d65d878de)
2024-03-20 14:13:11 +00:00
Ankush Menat
501b09a170 fix: style for tax breakup
(cherry picked from commit 967540da18)
2024-03-20 13:06:45 +00:00
Gursheen Anand
a95bb27c65 fix: use Text Editor for rendering tax breakup table
(cherry picked from commit 1c63983873)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.json
#	erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
2024-03-20 13:06:45 +00:00
Ankush Menat
07bb8360d5 fix: style for tax breakup
(cherry picked from commit 967540da18)
2024-03-20 13:06:38 +00:00
Gursheen Anand
64672e655a fix: use Text Editor for rendering tax breakup table
(cherry picked from commit 1c63983873)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.json
#	erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
2024-03-20 13:06:38 +00:00
barredterra
cf49cec203 fix: translatability of bom configurator labels
(cherry picked from commit 4cfe99d74f)
2024-03-20 10:50:39 +00:00
rohitwaghchaure
bc5654b900 Merge pull request #40562 from frappe/mergify/bp/version-15-hotfix/pr-40561
fix: Cannot read properties of undefined (reading 'rate') (backport #40561)
2024-03-20 16:17:08 +05:30
Rohit Waghchaure
d287ed5e47 fix: Cannot read properties of undefined (reading 'rate')
(cherry picked from commit ac92a22914)
2024-03-20 09:31:42 +00:00
rohitwaghchaure
f881d23c05 Merge pull request #40560 from frappe/mergify/bp/version-15-hotfix/pr-40559
fix: pick list not picked materials having required qty less than 1 (backport #40559)
2024-03-20 14:16:44 +05:30
Rohit Waghchaure
5310ec38ca fix: pick list not picked qty less than 1
(cherry picked from commit 1a9ae33110)
2024-03-20 08:23:08 +00:00
Frappe PR Bot
1937059870 chore(release): Bumped to Version 15.17.2
## [15.17.2](https://github.com/frappe/erpnext/compare/v15.17.1...v15.17.2) (2024-03-20)

### Bug Fixes

* achieved targets for sales partners ([1ac8887](1ac888715c))
* advance journal fetching logic in Advances section ([bfa78aa](bfa78aaf74))
* attribute error on Bank Statement Import retry ([a0bf22c](a0bf22c9a5))
* auto-update due date for invoices via data import ([9cdde50](9cdde50f7c))
* balance qty in the stock ledger report (backport [#40506](https://github.com/frappe/erpnext/issues/40506)) ([#40507](https://github.com/frappe/erpnext/issues/40507)) ([69f442d](69f442dd79))
* currency symbol for Landed Cost Voucher Amount (backport [#40550](https://github.com/frappe/erpnext/issues/40550)) ([#40554](https://github.com/frappe/erpnext/issues/40554)) ([1c90797](1c90797879))
* Data too long for column 'stock_queue' (backport [#40436](https://github.com/frappe/erpnext/issues/40436)) ([#40451](https://github.com/frappe/erpnext/issues/40451)) ([ba6b856](ba6b8561c9))
* don't validate for currency, if account is unavailable ([5729ee5](5729ee563c))
* flaky Accounts Receivable test case ([0bb4a7d](0bb4a7d13a))
* incorrect gross profit on the quotation (backport [#40438](https://github.com/frappe/erpnext/issues/40438)) ([#40442](https://github.com/frappe/erpnext/issues/40442)) ([ce2626e](ce2626e911))
* index error on Exchange Rate Revaluation creation ([ebd967d](ebd967d7d3))
* invalid exchange loss booking on invoice againts base accounts ([72c9cd5](72c9cd5582))
* missing range for ageing summary ([dacc69c](dacc69c56f))
* not able to delete cancelled delivery note (backport [#40508](https://github.com/frappe/erpnext/issues/40508)) ([#40510](https://github.com/frappe/erpnext/issues/40510)) ([917a21b](917a21b989))
* rate changing while making PR (negative discount) (backport [#40539](https://github.com/frappe/erpnext/issues/40539)) ([#40540](https://github.com/frappe/erpnext/issues/40540)) ([0bdda12](0bdda1226f))
* Remove custom queries for customer ([3133505](313350534f))
* serial and batch for internal transfer (backport [#40467](https://github.com/frappe/erpnext/issues/40467)) ([#40477](https://github.com/frappe/erpnext/issues/40477)) ([61d59b3](61d59b392a))
* show correct variance for durations with no vouchers ([bc78bc3](bc78bc33f1))
* stock reco negative batch (backport [#40533](https://github.com/frappe/erpnext/issues/40533)) ([#40535](https://github.com/frappe/erpnext/issues/40535)) ([7f1f780](7f1f7809c8))
* stopped mr showing in the popup (backport [#40478](https://github.com/frappe/erpnext/issues/40478)) ([#40480](https://github.com/frappe/erpnext/issues/40480)) ([74b30a6](74b30a6a1d))
* **Supplier Quotation Comparison:** group by options ([1e16d2c](1e16d2ca25))
* Taxes not getting updated on change ([75b610d](75b610d7fc))
* **test:** manually filter rows and assert ([df13517](df1351783e))
* Update Existing Price List Rate not working (backport [#40333](https://github.com/frappe/erpnext/issues/40333)) ([#40527](https://github.com/frappe/erpnext/issues/40527)) ([44bbf4b](44bbf4b983))
* use in_import flag for checking imported records ([8242a9c](8242a9cd46))
2024-03-20 05:23:16 +00:00
rohitwaghchaure
717c78dcda Merge pull request #40538 from frappe/version-15-hotfix
chore: release v15
2024-03-20 10:52:04 +05:30
mergify[bot]
1c90797879 fix: currency symbol for Landed Cost Voucher Amount (backport #40550) (#40554)
fix: currency symbol for Landed Cost Voucher Amount (#40550)

(cherry picked from commit 0725707cb1)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-19 20:42:57 +05:30
Raffael Meyer
ba9f9bff19 Merge pull request #40548 from frappe/mergify/bp/version-15-hotfix/pr-40546
fix(Supplier Quotation Comparison): group by options (backport #40546)
2024-03-19 13:07:25 +01:00
barredterra
1e16d2ca25 fix(Supplier Quotation Comparison): group by options
(cherry picked from commit 87e36d290e)
2024-03-19 11:51:36 +00:00
Raffael Meyer
745e653339 Merge pull request #40544 from frappe/mergify/bp/version-15-hotfix/pr-40542 2024-03-19 12:49:32 +01:00
barredterra
f0e00daa1c refactor: usage of in_list
(cherry picked from commit d238751e6b)
2024-03-19 11:13:02 +00:00
mergify[bot]
0bdda1226f fix: rate changing while making PR (negative discount) (backport #40539) (#40540)
fix: rate changing while making PR (negative discount) (#40539)

(cherry picked from commit 8136954484)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-19 15:37:37 +05:30
rohitwaghchaure
05436dfcac Merge branch 'version-15' into version-15-hotfix 2024-03-19 15:28:47 +05:30
mergify[bot]
7f1f7809c8 fix: stock reco negative batch (backport #40533) (#40535)
fix: stock reco negative batch (#40533)

(cherry picked from commit a64c2ecf39)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-19 15:18:50 +05:30
mergify[bot]
44bbf4b983 fix: Update Existing Price List Rate not working (backport #40333) (#40527)
fix: Update Existing Price List Rate not working (#40333)

(cherry picked from commit 09ea7edb86)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-19 14:26:41 +05:30
Gursheen Kaur Anand
0ddb310dd4 Merge pull request #40531 from frappe/mergify/bp/version-15-hotfix/pr-40528
fix(minor): missing range for ageing summary in PSOA (backport #40528)
2024-03-19 12:44:04 +05:30
Gursheen Anand
dacc69c56f fix: missing range for ageing summary
(cherry picked from commit 643cc022fd)
2024-03-19 07:11:18 +00:00
ruthra kumar
7b4ede9c04 Merge pull request #40523 from frappe/mergify/bp/version-15-hotfix/pr-40502
refactor: toggle between 'http' and 'https' on exchange rate API (backport #40502)
2024-03-19 10:21:28 +05:30
ruthra kumar
1a0a70b5cd refactor: toggle between 'http' and 'https' on exchange rate API
(cherry picked from commit 8b81274769)
2024-03-19 04:30:16 +00:00
ruthra kumar
093b95604f Merge pull request #40516 from frappe/mergify/bp/version-15-hotfix/pr-40511
fix: invalid exchange loss booking on invoice against base accounts (backport #40511)
2024-03-18 21:00:38 +05:30
ruthra kumar
dd30a46f29 refactor(test): ensure Exchange gain/loss journals aren't created
(cherry picked from commit e1c2d006ca)
2024-03-18 15:14:44 +00:00
ruthra kumar
72c9cd5582 fix: invalid exchange loss booking on invoice againts base accounts
(cherry picked from commit 3d5dba6976)
2024-03-18 15:14:43 +00:00
mergify[bot]
917a21b989 fix: not able to delete cancelled delivery note (backport #40508) (#40510)
fix: not able to delete cancelled delivery note (#40508)

(cherry picked from commit 7695759f3c)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-18 15:26:32 +05:30
mergify[bot]
69f442dd79 fix: balance qty in the stock ledger report (backport #40506) (#40507)
fix: balance qty in the stock ledger report (#40506)

(cherry picked from commit 00de529a1e)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-18 13:03:27 +05:30
Gursheen Kaur Anand
a82df6ac7b Merge pull request #40503 from GursheenK/achieved-targets-for-territory-v15
fix: achieved targets for territory / partners bpv15
2024-03-18 11:21:59 +05:30
Gursheen Anand
bc78bc33f1 fix: show correct variance for durations with no vouchers 2024-03-18 10:18:16 +05:30
Gursheen Anand
1a3ea0c108 test: sales partner targets with invoices 2024-03-18 10:18:16 +05:30
Gursheen Anand
1ac888715c fix: achieved targets for sales partners 2024-03-18 10:18:16 +05:30
Deepesh Garg
6ecd545f17 Merge pull request #40492 from frappe/mergify/bp/version-15-hotfix/pr-40428
chore: Remove custom party queries (#40428)
2024-03-17 19:47:39 +05:30
ruthra kumar
be730fe66a Merge pull request #40499 from frappe/mergify/bp/version-15-hotfix/pr-40494
fix: AttributeError in Bank Statement Import (backport #40494)
2024-03-17 08:10:18 +05:30
ruthra kumar
09c1da9570 Merge pull request #40497 from frappe/mergify/bp/version-15-hotfix/pr-40495
fix: index error on Exchange Rate Revaluation creation (backport #40495)
2024-03-17 08:06:17 +05:30
ruthra kumar
a0bf22c9a5 fix: attribute error on Bank Statement Import retry
(cherry picked from commit aec630c74f)
2024-03-17 02:23:46 +00:00
ruthra kumar
5729ee563c fix: don't validate for currency, if account is unavailable
(cherry picked from commit 3cde81be65)
2024-03-17 02:23:46 +00:00
ruthra kumar
ebd967d7d3 fix: index error on Exchange Rate Revaluation creation
(cherry picked from commit bb279e368c)
2024-03-17 02:21:37 +00:00
Deepesh Garg
313350534f fix: Remove custom queries for customer
(cherry picked from commit 60ba25caf2)
2024-03-16 14:34:36 +00:00
Deepesh Garg
e3336c603c chore: Add supplier query back
(cherry picked from commit effc9d1492)
2024-03-16 14:34:36 +00:00
Deepesh Garg
2e38f4d599 chore: remove invalid comment
(cherry picked from commit e87b44f758)
2024-03-16 14:34:36 +00:00
Deepesh Garg
2512d4c7f2 chore: Remove custom party queries
(cherry picked from commit c9623b6fd6)
2024-03-16 14:34:36 +00:00
Deepesh Garg
823e377ef1 Merge pull request #40454 from frappe/mergify/bp/version-15-hotfix/pr-40433
fix: Taxes not getting updated on change (#40433)
2024-03-16 19:23:22 +05:30
ruthra kumar
db5cd4eace Merge pull request #40490 from frappe/mergify/bp/version-15-hotfix/pr-40485
refactor: validate SO and SI references in Delivery Notes (backport #40485)
2024-03-16 15:27:37 +05:30
ruthra kumar
409a65ec64 test: SO reference validation
(cherry picked from commit 4f396d3049)
2024-03-16 09:29:15 +00:00
ruthra kumar
28d3525b42 refactor: validate SO and SI references
(cherry picked from commit 4d090bd3b8)
2024-03-16 09:29:14 +00:00
ruthra kumar
f7ef0d69b7 Merge pull request #40474 from frappe/mergify/bp/version-15-hotfix/pr-40473
refactor: enable no-copy for update_outstanding_for_self (backport #40473)
2024-03-16 10:07:51 +05:30
ruthra kumar
0ca5f98ba3 Merge pull request #40471 from frappe/mergify/bp/version-15-hotfix/pr-40260
refactor: support payment against reverse payment reconciliation (backport #40260)
2024-03-16 10:06:25 +05:30
ruthra kumar
78eb6bd9ea Merge pull request #40466 from frappe/mergify/bp/version-15-hotfix/pr-40461
refactor: disallow '0' qty return invoices with stock effect (backport #40461)
2024-03-16 10:05:22 +05:30
mergify[bot]
74b30a6a1d fix: stopped mr showing in the popup (backport #40478) (#40480)
fix: stopped mr showing in the popup (#40478)

(cherry picked from commit 1ff8a6f24f)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-16 09:35:56 +05:30
ruthra kumar
ed4c8461d3 chore: resolve conflict 2024-03-15 20:16:38 +05:30
mergify[bot]
61d59b392a fix: serial and batch for internal transfer (backport #40467) (#40477)
fix: serial and batch for internal transfer (#40467)

* fix: serial and batch for internal transfer

* chore: fix test cases

(cherry picked from commit 59222813af)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-15 18:19:56 +05:30
ruthra kumar
fba13c6dbc refactor: enable no-copy for update_outstanding_for_self
(cherry picked from commit a1e8caa5c1)
2024-03-15 11:33:09 +00:00
Gursheen Kaur Anand
3397b80ddf Merge pull request #40470 from frappe/mergify/bp/version-15-hotfix/pr-40198
fix: auto-update due date for invoices via data import (backport #40198)
2024-03-15 13:16:56 +05:30
ruthra kumar
d82b1fc600 refactor(test): generate and use unique party
(cherry picked from commit a3d4aff29c)
2024-03-15 07:22:42 +00:00
ruthra kumar
7ea1edc4ff test: advance payment reconciliation against payment
'advance' payments booked in separate party account

(cherry picked from commit 2a08072443)
2024-03-15 07:22:42 +00:00
ruthra kumar
1762f9919a test: reverse payment reconciliation
(cherry picked from commit 6d9074d585)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/test_payment_entry.py
2024-03-15 07:22:41 +00:00
ruthra kumar
17fb287cd9 refactor: support payment against reverse payment reconciliation
(cherry picked from commit 5f15297f28)
2024-03-15 07:22:41 +00:00
Gursheen Anand
8242a9cd46 fix: use in_import flag for checking imported records
(cherry picked from commit f504f2caf3)
2024-03-15 07:16:26 +00:00
Gursheen Anand
9cdde50f7c fix: auto-update due date for invoices via data import
(cherry picked from commit c096133897)
2024-03-15 07:16:25 +00:00
ruthra kumar
5957ef9b9d test: validation to prevent '0' qty return with stock effect
(cherry picked from commit 647bba0f00)
2024-03-15 05:37:36 +00:00
ruthra kumar
144aec8d6a refactor: disallow '0' qty return invoices with stock effect
(cherry picked from commit 898affbee9)
2024-03-15 05:37:36 +00:00
ruthra kumar
3fe45404ea Merge pull request #40460 from frappe/mergify/bp/version-15-hotfix/pr-40420
fix: advance journal fetching logic in Advances section (backport #40420)
2024-03-14 15:51:15 +05:30
ruthra kumar
bfa78aaf74 fix: advance journal fetching logic in Advances section
(cherry picked from commit e786a93c2e)
2024-03-14 10:06:05 +00:00
mergify[bot]
54b5064f76 chore: fix test case (backport #40457) (#40458)
chore: fix test case

(cherry picked from commit ec83d54826)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-03-14 14:43:05 +05:30
Deepesh Garg
75b610d7fc fix: Taxes not getting updated on change
(cherry picked from commit 65ae9616ba)
2024-03-14 06:44:05 +00:00
mergify[bot]
ba6b8561c9 fix: Data too long for column 'stock_queue' (backport #40436) (#40451)
fix: Data too long for column 'stock_queue' (#40436)

(cherry picked from commit cd79dcccb6)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-14 11:49:10 +05:30
ruthra kumar
54a8473a91 Merge pull request #40447 from frappe/mergify/copy/version-15-hotfix/pr-40445
fix: flaky Accounts Receivable test case (copy #40445)
2024-03-14 10:29:27 +05:30
ruthra kumar
df1351783e fix(test): manually filter rows and assert 2024-03-14 10:14:35 +05:30
ruthra kumar
0bb4a7d13a fix: flaky Accounts Receivable test case
(cherry picked from commit 40bce240bde508801d07b9c38ededea5b4f297c8)
2024-03-13 16:53:44 +00:00
mergify[bot]
ce2626e911 fix: incorrect gross profit on the quotation (backport #40438) (#40442)
fix: incorrect gross profit on the quotation (#40438)

(cherry picked from commit f4a945aee4)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-03-13 21:56:20 +05:30
ruthra kumar
bc53b67065 Merge pull request #40427 from frappe/mergify/bp/version-15-hotfix/pr-40372
refactor: checkbox to toggle standalone Credit/Debit note behaviour (backport #40372)
2024-03-13 16:28:50 +05:30
Nabin Hait
2d7ff5d4cf fix: Book depreciation until the asset disposal date and removed unwanted commits
(cherry picked from commit dedae4fecf)
2024-03-13 07:48:27 +00:00
ruthra kumar
70375dfbde refactor: pass 'name' to get_link_to_form 2024-03-13 13:15:07 +05:30
ruthra kumar
1093e0d46f chore: resolve conflicts 2024-03-13 12:17:46 +05:30
ruthra kumar
d87b98d945 chore: better popup message
(cherry picked from commit 445d2acf50)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2024-03-13 06:13:11 +00:00
ruthra kumar
752b01d83b test: cr note flag to update self
(cherry picked from commit ce3b1f09f5)
2024-03-13 06:13:11 +00:00
ruthra kumar
a0f78584e9 refactor: make AR/AP report aware of always standalone cr/dr notes
(cherry picked from commit 4d0c2d8e82)
2024-03-13 06:13:11 +00:00
ruthra kumar
4b44ae435a chore: patch for updating flag in Cr/Dr notes
(cherry picked from commit 849f478894)

# Conflicts:
#	erpnext/patches.txt
2024-03-13 06:13:10 +00:00
ruthra kumar
1c68946fb9 refactor: post ledger entries based on toggle
(cherry picked from commit 77aac6f571)
2024-03-13 06:13:10 +00:00
ruthra kumar
824d6b4102 refactor: checkbox in purchase invoice
(cherry picked from commit 767f2157e6)
2024-03-13 06:13:10 +00:00
ruthra kumar
b5f22c39ec chore: update popup message
(cherry picked from commit adf13a19c4)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2024-03-13 06:13:10 +00:00
ruthra kumar
f83098e493 refactor: checkbox to toggle always standalone credit note
(cherry picked from commit 2cefe2a20e)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json
2024-03-13 06:13:09 +00:00
707 changed files with 11738 additions and 8444 deletions

View File

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

View File

@@ -55,28 +55,15 @@ repos:
erpnext/templates/includes/.*
)$
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.0
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
'flake8-tuple',
]
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__ = "15.17.1"
__version__ = "15.20.5"
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

@@ -88,7 +88,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(
@@ -218,9 +218,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:
@@ -418,7 +416,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()
@@ -426,9 +424,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,
)
@@ -594,7 +591,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

@@ -261,28 +261,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(
@@ -291,9 +283,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",
@@ -318,9 +308,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

@@ -40,16 +40,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

@@ -40,7 +40,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",
@@ -48,13 +49,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"))
@@ -113,7 +111,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
repostable_doctypes = get_allowed_types_from_settings()
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break"
else:
@@ -148,7 +145,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}'",
}
)
@@ -182,19 +179,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")
@@ -243,7 +238,6 @@ def get_doctypes_with_dimensions():
def get_accounting_dimensions(as_list=True, filters=None):
if not filters:
filters = {"disabled": 0}
@@ -272,7 +266,6 @@ def get_checks_for_pl_and_bs_accounts():
def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str):
dimensions = [dimensions]
@@ -280,9 +273,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
@@ -290,14 +281,10 @@ def get_dimension_with_children(doctype, dimensions):
@frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False):
c = frappe.qb.DocType("Accounting Dimension Detail")
p = frappe.qb.DocType("Accounting Dimension")
dimension_filters = (
frappe.qb.from_(p)
.select(p.label, p.fieldname, p.document_type)
.where(p.disabled == 0)
.run(as_dict=1)
frappe.qb.from_(p).select(p.label, p.fieldname, p.document_type).where(p.disabled == 0).run(as_dict=1)
)
default_dimensions = (
frappe.qb.from_(c)

View File

@@ -55,9 +55,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

@@ -84,7 +84,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

@@ -54,6 +54,7 @@ class BankAccount(Document):
self.validate_company()
self.validate_iban()
self.validate_account()
self.update_default_bank_account()
def validate_account(self):
if self.account:
@@ -100,19 +101,51 @@ class BankAccount(Document):
if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid"))
def update_default_bank_account(self):
if self.is_default and not self.disabled:
frappe.db.set_value(
"Bank Account",
{
"party_type": self.party_type,
"party": self.party,
"is_company_account": self.is_company_account,
"is_default": 1,
"disabled": 0,
},
"is_default",
0,
)
@frappe.whitelist()
def make_bank_account(doctype, docname):
doc = frappe.new_doc("Bank Account")
doc.party_type = doctype
doc.party = docname
doc.is_default = 1
return doc
def get_party_bank_account(party_type, party):
return frappe.db.get_value(party_type, party, "default_bank_account")
return frappe.db.get_value(
"Bank Account",
{"party_type": party_type, "party": party, "is_default": 1, "disabled": 0},
"name",
)
def get_default_company_bank_account(company, party_type, party):
default_company_bank_account = frappe.db.get_value(party_type, party, "default_bank_account")
if default_company_bank_account:
if company != frappe.get_cached_value("Bank Account", default_company_bank_account, "company"):
default_company_bank_account = None
if not default_company_bank_account:
default_company_bank_account = frappe.db.get_value(
"Bank Account", {"company": company, "is_company_account": 1, "is_default": 1}
)
return default_company_bank_account
@frappe.whitelist()

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

@@ -127,7 +127,7 @@ def get_payment_entries_for_bank_clearance(
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,
@@ -141,9 +141,7 @@ def get_payment_entries_for_bank_clearance(
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": account, "from": from_date, "to": to_date},
as_dict=1,
)
@@ -152,7 +150,7 @@ def get_payment_entries_for_bank_clearance(
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,
@@ -167,9 +165,7 @@ def get_payment_entries_for_bank_clearance(
{condition}
order by
posting_date ASC, name DESC
""".format(
condition=condition
),
""",
{
"account": account,
"from": from_date,
@@ -239,10 +235,7 @@ def get_payment_entries_for_bank_clearance(
).run(as_dict=True)
entries = (
list(payment_entries)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
)
return entries

View File

@@ -68,9 +68,7 @@ class TestBankClearance(unittest.TestCase):
)
loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
repayment_entry = create_repayment_entry(
loan.name, "_Test Customer", getdate(), loan.loan_amount
)
repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
repayment_entry.save()
repayment_entry.submit()

View File

@@ -81,9 +81,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"])
@@ -96,10 +94,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
@@ -538,9 +533,7 @@ def check_matching(
for query in queries:
matching_vouchers.extend(query.run(as_dict=True))
return (
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
)
return sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
def get_queries(
@@ -654,17 +647,13 @@ def get_bt_matching_query(exact_match, transaction):
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
ref_rank = (
frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
)
ref_rank = frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
unallocated_rank = (
frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0)
)
party_condition = (
(bt.party_type == transaction.party_type)
& (bt.party == transaction.party)
& bt.party.isnotnull()
(bt.party_type == transaction.party_type) & (bt.party == transaction.party) & bt.party.isnotnull()
)
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -716,9 +705,7 @@ def get_pe_matching_query(
amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0
party_condition = (
(pe.party_type == transaction.party_type)
& (pe.party == transaction.party)
& pe.party.isnotnull()
(pe.party_type == transaction.party_type) & (pe.party == transaction.party) & pe.party.isnotnull()
)
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -749,7 +736,7 @@ def get_pe_matching_query(
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
)
if frappe.flags.auto_reconcile_vouchers == True:
if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition)
return query
@@ -810,7 +797,7 @@ def get_je_matching_query(
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
)
if frappe.flags.auto_reconcile_vouchers == True:
if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition)
return query

View File

@@ -1,12 +1,11 @@
# Copyright (c) 2020, 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, getdate, today
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, today
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
auto_reconcile_vouchers,
@@ -22,7 +21,7 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
self.create_customer()
self.clear_old_entries()
bank_dt = qb.DocType("Bank")
q = qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
self.create_bank_account()
def tearDown(self):

View File

@@ -45,7 +45,7 @@ class BankStatementImport(DataImport):
# end: auto-generated types
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()
@@ -54,7 +54,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)
@@ -69,7 +68,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
)
@@ -125,7 +123,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
@@ -135,9 +133,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)
@@ -148,6 +144,9 @@ def start_import(
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
data = parse_data_from_template(import_file.raw_data)
# Importer expects 'Data Import' class, which has 'payload_count' attribute
if not data_import.get("payload_count"):
data_import.payload_count = len(data) - 1
if import_file_path:
add_bank_account(data, bank_account)

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)
@@ -129,7 +127,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), choices=names, scorer=fuzz.token_set_ratio)
party_name, skip = self.process_fuzzy_result(result)
@@ -142,7 +140,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

@@ -56,17 +56,19 @@ class BankTransaction(Document):
Bank Transaction should be on the same currency as the Bank Account.
"""
if self.currency and self.bank_account:
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
account_currency = frappe.get_cached_value("Account", account, "account_currency")
if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
account_currency = frappe.get_cached_value("Account", account, "account_currency")
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency),
frappe.bold(self.bank_account),
frappe.bold(account_currency),
)
)
)
def set_status(self):
if self.docstatus == 2:
@@ -180,7 +182,7 @@ class BankTransaction(Document):
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
for payment_entry in to_remove:
self.remove(to_remove)
self.remove(payment_entry)
@frappe.whitelist()
def remove_payment_entries(self):
@@ -235,9 +237,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,
@@ -332,7 +332,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)
@@ -371,9 +370,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(
@@ -383,9 +380,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

@@ -436,9 +436,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

@@ -0,0 +1,100 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Bisect Accounting Statements", {
onload(frm) {
frm.trigger("render_heatmap");
},
refresh(frm) {
frm.add_custom_button(__("Bisect Left"), () => {
frm.trigger("bisect_left");
});
frm.add_custom_button(__("Bisect Right"), () => {
frm.trigger("bisect_right");
});
frm.add_custom_button(__("Up"), () => {
frm.trigger("move_up");
});
frm.add_custom_button(__("Build Tree"), () => {
frm.trigger("build_tree");
});
},
render_heatmap(frm) {
let bisect_heatmap = frm.get_field("bisect_heatmap").$wrapper;
bisect_heatmap.addClass("bisect_heatmap_location");
// milliseconds in a day
let msiad = 24 * 60 * 60 * 1000;
let datapoints = {};
let fr_dt = new Date(frm.doc.from_date).getTime();
let to_dt = new Date(frm.doc.to_date).getTime();
let bisect_start = new Date(frm.doc.current_from_date).getTime();
let bisect_end = new Date(frm.doc.current_to_date).getTime();
for (let x = fr_dt; x <= to_dt; x += msiad) {
let epoch_in_seconds = x / 1000;
if (bisect_start <= x && x <= bisect_end) {
datapoints[epoch_in_seconds] = 1.0;
} else {
datapoints[epoch_in_seconds] = 0.0;
}
}
new frappe.Chart(".bisect_heatmap_location", {
type: "heatmap",
data: {
dataPoints: datapoints,
start: new Date(frm.doc.from_date),
end: new Date(frm.doc.to_date),
},
countLabel: "Bisecting",
discreteDomains: 1,
});
},
bisect_left(frm) {
frm.call({
doc: frm.doc,
method: "bisect_left",
freeze: true,
freeze_message: __("Bisecting Left ..."),
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
bisect_right(frm) {
frm.call({
doc: frm.doc,
freeze: true,
freeze_message: __("Bisecting Right ..."),
method: "bisect_right",
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
move_up(frm) {
frm.call({
doc: frm.doc,
freeze: true,
freeze_message: __("Moving up in tree ..."),
method: "move_up",
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
build_tree(frm) {
frm.call({
doc: frm.doc,
freeze: true,
freeze_message: __("Rebuilding BTree for period ..."),
method: "build_tree",
callback: (r) => {
frm.trigger("render_heatmap");
},
});
},
});

View File

@@ -0,0 +1,194 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-15 21:28:28.054773",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section_break_cvfg",
"company",
"column_break_hcam",
"from_date",
"column_break_qxbi",
"to_date",
"column_break_iwny",
"algorithm",
"section_break_8ph9",
"current_node",
"section_break_ngid",
"bisect_heatmap",
"section_break_hmsy",
"bisecting_from",
"current_from_date",
"column_break_uqyd",
"bisecting_to",
"current_to_date",
"section_break_hbyo",
"heading_cppb",
"p_l_summary",
"column_break_aivo",
"balance_sheet_summary",
"b_s_summary",
"column_break_gvwx",
"difference_heading",
"difference"
],
"fields": [
{
"fieldname": "column_break_qxbi",
"fieldtype": "Column Break"
},
{
"fieldname": "from_date",
"fieldtype": "Datetime",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Datetime",
"label": "To Date"
},
{
"default": "BFS",
"fieldname": "algorithm",
"fieldtype": "Select",
"label": "Algorithm",
"options": "BFS\nDFS"
},
{
"fieldname": "column_break_iwny",
"fieldtype": "Column Break"
},
{
"fieldname": "current_node",
"fieldtype": "Link",
"label": "Current Node",
"options": "Bisect Nodes"
},
{
"fieldname": "section_break_hmsy",
"fieldtype": "Section Break"
},
{
"fieldname": "current_from_date",
"fieldtype": "Datetime",
"read_only": 1
},
{
"fieldname": "current_to_date",
"fieldtype": "Datetime",
"read_only": 1
},
{
"fieldname": "column_break_uqyd",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_hbyo",
"fieldtype": "Section Break"
},
{
"fieldname": "p_l_summary",
"fieldtype": "Float",
"read_only": 1
},
{
"fieldname": "b_s_summary",
"fieldtype": "Float",
"read_only": 1
},
{
"fieldname": "difference",
"fieldtype": "Float",
"read_only": 1
},
{
"fieldname": "column_break_aivo",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_gvwx",
"fieldtype": "Column Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "column_break_hcam",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_ngid",
"fieldtype": "Section Break"
},
{
"fieldname": "section_break_8ph9",
"fieldtype": "Section Break",
"hidden": 1
},
{
"fieldname": "bisect_heatmap",
"fieldtype": "HTML",
"label": "Heatmap"
},
{
"fieldname": "heading_cppb",
"fieldtype": "Heading",
"label": "Profit and Loss Summary"
},
{
"fieldname": "balance_sheet_summary",
"fieldtype": "Heading",
"label": "Balance Sheet Summary"
},
{
"fieldname": "difference_heading",
"fieldtype": "Heading",
"label": "Difference"
},
{
"fieldname": "bisecting_from",
"fieldtype": "Heading",
"label": "Bisecting From"
},
{
"fieldname": "bisecting_to",
"fieldtype": "Heading",
"label": "Bisecting To"
},
{
"fieldname": "section_break_cvfg",
"fieldtype": "Section Break"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-12-01 16:49:54.073890",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bisect Accounting Statements",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Administrator",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,226 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import datetime
from collections import deque
from math import floor
import frappe
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate
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()
def validate_dates(self):
if getdate(self.from_date) > getdate(self.to_date):
frappe.throw(
_("From Date: {0} cannot be greater than To date: {1}").format(
frappe.bold(self.from_date), frappe.bold(self.to_date)
)
)
def bfs(self, from_date: datetime, to_date: datetime):
# Make Root node
node = frappe.new_doc("Bisect Nodes")
node.root = None
node.period_from_date = from_date
node.period_to_date = to_date
node.insert()
period_queue = deque([node])
while period_queue:
cur_node = period_queue.popleft()
delta = cur_node.period_to_date - cur_node.period_from_date
if delta.days == 0:
continue
else:
cur_floor = floor(delta.days / 2)
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
left_node = frappe.new_doc("Bisect Nodes")
left_node.period_from_date = cur_node.period_from_date
left_node.period_to_date = next_to_date
left_node.root = cur_node.name
left_node.generated = False
left_node.insert()
cur_node.left_child = left_node.name
period_queue.append(left_node)
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
right_node = frappe.new_doc("Bisect Nodes")
right_node.period_from_date = next_from_date
right_node.period_to_date = cur_node.period_to_date
right_node.root = cur_node.name
right_node.generated = False
right_node.insert()
cur_node.right_child = right_node.name
period_queue.append(right_node)
cur_node.save()
def dfs(self, from_date: datetime, to_date: datetime):
# Make Root node
node = frappe.new_doc("Bisect Nodes")
node.root = None
node.period_from_date = from_date
node.period_to_date = to_date
node.insert()
period_stack = [node]
while period_stack:
cur_node = period_stack.pop()
delta = cur_node.period_to_date - cur_node.period_from_date
if delta.days == 0:
continue
else:
cur_floor = floor(delta.days / 2)
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
left_node = frappe.new_doc("Bisect Nodes")
left_node.period_from_date = cur_node.period_from_date
left_node.period_to_date = next_to_date
left_node.root = cur_node.name
left_node.generated = False
left_node.insert()
cur_node.left_child = left_node.name
period_stack.append(left_node)
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
right_node = frappe.new_doc("Bisect Nodes")
right_node.period_from_date = next_from_date
right_node.period_to_date = cur_node.period_to_date
right_node.root = cur_node.name
right_node.generated = False
right_node.insert()
cur_node.right_child = right_node.name
period_stack.append(right_node)
cur_node.save()
@frappe.whitelist()
def build_tree(self):
frappe.db.delete("Bisect Nodes")
# Convert str to datetime format
dt_format = guess_date_format(self.from_date)
from_date = datetime.datetime.strptime(self.from_date, dt_format)
to_date = datetime.datetime.strptime(self.to_date, dt_format)
if self.algorithm == "BFS":
self.bfs(from_date, to_date)
if self.algorithm == "DFS":
self.dfs(from_date, to_date)
# set root as current node
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
self.get_report_summary()
self.current_node = root.name
self.current_from_date = self.from_date
self.current_to_date = self.to_date
self.save()
def get_report_summary(self):
filters = {
"company": self.company,
"filter_based_on": "Date Range",
"period_start_date": self.current_from_date,
"period_end_date": self.current_to_date,
"periodicity": "Yearly",
}
pl_summary = frappe.get_doc("Report", "Profit and Loss Statement")
self.p_l_summary = pl_summary.execute_script_report(filters=filters)[5]
bs_summary = frappe.get_doc("Report", "Balance Sheet")
self.b_s_summary = bs_summary.execute_script_report(filters=filters)[5]
self.difference = abs(self.p_l_summary - self.b_s_summary)
def update_node(self):
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
current_node.balance_sheet_summary = self.b_s_summary
current_node.profit_loss_summary = self.p_l_summary
current_node.difference = self.difference
current_node.generated = True
current_node.save()
def current_node_has_summary_info(self):
"Assertion method"
return frappe.db.get_value("Bisect Nodes", self.current_node, "generated")
def fetch_summary_info_from_current_node(self):
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
self.p_l_summary = current_node.balance_sheet_summary
self.b_s_summary = current_node.profit_loss_summary
self.difference = abs(self.p_l_summary - self.b_s_summary)
def fetch_or_calculate(self):
if self.current_node_has_summary_info():
self.fetch_summary_info_from_current_node()
else:
self.get_report_summary()
self.update_node()
@frappe.whitelist()
def bisect_left(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
if cur_node.left_child is not None:
lft_node = frappe.get_doc("Bisect Nodes", cur_node.left_child)
self.current_node = cur_node.left_child
self.current_from_date = lft_node.period_from_date
self.current_to_date = lft_node.period_to_date
self.fetch_or_calculate()
self.save()
else:
frappe.msgprint(_("No more children on Left"))
@frappe.whitelist()
def bisect_right(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
if cur_node.right_child is not None:
rgt_node = frappe.get_doc("Bisect Nodes", cur_node.right_child)
self.current_node = cur_node.right_child
self.current_from_date = rgt_node.period_from_date
self.current_to_date = rgt_node.period_to_date
self.fetch_or_calculate()
self.save()
else:
frappe.msgprint(_("No more children on Right"))
@frappe.whitelist()
def move_up(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
if cur_node.root is not None:
root = frappe.get_doc("Bisect Nodes", cur_node.root)
self.current_node = cur_node.root
self.current_from_date = root.period_from_date
self.current_to_date = root.period_to_date
self.fetch_or_calculate()
self.save()
else:
frappe.msgprint(_("Reached Root"))

View File

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

View File

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

View File

@@ -0,0 +1,97 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2023-09-27 14:56:38.112462",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"root",
"left_child",
"right_child",
"period_from_date",
"period_to_date",
"difference",
"balance_sheet_summary",
"profit_loss_summary",
"generated"
],
"fields": [
{
"fieldname": "root",
"fieldtype": "Link",
"label": "Root",
"options": "Bisect Nodes"
},
{
"fieldname": "left_child",
"fieldtype": "Link",
"label": "Left Child",
"options": "Bisect Nodes"
},
{
"fieldname": "right_child",
"fieldtype": "Link",
"label": "Right Child",
"options": "Bisect Nodes"
},
{
"fieldname": "period_from_date",
"fieldtype": "Datetime",
"label": "Period_from_date"
},
{
"fieldname": "period_to_date",
"fieldtype": "Datetime",
"label": "Period To Date"
},
{
"fieldname": "difference",
"fieldtype": "Float",
"label": "Difference"
},
{
"fieldname": "balance_sheet_summary",
"fieldtype": "Float",
"label": "Balance Sheet Summary"
},
{
"fieldname": "profit_loss_summary",
"fieldtype": "Float",
"label": "Profit and Loss Summary"
},
{
"default": "0",
"fieldname": "generated",
"fieldtype": "Check",
"label": "Generated"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-12-01 17:46:12.437996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bisect Nodes",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class BisectNodes(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
balance_sheet_summary: DF.Float
difference: DF.Float
generated: DF.Check
left_child: DF.Link | None
name: DF.Int | None
period_from_date: DF.Datetime | None
period_to_date: DF.Datetime | None
profit_loss_summary: DF.Float
right_child: DF.Link | None
root: DF.Link | None
# end: auto-generated types
pass

View File

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

View File

@@ -70,10 +70,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,
)
@@ -96,12 +97,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:
@@ -174,30 +177,24 @@ def validate_expense_against_budget(args, expense_amount=0):
and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
):
doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.db.get_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,
@@ -210,9 +207,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
@@ -240,7 +235,12 @@ 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,
amount,
)
@@ -268,9 +268,8 @@ 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)
if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles(
frappe.session.user
):
action = "Warn"
@@ -316,10 +315,8 @@ def get_requested_amount(args, budget):
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,
)
@@ -332,12 +329,10 @@ def get_ordered_amount(args, budget):
condition = get_other_condition(args, budget, "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,
)
@@ -350,7 +345,7 @@ def get_other_condition(args, budget, for_doc):
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"
@@ -358,12 +353,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
@@ -382,21 +373,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
@@ -407,9 +394,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

@@ -38,9 +38,7 @@ 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):
@@ -116,7 +114,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
@@ -215,10 +213,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]
@@ -230,7 +228,7 @@ def build_forest(data):
frappe.bold(parent_account)
)
)
return [child] + parent_account_list
return [child, *parent_account_list]
charts_map, paths = {}, []
@@ -250,12 +248,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
@@ -352,9 +350,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
@@ -463,7 +461,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

@@ -66,71 +66,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

@@ -34,9 +34,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()
@@ -109,14 +107,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

@@ -48,7 +48,7 @@ class CostCenterAllocation(Document):
# end: auto-generated types
def __init__(self, *args, **kwargs):
super(CostCenterAllocation, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._skip_from_date_validation = False
def validate(self):
@@ -63,9 +63,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

@@ -3,22 +3,36 @@
frappe.ui.form.on("Currency Exchange Settings", {
service_provider: function (frm) {
if (frm.doc.service_provider == "exchangerate.host") {
let result = ["result"];
let params = {
date: "{transaction_date}",
from: "{from_currency}",
to: "{to_currency}",
};
add_param(frm, "https://api.exchangerate.host/convert", params, result);
} else if (frm.doc.service_provider == "frankfurter.app") {
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",
symbols: "{to_currency}",
};
add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
}
frm.call({
method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint",
args: {
service_provider: frm.doc.service_provider,
use_http: frm.doc.use_http,
},
callback: function (r) {
if (r && r.message) {
if (frm.doc.service_provider == "exchangerate.host") {
let result = ["result"];
let params = {
date: "{transaction_date}",
from: "{from_currency}",
to: "{to_currency}",
};
add_param(frm, r.message, params, result);
} else if (frm.doc.service_provider == "frankfurter.app") {
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",
symbols: "{to_currency}",
};
add_param(frm, r.message, params, result);
}
}
},
});
},
use_http: function (frm) {
frm.trigger("service_provider");
},
});

View File

@@ -9,6 +9,7 @@
"disabled",
"service_provider",
"api_endpoint",
"use_http",
"access_key",
"url",
"column_break_3",
@@ -91,12 +92,19 @@
"fieldname": "access_key",
"fieldtype": "Data",
"label": "Access Key"
},
{
"default": "0",
"depends_on": "eval: doc.service_provider != \"Custom\"",
"fieldname": "use_http",
"fieldtype": "Check",
"label": "Use HTTP Protocol"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-10-04 15:30:25.333860",
"modified": "2024-03-18 08:32:26.895076",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",

View File

@@ -31,6 +31,7 @@ class CurrencyExchangeSettings(Document):
result_key: DF.Table[CurrencyExchangeSettingsResult]
service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
url: DF.Data | None
use_http: DF.Check
# end: auto-generated types
def validate(self):
@@ -42,7 +43,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,7 +53,7 @@ class CurrencyExchangeSettings(Document):
self.set("result_key", [])
self.set("req_params", [])
self.api_endpoint = "https://api.exchangerate.host/convert"
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
self.append("result_key", {"key": "result"})
self.append("req_params", {"key": "access_key", "value": self.access_key})
self.append("req_params", {"key": "amount", "value": "1"})
@@ -64,7 +64,7 @@ class CurrencyExchangeSettings(Document):
self.set("result_key", [])
self.set("req_params", [])
self.api_endpoint = "https://frankfurter.app/{transaction_date}"
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
self.append("result_key", {"key": "rates"})
self.append("result_key", {"key": "{to_currency}"})
self.append("req_params", {"key": "base", "value": "{from_currency}"})
@@ -77,9 +77,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)
@@ -99,7 +97,23 @@ 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 = 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"
elif service_provider == "frankfurter.app":
api = "frankfurter.app/{transaction_date}"
protocol = "https://"
if use_http:
protocol = "http://"
return protocol + api
return None

View File

@@ -139,6 +139,10 @@ class Dunning(AccountsController):
)
row.dunning_level = len(past_dunnings) + 1
def on_cancel(self):
super().on_cancel()
self.ignore_linked_doctypes = ["GL Entry"]
def resolve_dunning(doc, state):
"""

View File

@@ -109,9 +109,7 @@ class TestDunning(FrappeTestCase):
def create_dunning(overdue_days, dunning_type_name=None):
posting_date = add_days(today(), -1 * overdue_days)
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=posting_date, qty=1, rate=100
)
sales_invoice = create_sales_invoice_against_cost_center(posting_date=posting_date, qty=1, rate=100)
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
if dunning_type_name:

View File

@@ -268,7 +268,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
@@ -281,7 +280,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 - (
@@ -335,9 +335,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,
@@ -394,7 +392,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,
@@ -520,7 +519,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,
}
@@ -598,7 +599,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"))
@@ -611,7 +612,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,
@@ -625,24 +626,22 @@ 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
):
account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
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
)
row = account_with_new_balance[0]
account_details.update(
{
"balance_in_base_currency": row["balance_in_base_currency"],
"balance_in_account_currency": row["balance_in_account_currency"],
"current_exchange_rate": row["current_exchange_rate"],
"new_exchange_rate": row["new_exchange_rate"],
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
"zero_balance": row["zero_balance"],
"gain_loss": row["gain_loss"],
}
)
):
row = account_with_new_balance[0]
account_details.update(
{
"balance_in_base_currency": row["balance_in_base_currency"],
"balance_in_account_currency": row["balance_in_account_currency"],
"current_exchange_rate": row["current_exchange_rate"],
"new_exchange_rate": row["new_exchange_rate"],
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
"zero_balance": row["zero_balance"],
"gain_loss": row["gain_loss"],
}
)
return account_details

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

@@ -108,9 +108,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,
)
@@ -126,9 +126,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

@@ -107,13 +107,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):
@@ -179,12 +184,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 (
@@ -192,12 +198,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):
@@ -245,9 +252,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(
@@ -316,7 +321,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:
@@ -324,23 +329,19 @@ def update_outstanding_amt(
if against_voucher_type == "Sales Invoice":
party_account = frappe.db.get_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
@@ -351,12 +352,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]
)
@@ -375,7 +374,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"]:
@@ -391,9 +392,7 @@ def update_outstanding_amt(
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == "Yes" and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value(
"Accounts Settings", None, "frozen_accounts_modifier"
)
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", None, "frozen_accounts_modifier")
if not frozen_accounts_modifier:
frappe.throw(_("Account {0} is frozen").format(account))
@@ -448,7 +447,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

@@ -83,9 +83,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):
@@ -105,7 +103,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

@@ -32,7 +32,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

@@ -196,7 +196,7 @@ frappe.ui.form.on("Journal Entry", {
!(frm.doc.accounts || []).length ||
((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
) {
if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
return frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
@@ -308,7 +308,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
filters: [[jvd.reference_type, "docstatus", "=", 1]],
};
if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
// Filter by cost center
if (jvd.cost_center) {
@@ -320,7 +320,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
}
if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
// party_type and party mandatory
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");
@@ -453,7 +453,10 @@ frappe.ui.form.on("Journal Entry Account", {
}
},
cost_center: function (frm, dt, dn) {
erpnext.journal_entry.set_account_details(frm, dt, dn);
// Don't reset for Gain/Loss type journals, as it will make Debit and Credit values '0'
if (frm.doc.voucher_type != "Exchange Gain Or Loss") {
erpnext.journal_entry.set_account_details(frm, dt, dn);
}
},
account: function (frm, dt, dn) {

View File

@@ -112,7 +112,7 @@ class JournalEntry(AccountsController):
# end: auto-generated types
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def validate(self):
if self.voucher_type == "Opening Entry":
@@ -191,7 +191,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",
@@ -226,10 +226,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")
@@ -375,10 +372,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,
@@ -406,17 +400,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:
@@ -485,10 +487,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,
@@ -510,9 +509,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
@@ -577,16 +576,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"
@@ -668,11 +669,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"):
@@ -690,7 +693,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
@@ -699,7 +705,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]
@@ -823,7 +830,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):
@@ -887,7 +896,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
@@ -1133,27 +1141,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,
)
@@ -1262,7 +1264,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,
@@ -1301,7 +1303,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,
@@ -1327,9 +1329,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",
@@ -1352,9 +1352,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.
@@ -1394,7 +1392,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
@@ -1405,16 +1403,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,
),
@@ -1436,19 +1432,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"
@@ -1461,9 +1453,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 = (
@@ -1501,9 +1491,7 @@ 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)

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()
@@ -477,9 +472,7 @@ class TestJournalEntry(unittest.TestCase):
query = query.select(gl[field])
query = query.where(
(gl.voucher_type == "Journal Entry")
& (gl.voucher_no == self.voucher_no)
& (gl.is_cancelled == 0)
(gl.voucher_type == "Journal Entry") & (gl.voucher_no == self.voucher_no) & (gl.is_cancelled == 0)
).orderby(gl.account)
gl_entries = query.run(as_dict=True)

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

@@ -52,13 +52,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,
)
@@ -79,9 +77,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

@@ -54,9 +54,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

@@ -38,7 +38,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

@@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("paid_from", function () {
frm.events.validate_company(frm);
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type)
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
return {
@@ -87,7 +87,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("paid_to", function () {
frm.events.validate_company(frm);
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type)
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
return {
@@ -134,7 +134,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("payment_term", "references", function (frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (
in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) &&
["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) &&
child.reference_name
) {
return {
@@ -395,10 +395,6 @@ frappe.ui.form.on("Payment Entry", {
return {
query: "erpnext.controllers.queries.employee_query",
};
} else if (frm.doc.party_type == "Customer") {
return {
query: "erpnext.controllers.queries.customer_query",
};
}
});
@@ -474,6 +470,9 @@ frappe.ui.form.on("Payment Entry", {
() => frm.events.set_dynamic_labels(frm),
() => {
frm.set_party_account_based_on_party = false;
if (r.message.party_bank_account) {
frm.set_value("party_bank_account", r.message.party_bank_account);
}
if (r.message.bank_account) {
frm.set_value("bank_account", r.message.bank_account);
}
@@ -627,7 +626,7 @@ frappe.ui.form.on("Payment Entry", {
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) {
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
@@ -1046,7 +1045,7 @@ frappe.ui.form.on("Payment Entry", {
}
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
if (paid_amount > total_negative_outstanding) {
if (total_negative_outstanding == 0) {
@@ -1217,7 +1216,7 @@ frappe.ui.form.on("Payment Entry", {
if (
frm.doc.party_type == "Customer" &&
!in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
!["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
frappe.msgprint(
@@ -1231,7 +1230,7 @@ frappe.ui.form.on("Payment Entry", {
if (
frm.doc.party_type == "Supplier" &&
!in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype)
!["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
frappe.msgprint(
@@ -1327,7 +1326,7 @@ frappe.ui.form.on("Payment Entry", {
bank_account: function (frm) {
const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to";
if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) {
if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) {
frappe.call({
method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details",
args: {
@@ -1666,6 +1665,8 @@ frappe.ui.form.on("Payment Entry Reference", {
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

@@ -579,6 +579,7 @@
"fieldtype": "Select",
"hidden": 1,
"label": "Payment Order Status",
"no_copy": 1,
"options": "Initiated\nPayment Ordered",
"read_only": 1
},
@@ -821,4 +822,4 @@
"states": [],
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -16,6 +16,7 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.bank_account.bank_account import (
get_bank_account_details,
get_default_company_bank_account,
get_party_bank_account,
)
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
@@ -52,7 +53,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()
@@ -112,6 +113,9 @@ class PaymentEntry(AccountsController):
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
return
if self.party_type not in ("Customer", "Supplier"):
return
if not frappe.db.get_value(
"Company", self.company, "book_advance_payments_in_separate_party_account"
):
@@ -165,7 +169,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()
@@ -275,9 +279,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"
@@ -289,7 +291,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 (
@@ -313,14 +317,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):
@@ -392,20 +396,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,
)
if ref_exchange_rate:
ref_details.update({"exchange_rate": ref_exchange_rate})
# Only update exchange rate when the reference is Journal Entry
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:
@@ -438,7 +452,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(
@@ -475,7 +491,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)
)
)
@@ -498,7 +514,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
@@ -511,7 +528,10 @@ class PaymentEntry(AccountsController):
):
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,
)
)
@@ -522,13 +542,15 @@ 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":
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
elif self.party_type == "Supplier":
return ("Purchase Order", "Purchase Invoice", "Journal Entry")
return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
@@ -693,9 +715,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
@@ -758,7 +778,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})
@@ -878,7 +897,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
@@ -1016,7 +1034,6 @@ class PaymentEntry(AccountsController):
)
]
else:
remarks = [
_("Amount {0} {1} {2} {3}").format(
_(self.party_account_currency),
@@ -1036,14 +1053,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))
@@ -1117,21 +1139,31 @@ class PaymentEntry(AccountsController):
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
cost_center = frappe.db.get_value(
d.reference_doctype, d.reference_name, "cost_center"
)
gle = party_gl_dict.copy()
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(
d
)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"):
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return and self.party_type in payable_party_types and (self.payment_type == "Receive")
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
@@ -1191,9 +1223,22 @@ class PaymentEntry(AccountsController):
"Journal Entry",
"Sales Order",
"Purchase Order",
"Payment Entry",
):
self.add_advance_gl_for_reference(gl_entries, ref)
def get_dr_and_account_for_advances(self, reference):
if reference.reference_doctype == "Sales Invoice":
return "credit", reference.account
if reference.reference_doctype == "Payment Entry":
if reference.account_type == "Receivable" and reference.payment_type == "Pay":
return "credit", self.party_account
else:
return "debit", self.party_account
return "debit", reference.account
def add_advance_gl_for_reference(self, gl_entries, invoice):
args_dict = {
"party_type": self.party_type,
@@ -1213,8 +1258,8 @@ class PaymentEntry(AccountsController):
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit"
args_dict["account"] = invoice.account
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
args_dict["account"] = account
args_dict[dr_or_cr] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict.update(
@@ -1596,7 +1641,8 @@ def get_outstanding_reference_documents(args, validate=False):
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 []
@@ -1606,7 +1652,7 @@ def get_outstanding_reference_documents(args, validate=False):
# 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"])
@@ -1621,7 +1667,7 @@ def get_outstanding_reference_documents(args, validate=False):
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 = {
@@ -1631,21 +1677,21 @@ def get_outstanding_reference_documents(args, validate=False):
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 = []
@@ -1660,7 +1706,7 @@ def get_outstanding_reference_documents(args, validate=False):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
party_account,
[party_account],
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
@@ -1761,12 +1807,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]
@@ -1861,7 +1905,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"
@@ -1994,14 +2038,16 @@ def get_party_details(company, party_type, party, date, cost_center=None):
party_name = frappe.db.get_value(party_type, party, _party_name)
party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center)
if party_type in ["Customer", "Supplier"]:
bank_account = get_party_bank_account(party_type, party)
party_bank_account = get_party_bank_account(party_type, party)
bank_account = get_default_company_bank_account(company, party_type, party)
return {
"party_account": party_account,
"party_name": party_name,
"party_account_currency": account_currency,
"party_balance": party_balance,
"account_balance": account_balance,
"party_bank_account": party_bank_account,
"bank_account": bank_account,
}
@@ -2011,18 +2057,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(
{
@@ -2039,53 +2081,75 @@ 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 = account = 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)
# Only applies for Reverse Payment Entries
account_type = None
payment_type = None
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 == "Payment Entry":
if reverse_payment_details := frappe.db.get_all(
"Payment Entry",
filters={"name": reference_name},
fields=["payment_type", "party_type"],
)[0]:
payment_type = reverse_payment_details.payment_type
account_type = frappe.db.get_value(
"Party Type", reverse_payment_details.party_type, "account_type"
)
exchange_rate = 1
elif reference_doctype != "Journal Entry":
if not total_amount:
@@ -2131,6 +2195,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"outstanding_amount": flt(outstanding_amount),
"exchange_rate": flt(exchange_rate),
"bill_no": ref_doc.get("bill_no"),
"account_type": account_type,
"payment_type": payment_type,
}
)
if account:
@@ -2151,9 +2217,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:
@@ -2206,9 +2270,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")
@@ -2237,7 +2299,6 @@ def get_payment_entry(
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
):
@@ -2421,9 +2482,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"]
@@ -2433,7 +2492,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)
@@ -2462,9 +2520,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)
@@ -2484,7 +2540,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,
}
)
@@ -2507,9 +2564,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
)
@@ -2552,9 +2607,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):
@@ -2571,7 +2624,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),
},
)
@@ -2594,7 +2648,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:
@@ -2622,7 +2677,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
@@ -2632,9 +2687,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

@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe import qb
@@ -10,7 +9,6 @@ from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.doctype.payment_entry.payment_entry import (
InvalidPaymentEntry,
get_outstanding_reference_documents,
get_payment_entry,
get_reference_details,
@@ -162,7 +160,7 @@ class TestPaymentEntry(FrappeTestCase):
supplier.on_hold = 0
supplier.save()
except:
except Exception:
pass
else:
raise Exception
@@ -469,9 +467,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()
@@ -638,9 +634,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")
@@ -896,9 +890,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)
@@ -939,9 +931,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")
@@ -1084,9 +1074,13 @@ 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 = {
"account": get_party_account("Customer", so.customer, so.company),
"account_type": None, # only applies for Reverse Payment Entry
"payment_type": None, # only applies for Reverse Payment Entry
"total_amount": 5000.0,
"outstanding_amount": 5000.0,
"exchange_rate": 1.0,
@@ -1203,7 +1197,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")
@@ -1324,8 +1318,6 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries()
def test_ledger_entries_for_advance_as_liability(self):
from erpnext.accounts.doctype.account.test_account import create_account
company = "_Test Company"
advance_account = create_account(
@@ -1427,7 +1419,7 @@ class TestPaymentEntry(FrappeTestCase):
self.check_pl_entries()
# Unreconcile
unrecon = (
(
frappe.get_doc(
{
"doctype": "Unreconcile Payment",
@@ -1483,9 +1475,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()
@@ -1514,6 +1504,168 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
self.assertEqual(references[2].payment_term, "Tax Receivable")
def test_reverse_payment_reconciliation(self):
customer = create_customer(frappe.generate_hash(length=10), "INR")
pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Receive",
paid_from="Debtors - _TC",
paid_to="_Test Cash - _TC",
)
pe.submit()
reverse_pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Pay",
paid_from="_Test Cash - _TC",
paid_to="Debtors - _TC",
)
reverse_pe.submit()
pr = frappe.get_doc("Payment Reconciliation")
pr.company = "_Test Company"
pr.party_type = "Customer"
pr.party = customer
pr.receivable_payable_account = "Debtors - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number)
self.assertEqual(pe.name, pr.payments[0].reference_name)
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[0].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
def test_advance_reverse_payment_reconciliation(self):
company = "_Test Company"
customer = create_customer(frappe.generate_hash(length=10), "INR")
advance_account = create_account(
parent_account="Current Liabilities - _TC",
account_name="Advances Received",
company=company,
account_type="Receivable",
)
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_received_account": advance_account,
},
)
# Reverse Payment(essentially an Invoice)
reverse_pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Pay",
paid_from="_Test Cash - _TC",
paid_to=advance_account,
)
reverse_pe.save() # use save() to trigger set_liability_account()
reverse_pe.submit()
# Advance Payment
pe = create_payment_entry(
party_type="Customer",
party=customer,
payment_type="Receive",
paid_from=advance_account,
paid_to="_Test Cash - _TC",
)
pe.save() # use save() to trigger set_liability_account()
pe.submit()
# Partially reconcile advance against invoice
pr = frappe.get_doc("Payment Reconciliation")
pr.company = company
pr.party_type = "Customer"
pr.party = customer
pr.receivable_payable_account = "Debtors - _TC"
pr.default_advance_account = advance_account
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = 400
pr.reconcile()
# assert General and Payment Ledger entries post partial reconciliation
self.expected_gle = [
{"account": advance_account, "debit": 400.0, "credit": 0.0},
{"account": advance_account, "debit": 0.0, "credit": 1000.0},
{"account": advance_account, "debit": 0.0, "credit": 400.0},
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
]
self.expected_ple = [
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": -1000.0,
},
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": reverse_pe.name,
"amount": -400.0,
},
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": 400.0,
},
]
self.voucher_no = pe.name
self.check_gl_entries()
self.check_pl_entries()
# Unreconcile
(
frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
"allocations": [
{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}
],
}
)
.save()
.submit()
)
# assert General and Payment Ledger entries post unreconciliation
self.expected_gle = [
{"account": advance_account, "debit": 0.0, "credit": 1000.0},
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
]
self.expected_ple = [
{
"account": advance_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": -1000.0,
},
]
self.voucher_no = pe.name
self.check_gl_entries()
self.check_pl_entries()
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
@@ -1541,12 +1693,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

@@ -10,6 +10,8 @@
"due_date",
"bill_no",
"payment_term",
"account_type",
"payment_type",
"column_break_4",
"total_amount",
"outstanding_amount",
@@ -108,12 +110,22 @@
"fieldtype": "Link",
"label": "Account",
"options": "Account"
},
{
"fieldname": "account_type",
"fieldtype": "Data",
"label": "Account Type"
},
{
"fieldname": "payment_type",
"fieldtype": "Data",
"label": "Payment Type"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-06-08 07:40:38.487874",
"modified": "2024-04-05 09:44:08.310593",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",

View File

@@ -15,6 +15,7 @@ class PaymentEntryReference(Document):
from frappe.types import DF
account: DF.Link | None
account_type: DF.Data | None
allocated_amount: DF.Float
bill_no: DF.Data | None
due_date: DF.Date | None
@@ -25,6 +26,7 @@ class PaymentEntryReference(Document):
parentfield: DF.Data
parenttype: DF.Data
payment_term: DF.Link | None
payment_type: DF.Data | None
reference_doctype: DF.Link
reference_name: DF.DynamicLink
total_amount: DF.Float

View File

@@ -137,9 +137,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 (
@@ -150,9 +150,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

@@ -90,9 +90,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

@@ -254,6 +254,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.data = [];
const dialog = new frappe.ui.Dialog({
title: __("Select Difference Account"),
size: "extra-large",
fields: [
{
fieldname: "allocation",
@@ -279,6 +280,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
in_list_view: 1,
read_only: 1,
},
{
fieldtype: "Date",
fieldname: "gain_loss_posting_date",
label: __("Posting Date"),
in_list_view: 1,
reqd: 1,
},
{
fieldtype: "Link",
options: "Account",
@@ -319,6 +327,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
"difference_account",
d.difference_account
);
frappe.model.set_value(
"Payment Reconciliation Allocation",
d.docname,
"gain_loss_posting_date",
d.gain_loss_posting_date
);
});
this.reconcile_payment_entries();
@@ -334,6 +348,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
reference_name: d.reference_name,
difference_amount: d.difference_amount,
difference_account: d.difference_account,
gain_loss_posting_date: d.gain_loss_posting_date,
});
}
});

View File

@@ -67,7 +67,7 @@ class PaymentReconciliation(Document):
# end: auto-generated types
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 = []
@@ -286,7 +286,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,10 +339,15 @@ class PaymentReconciliation(Document):
self.build_qb_filter_conditions(get_invoices=True)
accounts = [self.receivable_payable_account]
if self.default_advance_account:
accounts.append(self.default_advance_account)
non_reconciled_invoices = get_outstanding_invoices(
self.party_type,
self.party,
self.receivable_payable_account,
accounts,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
@@ -407,9 +411,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()
@@ -441,6 +443,7 @@ class PaymentReconciliation(Document):
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
res.update({"gain_loss_posting_date": pay.get("posting_date")})
if pay.get("amount") == 0:
entries.append(res)
@@ -526,9 +529,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
@@ -557,6 +560,7 @@ class PaymentReconciliation(Document):
"allocated_amount": flt(row.get("allocated_amount")),
"difference_amount": flt(row.get("difference_amount")),
"difference_account": row.get("difference_account"),
"difference_posting_date": row.get("gain_loss_posting_date"),
"cost_center": row.get("cost_center"),
}
)
@@ -620,9 +624,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"]
)
@@ -714,7 +716,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:
@@ -834,7 +836,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
@@ -102,6 +101,14 @@ class TestPaymentReconciliation(FrappeTestCase):
"account_currency": "USD",
"account_type": "Payable",
},
# 'Payable' account for capturing advance paid, under 'Assets' group
{
"attribute": "advance_payable_account",
"account_name": "Advance Paid",
"parent_account": "Current Assets - _PR",
"account_currency": "INR",
"account_type": "Payable",
},
]
for x in accounts:
@@ -127,11 +134,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 +165,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 +185,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 +217,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 +269,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 +419,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 +838,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 +1040,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 +1056,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()
@@ -1130,6 +1141,17 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(pr.allocation[0].allocated_amount, 85)
self.assertEqual(pr.allocation[0].difference_amount, 0)
pr.reconcile()
si.reload()
self.assertEqual(si.outstanding_amount, 0)
# No Exchange Gain/Loss journal should be generated
exc_gain_loss_journals = frappe.db.get_all(
"Journal Entry Account",
filters={"reference_type": si.doctype, "reference_name": si.name, "docstatus": 1},
fields=["parent"],
)
self.assertEqual(exc_gain_loss_journals, [])
def test_reconciliation_purchase_invoice_against_return(self):
self.supplier = "_Test Supplier USD"
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
@@ -1321,6 +1343,188 @@ class TestPaymentReconciliation(FrappeTestCase):
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
pr.reconcile()
def test_reverse_payment_against_payment_for_supplier(self):
"""
Reconcile a payment against a reverse payment, for a supplier.
"""
self.supplier = "_Test Supplier"
amount = 4000
pe = self.create_payment_entry(amount=amount)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.creditors
pe.save().submit()
reverse_pe = self.create_payment_entry(amount=amount)
reverse_pe.party_type = "Supplier"
reverse_pe.party = self.supplier
reverse_pe.payment_type = "Receive"
reverse_pe.paid_from = self.creditors
reverse_pe.paid_to = self.cash
reverse_pe.save().submit()
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name)
self.assertEqual(pr.payments[0].reference_name, pe.name)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pe.reload()
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.references[0].exchange_rate, 1)
# There should not be any Exc Gain/Loss
self.assertEqual(pe.references[0].exchange_gain_loss, 0)
self.assertEqual(pe.references[0].reference_name, reverse_pe.name)
journals = frappe.db.get_all(
"Journal Entry",
filters={
"voucher_type": "Exchange Gain Or Loss",
"reference_type": "Payment Entry",
"reference_name": ("in", [pe.name, reverse_pe.name]),
},
)
# There should be no Exchange Gain/Loss created
self.assertEqual(journals, [])
def test_advance_reverse_payment_against_payment_for_supplier(self):
"""
Reconcile an Advance payment against reverse payment, for a supplier.
"""
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
},
)
self.supplier = "_Test Supplier"
amount = 4000
pe = self.create_payment_entry(amount=amount)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.advance_payable_account
pe.save().submit()
reverse_pe = self.create_payment_entry(amount=amount)
reverse_pe.party_type = "Supplier"
reverse_pe.party = self.supplier
reverse_pe.payment_type = "Receive"
reverse_pe.paid_from = self.advance_payable_account
reverse_pe.paid_to = self.cash
reverse_pe.save().submit()
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.default_advance_account = self.advance_payable_account
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name)
self.assertEqual(pr.payments[0].reference_name, pe.name)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pe.reload()
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.references[0].exchange_rate, 1)
# There should not be any Exc Gain/Loss
self.assertEqual(pe.references[0].exchange_gain_loss, 0)
self.assertEqual(pe.references[0].reference_name, reverse_pe.name)
journals = frappe.db.get_all(
"Journal Entry",
filters={
"voucher_type": "Exchange Gain Or Loss",
"reference_type": "Payment Entry",
"reference_name": ("in", [pe.name, reverse_pe.name]),
},
)
# There should be no Exchange Gain/Loss created
self.assertEqual(journals, [])
# Assert Ledger Entries
gl_entries = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pe.name},
fields=["account", "voucher_no", "against_voucher", "debit", "credit"],
order_by="account, against_voucher, debit",
)
expected_gle = [
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher": pe.name,
"debit": 0.0,
"credit": amount,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher": pe.name,
"debit": amount,
"credit": 0.0,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher": reverse_pe.name,
"debit": amount,
"credit": 0.0,
},
{
"account": "Cash - _PR",
"voucher_no": pe.name,
"against_voucher": None,
"debit": 0.0,
"credit": amount,
},
]
self.assertEqual(gl_entries, expected_gle)
pl_entries = frappe.db.get_all(
"Payment Ledger Entry",
filters={"voucher_no": pe.name},
fields=["account", "voucher_no", "against_voucher_no", "amount"],
order_by="account, against_voucher_no, amount",
)
expected_ple = [
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": -amount,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher_no": pe.name,
"amount": amount,
},
{
"account": self.advance_payable_account,
"voucher_no": pe.name,
"against_voucher_no": reverse_pe.name,
"amount": -amount,
},
]
self.assertEqual(pl_entries, expected_ple)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -19,6 +19,7 @@
"is_advance",
"section_break_5",
"difference_amount",
"gain_loss_posting_date",
"column_break_7",
"difference_account",
"exchange_rate",

View File

@@ -28,7 +28,7 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
if (
frm.doc.payment_request_type == "Inward" &&
frm.doc.payment_channel !== "Phone" &&
!in_list(["Initiated", "Paid"], frm.doc.status) &&
!["Initiated", "Paid"].includes(frm.doc.status) &&
!frm.doc.__islocal &&
frm.doc.docstatus == 1
) {

View File

@@ -91,7 +91,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):
@@ -104,7 +104,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:
@@ -157,7 +157,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
@@ -209,7 +209,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)
@@ -295,14 +295,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
@@ -320,7 +316,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
),
}
@@ -334,21 +330,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()
@@ -443,15 +435,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(
@@ -471,6 +459,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"),
@@ -529,9 +523,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:
@@ -632,7 +626,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

@@ -35,10 +35,11 @@
}
],
"links": [],
"modified": "2020-04-01 15:35:18.112619",
"modified": "2024-04-07 11:26:42.021585",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Terms Template",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -76,6 +77,15 @@
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1
}
],
"sort_field": "modified",

View File

@@ -36,9 +36,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

@@ -67,7 +67,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)
@@ -109,9 +110,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()
@@ -141,7 +140,8 @@ class PeriodClosingVoucher(AccountsController):
previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
if previous_fiscal_year and not frappe.db.exists(
"GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
"GL Entry",
{"posting_date": ("<=", last_year_closing), "company": self.company, "is_cancelled": 0},
):
return
@@ -224,7 +224,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,
@@ -248,7 +250,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

@@ -70,7 +70,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:
@@ -165,9 +165,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]
@@ -238,7 +236,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

@@ -62,9 +62,7 @@ class TestPOSClosingEntry(unittest.TestCase):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv = create_pos_invoice(
rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1
)
pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv.submit()
@@ -211,7 +209,7 @@ def get_test_item_qty(pos_profile):
item_group="All Item Groups",
)
test_item_qty = [item for item in test_item_pos["items"] if item["item_code"] == "_Test Item"][
0
].get("actual_qty")
test_item_qty = next(item for item in test_item_pos["items"] if item["item_code"] == "_Test Item").get(
"actual_qty"
)
return test_item_qty

View File

@@ -775,7 +775,7 @@
},
{
"fieldname": "other_charges_calculation",
"fieldtype": "Long Text",
"fieldtype": "Text Editor",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -1563,7 +1563,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2023-11-20 12:27:12.848149",
"modified": "2024-03-20 16:00:34.268756",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

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