Compare commits

...

293 Commits

Author SHA1 Message Date
ljain112
64eaee0c5e chore: added test for Fetch Overdue Payments in dunning
(cherry picked from commit 3b613c44a6)

# Conflicts:
#	erpnext/accounts/doctype/dunning/test_dunning.py
2025-04-15 12:21:18 +00:00
ljain112
7071f583aa fix: correct outstanding amount for invoice in dunning
(cherry picked from commit c2bdd30e6d)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2025-04-15 12:21:17 +00:00
ruthra kumar
503b89a764 Merge pull request #46952 from frappe/mergify/bp/version-14-hotfix/pr-46949
fix: improve translatability of query report print formats (backport #46913) (backport #46949)
2025-04-15 15:59:10 +05:30
mergify[bot]
b88f6c1252 fix(Payment Entry): set account type if missing (backport #47069) (backport #47070) (#47072)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
fix(Payment Entry): set account type if missing (backport #47069) (#47070)
fix(Payment Entry): set account type if missing (#47069)
2025-04-14 19:19:50 +02:00
rohitwaghchaure
e5156e6666 Merge pull request #47068 from frappe/mergify/bp/version-14-hotfix/pr-46997
fix: update the modified date in for SLEs and GLs after rename (backport #46997)
2025-04-14 21:06:08 +05:30
Rohit Waghchaure
8801584c8d fix: update the modified date in for SLEs and GLs after rename
(cherry picked from commit dc5a5ef258)
2025-04-14 15:02:22 +00:00
rohitwaghchaure
55ac96ff09 Merge pull request #47062 from frappe/mergify/bp/version-14-hotfix/pr-46853
fix: stock entry repack amount calculation (backport #46853)
2025-04-14 20:30:38 +05:30
rohitwaghchaure
3f3fb323cf chore: fix conflicts 2025-04-14 18:02:24 +05:30
Rohit Waghchaure
253a067592 fix: stock entry repack amount calculation
(cherry picked from commit 544ceb93cd)

# Conflicts:
#	erpnext/stock/stock_ledger.py
2025-04-14 12:30:21 +00:00
ruthra kumar
4ac5c7e5e7 Merge pull request #47054 from frappe/mergify/bp/version-14-hotfix/pr-47049
Revert "fix: remove against_voucher and against_voucher_type column from General Ledger Report" (backport #47049)
2025-04-14 13:51:45 +05:30
ruthra kumar
694f158fc8 chore: resolve conflict 2025-04-14 13:25:36 +05:30
ruthra kumar
5844aafd12 fix: revert #46900 - against_voucher filter in general ledger
(cherry picked from commit adb331ef71)

# Conflicts:
#	erpnext/accounts/report/general_ledger/general_ledger.py
2025-04-14 07:49:35 +00:00
ruthra kumar
98ffbfb432 Merge pull request #47032 from frappe/mergify/bp/version-14-hotfix/pr-47012
fix: correct doctype in item_wise_purchase register (backport #47012)
2025-04-12 07:29:28 +05:30
ljain112
6ec33c0098 fix: correct doctype in item_wise_purchase register
(cherry picked from commit b8b8dce733)
2025-04-12 01:41:16 +00:00
rohitwaghchaure
a2595350f6 Merge pull request #46971 from rohitwaghchaure/fixed-support-35746
fix: serial no validation for stock reconciliation
2025-04-10 11:05:45 +05:30
rohitwaghchaure
ac7700dff0 Merge pull request #46983 from rohitwaghchaure/fixed-stock-ageing-report
perf: stock ageing report generation
2025-04-10 11:03:40 +05:30
Rohit Waghchaure
7a74dac2c2 perf: stock ageing report generation 2025-04-10 10:10:09 +05:30
Rohit Waghchaure
a3d4d34454 fix: serial no validation for stock reconciliation 2025-04-09 17:14:40 +05:30
barredterra
443ed5b2ce chore: add missing german translation
(cherry picked from commit d94ebd0c78)
2025-04-08 14:12:41 +00:00
barredterra
033fa09eb4 fix: remove redundant letter head
(cherry picked from commit 7896f8a855)
2025-04-08 14:12:41 +00:00
barredterra
056cc35379 fix: go for lower case "on" because we already have translations for that
(cherry picked from commit 7cf83ffce7)
2025-04-08 14:12:40 +00:00
barredterra
f1a864349e fix: make report's "printed on" translatable
(cherry picked from commit 18e9a9881c)
2025-04-08 14:12:40 +00:00
mergify[bot]
18282b2632 chore: fix german translations (backport #46912) (#46914)
* chore: fix german translations (#46912)

(cherry picked from commit 8276e8e8b3)

# Conflicts:
#	erpnext/translations/de.csv

* chore: resolve conflicts

* chore: delete duplicate translations

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-08 13:16:35 +02:00
ruthra kumar
46115d6f71 Merge pull request #46937 from frappe/mergify/bp/version-14-hotfix/pr-46631
fix: update outstanding for self (backport #46631)
2025-04-08 16:21:45 +05:30
ruthra kumar
f62905f7a7 chore: pass individual range 2025-04-08 15:53:30 +05:30
ruthra kumar
88facb7523 refactor: pass both doctype and name 2025-04-08 15:13:59 +05:30
ruthra kumar
512877ab46 chore: resolve conflicts 2025-04-08 14:34:59 +05:30
Bhavan23
c7e6b2356f test: add unit test to validate outstanding amount for update_outstanding_for_self checkbox enabled
(cherry picked from commit 7b0882600a)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2025-04-08 08:59:05 +00:00
Bhavan23
8e2bfc6bcb fix(accounting): update outstanding amount based on update_outstanding_for_self
fix(accounting): against voucher has been already paid show proper message and update update_outstanding_for_self as 1

(cherry picked from commit 222f1834f1)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2025-04-08 08:59:04 +00:00
ruthra kumar
98260b990c Merge pull request #46931 from frappe/mergify/bp/version-14-hotfix/pr-46821
fix: removed customer_group query in customer.js (backport #46821)
2025-04-08 13:10:04 +05:30
ljain112
3be0f00b3b fix: removed customer_group query in customer.js
(cherry picked from commit f49adfdd98)
2025-04-08 07:28:33 +00:00
ruthra kumar
f44478db78 Merge pull request #46924 from frappe/mergify/bp/version-14-hotfix/pr-46709
fix: user permissions in sales and purchase report (backport #46709)
2025-04-08 11:53:08 +05:30
ruthra kumar
db8cb70bd8 chore: resolve conflict 2025-04-08 11:13:44 +05:30
ljain112
f26b22ee7f fix: user permissions in sales and purchase report
(cherry picked from commit f4bc1dfd00)

# Conflicts:
#	erpnext/accounts/report/purchase_register/purchase_register.py
2025-04-08 05:40:47 +00:00
ruthra kumar
1e4807f16d Merge pull request #46920 from frappe/mergify/bp/version-14-hotfix/pr-46804
fix: update outstanding with precision (backport #46804)
2025-04-08 11:07:45 +05:30
ljain112
698d5be840 fix: update outstanding with precision
(cherry picked from commit aadda9f606)
2025-04-08 04:29:31 +00:00
ruthra kumar
59e46e22d3 Merge pull request #46904 from frappe/mergify/bp/version-14-hotfix/pr-46895
fix: empty party filter on change of party type in General Ledger Report (backport #46895)
2025-04-07 17:55:56 +05:30
ljain112
14b4d147a8 fix: empty party filter on change of party type in General Ledger Report.
(cherry picked from commit 9c68bc22fa)
2025-04-07 12:12:55 +00:00
ruthra kumar
2bed1c8336 Merge pull request #46901 from frappe/mergify/bp/version-14-hotfix/pr-46900
fix: remove against_voucher and against_voucher_type column from General Ledger Report (backport #46900)
2025-04-07 17:38:42 +05:30
ljain112
9e3a04136c fix: remove against_voucher from General Ledger Report
(cherry picked from commit 6d1f119a0f)
2025-04-07 17:14:49 +05:30
ruthra kumar
dcc53cea55 Merge pull request #46896 from frappe/mergify/bp/version-14-hotfix/pr-46728
fix: update posting date before running validations (backport #46728)
2025-04-07 14:54:37 +05:30
Dany Robert
430d1e8b2e fix: update posting date before running validations
(cherry picked from commit d04dbd8ed9)
2025-04-07 09:01:56 +00:00
ruthra kumar
36a366d962 Merge pull request #46890 from frappe/mergify/bp/version-14-hotfix/pr-46637
fix(payment term): allocate payment amount when payment term is fetched from order (backport #46637)
2025-04-07 11:25:58 +05:30
venkat102
dda35b8e51 fix: update payment amount if automatically_fetch_payment_terms is enabled
(cherry picked from commit 7bf1a39861)
2025-04-07 05:34:19 +00:00
venkat102
4e0d7d88ec test: validate payment schedule based on invoice amount
(cherry picked from commit 7785296573)
2025-04-07 05:34:19 +00:00
venkat102
36b951d018 fix(payment term): allocate payment amount when payment term is fetched from order
(cherry picked from commit 5618859bd8)
2025-04-07 05:34:18 +00:00
mergify[bot]
233e0d8049 fix: use grand_total_diff instead of rounding_adjustment in taxes_and_totals (backport #46829)
* fix: use `grand_total_diff` instead of `rounding_adjustment` in `taxes_and_totals`

(cherry picked from commit fd252da6b1)

* test: ensure correct grand total

---------

Co-authored-by: vishakhdesai <vishakhdesai@gmail.com>
Co-authored-by: Sagar Vora <sagar@resilient.tech>
2025-04-06 09:11:06 +05:30
mergify[bot]
5f467be0c8 fix: Translate UnReconcile dialog title (backport #46818) (#46862)
fix: Translate UnReconcile dialog title

(cherry picked from commit f2cfb03c2c)

Co-authored-by: Corentin Forler <corentin@dokos.io>
2025-04-05 17:37:16 +02:00
mergify[bot]
70d117e858 fix: make message translatable (backport #46863) (#46865)
fix: make message translatable (#46863)

(cherry picked from commit 7d12e9afd4)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-05 17:36:16 +02:00
Khushi Rawat
2fe9fa7ef7 feat: asset filter in asset depreciation and balances report (#46848) 2025-04-04 10:39:16 +05:30
Sagar Vora
b4ca51ad4c Merge pull request #46813 from frappe/mergify/bp/version-14-hotfix/pr-46812
fix: revert resetting `rounding_adjustment` (backport #46812)
2025-03-31 15:56:27 +05:30
Vishakh Desai
fb5777cf10 Merge pull request #46812 from vishakhdesai/fix-taxes-and-totals
fix: revert resetting `rounding_adjustment`
(cherry picked from commit 3a9dca0563)
2025-03-31 10:25:00 +00:00
mergify[bot]
06c32993ea fix: improved rounding adjustment when applying discount (backport #46720)
* fix: improved rounding adjustment when applying discount (#46720)

* fix: rounding adjustment in apply_discount_amount taxes_and_totals

* refactor: minor changes

* fix: set the rounding difference while calculating tax total in the last tax row and add test case

* fix: failing test case

* fix: made changes in get_total_for_discount_amount in taxes_and_totals

* fix: failing test cases

* fix: changes as per review

* refactor: remove unnecessary use of flt

* refactor: improve logic

* refactor: minor change

* refactor: minor changes

* fix: add a test case for applying discount with previous row total in taxes

* fix: failing test case

* refactor: flatter code, remove `flt` usage for accuracy

---------

Co-authored-by: Vishakh Desai <78500008+vishakhdesai@users.noreply.github.com>
Co-authored-by: Sagar Vora <sagar@resilient.tech>
2025-03-31 15:54:09 +05:30
rohitwaghchaure
466c6ee3d4 Merge pull request #46780 from frappe/mergify/bp/version-14-hotfix/pr-46777
fix: incorrect condition (backport #46777)
2025-03-29 12:17:59 +05:30
rohitwaghchaure
a8831351e3 chore: fix conflicts 2025-03-29 12:16:17 +05:30
Rohit Waghchaure
d1f9444be7 fix: incorrect condition
(cherry picked from commit 0c1a8e9c58)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2025-03-28 18:19:41 +00:00
rohitwaghchaure
9cadb89678 Merge pull request #46748 from frappe/revert-46646-mergify/bp/version-14-hotfix/pr-46641
Revert "perf: timeout while renaming cost center (backport #46641)"
2025-03-27 12:16:56 +05:30
rohitwaghchaure
da6affbba7 Revert "perf: timeout while renaming cost center (backport #46641)" 2025-03-27 11:57:24 +05:30
Sagar Vora
6293b08540 Merge pull request #46729 from vishakhdesai/bank-clearance-fix-v14-backport
fix: don't filter payment entries on Bank Account in Payment Clearance (backport #46669)
2025-03-26 13:00:57 +05:30
vishakhdesai
576ce7e882 fix: don't filter payment entries on Bank Account in Payment Clearance 2025-03-26 11:41:35 +05:30
ruthra kumar
15bb1fdb24 Merge pull request #46712 from frappe/mergify/bp/version-14-hotfix/pr-46616
fix: do not validate if conversion rate is 1 for different currency (backport #46616)
2025-03-25 16:06:08 +05:30
ljain112
509c5c4d17 fix: removed test case 2025-03-25 15:22:19 +05:30
ruthra kumar
cb028b8740 chore: resolve conflict 2025-03-25 14:26:14 +05:30
ljain112
c3447c030a fix: do not validate if conversion rate is 1 for different currency
(cherry picked from commit e8a66d03bc)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2025-03-25 08:42:17 +00:00
ruthra kumar
1d65f995e7 Merge pull request #46646 from frappe/mergify/bp/version-14-hotfix/pr-46641
perf: timeout while renaming cost center (backport #46641)
2025-03-25 13:27:09 +05:30
ruthra kumar
3a6d8cd9a1 Merge pull request #46704 from frappe/mergify/bp/version-14-hotfix/pr-46622
feat: repost accounting ledger for purchase receipt (backport #46622)
2025-03-25 12:13:50 +05:30
ljain112
88e664b79f feat: repost accounting ledger for purchase receipt
(cherry picked from commit b36e356469)
2025-03-25 06:19:33 +00:00
ruthra kumar
090ee7f042 Merge pull request #46690 from IMS94/fix-46686-so-update-items
fix: assign dialog instance to a variable in update_child_items function
2025-03-25 11:18:45 +05:30
mergify[bot]
b311b6eb7f ci: apply label "skip-release-notes" based on PR title (backport #46694) (#46696)
ci: apply label "skip-release-notes" based on PR title (#46694)

Workflow copied from frappe/frappe

(cherry picked from commit eb350012b0)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-03-24 16:55:07 +01:00
Imesha Sudasingha
df64d2ef4e fix: fix lint issues with trailing whitespaces 2025-03-24 12:50:48 +00:00
Imesha Sudasingha
690a939572 fix: assign dialog instance to a variable in update_child_items function
`dialog` is being referred in `onchange` handlers of the fields. Without this fix, they are failing because `dialog` is not defined.
2025-03-24 12:42:42 +00:00
ruthra kumar
a3639b055c Merge pull request #46676 from frappe/mergify/bp/version-14-hotfix/pr-46577
fix: customer credit limit check based on `bypass_credit_limit_check` in Journal Entry (backport #46577)
2025-03-24 13:58:00 +05:30
ljain112
331ecc1964 fix: customer credit limit check based on bypass_credit_limit_check in Journal Entry
(cherry picked from commit 8a84faebed)
2025-03-24 08:01:38 +00:00
rohitwaghchaure
736b125e14 chore: fix conflicts 2025-03-22 20:49:56 +05:30
Rohit Waghchaure
b9b08c35ef perf: timeout while renaming cost center
(cherry picked from commit 92be7cbbbf)

# Conflicts:
#	erpnext/accounts/doctype/gl_entry/gl_entry.json
2025-03-21 12:38:52 +00:00
rohitwaghchaure
300f1a860b Merge pull request #46609 from rohitwaghchaure/fixed-support-33137
fix: order by condition
2025-03-19 15:36:43 +05:30
rohitwaghchaure
1441ef2532 Merge pull request #46610 from frappe/mergify/bp/version-14-hotfix/pr-46595
fix: not able to make PR against stand alone Debit Note (backport #46595)
2025-03-19 15:36:15 +05:30
rohitwaghchaure
64fd5c0ba9 chore: fix conflicts 2025-03-19 15:31:05 +05:30
Rohit Waghchaure
16fe53b8c7 fix: not able to make PR against stand alone Debit Note
(cherry picked from commit 6a52c30591)

# Conflicts:
#	erpnext/public/js/controllers/buying.js
2025-03-19 08:05:24 +00:00
Rohit Waghchaure
af3089b96e fix: order by condition 2025-03-19 13:20:56 +05:30
rohitwaghchaure
3cb29fc035 Merge pull request #46583 from frappe/mergify/bp/version-14-hotfix/pr-46575
fix: fetch quality inspection parameter group (backport #46575)
2025-03-18 17:28:54 +05:30
rohitwaghchaure
8a93057844 Merge pull request #46586 from frappe/mergify/bp/version-14-hotfix/pr-46576
fix: repost future sle and gle after capitalization (backport #46576)
2025-03-18 17:28:15 +05:30
Khushi Rawat
805549f793 fix: repost future sle and gle after capitalization (#46576)
(cherry picked from commit 29d77aa19f)
2025-03-18 10:31:48 +00:00
Mihir Kandoi
6e497f73f1 fix: fetch quality inspection parameter group
(cherry picked from commit 0a482c7ea8)
2025-03-18 10:27:29 +00:00
ruthra kumar
33f090a8c8 Merge pull request #46561 from frappe/mergify/bp/version-14-hotfix/pr-46557
fix(Transaction Deletion Record): sql syntax error while deleting lead address (backport #46557)
2025-03-17 17:00:49 +05:30
venkat102
20b43b4d93 fix(Transaction Deletion Record): sql syntax error while fetching lead address
(cherry picked from commit af0d6eeae8)
2025-03-17 11:08:38 +00:00
ruthra kumar
5c1f6f0107 Merge pull request #46511 from aerele/fix-v14/item-stock-difference-account
fix: set stock adjustment account in difference account (backport #45606)
2025-03-14 09:23:52 +05:30
Bhavan23
6f0c67a242 fix: set stock adjustment account in difference account 2025-03-13 17:01:55 +05:30
Shanuka Hewage
f311a0fc1c Fix: add parenttype condition to payment schedule query in accounts receivable report (#46370)
fix: add parenttype condition to payment schedule query in accounts receivable report
2025-03-12 16:07:07 +05:30
mergify[bot]
a1585b3c53 ci: ignore PRs labeled with "skip-release-notes" when generating release notes (backport #46453) (#46456)
ci: ignore PRs labeled with "skip-release-notes" when generating release notes

(cherry picked from commit 57007bf937)

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-03-12 10:33:07 +05:30
ruthra kumar
05c0bf5d99 Merge pull request #46449 from frappe/mergify/bp/version-14-hotfix/pr-46398
chore: rename print and stationery account (backport #46398)
2025-03-11 18:12:08 +05:30
chethank1407
247a006b5a chore: rename print and stationery account
(cherry picked from commit 615997b774)
2025-03-11 12:03:21 +00:00
ruthra kumar
e0cf6393ee Merge pull request #46438 from frappe/mergify/bp/version-14-hotfix/pr-46372
fix(account): update account number from parent company (backport #46372)
2025-03-11 11:24:35 +05:30
venkat102
1f54c272ac fix(account): update account number from parent company
(cherry picked from commit 4a4894bc01)
2025-03-11 05:32:05 +00:00
rohitwaghchaure
9a1321ab02 Merge pull request #46414 from rohitwaghchaure/fixed-support-32397
fix: not able to save work order with alternative item
2025-03-10 17:38:37 +05:30
Rohit Waghchaure
6ca1f9bc73 fix: not able to save work order with alternative item 2025-03-10 12:46:45 +05:30
mergify[bot]
b3b7e62a90 fix: consolidate gl entries by project in General Ledger Report (backport #46314) (#46320)
fix: consolidate gl entries by project in General Ledger Report (#46314)

(cherry picked from commit 1f685efcaf)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-03-05 16:18:34 +05:30
rohitwaghchaure
35a728d22b Merge pull request #46316 from rohitwaghchaure/fixed-support-31024-v14
fix: incorrect batch picked in the pick list
2025-03-05 15:43:04 +05:30
Rohit Waghchaure
ac25d3e1c4 fix: incorrect batch picked in the pick list 2025-03-05 14:29:22 +05:30
mergify[bot]
90b5f0b7bf fix: only include submitted docs for internal received quantity validation (backport #46262) (#46303)
fix: only include submitted docs for internal received quantity validation (#46262)

(cherry picked from commit 88fcdbb81e)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-03-05 13:17:17 +05:30
mergify[bot]
557a05b0ad fix: Add permission check in POS's Toggle Recent Orders (backport #46010) (#46273)
* fix: use get_list to check permissions

(cherry picked from commit a08bc6b913)

# Conflicts:
#	erpnext/selling/page/point_of_sale/point_of_sale.py

* fix: resolve conflicts

---------

Co-authored-by: Sanket322 <shahsanket322003.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
2025-03-05 12:04:39 +05:30
mergify[bot]
b41ee667b9 perf: don't track seen for POS Invoice (backport #46187) (#46188)
* perf: don't track seen for POS Invoice (#46187)

This is a moving doctype. Do people even browse the list view?

It doesn't make much sense, either. POS INvoices are rarely "reviewed" by multiple users.

(cherry picked from commit ded0aab680)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-05 12:03:27 +05:30
mergify[bot]
fbf6d8c9e2 chore: erpnext.com -> frappe.io/erpnext (backport #46288) (#46289)
* chore: erpnext.com -> frappe.io/erpnext (#46288)

(cherry picked from commit 41fe30ea6e)

# Conflicts:
#	README.md
#	pyproject.toml

* Update README.md

* Update pyproject.toml

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-05 11:37:49 +05:30
mergify[bot]
167069b823 fix: don't allow renaming account while system is actively in use (backport #46176) (#46209)
* fix: don't allow renaming account while system is actively in use (#46176)

(cherry picked from commit 999f1cf96d)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-05 10:37:56 +05:30
Lakshit Jain
2d72a37d0c Merge pull request #46275 from frappe/mergify/bp/version-14-hotfix/pr-46039
fix: Ensure new line is added regardless of postal code presence (backport #46039)
2025-03-04 19:14:28 +05:30
Lakshit Jain
77ad224cf6 Merge pull request #46267 from frappe/mergify/bp/version-14-hotfix/pr-45896
fix: auto allocation for negative amount outstanding for Customers in Payment Entry (backport #45896)
2025-03-04 18:17:28 +05:30
Lakshit Jain
b5b55fad6c Merge pull request #46269 from frappe/mergify/bp/version-14-hotfix/pr-46260
fix: do not include opening invoices in billed items to be received report (backport #46260)
2025-03-04 18:16:57 +05:30
Sanket322
7cde990d69 refactor: add new line ragardless of postal code
(cherry picked from commit 746adfd057)
2025-03-04 12:36:40 +00:00
ljain112
cedf577b4c fix: do not include opening invoices in billed items to be received report
(cherry picked from commit c1ddf444c6)
2025-03-04 11:24:08 +00:00
ljain112
8e02dcfcaa fix: auto allocation for negative amount outstanding for Customers in Payment Entry
(cherry picked from commit 6275b44a0b)
2025-03-04 11:20:12 +00:00
Smit Vora
399ed331e3 Merge pull request #46247 from frappe/mergify/bp/version-14-hotfix/pr-46192
fix: Exclude Cancelled GL Entries (backport #46192)
2025-03-04 16:39:47 +05:30
ruthra kumar
59236bc5bf Merge pull request #46265 from frappe/mergify/bp/version-14-hotfix/pr-45751
fix: change voucher_type and voucher_name field type to data (backport #45751)
2025-03-04 16:12:16 +05:30
ruthra kumar
1089cdf213 chore: resolve conflicts 2025-03-04 15:46:05 +05:30
Sugesh393
df4f4d9a31 fix: change voucher_type and voucher_no field type to data
(cherry picked from commit f8ab021920)

# Conflicts:
#	erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
#	erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py
2025-03-04 10:10:04 +00:00
mergify[bot]
f8bbcab3a5 fix(patch): Ensure SLE indexes (backport #46131) (#46134)
* fix(patch): Ensure SLE indexes (#46131)

Because of the way this change was pushed in parts, some sites don't see
this as "update" and don't have the new indexes.

(cherry picked from commit f62aa8fc57)

# Conflicts:
#	erpnext/patches.txt

* fix: resolved conflict

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-03 21:36:57 +05:30
Ninad1306
369a692af9 fix: exclude cancelled gl entries
(cherry picked from commit 3251a331dd)
2025-03-03 11:31:08 +00:00
rohitwaghchaure
8cc92e9ca0 Merge pull request #46241 from frappe/mergify/bp/version-14-hotfix/pr-46239
fix: incorrectly billed amount in the purchase receipt (backport #46239)
2025-03-03 16:16:38 +05:30
rohitwaghchaure
eebf6cf877 chore: fix conflicts 2025-03-03 14:48:07 +05:30
Rohit Waghchaure
ca94ad3a24 fix: incorrectly billed amount in the purchase receipt
(cherry picked from commit a5271fdb2e)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2025-03-03 09:15:48 +00:00
rohitwaghchaure
1a8bf0cf3d Merge pull request #46040 from frappe/mergify/bp/version-14-hotfix/pr-46037
fix: inventory dimension for maintenance visit (backport #46037)
2025-02-28 14:53:56 +05:30
rohitwaghchaure
811953b9b0 chore: fix conflicts 2025-02-23 19:47:06 +05:30
rohitwaghchaure
6748780591 Merge pull request #46027 from frappe/mergify/bp/version-14-hotfix/pr-46021
fix: incorrect stock value difference for adjustment entry (backport #46021)
2025-02-20 13:06:07 +05:30
Rohit Waghchaure
69a8e0dfac fix: inventory dimension for maintence visit
(cherry picked from commit cd4ba69262)

# Conflicts:
#	erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
2025-02-20 06:13:14 +00:00
Akhil Narang
3e21e343d3 Merge pull request #46024 from frappe/mergify/bp/version-14-hotfix/pr-46003
fix(send_message): escape HTML in the text (backport #46003)
2025-02-19 16:43:09 +05:30
Rohit Waghchaure
c734373c9f fix: incorrect stock value difference for adjustment entry
(cherry picked from commit df83e427a3)
2025-02-19 11:11:09 +00:00
Akhil Narang
703fd816d1 fix(send_message): escape HTML in the text
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
(cherry picked from commit 448a5db20f)
2025-02-19 10:45:28 +00:00
rohitwaghchaure
101c71c508 Merge pull request #46004 from frappe/mergify/bp/version-14-hotfix/pr-45750
feat: added option to enforce free item qty in pricing rule (backport #45750)
2025-02-19 15:45:14 +05:30
Steve Wilson
a0cd08e9ea feat: added ability to use custom html format for process statement of accounts (#45746)
* feat: added ability to use custom print format for process statement of accounts documents.

* fix: handles missing hook issues

* chore: linter changes

---------

Co-authored-by: Boy4099 <mashtawayne4099@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-02-19 15:19:36 +05:30
rohitwaghchaure
57a0717778 chore: fix conflicts 2025-02-19 15:12:08 +05:30
rohitwaghchaure
0cf9c94a37 chore: fix conflicts 2025-02-19 15:10:34 +05:30
ruthra kumar
e48a03f130 Merge pull request #46009 from frappe/mergify/bp/version-14-hotfix/pr-45904
fix: fetch child account data for selected parent (backport #45904)
2025-02-19 14:21:34 +05:30
Bhavansathru
e7d97865e5 fix: fetch child account data for selected parent (#45904)
* fix: fetch child account data for selected parent

* fix: change reference name

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
(cherry picked from commit 73e82b7afa)
2025-02-19 08:29:00 +00:00
Mihir Kandoi
4b16272a01 fix: set default value to 0 as per new logic
(cherry picked from commit 844f1636c0)
2025-02-19 07:21:39 +00:00
Mihir Kandoi
1ff0edd492 refactor: rename field
(cherry picked from commit f3d598881c)
2025-02-19 07:21:39 +00:00
Mihir Kandoi
ef37388993 fix: add is_new in if condition
(cherry picked from commit 4dcac56486)
2025-02-19 07:21:39 +00:00
Mihir Kandoi
116798df96 test: added test
(cherry picked from commit ac3259b8f1)
2025-02-19 07:21:38 +00:00
Mihir Kandoi
e515b91988 fix: tests
(cherry picked from commit 366ae85d85)

# Conflicts:
#	erpnext/stock/doctype/pick_list/test_pick_list.py
2025-02-19 07:21:38 +00:00
Mihir Kandoi
2edf083c35 feat: added option to enforce free item qty in pricing rule
(cherry picked from commit 19c01b1457)

# Conflicts:
#	erpnext/accounts/doctype/pricing_rule/pricing_rule.py
2025-02-19 07:21:38 +00:00
rohitwaghchaure
86ddabeae6 Merge pull request #45985 from rohitwaghchaure/fixed-support-31935-1
fix: millisecond issue for posting datetime
2025-02-18 20:11:28 +05:30
Rohit Waghchaure
fc6f568a6c fix: millisecond issue for posting datetime 2025-02-18 18:54:17 +05:30
rohitwaghchaure
cd3a411401 Merge pull request #45931 from rohitwaghchaure/fixed-support-31345-1
fix: serial no is mandatory for zero qty validation
2025-02-15 22:01:40 +05:30
Rohit Waghchaure
6f6133f2e2 fix: serial no is mandatory for zero qty validation 2025-02-15 15:34:37 +05:30
rohitwaghchaure
5a8b81409e Merge pull request #45920 from rohitwaghchaure/fixed-support-31326
fix: on selection of batch qty is not fetching
2025-02-14 17:34:21 +05:30
Rohit Waghchaure
44a16bb544 fix: on selection of batch qty is not fetching 2025-02-14 17:26:50 +05:30
ruthra kumar
206d0f1856 Merge pull request #45918 from frappe/mergify/bp/version-14-hotfix/pr-45912
fix: include missing payment_gateway parameter in Payment Request URL (backport #45912)
2025-02-14 17:13:20 +05:30
Diptanil Saha
84432fc035 fix: pos return validation on v14 (#45859)
fix: pos return validation v-14
2025-02-14 17:11:03 +05:30
Navin-S-R
a344b8b9ae fix: include missing payment_gateway parameter in Payment Request URL
(cherry picked from commit dbac8cfc94)
2025-02-14 11:20:17 +00:00
ruthra kumar
c65f421da9 Merge pull request #45893 from frappe/mergify/bp/version-14-hotfix/pr-45804
fix(report): add options to multiselectlist fields (backport #45804)
2025-02-13 14:27:22 +05:30
ruthra kumar
5c6028340f chore: resolve conflicts 2025-02-13 14:25:41 +05:30
venkat102
aa0ada9670 fix(report): add options to multiselectlist fields
(cherry picked from commit 8785342fce)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js
#	erpnext/accounts/report/gross_profit/gross_profit.js
#	erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
#	erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js
2025-02-13 08:46:55 +00:00
mergify[bot]
534b25c448 fix: '0' rate LDC's Invoice net totals should be ignored (backport #45639) (#45783)
* fix: '0' rate LDC's Invoice net totals should be ignored

(cherry picked from commit 325c4e3536)

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

* test: ldc @ 0 rate

(cherry picked from commit 0cdd346f8f)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py

* chore: resolve conflicts

* fix: incorrect parameters

* fix: ignore 0 rate ldc invoices

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-02-13 13:19:34 +05:30
rohitwaghchaure
e53a78c2bd Merge pull request #45883 from frappe/mergify/bp/version-14-hotfix/pr-45786
fix: skip warning for free items (backport #45786)
2025-02-12 15:16:15 +05:30
barredterra
2ed3bdcc2e fix: skip warning for free items
(cherry picked from commit 772776ad8a)
2025-02-12 09:20:04 +00:00
mergify[bot]
c61e4e2ddf fix: correct amt in account currency for lcv with manually distributed charges. (backport #45532) (#45863)
fix: correct amt in account currency for lcv with manually distributed charges.

(cherry picked from commit db38e7bf5a)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 16:05:35 +05:30
mergify[bot]
08ba77538b fix: do not allow "Finance Book" in Accounting Dimensions (backport #45696) (#45855)
fix: do not allow "Finance Book" in Accounting Dimensions

(cherry picked from commit a44be73a98)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 15:42:27 +05:30
mergify[bot]
de14bf1010 fix(regional): removed payment schedule validation in sales invoice for italy (backport #45852) (#45853)
fix(regional): removed payment schedule validation in sales invoice for italy (#45852)

(cherry picked from commit 494310293c)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-02-11 15:41:39 +05:30
mergify[bot]
46eba50c8c fix: Party name in Supplier Portal for Purchase Order (backport #45772) (#45857)
fix: Party name in Supplier Portal for Purchase Order

(cherry picked from commit fc8663421b)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 15:41:04 +05:30
mergify[bot]
c4358c049a fix: map project from rfq to supplier quotation (backport #45745) (#45827)
* fix: map project from rfq to supplier quotation

(cherry picked from commit d0479036bb)

* fix: add project field map from mr to rfq

(cherry picked from commit 8fa39bec61)

---------

Co-authored-by: HenningWendtland <156231187+HenningWendtland@users.noreply.github.com>
2025-02-11 14:52:21 +05:30
ruthra kumar
a43f1badd5 Merge pull request #45846 from frappe/mergify/bp/version-14-hotfix/pr-45792
fix: do not validate party against Receivable and Payable account for cancelled gl entries (backport #45792)
2025-02-11 12:53:20 +05:30
ljain112
9f79da0015 fix: do not validate party against Receivable and Payable account for cancelled gl entries
(cherry picked from commit 0809e00455)
2025-02-11 06:44:37 +00:00
ruthra kumar
ca9df9db07 Merge pull request #45843 from frappe/mergify/bp/version-14-hotfix/pr-45781
fix: Added Total Row for `Gross Profit` Report in Non-Grouped Invoices (backport #45781)
2025-02-11 10:55:37 +05:30
Sanket322
49074aa2fa fix: add total row in non_grouped_invoices
(cherry picked from commit 2d32ddacc3)
2025-02-11 04:51:32 +00:00
mergify[bot]
6b9dad7768 fix: pos numpad editable action buttons (backport #45823) (#45825)
fix: pos numpad editable action buttons (#45823)

(cherry picked from commit 0b9c28620f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-10 16:33:21 +05:30
ruthra kumar
9e36cac0d1 Merge pull request #45812 from frappe/mergify/bp/version-14-hotfix/pr-45793
fix: unable to remove image from employee (backport #45793)
2025-02-10 11:01:25 +05:30
ruthra kumar
256318bb1c chore: resolve conflict 2025-02-10 10:37:50 +05:30
Asmita Hase
91caca05bb fix: unable to remove image from employee
fix: employee image disappears when newly created user_id is linked to employee

(cherry picked from commit 0207d2d7b6)

# Conflicts:
#	erpnext/setup/doctype/employee/employee.json
2025-02-10 05:02:53 +00:00
ruthra kumar
ceb5997256 Merge pull request #45796 from frappe/mergify/copy/version-14-hotfix/pr-45770
Fix: Update `ctx` to `args` for compatibility. (copy #45770)
2025-02-07 19:50:15 +05:30
Sanket322
573ce645b2 fix: update ctx to args
(cherry picked from commit d4bc3d182f)
2025-02-07 14:15:35 +00:00
ruthra kumar
09cefd9d63 Merge pull request #45785 from frappe/mergify/bp/version-14-hotfix/pr-45327
perf: Ignore is_opening column in GL Queries (backport #45327)
2025-02-07 17:15:58 +05:30
ruthra kumar
222bd9351d chore: resolve conflicts 2025-02-07 15:39:21 +05:30
Deepesh Garg
9985a03f39 perf: Ignore is_opening column in GL Queries (#45327)
* perf: Ignore is_opening column in GL Queries

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

(cherry picked from commit 993f40fa43)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
2025-02-07 10:06:07 +00:00
ruthra kumar
b217a7ee3e Merge pull request #45736 from frappe/mergify/bp/version-14-hotfix/pr-45717
fix(Purchase Invoice): default payment terms template selected while duplicating (backport #45717)
2025-02-05 15:30:42 +05:30
ruthra kumar
49787b6d84 chore: resolve conflicts 2025-02-05 15:22:13 +05:30
Ejaaz Khan
95903c9f96 refactor: remove log
(cherry picked from commit bfc01441a0)
2025-02-05 09:47:58 +00:00
Ejaaz Khan
d0ff91b0e0 fix: payment schedule table is empty while duplicating record
(cherry picked from commit fb3f08a441)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
2025-02-05 09:47:58 +00:00
Ejaaz Khan
82cfafb610 fix: default payment terms template selected while duplicating
(cherry picked from commit 18127603fe)
2025-02-05 09:47:57 +00:00
ruthra kumar
d14acb4f58 Merge pull request #45729 from frappe/mergify/bp/version-14-hotfix/pr-45686
fix: allow multiple email ids (backport #45686)
2025-02-05 14:27:57 +05:30
ruthra kumar
202693d4c3 Merge pull request #45730 from frappe/mergify/bp/version-14-hotfix/pr-45447
fix(pos): add item in the existing item row when discount is applied (backport #45447)
2025-02-05 14:10:21 +05:30
venkat102
3c6ed0a565 fix(pos): add item in the existing item row when discount is applied
(cherry picked from commit bee2c04d0b)
2025-02-05 14:03:54 +05:30
Sudharsanan11
2f33f6bdf5 fix: check billing address
(cherry picked from commit 9950e4aa0c)
2025-02-05 08:19:59 +00:00
Sudharsanan11
9510758ce4 fix: allow multiple email ids
(cherry picked from commit 423decb93c)
2025-02-05 08:19:58 +00:00
ruthra kumar
f01765db6f Merge pull request #45722 from frappe/mergify/bp/version-14-hotfix/pr-45721
fix: closing stock balance report not generating (backport #45721)
2025-02-05 10:38:34 +05:30
Rohit Waghchaure
3f6beebeec fix: closing stock balance report not generating
(cherry picked from commit 47d1c3b5a3)
2025-02-05 03:16:09 +00:00
ruthra kumar
8a9d554c32 Merge pull request #45712 from frappe/mergify/bp/version-14-hotfix/pr-45582
fix: handling company in bank reconciliation tool (backport #45582)
2025-02-04 17:32:26 +05:30
Aayush Dalal
7823f1b06f fix: handling company in bank reconciliation tool (#45582)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit d1c927530e)
2025-02-04 11:45:02 +00:00
ruthra kumar
4d5becbd7c Merge pull request #45676 from frappe/mergify/bp/version-14-hotfix/pr-45674
fix: track employee changes (backport #45674)
2025-02-03 15:27:16 +05:30
Ankush Menat
43a5c33dbf fix: track employee changes (#45674)
closes https://github.com/frappe/erpnext/issues/45571

(cherry picked from commit 827afbfa2e)
2025-02-03 08:52:37 +00:00
ruthra kumar
50c26ba017 Merge pull request #45668 from frappe/mergify/bp/version-14-hotfix/pr-45619
feat: set bank account of company to default company bank account fro… (backport #45619)
2025-02-03 14:05:38 +05:30
ruthra kumar
7bfe0526a1 Merge pull request #45670 from frappe/mergify/bp/version-14-hotfix/pr-44790
refactor: configurable posting date for Exc Gain / Loss journal (backport #44790)
2025-02-03 14:04:56 +05:30
Shariq Ansari
235b38a3af Merge pull request #45672 from frappe/mergify/bp/version-14-hotfix/pr-45637
fix: renamed Comments Tab to Notes tab in Lead doctype (backport #45637)
2025-02-03 13:27:22 +05:30
Shariq Ansari
c6ed82a304 chore: resolved conflict 2025-02-03 13:25:10 +05:30
ruthra kumar
e9d934d378 chore: resolve conflicts 2025-02-03 13:24:27 +05:30
Shariq Ansari
0eddd1e2d7 fix: renamed Commments Tab to Notes tab in Lead doctype
(cherry picked from commit 018df3135a)

# Conflicts:
#	erpnext/crm/doctype/lead/lead.json
2025-02-03 07:53:25 +00:00
ruthra kumar
cc275318e3 refactor: support JE posting date in semi-auto reconciilation tool
(cherry picked from commit a71718883e)

# Conflicts:
#	erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json
#	erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py
2025-02-03 07:29:24 +00:00
ruthra kumar
620cdc2489 test: exc gain/loss posting date based on configuration
(cherry picked from commit 2f3281579a)

# Conflicts:
#	erpnext/controllers/tests/test_accounts_controller.py
2025-02-03 07:29:24 +00:00
ruthra kumar
ef6e264887 refactor: only apply configuration on normal payments
patch to update default value

(cherry picked from commit b2c3da135e)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
#	erpnext/patches.txt
2025-02-03 07:29:24 +00:00
ruthra kumar
5a62bd6e85 refactor: allow reconciliation date for exchange gain / loss
(cherry picked from commit 95af63e305)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
2025-02-03 07:29:23 +00:00
ruthra kumar
035139d4c7 refactor: configurable posting date for Exc Gain / Loss journal
(cherry picked from commit 5257413a93)
2025-02-03 07:29:23 +00:00
ruthra kumar
b5637c43fa refactor: configurable posting date for Exc Gain / Loss journal
(cherry picked from commit 3fbd2ca0d9)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
2025-02-03 07:29:23 +00:00
Mihir Kandoi
dac53074f2 feat: set bank account of company to default company bank account from masters
(cherry picked from commit ce7702cc19)
2025-02-03 06:31:55 +00:00
rohitwaghchaure
b6a6bced61 Merge pull request #45666 from frappe/mergify/bp/version-14-hotfix/pr-45621
fix: delivered button of purchase order (backport #45621)
2025-02-03 12:01:11 +05:30
rohitwaghchaure
7b3c35c167 chore: fix conflicts 2025-02-03 11:58:10 +05:30
Mihir Kandoi
83bce785ff fix: bind this to function
(cherry picked from commit 41649cf52d)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.js
2025-02-03 06:22:42 +00:00
mergify[bot]
0086656748 fix: Gross Profit Report with Correct Totals and Gross Margin (backport #45548) (#45597)
* fix: Gross Profit Report with Correct Totals and Gross Margin (#45548)

Co-authored-by: Sanket322 <shahsanket322003.com>
(cherry picked from commit aaf720ab61)

# Conflicts:
#	erpnext/accounts/report/gross_profit/test_gross_profit.py
#	erpnext/patches.txt

* fix: conflicts

---------

Co-authored-by: Sanket Shah <113279972+Sanket322@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
2025-01-31 16:10:25 +05:30
rohitwaghchaure
36b1c436ea Merge pull request #45618 from frappe/mergify/bp/version-14-hotfix/pr-45612
fix: posting_date to posting_datetime in stock related queries (backport #45612)
2025-01-31 14:43:40 +05:30
ruthra kumar
9f4b3e86b3 Merge pull request #45635 from frappe/mergify/bp/version-14-hotfix/pr-45615
fix: correct error message in payment entry (backport #45615)
2025-01-31 14:18:49 +05:30
ljain112
9f5d7e41ec fix: correct error message in payment entry
(cherry picked from commit 592704cfd0)
2025-01-31 07:27:37 +00:00
rohitwaghchaure
50aa4ed55a chore: fix conflicts 2025-01-31 12:01:45 +05:30
rohitwaghchaure
dee6e2b697 chore: fix conflicts 2025-01-31 10:38:08 +05:30
rohitwaghchaure
9217e919c3 chore: fix conflicts 2025-01-31 10:36:25 +05:30
Rohit Waghchaure
bf3d68e76d fix: posting_date to posting_datetime in stock related queries
(cherry picked from commit e61ab48145)

# Conflicts:
#	erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
#	erpnext/stock/report/stock_ledger/stock_ledger.py
2025-01-30 15:50:38 +00:00
ruthra kumar
39e82dfbc1 Merge pull request #45593 from frappe/mergify/bp/version-14-hotfix/pr-45569
fix: update voucher outstanding from payment ledger (backport #45569)
2025-01-29 16:33:00 +05:30
ljain112
a0155279e0 fix: update voucher outstanding from payment ledger
(cherry picked from commit dd77070351)
2025-01-29 10:42:17 +00:00
mergify[bot]
e682d2c9ae fix: get stock balance filtered by company for validating stock value in jv (backport #45549) (#45577)
* fix: get stock balance filtered by company for validating stock value in jv (#45549)

* fix: get stock balance filtered by company for validating stock value in jv

* test: error is raised  on validate

(cherry picked from commit 9f20854bd9)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/test_journal_entry.py

* fix: conflict

---------

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
2025-01-29 12:46:13 +05:30
mergify[bot]
1160df9350 fix: add multiple item issue in stock entry (backport #45544) (#45579)
fix: add multiple item issue in stock entry (#45544)

(cherry picked from commit 5a023dc8d4)

Co-authored-by: Ejaaz Khan <67804911+iamejaaz@users.noreply.github.com>
2025-01-29 12:45:48 +05:30
ruthra kumar
80ed2fb1fb Merge pull request #45538 from frappe/mergify/bp/version-14-hotfix/pr-45345
fix:  maintain existing discounts in get_pricing_rule_for_item (backport #45345)
2025-01-29 12:26:52 +05:30
ruthra kumar
9eda931b97 chore: resolve conflicts 2025-01-29 11:58:21 +05:30
ruthra kumar
30f001edea Merge pull request #45556 from frappe/mergify/bp/version-14-hotfix/pr-45125
fix: update fields on change of item code In `Update Items` of `Sales Order` (backport #45125)
2025-01-28 11:32:08 +05:30
Sanket Shah
8befe7f244 fix: update fields on change of item code In Update Items of Sales Order (#45125)
* fix: update fields on change of item code

* fix: minor update

* fix: set the new values always

* Revert "fix: set the new values always"

This reverts commit 44daa0a641.

---------

Co-authored-by: Sanket322 <shahsanket322003.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit 9933d3c8ff)
2025-01-28 05:58:26 +00:00
mergify[bot]
de531a81b6 chore: bump actions/cache to v4 (backport #45541) (#45545)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-01-27 15:57:05 +01:00
ruthra kumar
ecbeaaf533 Merge pull request #45533 from frappe/mergify/bp/version-14-hotfix/pr-45302
feat: add company level validation for accounting dimension (backport #45302)
2025-01-27 17:30:52 +05:30
ruthra kumar
befc16cc97 refactor(test): update test data 2025-01-27 16:12:18 +05:30
ruthra kumar
b8e4d80b4e chore: resolve conflicts 2025-01-27 16:12:18 +05:30
Sugesh393
9767dc61a6 chore: update variable names for improved readability
(cherry picked from commit 36bae55299)
2025-01-27 16:12:18 +05:30
Sugesh393
03068ab96c fix: set company related values
(cherry picked from commit 454067198e)
2025-01-27 16:12:12 +05:30
Sanket322
c484563bea fix: remove applied pricing rule
(cherry picked from commit 50223c6bec)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2025-01-27 10:32:53 +00:00
Sanket322
914f4bffea fix: use user defined discount amount or default
(cherry picked from commit e2a32b7257)
2025-01-27 10:32:52 +00:00
Sugesh393
2fb1aaa5c3 test: add new unit test for company validation in accounting dimension
(cherry picked from commit c94091d68f)

# Conflicts:
#	erpnext/controllers/tests/test_accounts_controller.py
2025-01-27 09:24:59 +00:00
Sugesh393
cca5fbd81a feat: add company level validation for accounting dimension
(cherry picked from commit 60efd3e219)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2025-01-27 09:24:59 +00:00
mergify[bot]
4d2352af00 fix: currency decimal on POS Past Order List (backport #45524) (#45526)
fix: currency decimal on POS Past Order List (#45524)

* fix: currency decimal on POS

* fix: removed precision

(cherry picked from commit 2ac8c92e7f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-27 13:21:34 +05:30
Diptanil Saha
24dc1bf1a3 fix: resolved pos return setting to default mode of payment instead of user selection (#45377) (#45436)
* fix: resolved pos return setting to default mode of payment instead of user selection

* refactor: removed console log statement

* refactor: moved get_payment_data to sales_and_purchase_return.py
2025-01-24 22:51:22 +05:30
mergify[bot]
c6bc928f50 fix: secure bulk transaction (backport #45386) (#45425)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: secure bulk transaction (#45386)
2025-01-24 17:19:17 +01:00
ruthra kumar
9518063a81 Merge pull request #45401 from frappe/mergify/bp/version-14-hotfix/pr-45129
fix: Wrong `bank_ac_no` filter + simplify logic in automatch (backport #45129)
2025-01-24 10:43:32 +05:30
mergify[bot]
452b205021 fix: fix creating documents from sales invoice (backport #45346) (#45407)
* fix: fix creating documents from sales invoice (#45346)

Co-authored-by: Meike Nedwidek <nedwidek@kk-software.de>
(cherry picked from commit 1758e125e0)

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

* fix: resolved conflict

---------

Co-authored-by: meike289 <63092915+meike289@users.noreply.github.com>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-01-23 18:19:32 +05:30
marination
35f801feda fix: Merge Conflicts 2025-01-23 13:28:21 +01:00
Shanuka Hewage
69464ab7ff fix: add condition to check if item is delivered by supplier in make_purchase_order_for_default_supplier() (#45370) 2025-01-23 17:57:04 +05:30
marination
b6b453ca5d fix: Wrong bank_ac_no filter + simplify convoluted logic
(cherry picked from commit 8521796811)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/auto_match_party.py
2025-01-23 11:13:05 +00:00
mergify[bot]
1622fc8728 fix: set preferred email in Employee via backend controller (backport #45320) (#45378)
fix: set preferred email in Employee via backend controller (#45320)

fix: set preferred email in Employee (backend)

Set "Preferred Email" for Employee via validate. Unset value when
prefered_contact_email is also unset.

(cherry picked from commit 4481ca83ff)

Co-authored-by: gavin <gavin18d@gmail.com>
2025-01-23 11:15:13 +05:30
HENRY Florian
ecdff8f320 fix: heatmap for Customer and Supplier not rendering (#44717) 2025-01-22 12:11:16 +05:30
ruthra kumar
0c7219159a Merge pull request #45359 from frappe/mergify/bp/version-14-hotfix/pr-45242
fix: include pos invoice in modifing key for returned item validation (backport #45242)
2025-01-21 16:58:13 +05:30
venkat102
431fa225e3 fix: include pos invoice in modifing key for returned item validation
(cherry picked from commit 2936139c79)
2025-01-21 11:06:02 +00:00
ruthra kumar
f27e35c8f4 Merge pull request #45351 from frappe/mergify/bp/version-14-hotfix/pr-44808
feat: Added difference_posting_date field in Sales Invoice Advance and Purchase Invoice Advance (backport #44808)
2025-01-21 14:48:27 +05:30
ruthra kumar
5fbffcbd7b Merge pull request #45311 from frappe/mergify/bp/version-14-hotfix/pr-45175
fix(Project): re-phrase welcome email (backport #45175)
2025-01-21 13:56:30 +05:30
ruthra kumar
bb949da334 Merge pull request #45313 from frappe/mergify/bp/version-14-hotfix/pr-45271
fix: round off tax withholding amount (backport #45271)
2025-01-21 13:55:19 +05:30
ruthra kumar
8764a321c7 chore: resolve conflicts 2025-01-21 12:57:10 +05:30
rs-rethik
49e3865265 fix: update query
(cherry picked from commit 854e37c05c)
2025-01-21 06:54:23 +00:00
rs-rethik
33a1da8194 refactor: convert sql query to query builder
(cherry picked from commit 2d58e845e6)
2025-01-21 06:54:23 +00:00
rs-rethik
52309fe0b6 test: add unit test to validate journal entry posting date
(cherry picked from commit c14a2d73bf)

# Conflicts:
#	erpnext/controllers/tests/test_accounts_controller.py
2025-01-21 06:54:23 +00:00
rs-rethik
0fdd6817a6 feat: use difference_posting_date for journal entry posting_date
(cherry picked from commit ff1d040a6e)
2025-01-21 06:54:22 +00:00
rs-rethik
17535095e2 feat: add difference_posting_date field
(cherry picked from commit 225e56cbca)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
#	erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
#	erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
#	erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
2025-01-21 06:54:22 +00:00
ruthra kumar
4d74597f94 Merge pull request #45336 from frappe/mergify/bp/version-14-hotfix/pr-45167
fix: validate linked sales person (backport #45167)
2025-01-20 12:10:39 +05:30
Sudharsanan11
f9420db3ca fix: validate linked sales person
(cherry picked from commit e614f07795)
2025-01-20 06:20:20 +00:00
rohitwaghchaure
8996685f44 Merge pull request #45326 from frappe/mergify/bp/version-14-hotfix/pr-44783
fix: do not reset picked items (backport #44783)
2025-01-19 15:07:00 +05:30
rohitwaghchaure
7046a01921 chore: fix test case 2025-01-19 14:22:52 +05:30
rohitwaghchaure
0b8cf3a369 chore: fix conflicts 2025-01-19 13:52:49 +05:30
Rohit Waghchaure
fe5de30256 fix: do not reset picked items
(cherry picked from commit 34a80bfcd3)

# Conflicts:
#	erpnext/stock/doctype/pick_list/test_pick_list.py
2025-01-18 10:15:22 +00:00
barredterra
20bb15167d revert: avoid change to translatable string 2025-01-17 14:38:27 +01:00
barredterra
1ccf30d97b chore: resolve confilcts 2025-01-17 14:38:03 +01:00
mergify[bot]
524a8d77f7 fix: pos search by term items price (backport #45006) (#45102)
* fix: load price list rate for pos search term

(cherry picked from commit 4b6cae156e)

# Conflicts:
#	erpnext/selling/page/point_of_sale/point_of_sale.py

* fix: load search term price with customer default price list

(cherry picked from commit 2beb485d77)

* chore: resolve conflict

---------

Co-authored-by: diptanilsaha <diptanil.dev@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-01-17 17:02:34 +05:30
Lakshit Jain
667e659e3f fix: round off tax withholding amount (#45271)
(cherry picked from commit ada272a29b)
2025-01-17 11:21:09 +00:00
Patrick Eißler
77e92b38eb fix(Project): re-phrase welcome email (#45175)
(cherry picked from commit 8d66142865)

# Conflicts:
#	erpnext/projects/doctype/project/project.py
2025-01-17 11:19:03 +00:00
ruthra kumar
a66d475b56 Merge pull request #45253 from frappe/mergify/bp/version-14-hotfix/pr-45001
fix: deduct tds on excess amount if checked (backport #45001)
2025-01-14 10:28:19 +05:30
ljain112
6a52f79cce fix: deduct tds on excess amount if checked
(cherry picked from commit a203e3ffaf)
2025-01-14 04:33:09 +00:00
mergify[bot]
49ffeccafa fix(Timesheet): ignore permissions when updating Task and Project (backport #45168) (#45171)
* fix(Timesheet): ignore permissions when updating Task and Project (#45168)

(cherry picked from commit 9e760e54a5)

# Conflicts:
#	erpnext/projects/doctype/timesheet/timesheet.py

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-01-13 12:08:26 +05:30
Nabin Hait
6bc210d9f4 fix: typo (#45233) 2025-01-13 12:04:40 +05:30
mergify[bot]
f4b7fa8980 feat: validate discount date in payment schedule (backport #44646) (#44726)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-01-09 18:51:27 +01:00
rohitwaghchaure
c331a4fa84 Merge pull request #45185 from frappe/mergify/bp/version-14-hotfix/pr-45180
fix: do not add ordered items from Quotation to new Sales Order (backport #45180)
2025-01-09 16:27:15 +05:30
Rohit Waghchaure
d42173beb5 fix: do not add ordered items from Quotation to new Sales Order
(cherry picked from commit 2e930eb97b)
2025-01-09 09:09:56 +00:00
ruthra kumar
b2d35fae10 Merge pull request #45156 from frappe/revert-45152-mergify/bp/version-14-hotfix/pr-45112
Revert "fix: Missing company filter breaks `get_account_balance` in Bank Reco (backport #45112)"
2025-01-08 09:01:37 +05:30
ruthra kumar
8d13ef050e Revert "fix: Missing company filter breaks get_account_balance in Bank Reco (backport #45112)" 2025-01-08 09:00:21 +05:30
ruthra kumar
8ac40f07e3 Merge pull request #45152 from frappe/mergify/bp/version-14-hotfix/pr-45112
fix: Missing company filter breaks `get_account_balance` in Bank Reco (backport #45112)
2025-01-08 08:59:22 +05:30
marination
46894a5b86 fix: Override pre-commit behaviour due to conflicts with CI
(cherry picked from commit d7bf73cffa)
2025-01-08 02:46:51 +00:00
marination
821cfe2c39 fix: Missing company filter breaks get_account_balance in Bank Reco
(cherry picked from commit 8de0fe78ea)
2025-01-08 02:46:51 +00:00
ruthra kumar
b8b76a5b58 Merge pull request #45122 from frappe/mergify/bp/version-14-hotfix/pr-45121
fix: discount resetting on date change (backport #45121)
2025-01-07 11:26:24 +05:30
ruthra kumar
01d2794968 fix: discount resetting on date change
revert #44989

(cherry picked from commit 886281f81a)
2025-01-07 05:51:46 +00:00
ruthra kumar
02cfb589a2 Merge pull request #45108 from frappe/mergify/bp/version-14-hotfix/pr-45107
fix: Returned Qty in Work Order Consumed Materials report (backport #45107)
2025-01-07 10:12:56 +05:30
ruthra kumar
caf5faceda Merge pull request #45119 from frappe/mergify/bp/version-14-hotfix/pr-45118
fix(Project): make status in confirmation dialog translatable (backport #45118)
2025-01-07 10:12:04 +05:30
Raffael Meyer
07653c54f3 fix(Project): make status in confirmation dialog translatable (#45118)
(cherry picked from commit 9eede907f8)
2025-01-06 22:13:47 +00:00
Rohit Waghchaure
affa67e74d fix: Returned Qty in Work Order Consumed Materials report
(cherry picked from commit 30d68a31e0)
2025-01-06 10:37:28 +00:00
mergify[bot]
2d63fc98d0 fix: update customer contact details on pos (backport #45071) (#45105)
fix: update customer contact details on pos (#45071)

* fix: update customer contact details on pos

* refactor: removed console log statement

(cherry picked from commit d79e561248)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-06 14:58:36 +05:30
ruthra kumar
8bb4415f65 Merge pull request #45079 from frappe/mergify/bp/version-14-hotfix/pr-44983
fix: add monthly distributation and write query in qb (backport #44983)
2025-01-06 10:28:45 +05:30
ruthra kumar
90b2ec9aba Merge pull request #42438 from trustedcomputer/version-14-hotfix
fix: header_img field schema (backport payments #83)
2025-01-04 05:20:58 +05:30
ruthra kumar
24ae74ebb3 refactor: store result in variable before enumeration
helps to inspect result while debugging

(cherry picked from commit b60bd17d1d)
2025-01-03 23:48:31 +00:00
Sanket322
7d1d0c8e0c fix: add monthly distributation and write query in qb
(cherry picked from commit 27195c7c96)
2025-01-03 23:48:30 +00:00
ruthra kumar
b3d545f91a Merge pull request #45057 from frappe/mergify/bp/version-14-hotfix/pr-45056
chore: partial revert #44989 (backport #45056)
2025-01-02 21:04:16 +05:30
ruthra kumar
7b0e19499b chore: partial revert #44989
(cherry picked from commit 63d547fb4a)
2025-01-02 15:27:38 +00:00
rohitwaghchaure
34504a23ec Merge pull request #45041 from frappe/mergify/bp/version-14-hotfix/pr-45039
fix: removed unused code (backport #45039)
2025-01-02 13:32:23 +05:30
rohitwaghchaure
b603adce5e chore: fix conflicts 2025-01-02 12:21:03 +05:30
Rohit Waghchaure
9efc1de40e fix: removed unused code
(cherry picked from commit dc5f2d35ac)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2025-01-02 06:47:37 +00:00
rohitwaghchaure
ff622ef552 Merge pull request #45037 from frappe/mergify/bp/version-14-hotfix/pr-45036
fix: Auto BOM cost update issue (backport #45036)
2025-01-02 12:15:37 +05:30
Rohit Waghchaure
49778432ea fix: BOM cost update issue
(cherry picked from commit 28ea3ddd51)
2025-01-02 04:55:15 +00:00
mergify[bot]
b7509e326e fix: slow stock transactions (backport #45025) (#45026)
* fix: slow stock transactions (#45025)

(cherry picked from commit e92af10f14)

# Conflicts:
#	erpnext/stock/stock_ledger.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-01-01 15:29:06 +05:30
trustedcomputer
eb5505187e fix: header_img field schema 2024-07-22 12:35:46 -07:00
165 changed files with 5234 additions and 2876 deletions

4
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
changelog:
exclude:
labels:
- skip-release-notes

View File

@@ -0,0 +1,30 @@
name: "Auto-label PRs based on title"
on:
pull_request_target:
types: [opened, reopened]
jobs:
add-label-if-prefix-matches:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check PR title and add label if it matches prefixes
uses: actions/github-script@v7
continue-on-error: true
with:
script: |
const title = context.payload.pull_request.title.toLowerCase();
const prefixes = ['chore', 'ci', 'style', 'test', 'refactor'];
// Check if the PR title starts with any of the prefixes
if (prefixes.some(prefix => title.startsWith(prefix))) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['skip-release-notes']
});
}

View File

@@ -59,7 +59,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -68,7 +68,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
@@ -83,7 +83,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -79,7 +79,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -88,7 +88,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
@@ -103,7 +103,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -66,7 +66,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -75,7 +75,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
@@ -90,7 +90,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _, throw
from frappe.utils import cint, cstr
from frappe.utils import add_to_date, cint, cstr, pretty_date
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
import erpnext
@@ -400,6 +400,7 @@ def validate_account_number(name, account_number, company):
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False):
_ensure_idle_system()
account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account:
return
@@ -420,7 +421,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
"name",
)
if old_name:
if old_name and not from_descendant:
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
@@ -461,6 +462,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
@frappe.whitelist()
def merge_account(old, new):
_ensure_idle_system()
# Validate properties before merging
new_account = frappe.get_cached_doc("Account", new)
old_account = frappe.get_cached_doc("Account", old)
@@ -514,3 +516,27 @@ def sync_update_account_number_in_child(
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)
def _ensure_idle_system():
# Don't allow renaming if accounting entries are actively being updated, there are two main reasons:
# 1. Correctness: It's next to impossible to ensure that renamed account is not being used *right now*.
# 2. Performance: Renaming requires locking out many tables entirely and severely degrades performance.
if frappe.flags.in_test:
return
try:
# We also lock inserts to GL entry table with for_update here.
last_gl_update = frappe.db.get_value("GL Entry", {}, "modified", for_update=True, wait=False)
except frappe.QueryTimeoutError:
# wait=False fails immediately if there's an active transaction.
last_gl_update = add_to_date(None, seconds=-1)
if last_gl_update > add_to_date(None, minutes=-5):
frappe.throw(
_(
"Last GL Entry update was done {}. This operation is not allowed while system is actively being used. Please wait for 5 minutes before retrying."
).format(pretty_date(last_gl_update)),
title=_("System In Use"),
)

View File

@@ -98,7 +98,7 @@
"Office Maintenance Expenses": {},
"Office Rent": {},
"Postal Expenses": {},
"Print and Stationary": {},
"Print and Stationery": {},
"Rounded Off": {
"account_type": "Round Off"
},

View File

@@ -31,7 +31,8 @@
"label": "Reference Document Type",
"options": "DocType",
"read_only_depends_on": "eval:!doc.__islocal",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"default": "0",

View File

@@ -25,6 +25,7 @@ class AccountingDimension(Document):
"Accounting Dimension Detail",
"Company",
"Account",
"Finance Book",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)

View File

@@ -44,6 +44,7 @@
"section_break_jpd0",
"auto_reconcile_payments",
"stale_days",
"exchange_gain_loss_posting_date",
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
@@ -72,6 +73,7 @@
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"ignore_is_opening_check_for_reporting",
"column_break_lvjk",
"receivable_payable_remarks_length"
],
@@ -383,7 +385,7 @@
{
"fieldname": "section_break_jpd0",
"fieldtype": "Section Break",
"label": "Payment Reconciliations"
"label": "Payment Reconciliation Settings"
},
{
"default": "0",
@@ -462,6 +464,21 @@
"fieldname": "remarks_section",
"fieldtype": "Section Break",
"label": "Remarks Column Length"
},
{
"default": "Payment",
"description": "Only applies for Normal Payments",
"fieldname": "exchange_gain_loss_posting_date",
"fieldtype": "Select",
"label": "Posting Date Inheritance for Exchange Gain / Loss",
"options": "Invoice\nPayment\nReconciliation Date"
},
{
"default": "0",
"description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports",
"fieldname": "ignore_is_opening_check_for_reporting",
"fieldtype": "Check",
"label": "Ignore Is Opening check for reporting"
}
],
"icon": "icon-cog",
@@ -469,7 +486,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-22 12:10:10.151819",
"modified": "2025-01-23 13:15:44.077853",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -498,4 +515,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -46,9 +46,6 @@ class BankClearance(Document):
as_dict=1,
)
if self.bank_account:
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
f"""
select
@@ -70,7 +67,6 @@ class BankClearance(Document):
"account": self.account,
"from": self.from_date,
"to": self.to_date,
"bank_account": self.bank_account,
},
as_dict=1,
)
@@ -93,7 +89,7 @@ class BankClearance(Document):
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.disbursement_date >= self.from_date)
.where(loan_disbursement.disbursement_date <= self.to_date)
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
.where(loan_disbursement.disbursement_account == self.account)
.orderby(loan_disbursement.disbursement_date)
.orderby(loan_disbursement.name, order=frappe.qb.desc)
)
@@ -121,7 +117,7 @@ class BankClearance(Document):
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.posting_date >= self.from_date)
.where(loan_repayment.posting_date <= self.to_date)
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
.where(loan_repayment.payment_account == self.account)
)
if not self.include_reconciled_entries:

View File

@@ -19,10 +19,15 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
if (!frm.doc.company) {
frm.set_value("company", frappe.defaults.get_default("company"));
}
// Set default filter dates
let today = frappe.datetime.get_today();
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger("bank_account");
},
@@ -94,7 +99,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
make_reconciliation_tool(frm) {
frm.get_field("reconciliation_tool_cards").$wrapper.empty();
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
frm.trigger("get_cleared_balance").then(() => {
if (
frm.doc.bank_account &&
@@ -110,7 +115,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
@@ -125,7 +130,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {

View File

@@ -45,42 +45,41 @@ class AutoMatchbyAccountIBAN:
if not (self.bank_party_account_number or self.bank_party_iban):
return None
result = self.match_account_in_party()
return result
return self.match_account_in_party()
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)
or_filters = self.get_or_filters()
"""
Returns (Party Type, Party) if a matching account is found in Bank Account or Employee:
1. Get party from a matching (iban/account no) Bank Account
2. If not found, get party from Employee with matching bank account details (iban/account no)
"""
if not (self.bank_party_account_number or self.bank_party_iban):
# Nothing to match
return None
for party in parties:
party_result = frappe.db.get_all(
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
)
# Search for a matching Bank Account that has party set
party_result = frappe.db.get_all(
"Bank Account",
or_filters=self.get_or_filters(),
filters={"party_type": ("is", "set"), "party": ("is", "set")},
fields=["party", "party_type"],
limit_page_length=1,
)
if result := party_result[0] if party_result else None:
return (result["party_type"], result["party"])
if party == "Employee" and not party_result:
# Search in Bank Accounts first for Employee, and then Employee record
if "bank_account_no" in or_filters:
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
# If no party is found, search in Employee (since it has bank account details)
if employee_result := frappe.db.get_all(
"Employee", or_filters=self.get_or_filters("Employee"), pluck="name", limit_page_length=1
):
return ("Employee", employee_result[0])
party_result = frappe.db.get_all(
party, or_filters=or_filters, pluck="name", limit_page_length=1
)
if party_result:
result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
def get_or_filters(self, party: str | None = None) -> dict:
"""Return OR filters for Bank Account and IBAN"""
or_filters = {}
if self.bank_party_account_number:
or_filters["bank_account_no"] = self.bank_party_account_number
bank_ac_field = "bank_ac_no" if party == "Employee" else "bank_account_no"
or_filters[bank_ac_field] = self.bank_party_account_number
if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban
@@ -100,8 +99,7 @@ class AutoMatchbyPartyNameDescription:
if not (self.bank_party_name or self.description):
return None
result = self.match_party_name_desc_in_party()
return result
return self.match_party_name_desc_in_party()
def match_party_name_desc_in_party(self) -> tuple | None:
"""Fuzzy search party name and/or description against parties in the system"""
@@ -110,7 +108,7 @@ class AutoMatchbyPartyNameDescription:
for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
field = party.lower() + "_name"
field = f"{party.lower()}_name"
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
for field in ["bank_party_name", "description"]:
@@ -137,13 +135,7 @@ class AutoMatchbyPartyNameDescription:
)
party_name, skip = self.process_fuzzy_result(result)
if not party_name:
return None, skip
return (
party,
party_name,
), skip
return ((party, party_name), skip) if party_name else (None, skip)
def process_fuzzy_result(self, result: list | None):
"""
@@ -161,8 +153,8 @@ class AutoMatchbyPartyNameDescription:
if len(result) == 1:
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1]
if first_result[SCORE] > CUTOFF:
second_result = result[1]
# If multiple matches with the same score, return None but discontinue matching
# Matches were found but were too close to distinguish between
if first_result[SCORE] == second_result[SCORE]:
@@ -174,8 +166,8 @@ class AutoMatchbyPartyNameDescription:
def get_parties_in_order(deposit: float) -> list:
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
if flt(deposit) > 0:
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
return parties
return (
["Customer", "Supplier", "Employee"] # most -> least likely to pay us
if flt(deposit) > 0
else ["Supplier", "Employee", "Customer"] # most -> least likely to receive from us
)

View File

@@ -460,13 +460,20 @@ def get_actual_expense(args):
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
if monthly_distribution:
for d in frappe.db.sql(
"""select mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year=%s""",
fiscal_year,
as_dict=1,
):
mdp = frappe.qb.DocType("Monthly Distribution Percentage")
md = frappe.qb.DocType("Monthly Distribution")
res = (
frappe.qb.from_(mdp)
.join(md)
.on(mdp.parent == md.name)
.select(mdp.month, mdp.percentage_allocation)
.where(md.fiscal_year == fiscal_year)
.where(md.name == monthly_distribution)
.run(as_dict=True)
)
for d in res:
distribution.setdefault(d.month, d.percentage_allocation)
dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date")

View File

@@ -1,9 +1,17 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
<<<<<<< HEAD
import unittest
import frappe
=======
import json
import frappe
from frappe.model import mapper
from frappe.tests import IntegrationTestCase, UnitTestCase
>>>>>>> 3b613c44a6 (chore: added test for `Fetch Overdue Payments` in dunning)
from frappe.utils import add_days, nowdate, today
from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
@@ -76,8 +84,88 @@ class TestDunning(unittest.TestCase):
pe.target_exchange_rate = 1
pe.insert()
pe.submit()
<<<<<<< HEAD
si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice)
self.assertEqual(si_doc.outstanding_amount, 0)
=======
for overdue_payment in dunning.overdue_payments:
outstanding_amount = frappe.get_value(
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
)
self.assertEqual(outstanding_amount, 0)
dunning.reload()
self.assertEqual(dunning.status, "Resolved")
def test_fetch_overdue_payments(self):
"""
Create SI with overdue payment. Check if overdue payment is fetched in Dunning.
"""
si1 = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -1 * 6),
qty=1,
rate=100,
)
si2 = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -1 * 6),
qty=1,
rate=300,
)
dunning = create_dunning_from_sales_invoice(si1.name)
dunning.overdue_payments = []
method = "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning"
updated_dunning = mapper.map_docs(method, json.dumps([si1.name, si2.name]), dunning)
self.assertEqual(len(updated_dunning.overdue_payments), 2)
self.assertEqual(updated_dunning.overdue_payments[0].sales_invoice, si1.name)
self.assertEqual(updated_dunning.overdue_payments[0].outstanding, si1.outstanding_amount)
self.assertEqual(updated_dunning.overdue_payments[1].sales_invoice, si2.name)
self.assertEqual(updated_dunning.overdue_payments[1].outstanding, si2.outstanding_amount)
def test_dunning_and_payment_against_partially_due_invoice(self):
"""
Create SI with first installment overdue. Check impact of Dunning and Payment Entry.
"""
create_payment_terms_template_for_dunning()
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -1 * 6),
qty=1,
rate=100,
do_not_submit=True,
)
sales_invoice.payment_terms_template = "_Test 50-50 for Dunning"
sales_invoice.submit()
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
self.assertEqual(len(dunning.overdue_payments), 1)
self.assertEqual(dunning.overdue_payments[0].payment_term, "_Test Payment Term 1 for Dunning")
dunning.submit()
pe = get_payment_entry("Dunning", dunning.name)
pe.reference_no, pe.reference_date = "2", nowdate()
pe.insert()
pe.submit()
sales_invoice.load_from_db()
dunning.load_from_db()
self.assertEqual(sales_invoice.status, "Partly Paid")
self.assertEqual(sales_invoice.payment_schedule[0].outstanding, 0)
self.assertEqual(dunning.status, "Resolved")
# Test impact on cancellation of PE
pe.cancel()
sales_invoice.reload()
dunning.reload()
self.assertEqual(sales_invoice.status, "Overdue")
self.assertEqual(dunning.status, "Unresolved")
>>>>>>> 3b613c44a6 (chore: added test for `Fetch Overdue Payments` in dunning)
def create_dunning():

View File

@@ -7,7 +7,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.model.naming import set_name_from_naming_options
from frappe.utils import flt, fmt_money
from frappe.utils import flt, fmt_money, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -83,7 +83,7 @@ class GLEntry(Document):
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
if not (self.party_type and self.party):
if not self.is_cancelled and not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type == "Receivable":
frappe.throw(
@@ -405,7 +405,7 @@ def rename_temporarily_named_docs(doctype):
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
newname = doc.name
frappe.db.sql(
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0 where name = %s",
(newname, oldname),
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
(newname, now(), oldname),
auto_commit=True,
)

View File

@@ -433,8 +433,22 @@ class JournalEntry(AccountsController):
if customers:
from erpnext.selling.doctype.customer.customer import check_credit_limit
customer_details = frappe._dict(
frappe.db.get_all(
"Customer Credit Limit",
filters={
"parent": ["in", customers],
"parenttype": ["=", "Customer"],
"company": ["=", self.company],
},
fields=["parent", "bypass_credit_limit_check"],
as_list=True,
)
)
for customer in customers:
check_credit_limit(customer, self.company)
ignore_outstanding_sales_order = bool(customer_details.get(customer))
check_credit_limit(customer, self.company, ignore_outstanding_sales_order)
def validate_cheque_info(self):
if self.voucher_type in ["Bank Entry"]:

View File

@@ -350,15 +350,25 @@ class PaymentEntry(AccountsController):
self.set(self.party_account_field, party_account)
self.party_account = party_account
if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance):
if self.paid_from and (
not self.paid_from_account_currency
or not self.paid_from_account_balance
or not self.paid_from_account_type
):
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
self.paid_from_account_currency = acc.account_currency
self.paid_from_account_balance = acc.account_balance
self.paid_from_account_type = acc.account_type
if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance):
if self.paid_to and (
not self.paid_to_account_currency
or not self.paid_to_account_balance
or not self.paid_to_account_type
):
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
self.paid_to_account_currency = acc.account_currency
self.paid_to_account_balance = acc.account_balance
self.paid_to_account_type = acc.account_type
self.party_account_currency = (
self.paid_from_account_currency
@@ -465,7 +475,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])
)
)
@@ -1519,7 +1529,7 @@ class PaymentEntry(AccountsController):
allocated_positive_outstanding = paid_amount + allocated_negative_outstanding
elif self.party_type in ("Supplier", "Employee"):
elif self.party_type in ("Supplier", "Customer"):
if paid_amount > total_negative_outstanding:
if total_negative_outstanding == 0:
frappe.msgprint(
@@ -2481,6 +2491,7 @@ def get_payment_entry(
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name")
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
pe.project = doc.get("project") or reduce(

View File

@@ -270,6 +270,7 @@ class PaymentReconciliation(Document):
for payment in non_reconciled_payments:
row = self.append("payments", {})
row.update(payment)
row.is_advance = payment.book_advance_payments_in_separate_party_account
def get_invoice_entries(self):
# Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
@@ -354,6 +355,9 @@ class PaymentReconciliation(Document):
def allocate_entries(self, args):
self.validate_entries()
exc_gain_loss_posting_date = frappe.db.get_single_value(
"Accounts Settings", "exchange_gain_loss_posting_date", cache=True
)
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
default_exchange_gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
@@ -380,6 +384,11 @@ class PaymentReconciliation(Document):
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 not pay.get("is_advance"):
if exc_gain_loss_posting_date == "Invoice":
res.update({"gain_loss_posting_date": inv.get("invoice_date")})
elif exc_gain_loss_posting_date == "Reconciliation Date":
res.update({"gain_loss_posting_date": nowdate()})
if pay.get("amount") == 0:
entries.append(res)

View File

@@ -246,6 +246,7 @@ class PaymentRequest(Document):
"payer_name": data.customer_name,
"order_id": self.name,
"currency": self.currency,
"payment_gateway": self.payment_gateway,
}
)

View File

@@ -1623,6 +1623,5 @@
"states": [],
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
"track_changes": 1
}

View File

@@ -53,6 +53,7 @@
"column_break_42",
"free_item_uom",
"round_free_qty",
"dont_enforce_free_item_qty",
"is_recursive",
"recurse_for",
"apply_recursion_over",
@@ -643,12 +644,19 @@
"fieldname": "has_priority",
"fieldtype": "Check",
"label": "Has Priority"
},
{
"default": "0",
"depends_on": "eval:doc.price_or_product_discount == 'Product'",
"fieldname": "dont_enforce_free_item_qty",
"fieldtype": "Check",
"label": "Don't Enforce Free Item Qty"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2024-09-16 18:14:51.314765",
"modified": "2025-02-17 18:15:39.824639",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -328,8 +328,6 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
"parent": args.parent,
"parenttype": args.parenttype,
"child_docname": args.get("child_docname"),
"discount_percentage": 0.0,
"discount_amount": 0,
}
)
@@ -555,7 +553,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
if pricing_rule.margin_type in ["Percentage", "Amount"]:
item_details.margin_rate_or_amount = 0.0
item_details.margin_type = None
elif pricing_rule.get("free_item"):
elif pricing_rule.get("free_item") and not pricing_rule.get("dont_enforce_free_item_qty"):
item_details.remove_free_item = (
item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item")
)

View File

@@ -428,6 +428,54 @@ class TestPricingRule(FrappeTestCase):
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2")
def test_dont_enforce_free_item_qty(self):
# this test is only for testing non-enforcement as all other tests in this file already test with enforcement
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
"min_qty": 0,
"max_qty": 7,
"discount_percentage": 17.5,
"price_or_product_discount": "Product",
"same_item": 0,
"free_item": "_Test Item 2",
"free_qty": 1,
"company": "_Test Company",
}
pricing_rule = frappe.get_doc(test_record.copy()).insert()
# With enforcement
so = make_sales_order(item_code="_Test Item", qty=1, do_not_submit=True)
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2")
# Test 1 : Saving a document with an item with pricing list without it's corresponding free item will cause it the free item to be refetched on save
so.items.pop(1)
so.save()
so.reload()
self.assertEqual(len(so.items), 2)
# Without enforcement
pricing_rule.dont_enforce_free_item_qty = 1
pricing_rule.save()
# Test 2 : Deleted free item will not be fetched again on save without enforcement
so.items.pop(1)
so.save()
so.reload()
self.assertEqual(len(so.items), 1)
def test_cumulative_pricing_rule(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Cumulative Pricing Rule")
test_record = {
@@ -1239,6 +1287,7 @@ def make_pricing_rule(**args):
"discount_amount": args.discount_amount or 0.0,
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
"has_priority": args.has_priority or 0,
"enforce_free_item_qty": args.dont_enforce_free_item_qty or 0,
}
)

View File

@@ -691,7 +691,10 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
args.pop((item.item_code, item.pricing_rules))
for free_item in args.values():
doc.append("items", free_item)
if doc.is_new() or not frappe.get_value(
"Pricing Rule", free_item["pricing_rules"], "dont_enforce_free_item_qty"
):
doc.append("items", free_item)
def get_pricing_rule_items(pr_doc, other_items=False) -> list:

View File

@@ -20,6 +20,7 @@
"is_advance",
"section_break_5",
"difference_amount",
"gain_loss_posting_date",
"column_break_7",
"difference_account",
"exchange_rate",
@@ -153,11 +154,16 @@
"fieldtype": "Check",
"in_list_view": 1,
"label": "Reconciled"
},
{
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
}
],
"istable": 1,
"links": [],
"modified": "2023-03-20 21:05:43.121945",
"modified": "2025-01-23 16:09:01.058574",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Payment Reconciliation Log Allocations",

View File

@@ -177,17 +177,21 @@ def get_ar_filters(doc, entry):
def get_html(doc, filters, entry, col, res, ageing):
base_template_path = "frappe/www/printview.html"
template_path = (
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
if doc.report == "General Ledger"
else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
)
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
if doc.report == "General Ledger":
template_path = (
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
)
process_soa_html = frappe.get_hooks("process_soa_html")
# fetching custom print format for Process Statement of Accounts
if process_soa_html and process_soa_html.get(doc.report):
template_path = process_soa_html[doc.report][-1]
if doc.letter_head:
from frappe.www.printview import get_letter_head
letter_head = get_letter_head(doc, 0)
html = frappe.render_template(
template_path,
{
@@ -203,7 +207,6 @@ def get_html(doc, filters, entry, col, res, ageing):
else None,
},
)
html = frappe.render_template(
base_template_path,
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
@@ -262,9 +265,12 @@ def get_recipients_and_cc(customer, doc):
recipients = []
for clist in doc.customers:
if clist.customer == customer:
recipients.append(clist.billing_email)
if clist.billing_email:
for email in clist.billing_email.split(","):
recipients.append(email.strip())
if doc.primary_mandatory and clist.primary_email:
recipients.append(clist.primary_email)
for email in clist.primary_email.split(","):
recipients.append(email.strip())
cc = []
if doc.cc_to != "":
try:

View File

@@ -302,7 +302,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return;
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
let payment_terms_template = this.frm.doc.payment_terms_template;
erpnext.utils.get_party_details(
this.frm,
"erpnext.accounts.party.get_party_details",
{
posting_date: this.frm.doc.posting_date,
bill_date: this.frm.doc.bill_date,
@@ -320,7 +324,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
})
// while duplicating, don't change payment terms
if (me.frm.doc.__run_link_triggers === false) {
me.frm.set_value("payment_terms_template", payment_terms_template);
me.frm.refresh_field("payment_terms_template");
}
}
);
}
apply_tds(frm) {

View File

@@ -10,7 +10,6 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate,
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
@@ -33,7 +32,7 @@ from erpnext.accounts.general_ledger import (
merge_similar_entries,
)
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update_voucher_outstanding
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
@@ -661,12 +660,12 @@ class PurchaseInvoice(BuyingController):
def update_supplier_outstanding(self, update_outstanding):
if update_outstanding == "No":
update_outstanding_amt(
self.credit_to,
"Supplier",
self.supplier,
self.doctype,
self.return_against if cint(self.is_return) and self.return_against else self.name,
update_voucher_outstanding(
voucher_type=self.doctype,
voucher_no=self.return_against if cint(self.is_return) and self.return_against else self.name,
account=self.credit_to,
party_type="Supplier",
party=self.supplier,
)
def get_gl_entries(self, warehouse_account=None):

View File

@@ -45,12 +45,16 @@ frappe.listview_settings["Purchase Invoice"] = {
},
onload: function (listview) {
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Purchase Receipt");
});
if (frappe.model.can_create("Purchase Receipt")) {
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Purchase Receipt");
});
}
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Payment Entry");
});
}
},
};

View File

@@ -1852,7 +1852,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
1,
)
pi = make_pi_from_pr(pr.name)
self.assertEqual(pi.payment_schedule[0].payment_amount, 2500)
self.assertEqual(pi.payment_schedule[0].payment_amount, 1000)
automatically_fetch_payment_terms(enable=0)
frappe.db.set_value(
@@ -1984,6 +1984,78 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertRaises(frappe.ValidationError, dr_note.save)
def test_apply_discount_on_grand_total(self):
"""
To test if after applying discount on grand total,
the grand total is calculated correctly without any rounding errors
"""
invoice = make_purchase_invoice(qty=2, rate=100, do_not_save=True, do_not_submit=True)
invoice.append(
"items",
{
"item_code": "_Test Item",
"qty": 1,
"rate": 21.39,
},
)
invoice.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"rate": 15.5,
},
)
# the grand total here will be 255.71
invoice.disable_rounded_total = 1
# apply discount on grand total to adjust the grand total to 255
invoice.discount_amount = 0.71
invoice.save()
# check if grand total is 496 and not something like 254.99 due to rounding errors
self.assertEqual(invoice.grand_total, 255)
def test_apply_discount_on_grand_total_with_previous_row_total_tax(self):
"""
To test if after applying discount on grand total,
where the tax is calculated on previous row total, the grand total is calculated correctly
"""
invoice = make_purchase_invoice(qty=2, rate=100, do_not_save=True, do_not_submit=True)
invoice.extend(
"taxes",
[
{
"charge_type": "Actual",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"tax_amount": 100,
},
{
"charge_type": "On Previous Row Amount",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"row_id": 1,
"rate": 10,
},
{
"charge_type": "On Previous Row Total",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"row_id": 1,
"rate": 10,
},
],
)
# the total here will be 340, so applying 40 discount
invoice.discount_amount = 40
invoice.save()
self.assertEqual(invoice.grand_total, 300)
def check_gl_entries(
doc,

View File

@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
"ref_exchange_rate"
"ref_exchange_rate",
"difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "180px"
},
{
"columns": 3,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -40,7 +41,7 @@
"read_only": 1
},
{
"columns": 3,
"columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -111,13 +112,20 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "difference_posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-26 15:47:28.167371",
"modified": "2024-12-20 12:04:46.729972",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",

View File

@@ -6,6 +6,8 @@ from frappe import _, qb
from frappe.model.document import Document
from frappe.utils.data import comma_and
from erpnext.stock import get_warehouse_account_map
class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs):
@@ -77,6 +79,9 @@ class RepostAccountingLedger(Document):
doc = frappe.get_doc(x.voucher_type, x.voucher_no)
if doc.doctype in ["Payment Entry", "Journal Entry"]:
gle_map = doc.build_gl_map()
elif doc.doctype == "Purchase Receipt":
warehouse_account_map = get_warehouse_account_map(doc.company)
gle_map = doc.get_gl_entries(warehouse_account_map)
else:
gle_map = doc.get_gl_entries()
@@ -155,6 +160,14 @@ def start_repost(account_repost_doc=str) -> None:
doc.force_set_against_expense_account()
doc.make_gl_entries()
elif doc.doctype == "Purchase Receipt":
if not repost_doc.delete_cancelled_entries:
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
doc.make_gl_entries(from_repost=True)
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
if not repost_doc.delete_cancelled_entries:
doc.make_gl_entries(1)

View File

@@ -12,6 +12,8 @@ from erpnext.accounts.doctype.payment_request.payment_request import make_paymen
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt
class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
@@ -202,9 +204,81 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
def test_06_repost_purchase_receipt(self):
from erpnext.accounts.doctype.account.test_account import create_account
provisional_account = create_account(
account_name="Provision Account",
parent_account="Current Liabilities - _TC",
company=self.company,
)
another_provisional_account = create_account(
account_name="Another Provision Account",
parent_account="Current Liabilities - _TC",
company=self.company,
)
company = frappe.get_doc("Company", self.company)
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
test_cc = company.cost_center
default_expense_account = company.default_expense_account
item = make_item(properties={"is_stock_item": 0})
pr = make_purchase_receipt(company=self.company, item_code=item.name, rate=1000.0, qty=1.0)
pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
expected_pr_gles = [
{"account": provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
{"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
]
self.assertEqual(expected_pr_gles, pr_gl_entries)
# change the provisional account
frappe.db.set_value(
"Purchase Receipt Item",
pr.items[0].name,
"provisional_expense_account",
another_provisional_account,
)
repost_doc = frappe.new_doc("Repost Accounting Ledger")
repost_doc.company = self.company
repost_doc.delete_cancelled_entries = True
repost_doc.append("vouchers", {"voucher_type": pr.doctype, "voucher_no": pr.name})
repost_doc.save().submit()
pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
expected_pr_gles_after_repost = [
{"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
{"account": another_provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
]
self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost))
self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost)
# teardown
repost_doc.cancel()
repost_doc.delete()
pr.reload()
pr.cancel()
company.enable_provisional_accounting_for_non_stock_items = 0
company.default_provisional_account = None
company.save()
def update_repost_settings():
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
allowed_types = [
"Sales Invoice",
"Purchase Invoice",
"Payment Entry",
"Journal Entry",
"Purchase Receipt",
]
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
for x in allowed_types:
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})

View File

@@ -9,6 +9,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
setup(doc) {
this.setup_posting_date_time_check();
super.setup(doc);
this.frm.make_methods = {
Dunning: this.make_dunning.bind(this),
"Invoice Discounting": this.make_invoice_discounting.bind(this),
};
}
company() {
super.company();
@@ -94,26 +98,35 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
}
}
if (doc.outstanding_amount>0) {
cur_frm.add_custom_button(__('Payment Request'), function() {
me.make_payment_request();
}, __('Create'));
if (doc.outstanding_amount > 0) {
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request();
},
__("Create")
);
this.frm.add_custom_button(
__("Invoice Discounting"),
this.make_invoice_discounting.bind(this),
__("Create")
);
cur_frm.add_custom_button(__('Invoice Discounting'), function() {
cur_frm.events.create_invoice_discounting(cur_frm);
}, __('Create'));
const payment_is_overdue = doc.payment_schedule
.map((row) => Date.parse(row.due_date) < Date.now())
.reduce((prev, current) => prev || current, false);
if (doc.due_date < frappe.datetime.get_today()) {
cur_frm.add_custom_button(__('Dunning'), function() {
cur_frm.events.create_dunning(cur_frm);
}, __('Create'));
if (payment_is_overdue) {
this.frm.add_custom_button(__("Dunning"), this.make_dunning.bind(this), __("Create"));
}
}
if (doc.docstatus === 1) {
cur_frm.add_custom_button(__('Maintenance Schedule'), function () {
cur_frm.cscript.make_maintenance_schedule();
}, __('Create'));
this.frm.add_custom_button(
__("Maintenance Schedule"),
this.make_maintenance_schedule.bind(this),
__("Create")
);
}
if(!doc.auto_repeat) {
@@ -146,6 +159,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
}
make_invoice_discounting() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: this.frm,
});
}
make_dunning() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: this.frm,
});
}
make_maintenance_schedule() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
@@ -948,20 +975,6 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_df_property('return_against', 'label', __('Adjustment Against'));
}
},
create_invoice_discounting: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: frm
});
},
create_dunning: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: frm
});
}
});
frappe.ui.form.on("Sales Invoice Timesheet", {

View File

@@ -24,7 +24,11 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
)
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import cancel_exchange_gain_loss_journal, get_account_currency
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
update_voucher_outstanding,
)
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_disposal_account_and_cost_center,
@@ -87,8 +91,8 @@ class SalesInvoice(SellingController):
self.indicator_title = _("Paid")
def validate(self):
super().validate()
self.validate_auto_set_posting_time()
super().validate()
if not (self.is_pos or self.is_debit_note):
self.so_dn_required()
@@ -1019,14 +1023,14 @@ class SalesInvoice(SellingController):
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(
self.debit_to,
"Customer",
self.customer,
self.doctype,
self.return_against if cint(self.is_return) and self.return_against else self.name,
update_voucher_outstanding(
voucher_type=self.doctype,
voucher_no=self.return_against
if cint(self.is_return) and self.return_against
else self.name,
account=self.debit_to,
party_type="Customer",
party=self.customer,
)
elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock):
@@ -2537,10 +2541,26 @@ def create_dunning(source_name, target_doc=None):
target.dunning_amount = amounts.get("dunning_amount")
target.grand_total = amounts.get("grand_total")
<<<<<<< HEAD
doclist = get_mapped_doc(
"Sales Invoice",
source_name,
{
=======
# update outstanding from doc
if source.payment_schedule and len(source.payment_schedule) == 1:
for row in target.overdue_payments:
if row.payment_schedule == source.payment_schedule[0].name:
row.outstanding = source.get("outstanding_amount")
target.validate()
return get_mapped_doc(
from_doctype="Sales Invoice",
from_docname=source_name,
target_doc=target_doc,
table_maps={
>>>>>>> c2bdd30e6d (fix: correct outstanding amount for invoice in dunning)
"Sales Invoice": {
"doctype": "Dunning",
}

View File

@@ -32,12 +32,16 @@ frappe.listview_settings["Sales Invoice"] = {
right_column: "grand_total",
onload: function (listview) {
listview.page.add_action_item(__("Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Delivery Note");
});
if (frappe.model.can_create("Delivery Note")) {
listview.page.add_action_item(__("Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Delivery Note");
});
}
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Payment Entry");
});
}
},
};

View File

@@ -416,9 +416,9 @@ class TestSalesInvoice(FrappeTestCase):
for i, k in enumerate(expected_values["keys"]):
self.assertEqual(d.get(k), expected_values[d.account_head][i])
self.assertEqual(si.base_grand_total, 1500.01)
self.assertEqual(si.grand_total, 1500.01)
self.assertEqual(si.rounding_adjustment, -0.01)
self.assertEqual(si.base_grand_total, 1500)
self.assertEqual(si.grand_total, 1500)
self.assertEqual(si.rounding_adjustment, 0)
def test_discount_amount_gl_entry(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
@@ -1773,17 +1773,6 @@ class TestSalesInvoice(FrappeTestCase):
self.assertTrue(gle)
def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=1,
do_not_save=1,
)
self.assertRaises(frappe.ValidationError, si.save)
def test_invalid_currency(self):
# Customer currency = USD
@@ -3860,6 +3849,7 @@ class TestSalesInvoice(FrappeTestCase):
si = create_sales_invoice(do_not_submit=True)
project = frappe.new_doc("Project")
project.company = "_Test Company"
project.project_name = "Test Total Billed Amount"
project.save()
@@ -3870,6 +3860,35 @@ class TestSalesInvoice(FrappeTestCase):
doc = frappe.get_doc("Project", project.name)
self.assertEqual(doc.total_billed_amount, si.grand_total)
def test_create_return_invoice_for_self_update(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.controllers.sales_and_purchase_return import make_return_doc
invoice = create_sales_invoice()
payment_entry = get_payment_entry(dt=invoice.doctype, dn=invoice.name)
payment_entry.reference_no = "test001"
payment_entry.reference_date = getdate()
payment_entry.save()
payment_entry.submit()
r_invoice = make_return_doc(invoice.doctype, invoice.name)
r_invoice.update_outstanding_for_self = 0
r_invoice.save()
self.assertEqual(r_invoice.update_outstanding_for_self, 1)
r_invoice.submit()
self.assertNotEqual(r_invoice.outstanding_amount, 0)
invoice.reload()
self.assertEqual(invoice.outstanding_amount, 0)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
"ref_exchange_rate"
"ref_exchange_rate",
"difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "250px"
},
{
"columns": 3,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -41,7 +42,7 @@
"read_only": 1
},
{
"columns": 3,
"columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -112,13 +113,20 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "difference_posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-26 15:47:46.911595",
"modified": "2024-12-20 11:58:28.962370",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",

View File

@@ -13,17 +13,15 @@
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Voucher Type",
"options": "DocType"
"label": "Voucher Type"
},
{
"fieldname": "voucher_name",
"fieldtype": "Dynamic Link",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Voucher Name",
"options": "voucher_type"
"label": "Voucher Name"
},
{
"fieldname": "taxable_amount",
@@ -36,7 +34,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-01-13 13:40:41.479208",
"modified": "2025-02-05 16:39:14.863698",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",

View File

@@ -124,6 +124,9 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
cost_center = get_cost_center(inv)
tax_row.update({"cost_center": cost_center})
if cint(tax_details.round_off_tax_amount):
inv.round_off_applicable_accounts_for_tax_withholding = tax_details.account_head
if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances, voucher_wise_amount
else:
@@ -233,7 +236,7 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no):
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
vouchers, voucher_wise_amount = get_invoice_vouchers(
vouchers, voucher_wise_amount, zero_rate_ldc_invoices = get_invoice_vouchers(
parties, tax_details, inv.company, party_type=party_type
)
@@ -287,7 +290,8 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
# once tds is deducted, not need to add vouchers in the invoice
voucher_wise_amount = {}
else:
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers)
taxable_vouchers = list(set(vouchers) - set(zero_rate_ldc_invoices))
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, taxable_vouchers)
elif party_type == "Customer":
if tax_deducted:
@@ -306,12 +310,33 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
field = (
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total"
)
field = [
"name",
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total",
"posting_date",
]
voucher_wise_amount = {}
vouchers = []
ldcs = frappe.db.get_all(
"Lower Deduction Certificate",
filters={
"valid_from": [">=", tax_details.from_date],
"valid_upto": ["<=", tax_details.to_date],
"company": company,
"supplier": ["in", parties],
},
fields=["supplier", "valid_from", "valid_upto", "rate"],
)
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
field = [
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total",
"name",
"grand_total",
"posting_date",
]
filters = {
"company": company,
frappe.scrub(party_type): ["in", parties],
@@ -325,11 +350,31 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field])
invoices_details = frappe.get_all(doctype, filters=filters, fields=field)
ldcs = frappe.db.get_all(
"Lower Deduction Certificate",
filters={
"valid_from": [">=", tax_details.from_date],
"valid_upto": ["<=", tax_details.to_date],
"company": company,
"supplier": ["in", parties],
"rate": 0,
},
fields=["name", "supplier", "valid_from", "valid_upto"],
)
zero_rate_ldc_invoices = []
for d in invoices_details:
vouchers.append(d.name)
voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}})
_voucher_detail = {"amount": d.base_net_total, "voucher_type": doctype}
if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]:
if ldc[0].supplier in parties:
_voucher_detail.update({"amount": 0})
zero_rate_ldc_invoices.append(d.name)
voucher_wise_amount.update({d.name: _voucher_detail})
journal_entries_details = frappe.db.sql(
"""
@@ -360,7 +405,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
vouchers.append(d.name)
voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}})
return vouchers, voucher_wise_amount
return vouchers, voucher_wise_amount, zero_rate_ldc_invoices
def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"):
@@ -523,9 +568,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
else:
tax_withholding_net_total = inv.get("tax_withholding_net_total", 0)
if (threshold and tax_withholding_net_total >= threshold) or (
has_cumulative_threshold_breached = (
cumulative_threshold and (supp_credit_amt + supp_inv_credit_amt) >= cumulative_threshold
):
)
if (threshold and tax_withholding_net_total >= threshold) or (has_cumulative_threshold_breached):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
net_total = (
@@ -533,9 +580,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
)
supp_credit_amt += net_total
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
tax_details.tax_on_excess_amount
):
if has_cumulative_threshold_breached and cint(tax_details.tax_on_excess_amount):
supp_credit_amt = net_total + tax_withholding_net_total - cumulative_threshold
if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today
from frappe.utils import add_days, add_months, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.utils import get_fiscal_year
@@ -614,6 +614,49 @@ class TestTaxWithholdingCategory(FrappeTestCase):
pi2.cancel()
pi3.cancel()
def test_ldc_at_0_rate(self):
frappe.db.set_value(
"Supplier",
"Test LDC Supplier",
{
"tax_withholding_category": "Test Service Category",
"pan": "ABCTY1234D",
},
)
fiscal_year = get_fiscal_year(today(), company="_Test Company")
valid_from = fiscal_year[1]
valid_upto = add_months(valid_from, 1)
create_lower_deduction_certificate(
supplier="Test LDC Supplier",
certificate_no="1AE0423AAJ",
tax_withholding_category="Test Service Category",
tax_rate=0,
limit=50000,
valid_from=valid_from,
valid_upto=valid_upto,
)
pi1 = create_purchase_invoice(
supplier="Test LDC Supplier", rate=35000, posting_date=valid_from, set_posting_time=True
)
pi1.submit()
self.assertEqual(pi1.taxes, [])
pi2 = create_purchase_invoice(
supplier="Test LDC Supplier",
rate=35000,
posting_date=add_days(valid_upto, 1),
set_posting_time=True,
)
pi2.submit()
self.assertEqual(len(pi2.taxes), 1)
# pi1 net total shouldn't be included as it lies within LDC at rate of '0'
self.assertEqual(pi2.taxes[0].tax_amount, 3500)
pi1.cancel()
pi2.cancel()
def set_previous_fy_and_tax_category(self):
test_company = "_Test Company"
category = "Cumulative Threshold TDS"
@@ -771,7 +814,8 @@ def create_purchase_invoice(**args):
pi = frappe.get_doc(
{
"doctype": "Purchase Invoice",
"posting_date": today(),
"set_posting_time": args.set_posting_time or False,
"posting_date": args.posting_date or today(),
"apply_tds": 0 if args.do_not_apply_tds else 1,
"supplier": args.supplier,
"company": "_Test Company",
@@ -1099,7 +1143,9 @@ def create_tax_withholding_category(
).insert()
def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit):
def create_lower_deduction_certificate(
supplier, tax_withholding_category, tax_rate, certificate_no, limit, valid_from=None, valid_upto=None
):
fiscal_year = get_fiscal_year(today(), company="_Test Company")
if not frappe.db.exists("Lower Deduction Certificate", certificate_no):
frappe.get_doc(
@@ -1110,8 +1156,8 @@ def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_r
"certificate_no": certificate_no,
"tax_withholding_category": tax_withholding_category,
"fiscal_year": fiscal_year[0],
"valid_from": fiscal_year[1],
"valid_upto": fiscal_year[2],
"valid_from": valid_from or fiscal_year[1],
"valid_upto": valid_upto or fiscal_year[2],
"rate": tax_rate,
"certificate_limit": limit,
}

View File

@@ -111,6 +111,7 @@ frappe.query_reports["Accounts Payable"] = {
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -88,6 +88,7 @@ frappe.query_reports["Accounts Payable Summary"] = {
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

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

View File

@@ -56,6 +56,7 @@ frappe.query_reports["Accounts Receivable"] = {
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -498,7 +498,7 @@ class ReceivablePayableReport:
ps.description, ps.paid_amount, ps.discounted_amount
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
si.name = %s and
si.is_return = 0
order by ps.paid_amount desc, due_date

View File

@@ -21,7 +21,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False, **args):
frappe.set_user("Administrator")
si = create_sales_invoice(
item=self.item,
@@ -34,6 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
rate=100,
price_list_rate=100,
do_not_save=1,
**args,
)
if not no_payment_schedule:
si.append(
@@ -111,7 +112,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note])
pos_inv.cancel()
def test_accounts_receivable(self):
def test_accounts_receivable_with_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
@@ -151,11 +152,15 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
cr_note = self.create_credit_note(si.name, do_not_submit=True)
cr_note.update_outstanding_for_self = False
cr_note.save().submit()
# as the invoice partially paid and returning the full amount so the outstanding amount should be True
self.assertEqual(cr_note.update_outstanding_for_self, True)
report = execute(filters)
expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to]
expected_data_after_credit_note = [0, 0, 100, 0, -100, self.debit_to]
row = report[1][0]
row = report[1][-1]
self.assertEqual(
expected_data_after_credit_note,
[
@@ -168,6 +173,105 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
],
)
def test_accounts_receivable_without_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
"show_remarks": True,
}
# check invoice grand total and invoiced column's value for 3 payment terms
si = self.create_sales_invoice()
report = execute(filters)
expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
cr_note = self.create_credit_note(si.name, do_not_submit=True)
cr_note.update_outstanding_for_self = False
cr_note.save().submit()
self.assertEqual(cr_note.update_outstanding_for_self, False)
report = execute(filters)
row = report[1]
self.assertTrue(len(row) == 0)
def test_accounts_receivable_with_partial_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
"show_remarks": True,
}
# check invoice grand total and invoiced column's value for 3 payment terms
si = self.create_sales_invoice(qty=2)
report = execute(filters)
expected_data = [[200, 60, "No Remarks"], [200, 100, "No Remarks"], [200, 40, "No Remarks"]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
# check invoice grand total, invoiced, paid and outstanding column's value after payment
self.create_payment_entry(si.name)
report = execute(filters)
expected_data_after_payment = [[200, 60, 40, 20], [200, 100, 0, 100], [200, 40, 0, 40]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(
expected_data_after_payment[i - 1],
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
)
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
cr_note = self.create_credit_note(si.name, do_not_submit=True)
cr_note.update_outstanding_for_self = False
cr_note.save().submit()
self.assertFalse(cr_note.update_outstanding_for_self)
report = execute(filters)
expected_data_after_credit_note = [
[200, 100, 0, 80, 20, self.debit_to],
[200, 40, 0, 0, 40, self.debit_to],
]
for i in range(2):
row = report[1][i - 1]
self.assertEqual(
expected_data_after_credit_note[i - 1],
[
row.invoice_grand_total,
row.invoiced,
row.paid,
row.credit_note,
row.outstanding,
row.party_account,
],
)
def test_cr_note_flag_to_update_self(self):
filters = {
"company": self.company,

View File

@@ -88,6 +88,7 @@ frappe.query_reports["Accounts Receivable Summary"] = {
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -25,11 +25,26 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{
fieldname: "group_by",
label: __("Group By"),
fieldtype: "Select",
options: ["Asset Category", "Asset"],
default: "Asset Category",
},
{
fieldname: "asset_category",
label: __("Asset Category"),
fieldtype: "Link",
options: "Asset Category",
depends_on: "eval: doc.group_by == 'Asset Category'",
},
{
fieldname: "asset",
label: __("Asset"),
fieldtype: "Link",
options: "Asset",
depends_on: "eval: doc.group_by == 'Asset'",
},
],
};

View File

@@ -14,21 +14,28 @@ def execute(filters=None):
def get_data(filters):
if filters.get("group_by") == "Asset Category":
return get_group_by_asset_category_data(filters)
elif filters.get("group_by") == "Asset":
return get_group_by_asset_data(filters)
def get_group_by_asset_category_data(filters):
data = []
asset_categories = get_asset_categories(filters)
assets = get_assets(filters)
asset_categories = get_asset_categories_for_grouped_by_category(filters)
assets = get_assets_for_grouped_by_category(filters)
for asset_category in asset_categories:
row = frappe._dict()
# row.asset_category = asset_category
row.update(asset_category)
row.cost_as_on_to_date = (
flt(row.cost_as_on_from_date)
+ flt(row.cost_of_new_purchase)
- flt(row.cost_of_sold_asset)
- flt(row.cost_of_scrapped_asset)
row.value_as_on_to_date = (
flt(row.value_as_on_from_date)
+ flt(row.value_of_new_purchase)
- flt(row.value_of_sold_asset)
- flt(row.value_of_scrapped_asset)
- flt(row.value_of_capitalized_asset)
)
row.update(
@@ -38,17 +45,19 @@ def get_data(filters):
if asset["asset_category"] == asset_category.get("asset_category", "")
)
)
row.accumulated_depreciation_as_on_to_date = (
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
- flt(row.depreciation_eliminated_via_reversal)
)
row.net_asset_value_as_on_from_date = flt(row.cost_as_on_from_date) - flt(
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
row.accumulated_depreciation_as_on_from_date
)
row.net_asset_value_as_on_to_date = flt(row.cost_as_on_to_date) - flt(
row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
row.accumulated_depreciation_as_on_to_date
)
@@ -57,52 +66,71 @@ def get_data(filters):
return data
def get_asset_categories(filters):
def get_asset_categories_for_grouped_by_category(filters):
condition = ""
if filters.get("asset_category"):
condition += " and asset_category = %(asset_category)s"
condition += " and a.asset_category = %(asset_category)s"
# nosemgrep
return frappe.db.sql(
f"""
SELECT asset_category,
ifnull(sum(case when purchase_date < %(from_date)s then
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
gross_purchase_amount
SELECT a.asset_category,
ifnull(sum(case when a.purchase_date < %(from_date)s then
case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as cost_as_on_from_date,
ifnull(sum(case when purchase_date >= %(from_date)s then
gross_purchase_amount
end), 0) as value_as_on_from_date,
ifnull(sum(case when a.purchase_date >= %(from_date)s then
a.gross_purchase_amount
else
0
end), 0) as cost_of_new_purchase,
ifnull(sum(case when ifnull(disposal_date, 0) != 0
and disposal_date >= %(from_date)s
and disposal_date <= %(to_date)s then
case when status = "Sold" then
gross_purchase_amount
end), 0) as value_of_new_purchase,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then
case when a.status = "Sold" then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as cost_of_sold_asset,
ifnull(sum(case when ifnull(disposal_date, 0) != 0
and disposal_date >= %(from_date)s
and disposal_date <= %(to_date)s then
case when status = "Scrapped" then
gross_purchase_amount
end), 0) as value_of_sold_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then
case when a.status = "Scrapped" then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as cost_of_scrapped_asset
from `tabAsset`
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition}
group by asset_category
end), 0) as value_of_scrapped_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then
case when a.status = "Capitalized" then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as value_of_capitalized_asset
from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
and not exists(
select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name
where acai.asset = a.name
and ac.posting_date < %(from_date)s
and ac.docstatus=1
)
group by a.asset_category
""",
{
"to_date": filters.to_date,
@@ -114,14 +142,17 @@ def get_asset_categories(filters):
)
def get_assets(filters):
def get_assets_for_grouped_by_category(filters):
condition = ""
if filters.get("asset_category"):
condition = " and a.asset_category = '{}'".format(filters.get("asset_category"))
condition = f" and a.asset_category = '{filters.get('asset_category')}'"
# nosemgrep
return frappe.db.sql(
"""
f"""
SELECT results.asset_category,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
@@ -130,6 +161,11 @@ def get_assets(filters):
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
gle.credit
else
0
end), 0) as depreciation_eliminated_via_reversal,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
@@ -149,15 +185,22 @@ def get_assets(filters):
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) {0}
where
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{condition}
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then
0
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
0 as depreciation_eliminated_via_reversal,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
@@ -165,51 +208,272 @@ def get_assets(filters):
end), 0) as depreciation_eliminated_during_the_period,
0 as depreciation_amount_during_the_period
from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {0}
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
group by a.asset_category) as results
group by results.asset_category
""".format(condition),
{"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company},
""",
{
"to_date": filters.to_date,
"from_date": filters.from_date,
"company": filters.company,
},
as_dict=1,
)
def get_group_by_asset_data(filters):
data = []
asset_details = get_asset_details_for_grouped_by_category(filters)
assets = get_assets_for_grouped_by_asset(filters)
for asset_detail in asset_details:
row = frappe._dict()
row.update(asset_detail)
row.value_as_on_to_date = (
flt(row.value_as_on_from_date)
+ flt(row.value_of_new_purchase)
- flt(row.value_of_sold_asset)
- flt(row.value_of_scrapped_asset)
- flt(row.value_of_capitalized_asset)
)
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
row.accumulated_depreciation_as_on_to_date = (
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
- flt(row.depreciation_eliminated_via_reversal)
)
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
row.accumulated_depreciation_as_on_from_date
)
row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
row.accumulated_depreciation_as_on_to_date
)
data.append(row)
return data
def get_asset_details_for_grouped_by_category(filters):
condition = ""
if filters.get("asset"):
condition += " and a.name = %(asset)s"
# nosemgrep
return frappe.db.sql(
f"""
SELECT a.name,
ifnull(sum(case when a.purchase_date < %(from_date)s then
case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as value_as_on_from_date,
ifnull(sum(case when a.purchase_date >= %(from_date)s then
a.gross_purchase_amount
else
0
end), 0) as value_of_new_purchase,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then
case when a.status = "Sold" then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as value_of_sold_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then
case when a.status = "Scrapped" then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as value_of_scrapped_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then
case when a.status = "Capitalized" then
a.gross_purchase_amount
else
0
end
else
0
end), 0) as value_of_capitalized_asset
from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
and not exists(
select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name
where acai.asset = a.name
and ac.posting_date < %(from_date)s
and ac.docstatus=1
)
group by a.name
""",
{
"to_date": filters.to_date,
"from_date": filters.from_date,
"company": filters.company,
"asset": filters.get("asset"),
},
as_dict=1,
)
def get_assets_for_grouped_by_asset(filters):
condition = ""
if filters.get("asset"):
condition = f" and a.name = '{filters.get('asset')}'"
# nosemgrep
return frappe.db.sql(
f"""
SELECT results.name as asset,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.name as name,
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
gle.debit
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
gle.credit
else
0
end), 0) as depreciation_eliminated_via_reversal,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
else
0
end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
gle.debit
else
0
end), 0) as depreciation_amount_during_the_period
from `tabGL Entry` gle
join `tabAsset` a on
gle.against_voucher = a.name
join `tabAsset Category Account` aca on
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{condition}
group by a.name
union
SELECT a.name as name,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then
0
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
0 as depreciation_as_on_from_date_credit,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
0
end), 0) as depreciation_eliminated_during_the_period,
0 as depreciation_amount_during_the_period
from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
group by a.name) as results
group by results.name
""",
{
"to_date": filters.to_date,
"from_date": filters.from_date,
"company": filters.company,
},
as_dict=1,
)
def get_columns(filters):
return [
columns = []
if filters.get("group_by") == "Asset Category":
columns.append(
{
"label": _("Asset Category"),
"fieldname": "asset_category",
"fieldtype": "Link",
"options": "Asset Category",
"width": 120,
}
)
elif filters.get("group_by") == "Asset":
columns.append(
{
"label": _("Asset"),
"fieldname": "asset",
"fieldtype": "Link",
"options": "Asset",
"width": 120,
}
)
columns += [
{
"label": _("Asset Category"),
"fieldname": "asset_category",
"fieldtype": "Link",
"options": "Asset Category",
"width": 120,
},
{
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "cost_as_on_from_date",
"label": _("Value as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "value_as_on_from_date",
"fieldtype": "Currency",
"width": 140,
},
{
"label": _("Cost of New Purchase"),
"fieldname": "cost_of_new_purchase",
"label": _("Value of New Purchase"),
"fieldname": "value_of_new_purchase",
"fieldtype": "Currency",
"width": 140,
},
{
"label": _("Cost of Sold Asset"),
"fieldname": "cost_of_sold_asset",
"label": _("Value of Sold Asset"),
"fieldname": "value_of_sold_asset",
"fieldtype": "Currency",
"width": 140,
},
{
"label": _("Cost of Scrapped Asset"),
"fieldname": "cost_of_scrapped_asset",
"label": _("Value of Scrapped Asset"),
"fieldname": "value_of_scrapped_asset",
"fieldtype": "Currency",
"width": 140,
},
{
"label": _("Cost as on") + " " + formatdate(filters.to_date),
"fieldname": "cost_as_on_to_date",
"label": _("Value of New Capitalized Asset"),
"fieldname": "value_of_capitalized_asset",
"fieldtype": "Currency",
"width": 140,
},
{
"label": _("Value as on") + " " + formatdate(filters.to_date),
"fieldname": "value_as_on_to_date",
"fieldtype": "Currency",
"width": 140,
},
@@ -237,6 +501,12 @@ def get_columns(filters):
"fieldtype": "Currency",
"width": 270,
},
{
"label": _("Depreciation eliminated via reversal"),
"fieldname": "depreciation_eliminated_via_reversal",
"fieldtype": "Currency",
"width": 270,
},
{
"label": _("Net Asset value as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "net_asset_value_as_on_from_date",
@@ -250,3 +520,5 @@ def get_columns(filters):
"width": 200,
},
]
return columns

View File

@@ -1,6 +1,3 @@
<div style="margin-bottom: 7px;">
{%= frappe.boot.letter_heads[frappe.defaults.get_default("letter_head")] %}
</div>
<h2 class="text-center">{%= __("Bank Reconciliation Statement") %}</h2>
<h4 class="text-center">{%= filters.account && (filters.account + ", "+filters.report_date) || "" %} {%= filters.company %}</h4>
<hr>
@@ -46,4 +43,4 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>

View File

@@ -27,6 +27,7 @@ def get_report_filters(report_filters):
["Purchase Invoice", "docstatus", "=", 1],
["Purchase Invoice", "per_received", "<", 100],
["Purchase Invoice", "update_stock", "=", 0],
["Purchase Invoice", "is_opening", "!=", "Yes"],
]
if report_filters.get("purchase_invoice"):

View File

@@ -91,6 +91,7 @@ function get_filters() {
fieldname: "budget_against_filter",
label: __("Dimension Filter"),
fieldtype: "MultiSelectList",
options: "budget_against",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -263,6 +263,7 @@ def get_actual_details(name, filters):
and ba.account=gl.account
and b.{budget_against} = gl.{budget_against}
and gl.fiscal_year between %s and %s
and gl.is_cancelled = 0
and b.{budget_against} = %s
and exists(
select

View File

@@ -67,5 +67,5 @@
</tbody>
</table>
<p class="text-right text-muted">
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}
</p>

View File

@@ -512,12 +512,16 @@ def get_accounting_entries(
.where(gl_entry.company == filters.company)
)
ignore_is_opening = frappe.db.get_single_value(
"Accounts Settings", "ignore_is_opening_check_for_reporting"
)
if doctype == "GL Entry":
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
query = query.where(gl_entry.is_cancelled == 0)
query = query.where(gl_entry.posting_date <= to_date)
if ignore_opening_entries:
if ignore_opening_entries and not ignore_is_opening:
query = query.where(gl_entry.is_opening == "No")
else:
query = query.select(gl_entry.closing_date.as_("posting_date"))

View File

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

View File

@@ -52,6 +52,11 @@ frappe.query_reports["General Ledger"] = {
frappe.query_report.set_filter_value("group_by", "Group by Voucher (Consolidated)");
},
},
{
fieldname: "against_voucher_no",
label: __("Against Voucher No"),
fieldtype: "Data",
},
{
fieldtype: "Break",
},
@@ -61,13 +66,14 @@ frappe.query_reports["General Ledger"] = {
fieldtype: "Autocomplete",
options: Object.keys(frappe.boot.party_account_types),
on_change: function () {
frappe.query_report.set_filter_value("party", "");
frappe.query_report.set_filter_value("party", []);
},
},
{
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;
@@ -146,6 +152,7 @@ frappe.query_reports["General Ledger"] = {
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "MultiSelectList",
options: "Cost Center",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
@@ -156,6 +163,7 @@ frappe.query_reports["General Ledger"] = {
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),

View File

@@ -209,6 +209,10 @@ def get_gl_entries(filters, accounting_dimensions):
def get_conditions(filters):
conditions = []
ignore_is_opening = frappe.db.get_single_value(
"Accounts Settings", "ignore_is_opening_check_for_reporting"
)
if filters.get("account"):
filters.account = get_accounts_with_children(filters.account)
if filters.account:
@@ -221,6 +225,9 @@ def get_conditions(filters):
if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s")
if filters.get("against_voucher_no"):
conditions.append("against_voucher=%(against_voucher_no)s")
if filters.get("ignore_err"):
err_journals = frappe.db.get_all(
"Journal Entry",
@@ -268,9 +275,15 @@ def get_conditions(filters):
or filters.get("party")
or filters.get("group_by") in ["Group by Account", "Group by Party"]
):
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
if not ignore_is_opening:
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
else:
conditions.append("posting_date >=%(from_date)s")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
if not ignore_is_opening:
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
else:
conditions.append("posting_date <=%(to_date)s")
if filters.get("project"):
conditions.append("project in %(project)s")
@@ -496,6 +509,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
for dim in accounting_dimensions:
keylist.append(gle.get(dim))
keylist.append(gle.get("cost_center"))
keylist.append(gle.get("project"))
key = tuple(keylist)
if key not in consolidated_gle:
@@ -607,10 +621,11 @@ def get_columns(filters):
{"label": _("Against Account"), "fieldname": "against", "width": 120},
{"label": _("Party Type"), "fieldname": "party_type", "width": 100},
{"label": _("Party"), "fieldname": "party", "width": 100},
{"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100},
]
if filters.get("include_dimensions"):
columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100})
for dim in get_accounting_dimensions(as_list=False):
columns.append(
{"label": _(dim.label), "options": dim.label, "fieldname": dim.fieldname, "width": 100}

View File

@@ -1,5 +1,5 @@
{
"add_total_row": 1,
"add_total_row": 0,
"columns": [],
"creation": "2013-02-25 17:03:34",
"disable_prepared_report": 0,
@@ -9,7 +9,7 @@
"filters": [],
"idx": 3,
"is_standard": "Yes",
"modified": "2022-02-11 10:18:36.956558",
"modified": "2025-01-27 18:40:24.493829",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",

View File

@@ -166,7 +166,14 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
# removing Item Code and Item Name columns
del columns[4:6]
total_base_amount = 0
total_buying_amount = 0
for src in gross_profit_data.si_list:
if src.indent == 1:
total_base_amount += src.base_amount or 0.0
total_buying_amount += src.buying_amount or 0.0
row = frappe._dict()
row.indent = src.indent
row.parent_invoice = src.parent_invoice
@@ -177,17 +184,57 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
data.append(
frappe._dict(
{
"sales_invoice": "Total",
"qty": None,
"avg._selling_rate": None,
"valuation_rate": None,
"selling_amount": total_base_amount,
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_%": flt(
(total_gross_profit / total_base_amount) * 100.0,
cint(frappe.db.get_default("currency_precision")) or 3,
)
if total_base_amount
else 0,
}
)
)
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
for src in gross_profit_data.grouped_data:
row = []
for col in group_wise_columns.get(scrub(filters.group_by)):
row.append(src.get(col))
total_base_amount = 0
total_buying_amount = 0
row.append(filters.currency)
group_columns = group_wise_columns.get(scrub(filters.group_by))
for src in gross_profit_data.grouped_data:
total_base_amount += src.base_amount or 0.00
total_buying_amount += src.buying_amount or 0.00
row = [src.get(col) for col in group_columns] + [filters.currency]
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
gross_profit_percent = (total_gross_profit / total_base_amount * 100.0) if total_base_amount else 0
total_row = {
group_columns[0]: "Total",
"base_amount": total_base_amount,
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_percent": flt(gross_profit_percent, currency_precision),
}
total_row = [total_row.get(col, None) for col in [*group_columns, "currency"]]
data.append(total_row)
def get_columns(group_wise_columns, filters):
columns = []

View File

@@ -558,3 +558,33 @@ class TestGrossProfit(FrappeTestCase):
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
def test_gross_profit_groupby_invoices(self):
create_sales_invoice(
qty=1,
rate=100,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
income_account=self.income_account,
expense_account=self.expense_account,
)
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)

View File

@@ -11,7 +11,7 @@ import erpnext
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (
add_sub_total_row,
add_total_row,
apply_group_by_conditions,
apply_order_by_conditions,
get_grand_total,
get_group_by_and_display_fields,
get_tax_accounts,
@@ -305,12 +305,6 @@ def apply_conditions(query, pi, pii, filters):
if filters.get("item_group"):
query = query.where(pii.item_group == filters.get("item_group"))
if not filters.get("group_by"):
query = query.orderby(pi.posting_date, order=Order.desc)
query = query.orderby(pii.item_group, order=Order.desc)
else:
query = apply_group_by_conditions(query, pi, pii, filters)
return query
@@ -372,7 +366,17 @@ def get_items(filters, additional_table_columns):
query = apply_conditions(query, pi, pii, filters)
return query.run(as_dict=True)
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions(doctype)
if match_conditions:
query += " and " + match_conditions
query = apply_order_by_conditions(query, pi, pii, filters)
return frappe.db.sql(query, params, as_dict=True)
def get_aii_accounts():

View File

@@ -384,27 +384,24 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None):
| (si.unrealized_profit_loss_account == filters.get("income_account"))
)
if not filters.get("group_by"):
query = query.orderby(si.posting_date, order=Order.desc)
query = query.orderby(sii.item_group, order=Order.desc)
else:
query = apply_group_by_conditions(query, si, sii, filters)
for key, value in (additional_conditions or {}).items():
query = query.where(si[key] == value)
return query
def apply_group_by_conditions(query, si, ii, filters):
if filters.get("group_by") == "Invoice":
query = query.orderby(ii.parent, order=Order.desc)
def apply_order_by_conditions(query, si, ii, filters):
if not filters.get("group_by"):
query += f" order by {si.posting_date} desc, {ii.item_group} desc"
elif filters.get("group_by") == "Invoice":
query += f" order by {ii.parent} desc"
elif filters.get("group_by") == "Item":
query = query.orderby(ii.item_code)
query += f" order by {ii.item_code}"
elif filters.get("group_by") == "Item Group":
query = query.orderby(ii.item_group)
query += f" order by {ii.item_group}"
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
query = query.orderby(si[frappe.scrub(filters.get("group_by"))])
filter_field = frappe.scrub(filters.get("group_by"))
query += f" order by {filter_field} desc"
return query
@@ -479,7 +476,17 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
query = apply_conditions(query, si, sii, filters, additional_conditions)
return query.run(as_dict=True)
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions("Sales Invoice")
if match_conditions:
query += " and " + match_conditions
query = apply_order_by_conditions(query, si, sii, filters)
return frappe.db.sql(query, params, as_dict=True)
def get_delivery_notes_against_sales_order(item_list):

View File

@@ -51,6 +51,7 @@ function get_filters() {
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -397,7 +397,6 @@ def get_invoices(filters, additional_query_columns):
pi.mode_of_payment,
)
.where(pi.docstatus == 1)
.orderby(pi.posting_date, pi.name, order=Order.desc)
)
if additional_query_columns:
@@ -413,8 +412,17 @@ def get_invoices(filters, additional_query_columns):
filters, query, doctype="Purchase Invoice", child_doctype="Purchase Invoice Item"
)
invoices = query.run(as_dict=True)
return invoices
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions("Purchase Invoice")
if match_conditions:
query += " and " + match_conditions
query += " order by posting_date desc, name desc"
return frappe.db.sql(query, params, as_dict=True)
def get_conditions(filters, query, doctype):

View File

@@ -438,7 +438,6 @@ def get_invoices(filters, additional_query_columns):
si.company,
)
.where(si.docstatus == 1)
.orderby(si.posting_date, si.name, order=Order.desc)
)
if additional_query_columns:
@@ -453,8 +452,17 @@ def get_invoices(filters, additional_query_columns):
filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item"
)
invoices = query.run(as_dict=True)
return invoices
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions("Sales Invoice")
if match_conditions:
query += " and " + match_conditions
query += " order by posting_date desc, name desc"
return frappe.db.sql(query, params, as_dict=True)
def get_conditions(filters, query, doctype):

View File

@@ -14,14 +14,14 @@
"owner": "Administrator",
"ref_doctype": "GL Entry",
"report_name": "Trial Balance",
"report_type": "Script Report",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
},
{
"role": "Accounts Manager"
},
},
{
"role": "Auditor"
}

View File

@@ -89,6 +89,10 @@ def get_data(filters):
)
company_currency = filters.presentation_currency or erpnext.get_company_currency(filters.company)
ignore_is_opening = frappe.db.get_single_value(
"Accounts Settings", "ignore_is_opening_check_for_reporting"
)
if not accounts:
return None
@@ -102,7 +106,7 @@ def get_data(filters):
gl_entries_by_account = {}
opening_balances = get_opening_balances(filters)
opening_balances = get_opening_balances(filters, ignore_is_opening)
# add filter inside list so that the query in financial_statements.py doesn't break
if filters.project:
@@ -120,7 +124,13 @@ def get_data(filters):
ignore_opening_entries=True,
)
calculate_values(accounts, gl_entries_by_account, opening_balances, filters.get("show_net_values"))
calculate_values(
accounts,
gl_entries_by_account,
opening_balances,
filters.get("show_net_values"),
ignore_is_opening=ignore_is_opening,
)
accumulate_values_into_parents(accounts, accounts_by_name)
data = prepare_data(accounts, filters, parent_children_map, company_currency)
@@ -131,15 +141,15 @@ def get_data(filters):
return data
def get_opening_balances(filters):
balance_sheet_opening = get_rootwise_opening_balances(filters, "Balance Sheet")
pl_opening = get_rootwise_opening_balances(filters, "Profit and Loss")
def get_opening_balances(filters, ignore_is_opening):
balance_sheet_opening = get_rootwise_opening_balances(filters, "Balance Sheet", ignore_is_opening)
pl_opening = get_rootwise_opening_balances(filters, "Profit and Loss", ignore_is_opening)
balance_sheet_opening.update(pl_opening)
return balance_sheet_opening
def get_rootwise_opening_balances(filters, report_type):
def get_rootwise_opening_balances(filters, report_type, ignore_is_opening):
gle = []
last_period_closing_voucher = ""
@@ -165,16 +175,24 @@ def get_rootwise_opening_balances(filters, report_type):
report_type,
accounting_dimensions,
period_closing_voucher=last_period_closing_voucher[0].name,
ignore_is_opening=ignore_is_opening,
)
# Report getting generate from the mid of a fiscal year
if getdate(last_period_closing_voucher[0].posting_date) < getdate(add_days(filters.from_date, -1)):
start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
gle += get_opening_balance(
"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
"GL Entry",
filters,
report_type,
accounting_dimensions,
start_date=start_date,
ignore_is_opening=ignore_is_opening,
)
else:
gle = get_opening_balance("GL Entry", filters, report_type, accounting_dimensions)
gle = get_opening_balance(
"GL Entry", filters, report_type, accounting_dimensions, ignore_is_opening=ignore_is_opening
)
opening = frappe._dict()
for d in gle:
@@ -193,7 +211,13 @@ def get_rootwise_opening_balances(filters, report_type):
def get_opening_balance(
doctype, filters, report_type, accounting_dimensions, period_closing_voucher=None, start_date=None
doctype,
filters,
report_type,
accounting_dimensions,
period_closing_voucher=None,
start_date=None,
ignore_is_opening=0,
):
closing_balance = frappe.qb.DocType(doctype)
account = frappe.qb.DocType("Account")
@@ -229,11 +253,16 @@ def get_opening_balance(
(closing_balance.posting_date >= start_date)
& (closing_balance.posting_date < filters.from_date)
)
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
if not ignore_is_opening:
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
else:
opening_balance = opening_balance.where(
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
)
if not ignore_is_opening:
opening_balance = opening_balance.where(
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
)
else:
opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)
if doctype == "GL Entry":
opening_balance = opening_balance.where(closing_balance.is_cancelled == 0)
@@ -304,7 +333,7 @@ def get_opening_balance(
return gle
def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values):
def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values, ignore_is_opening=0):
init = {
"opening_debit": 0.0,
"opening_credit": 0.0,
@@ -322,7 +351,7 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net
d["opening_credit"] = opening_balances.get(d.name, {}).get("opening_credit", 0)
for entry in gl_entries_by_account.get(d.name, []):
if cstr(entry.is_opening) != "Yes":
if cstr(entry.is_opening) != "Yes" or ignore_is_opening:
d["debit"] += flt(entry.debit)
d["credit"] += flt(entry.credit)

View File

@@ -68,16 +68,12 @@ frappe.query_reports["Trial Balance for Party"] = {
{
fieldname: "account",
label: __("Account"),
fieldtype: "Link",
fieldtype: "MultiSelectList",
options: "Account",
get_query: function () {
var company = frappe.query_report.get_filter_value("company");
return {
doctype: "Account",
filters: {
company: company,
},
};
get_data: function (txt) {
return frappe.db.get_link_options("Account", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
},
{

View File

@@ -4,8 +4,10 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
from erpnext.accounts.report.general_ledger.general_ledger import get_accounts_with_children
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
@@ -35,9 +37,14 @@ def get_data(filters, show_party_name):
filters=party_filters,
order_by="name",
)
account_filter = []
if filters.get("account"):
account_filter = get_accounts_with_children(filters.get("account"))
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
opening_balances = get_opening_balances(filters)
balances_within_period = get_balances_within_period(filters)
opening_balances = get_opening_balances(filters, account_filter)
balances_within_period = get_balances_within_period(filters, account_filter)
data = []
# total_debit, total_credit = 0, 0
@@ -89,30 +96,34 @@ def get_data(filters, show_party_name):
return data
def get_opening_balances(filters):
account_filter = ""
if filters.get("account"):
account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
def get_opening_balances(filters, account_filter=None):
GL_Entry = frappe.qb.DocType("GL Entry")
gle = frappe.db.sql(
f"""
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
where company=%(company)s
and is_cancelled=0
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
{account_filter}
group by party""",
{
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
"party_type": filters.party_type,
},
as_dict=True,
query = (
frappe.qb.from_(GL_Entry)
.select(
GL_Entry.party,
Sum(GL_Entry.debit).as_("opening_debit"),
Sum(GL_Entry.credit).as_("opening_credit"),
)
.where(
(GL_Entry.company == filters.company)
& (GL_Entry.is_cancelled == 0)
& (GL_Entry.party_type == filters.party_type)
& (GL_Entry.party != "")
& (
(GL_Entry.posting_date < filters.from_date)
| ((GL_Entry.is_opening == "Yes") & (GL_Entry.posting_date <= filters.to_date))
)
)
.groupby(GL_Entry.party)
)
if account_filter:
query = query.where(GL_Entry.account.isin(account_filter))
gle = query.run(as_dict=True)
opening = frappe._dict()
for d in gle:
opening_debit, opening_credit = toggle_debit_credit(d.opening_debit, d.opening_credit)
@@ -121,31 +132,33 @@ def get_opening_balances(filters):
return opening
def get_balances_within_period(filters):
account_filter = ""
if filters.get("account"):
account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
def get_balances_within_period(filters, account_filter=None):
GL_Entry = frappe.qb.DocType("GL Entry")
gle = frappe.db.sql(
f"""
select party, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry`
where company=%(company)s
and is_cancelled = 0
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
and ifnull(is_opening, 'No') = 'No'
{account_filter}
group by party""",
{
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
"party_type": filters.party_type,
},
as_dict=True,
query = (
frappe.qb.from_(GL_Entry)
.select(
GL_Entry.party,
Sum(GL_Entry.debit).as_("debit"),
Sum(GL_Entry.credit).as_("credit"),
)
.where(
(GL_Entry.company == filters.company)
& (GL_Entry.is_cancelled == 0)
& (GL_Entry.party_type == filters.party_type)
& (GL_Entry.party != "")
& (GL_Entry.posting_date >= filters.from_date)
& (GL_Entry.posting_date <= filters.to_date)
& (GL_Entry.is_opening == "No")
)
.groupby(GL_Entry.party)
)
if account_filter:
query = query.where(GL_Entry.account.isin(account_filter))
gle = query.run(as_dict=True)
balances_within_period = frappe._dict()
for d in gle:
balances_within_period.setdefault(d.party, [d.debit, d.credit])

View File

@@ -1587,7 +1587,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
if wh_details.account == account and not wh_details.is_group
]
total_stock_value = get_stock_value_on(related_warehouses, posting_date)
total_stock_value = get_stock_value_on(related_warehouses, posting_date, company=company)
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
@@ -1788,14 +1788,17 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
):
outstanding = voucher_outstanding[0]
ref_doc = frappe.get_doc(voucher_type, voucher_no)
outstanding_amount = flt(
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
)
# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"] or 0.0
ref_doc.outstanding_amount = outstanding_amount
frappe.db.set_value(
voucher_type,
voucher_no,
"outstanding_amount",
outstanding["outstanding_in_account_currency"] or 0.0,
outstanding_amount,
)
ref_doc.set_status(update=True)

View File

@@ -69,6 +69,7 @@ class AssetCapitalization(StockController):
def on_submit(self):
self.update_stock_ledger()
self.make_gl_entries()
self.repost_future_sle_and_gle()
self.update_target_asset()
def on_cancel(self):
@@ -82,6 +83,7 @@ class AssetCapitalization(StockController):
self.cancel_target_asset()
self.update_stock_ledger()
self.make_gl_entries()
self.repost_future_sle_and_gle()
self.restore_consumed_asset_items()
def on_trash(self):

View File

@@ -205,9 +205,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
}
}
if(is_drop_ship && doc.status!="Delivered") {
this.frm.add_custom_button(__('Delivered'),
this.delivered_by_supplier, __("Status"));
if (is_drop_ship && doc.status != "Delivered") {
this.frm.add_custom_button(
__("Delivered"),
this.delivered_by_supplier.bind(this),
__("Status")
);
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
}
@@ -582,4 +585,4 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
if (frm.doc.is_old_subcontracting_flow) {
erpnext.buying.get_default_bom(frm);
}
});
});

View File

@@ -51,16 +51,22 @@ frappe.listview_settings["Purchase Order"] = {
listview.call_for_selected_items(method, { status: "Submitted" });
});
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Invoice");
});
if (frappe.model.can_create("Purchase Invoice")) {
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Invoice");
});
}
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Receipt");
});
if (frappe.model.can_create("Purchase Receipt")) {
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Receipt");
});
}
listview.page.add_action_item(__("Advance Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Advance Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Payment Entry");
});
}
},
};

View File

@@ -330,7 +330,11 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=
},
"Request for Quotation Item": {
"doctype": "Supplier Quotation Item",
"field_map": {"name": "request_for_quotation_item", "parent": "request_for_quotation"},
"field_map": {
"name": "request_for_quotation_item",
"parent": "request_for_quotation",
"project_name": "project",
},
},
},
target_doc,

View File

@@ -13,6 +13,7 @@ from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_
from erpnext.accounts.party import (
get_dashboard_info,
get_timeline_data,
validate_party_accounts,
)
from erpnext.utilities.transaction_base import TransactionBase

View File

@@ -11,12 +11,20 @@ frappe.listview_settings["Supplier Quotation"] = {
},
onload: function (listview) {
listview.page.add_action_item(__("Purchase Order"), () => {
erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Order");
});
if (frappe.model.can_create("Purchase Order")) {
listview.page.add_action_item(__("Purchase Order"), () => {
erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Order");
});
}
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Invoice");
});
if (frappe.model.can_create("Purchase Invoice")) {
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(
listview,
"Supplier Quotation",
"Purchase Invoice"
);
});
}
},
};

View File

@@ -53,6 +53,7 @@ frappe.query_reports["Purchase Order Analysis"] = {
label: __("Status"),
fieldtype: "MultiSelectList",
width: "80",
options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"],
get_data: function (txt) {
let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"];
let options = [];

View File

@@ -94,9 +94,6 @@
</script>
</head>
<div style="margin-bottom: 7px;" class="text-center">
{%= frappe.boot.letter_heads[frappe.defaults.get_default("letter_head")] %}
</div>
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h4 class="text-center">{%= filters.item %} </h4>
@@ -124,9 +121,7 @@
</tbody>
</table>
<h4 class="text-center"> Analysis Chart </h4>
<h4 class="text-center">{%= __("Analysis Chart") %}</h4>
<div id="chart_div"></div>
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>

View File

@@ -50,6 +50,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
fieldname: "supplier",
label: __("Supplier"),
fieldtype: "MultiSelectList",
options: "Supplier",
get_data: function (txt) {
return frappe.db.get_link_options("Supplier", txt);
},
@@ -58,6 +59,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
fieldtype: "MultiSelectList",
label: __("Supplier Quotation"),
fieldname: "supplier_quotation",
options: "Supplier Quotation",
default: "",
get_data: function (txt) {
return frappe.db.get_link_options("Supplier Quotation", txt, { docstatus: ["<", 2] });

View File

@@ -8,7 +8,7 @@ from collections import defaultdict
import frappe
from frappe import _, bold, qb, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
from frappe.query_builder import Criterion
from frappe.query_builder import Criterion, DocType
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
@@ -153,12 +153,54 @@ class AccountsController(TransactionBase):
raise_exception=1,
)
def validate_against_voucher_outstanding(self):
from frappe.model.meta import get_meta
if not get_meta(self.doctype).has_field("outstanding_amount"):
return
if self.get("is_return") and self.return_against and not self.get("is_pos"):
against_voucher_outstanding = frappe.get_value(
self.doctype, self.return_against, "outstanding_amount"
)
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
msg = ""
if self.get("update_outstanding_for_self"):
msg = (
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, "
"uncheck '{2}' checkbox. <br><br>Or"
).format(
frappe.bold(document_type),
get_link_to_form(self.doctype, self.get("return_against")),
frappe.bold(_("Update Outstanding for Self")),
)
elif not self.update_outstanding_for_self and (
abs(flt(self.rounded_total) or flt(self.grand_total)) > flt(against_voucher_outstanding)
):
self.update_outstanding_for_self = 1
msg = (
"The outstanding amount {} in {} is lesser than {}. Updating the outstanding to this invoice. <br><br>And"
).format(
against_voucher_outstanding,
get_link_to_form(self.doctype, self.get("return_against")),
flt(abs(self.outstanding_amount)),
)
if msg:
msg += " you can use {} tool to reconcile against {} later.".format(
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
get_link_to_form(self.doctype, self.get("return_against")),
)
frappe.msgprint(_(msg))
def validate(self):
if not self.get("is_return") and not self.get("is_debit_note"):
self.validate_qty_is_not_zero()
if (
self.doctype in ["Sales Invoice", "Purchase Invoice"]
self.doctype in ["Sales Invoice", "Purchase Invoice", "POS Invoice"]
and self.get("is_return")
and self.get("update_stock")
):
@@ -178,6 +220,7 @@ class AccountsController(TransactionBase):
self.disable_tax_included_prices_for_internal_transfer()
self.set_incoming_rate()
self.init_internal_values()
self.validate_against_voucher_outstanding()
if self.meta.get_field("currency"):
self.calculate_taxes_and_totals()
@@ -209,20 +252,6 @@ class AccountsController(TransactionBase):
)
)
if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
if self.get("update_outstanding_for_self"):
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
frappe.msgprint(
_(
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox. <br><br> Or you can use {3} tool to reconcile against {1} later."
).format(
frappe.bold(document_type),
get_link_to_form(self.doctype, self.get("return_against")),
frappe.bold("Update Outstanding for Self"),
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
)
)
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
@@ -250,6 +279,7 @@ class AccountsController(TransactionBase):
apply_pricing_rule_on_transaction(self)
self.set_total_in_words()
self.validate_company_in_accounting_dimension()
def init_internal_values(self):
# init all the internal values as 0 on sa
@@ -346,13 +376,47 @@ class AccountsController(TransactionBase):
== 1
)
).run()
frappe.db.sql(
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
)
frappe.db.sql(
"delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
(self.doctype, self.name),
)
gle = frappe.qb.DocType("GL Entry")
frappe.qb.from_(gle).delete().where(
(gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
).run()
sle = frappe.qb.DocType("Stock Ledger Entry")
frappe.qb.from_(sle).delete().where(
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
).run()
def validate_company_in_accounting_dimension(self):
doc_field = DocType("DocField")
accounting_dimension = DocType("Accounting Dimension")
dimension_list = (
frappe.qb.from_(accounting_dimension)
.select(accounting_dimension.document_type)
.join(doc_field)
.on(doc_field.parent == accounting_dimension.document_type)
.where(doc_field.fieldname == "company")
).run(as_list=True)
dimension_list = sum(dimension_list, ["Project"])
self.validate_company(dimension_list)
for child in self.get_all_children() or []:
self.validate_company(dimension_list, child)
def validate_company(self, dimension_list, child=None):
for dimension in dimension_list:
if not child:
dimension_value = self.get(frappe.scrub(dimension))
else:
dimension_value = child.get(frappe.scrub(dimension))
if dimension_value:
company = frappe.get_cached_value(dimension, dimension_value, "company")
if company and company != self.company:
frappe.throw(
_("{0}: {1} does not belong to the Company: {2}").format(
dimension, frappe.bold(dimension_value), self.company
)
)
def validate_return_against_account(self):
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
@@ -1027,11 +1091,12 @@ class AccountsController(TransactionBase):
def clear_unallocated_advances(self, childtype, parentfield):
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
frappe.db.sql(
"""delete from `tab{}` where parentfield={} and parent = {}
and allocated_amount = 0""".format(childtype, "%s", "%s"),
(parentfield, self.name),
)
doctype = frappe.qb.DocType(childtype)
frappe.qb.from_(doctype).delete().where(
(doctype.parentfield == parentfield)
& (doctype.parent == self.name)
& (doctype.allocated_amount == 0)
).run()
@frappe.whitelist()
def apply_shipping_rule(self):
@@ -1082,6 +1147,7 @@ class AccountsController(TransactionBase):
"advance_amount": flt(d.amount),
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
"difference_posting_date": self.posting_date,
}
self.append("advances", advance_row)
@@ -1332,7 +1398,6 @@ class AccountsController(TransactionBase):
gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
je = create_gain_loss_journal(
self.company,
args.get("difference_posting_date") if args else self.posting_date,
@@ -1445,6 +1510,7 @@ class AccountsController(TransactionBase):
"Company", self.company, "exchange_gain_loss_account"
),
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
"difference_posting_date": d.get("difference_posting_date"),
}
)
lst.append(args)
@@ -1691,22 +1757,22 @@ class AccountsController(TransactionBase):
continue
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
based_on_amt = flt(item.get(based_on))
if not ref_amt:
frappe.msgprint(
_("System will not check over billing since amount for Item {0} in {1} is zero").format(
item.item_code, ref_dt
),
title=_("Warning"),
indicator="orange",
)
if based_on_amt: # Skip warning for free items
frappe.msgprint(
_(
"System will not check over billing since amount for Item {0} in {1} is zero"
).format(item.item_code, ref_dt),
title=_("Warning"),
indicator="orange",
)
continue
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
total_billed_amt = flt(
flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)
)
total_billed_amt = flt(flt(already_billed) + based_on_amt, self.precision(based_on, item))
allowance, item_allowance, global_qty_allowance, global_amount_allowance = get_allowance_for(
item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount"
@@ -1971,11 +2037,9 @@ class AccountsController(TransactionBase):
for adv in self.advances:
consider_for_total_advance = True
if adv.reference_name == linked_doc_name:
frappe.db.sql(
f"""delete from `tab{self.doctype} Advance`
where name = %s""",
adv.name,
)
doctype = frappe.qb.DocType(self.doctype + " Advance")
frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
consider_for_total_advance = False
if consider_for_total_advance:
@@ -2065,7 +2129,9 @@ class AccountsController(TransactionBase):
and automatically_fetch_payment_terms
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
):
self.fetch_payment_terms_from_order(po_or_so, doctype)
self.fetch_payment_terms_from_order(
po_or_so, doctype, grand_total, base_grand_total, automatically_fetch_payment_terms
)
if self.get("payment_terms_template"):
self.ignore_default_payment_terms_template = 1
elif self.get("payment_terms_template"):
@@ -2106,7 +2172,9 @@ class AccountsController(TransactionBase):
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
)
else:
self.fetch_payment_terms_from_order(po_or_so, doctype)
self.fetch_payment_terms_from_order(
po_or_so, doctype, grand_total, base_grand_total, automatically_fetch_payment_terms
)
self.ignore_default_payment_terms_template = 1
def get_order_details(self):
@@ -2144,7 +2212,9 @@ class AccountsController(TransactionBase):
def linked_order_has_payment_schedule(self, po_or_so):
return frappe.get_all("Payment Schedule", filters={"parent": po_or_so})
def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
def fetch_payment_terms_from_order(
self, po_or_so, po_or_so_doctype, grand_total, base_grand_total, automatically_fetch_payment_terms
):
"""
Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
"""
@@ -2160,12 +2230,25 @@ class AccountsController(TransactionBase):
"invoice_portion": schedule.invoice_portion,
"mode_of_payment": schedule.mode_of_payment,
"description": schedule.description,
"payment_amount": schedule.payment_amount,
"base_payment_amount": schedule.base_payment_amount,
"outstanding": schedule.outstanding,
"paid_amount": schedule.paid_amount,
}
if automatically_fetch_payment_terms:
payment_schedule["payment_amount"] = flt(
grand_total * flt(payment_schedule["invoice_portion"]) / 100,
schedule.precision("payment_amount"),
)
payment_schedule["base_payment_amount"] = flt(
base_grand_total * flt(payment_schedule["invoice_portion"]) / 100,
schedule.precision("base_payment_amount"),
)
payment_schedule["outstanding"] = payment_schedule["payment_amount"]
else:
payment_schedule["base_payment_amount"] = flt(
schedule.base_payment_amount * self.get("conversion_rate"),
schedule.precision("base_payment_amount"),
)
if schedule.discount_type == "Percentage":
payment_schedule["discount_type"] = schedule.discount_type
payment_schedule["discount"] = schedule.discount
@@ -2188,6 +2271,9 @@ class AccountsController(TransactionBase):
return
for d in self.get("payment_schedule"):
if d.due_date and d.discount_date:
d.validate_from_to_dates("discount_date", "due_date")
if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
frappe.throw(
_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(
@@ -2421,12 +2507,17 @@ class AccountsController(TransactionBase):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
throw(_("Please enter default currency in Company Master"))
if (
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
or not self.conversion_rate
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
):
throw(_("Conversion rate cannot be 0 or 1"))
if not self.conversion_rate:
throw(_("Conversion rate cannot be 0"))
if self.currency == default_currency and flt(self.conversion_rate) != 1.00:
throw(_("Conversion rate must be 1.00 if document currency is same as company currency"))
if self.currency != default_currency and flt(self.conversion_rate) == 1.00:
frappe.msgprint(
_("Conversion rate is 1.00, but document currency is different from company currency")
)
def check_finance_books(self, item, asset):
if (

View File

@@ -75,7 +75,11 @@ def validate_returned_items(doc):
if doc.doctype != "Purchase Invoice":
select_fields += ",serial_no, batch_no"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
if doc.doctype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
select_fields += ",rejected_qty, received_qty"
for d in frappe.db.sql(
@@ -105,7 +109,12 @@ def validate_returned_items(doc):
for d in doc.get("items"):
key = d.item_code
raise_exception = False
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice"]:
if doc.doctype in [
"Purchase Receipt",
"Purchase Invoice",
"Sales Invoice",
"POS Invoice",
]:
field = frappe.scrub(doc.doctype) + "_item"
if d.get(field):
key = (d.item_code, d.get(field))
@@ -175,7 +184,11 @@ def validate_returned_items(doc):
def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
fields = ["stock_qty"]
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
if doc.doctype in [
"Purchase Receipt",
"Purchase Invoice",
"Subcontracting Receipt",
]:
fields.extend(["received_qty", "rejected_qty"])
already_returned_data = already_returned_items.get(key) or {}
@@ -203,7 +216,8 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
frappe.throw(_("{0} must be negative in return document").format(label))
elif returned_qty >= reference_qty and args.get(column):
frappe.throw(
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
_("Item {0} has already been returned").format(args.item_code),
StockOverReturnError,
)
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
frappe.throw(
@@ -242,7 +256,11 @@ def get_ref_item_dict(valid_items, ref_item_row):
if ref_item_row.get("rate", 0) > item_dict["rate"]:
item_dict["rate"] = ref_item_row.get("rate", 0)
if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
if ref_item_row.parenttype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
item_dict["received_qty"] += ref_item_row.received_qty
item_dict["rejected_qty"] += ref_item_row.rejected_qty
@@ -257,13 +275,17 @@ def get_ref_item_dict(valid_items, ref_item_row):
def get_already_returned_items(doc):
column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
if doc.doctype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
field = (
frappe.scrub(doc.doctype) + "_item"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice"]
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice", "POS Invoice"]
else "dn_detail"
)
data = frappe.db.sql(
@@ -384,7 +406,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
paid_amount = 0.00
base_paid_amount = 0.00
data.base_amount = flt(
data.amount * source.conversion_rate, source.precision("base_paid_amount")
data.amount * source.conversion_rate,
source.precision("base_paid_amount"),
)
paid_amount += data.amount
base_paid_amount += data.base_amount
@@ -544,10 +567,17 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
},
doctype + " Item": {
"doctype": doctype + " Item",
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no", "bom": "bom"},
"field_map": {
"serial_no": "serial_no",
"batch_no": "batch_no",
"bom": "bom",
},
"postprocess": update_item,
},
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
"Payment Schedule": {
"doctype": "Payment Schedule",
"postprocess": update_terms,
},
},
target_doc,
set_missing_values,
@@ -580,13 +610,20 @@ def get_rate_for_return(
item_row,
)
if voucher_type in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"):
if voucher_type in (
"Purchase Receipt",
"Purchase Invoice",
"Subcontracting Receipt",
):
select_field = "incoming_rate"
else:
select_field = "abs(stock_value_difference / actual_qty)"
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
if not (rate and return_against) and voucher_type in [
"Sales Invoice",
"Delivery Note",
]:
rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
if not rate and sle:
@@ -616,6 +653,7 @@ def get_return_against_item_fields(voucher_type):
"Delivery Note": "dn_detail",
"Sales Invoice": "sales_invoice_item",
"Subcontracting Receipt": "subcontracting_receipt_item",
"POS Invoice": "sales_invoice_item",
}
return return_against_item_fields[voucher_type]
@@ -629,7 +667,11 @@ def get_filters(
return_against_item_field,
item_row,
):
filters = {"voucher_type": voucher_type, "voucher_no": return_against, "item_code": item_code}
filters = {
"voucher_type": voucher_type,
"voucher_no": return_against,
"item_code": item_code,
}
if item_row:
reference_voucher_detail_no = item_row.get(return_against_item_field)
@@ -669,3 +711,9 @@ def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
return serial_nos
@frappe.whitelist()
def get_payment_data(invoice):
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
return payment

View File

@@ -801,7 +801,7 @@ class StockController(AccountsController):
child_tab.item_code,
child_tab.qty,
)
.where(parent_tab.docstatus < 2)
.where(parent_tab.docstatus == 1)
)
if self.doctype == "Purchase Invoice":

View File

@@ -27,6 +27,11 @@ class calculate_taxes_and_totals:
self.doc = doc
frappe.flags.round_off_applicable_accounts = []
if doc.get("round_off_applicable_accounts_for_tax_withholding"):
frappe.flags.round_off_applicable_accounts.append(
doc.round_off_applicable_accounts_for_tax_withholding
)
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
@@ -368,9 +373,7 @@ class calculate_taxes_and_totals:
self._calculate()
def calculate_taxes(self):
rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment")
if not rounding_adjustment_computed:
self.doc.rounding_adjustment = 0
self.grand_total_diff = 0
# maintain actual tax rate based on idx
actual_tax_dict = dict(
@@ -435,9 +438,8 @@ class calculate_taxes_and_totals:
and self.discount_amount_applied
and self.doc.discount_amount
and self.doc.apply_discount_on == "Grand Total"
and not rounding_adjustment_computed
):
self.doc.rounding_adjustment = flt(
self.grand_total_diff = flt(
self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment"),
)
@@ -523,11 +525,11 @@ class calculate_taxes_and_totals:
return self.adjust_grand_total_for_inclusive_tax()
def adjust_grand_total_for_inclusive_tax(self):
# if fully inclusive taxes and diff
# if any inclusive taxes and diff
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
last_tax = self.doc.get("taxes")[-1]
non_inclusive_tax_amount = sum(
flt(d.tax_amount_after_discount_amount)
self.get_tax_amount_if_for_valuation_or_deduction(d.tax_amount_after_discount_amount, d)
for d in self.doc.get("taxes")
if not d.included_in_print_rate
)
@@ -544,27 +546,23 @@ class calculate_taxes_and_totals:
diff = flt(diff, self.doc.precision("rounding_adjustment"))
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
self.doc.grand_total_diff = diff
else:
self.doc.grand_total_diff = 0
self.grand_total_diff = diff
def calculate_totals(self):
if self.doc.get("taxes"):
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(
self.doc.get("grand_total_diff")
)
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + self.grand_total_diff
else:
self.doc.grand_total = flt(self.doc.net_total)
if self.doc.get("taxes"):
self.doc.total_taxes_and_charges = flt(
self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")),
self.doc.grand_total - self.doc.net_total - self.grand_total_diff,
self.doc.precision("total_taxes_and_charges"),
)
else:
self.doc.total_taxes_and_charges = 0.0
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
self._set_in_company_currency(self.doc, ["total_taxes_and_charges"])
if self.doc.doctype in [
"Quotation",
@@ -614,7 +612,9 @@ class calculate_taxes_and_totals:
if self.doc.meta.get_field("rounded_total"):
if self.doc.is_rounded_total_disabled():
self.doc.rounded_total = self.doc.base_rounded_total = 0
self.doc.rounded_total = 0
self.doc.base_rounded_total = 0
self.doc.rounding_adjustment = 0
return
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
@@ -658,33 +658,29 @@ class calculate_taxes_and_totals:
return
total_for_discount_amount = self.get_total_for_discount_amount()
taxes = self.doc.get("taxes")
net_total = 0
expected_net_total = 0
if total_for_discount_amount:
# calculate item amount after Discount Amount
for i, item in enumerate(self._items):
for item in self._items:
distributed_amount = (
flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
)
item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
adjusted_net_amount = item.net_amount - distributed_amount
expected_net_total += adjusted_net_amount
item.net_amount = flt(adjusted_net_amount, item.precision("net_amount"))
net_total += item.net_amount
# discount amount rounding loss adjustment if no taxes
if (
self.doc.apply_discount_on == "Net Total"
or not taxes
or total_for_discount_amount == self.doc.net_total
) and i == len(self._items) - 1:
discount_amount_loss = flt(
self.doc.net_total - net_total - self.doc.discount_amount,
self.doc.precision("net_total"),
)
# discount amount rounding adjustment
if rounding_difference := flt(
expected_net_total - net_total, self.doc.precision("net_total")
):
item.net_amount = flt(
item.net_amount + discount_amount_loss, item.precision("net_amount")
item.net_amount + rounding_difference, item.precision("net_amount")
)
net_total += rounding_difference
item.net_rate = (
flt(item.net_amount / item.qty, item.precision("net_rate")) if item.qty else 0
@@ -700,20 +696,44 @@ class calculate_taxes_and_totals:
def get_total_for_discount_amount(self):
if self.doc.apply_discount_on == "Net Total":
return self.doc.net_total
else:
actual_taxes_dict = {}
for tax in self.doc.get("taxes"):
if tax.charge_type in ["Actual", "On Item Quantity"]:
tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
actual_taxes_dict.setdefault(tax.idx, tax_amount)
elif tax.row_id in actual_taxes_dict:
actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
total_actual_tax = 0
actual_taxes_dict = {}
return flt(
self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
def update_actual_tax_dict(tax, tax_amount):
nonlocal total_actual_tax
if tax.get("add_deduct_tax") == "Deduct":
tax_amount *= -1
if tax.get("category") != "Valuation":
total_actual_tax += tax_amount
actual_taxes_dict[int(tax.idx)] = {
"tax_amount": tax_amount,
"cumulative_tax_amount": total_actual_tax,
}
for tax in self.doc.get("taxes"):
if tax.charge_type in ["Actual", "On Item Quantity"]:
update_actual_tax_dict(tax, tax.tax_amount)
continue
if not tax.row_id:
continue
base_row = actual_taxes_dict.get(int(tax.row_id))
if not base_row:
continue
base_tax_amount = (
base_row["tax_amount"]
if tax.charge_type == "On Previous Row Amount"
else base_row["cumulative_tax_amount"]
)
update_actual_tax_dict(tax, base_tax_amount * tax.rate / 100)
return self.doc.grand_total - total_actual_tax
def calculate_total_advance(self):
if not self.doc.docstatus.is_cancelled():
@@ -775,9 +795,12 @@ class calculate_taxes_and_totals:
if (
self.doc.is_return
and self.doc.return_against
and not self.doc.update_outstanding_for_self
and not self.doc.get("is_pos")
or self.is_internal_invoice()
):
# Do not calculate the outstanding amount for a return invoice if 'update_outstanding_for_self' is not enabled.
self.doc.outstanding_amount = 0
return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])

View File

@@ -2,11 +2,14 @@
# For license information, please see license.txt
from datetime import datetime
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, getdate, nowdate
from frappe.utils.data import getdate as convert_to_date
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
@@ -705,6 +708,67 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
@change_settings("Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"})
def test_17_gain_loss_posting_date_for_normal_payment(self):
# Sales Invoice in Foreign Currency
rate = 80
rate_in_account_currency = 1
adv_date = convert_to_date(add_days(nowdate(), -2))
inv_date = convert_to_date(add_days(nowdate(), -1))
si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=rate_in_account_currency)
# Test payments with different exchange rates
pe = self.create_payment_entry(posting_date=adv_date, amount=1, source_exc_rate=75.1).save().submit()
pr = self.create_payment_reconciliation()
pr.from_invoice_date = add_days(nowdate(), -1)
pr.to_invoice_date = nowdate()
pr.from_payment_date = add_days(nowdate(), -2)
pr.to_payment_date = nowdate()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
# Outstanding in both currencies should be '0'
si.reload()
self.assertEqual(si.outstanding_amount, 0)
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
# Exchange Gain/Loss Journal should've been created.
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertNotEqual(exc_je_for_si, [])
self.assertEqual(len(exc_je_for_si), 1)
self.assertEqual(len(exc_je_for_pe), 1)
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
self.assertEqual(
getdate(nowdate()), frappe.db.get_value("Journal Entry", exc_je_for_pe[0].parent, "posting_date")
)
# Cancel Payment
pe.reload()
pe.cancel()
# outstanding should be same as grand total
si.reload()
self.assertEqual(si.outstanding_amount, rate_in_account_currency)
self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency)
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
@@ -1342,32 +1406,32 @@ class TestAccountsController(FrappeTestCase):
# Invoices
si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
si1.department = "Management"
si1.department = "Management - _TC"
si1.save().submit()
si2 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
si2.department = "Operations"
si2.department = "Operations - _TC"
si2.save().submit()
# Payments
cr_note1 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
cr_note1.department = "Management"
cr_note1.department = "Management - _TC"
cr_note1.is_return = 1
cr_note1.save().submit()
cr_note2 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
cr_note2.department = "Legal"
cr_note2.department = "Legal - _TC"
cr_note2.is_return = 1
cr_note2.save().submit()
pe1 = get_payment_entry(si1.doctype, si1.name)
pe1.references = []
pe1.department = "Research & Development"
pe1.department = "Research & Development - _TC"
pe1.save().submit()
pe2 = get_payment_entry(si1.doctype, si1.name)
pe2.references = []
pe2.department = "Management"
pe2.department = "Management - _TC"
pe2.save().submit()
je1 = self.create_journal_entry(
@@ -1380,7 +1444,7 @@ class TestAccountsController(FrappeTestCase):
)
je1.accounts[0].party_type = "Customer"
je1.accounts[0].party = self.customer
je1.accounts[0].department = "Management"
je1.accounts[0].department = "Management - _TC"
je1.save().submit()
# assert dimension filter's result
@@ -1389,17 +1453,17 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(len(pr.invoices), 2)
self.assertEqual(len(pr.payments), 5)
pr.department = "Legal"
pr.department = "Legal - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 1)
pr.department = "Management"
pr.department = "Management - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 3)
pr.department = "Research & Development"
pr.department = "Research & Development - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 1)
@@ -1411,17 +1475,17 @@ class TestAccountsController(FrappeTestCase):
# Invoice
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
si.department = "Management"
si.department = "Management - _TC"
si.save().submit()
# Payment
cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
cr_note.department = "Management"
cr_note.department = "Management - _TC"
cr_note.is_return = 1
cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.department = "Management"
pr.department = "Management - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
@@ -1454,7 +1518,7 @@ class TestAccountsController(FrappeTestCase):
# Sales Invoice in Foreign Currency
self.setup_dimensions()
rate_in_account_currency = 1
dpt = "Research & Development"
dpt = "Research & Development - _TC"
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True)
si.department = dpt
@@ -1490,7 +1554,7 @@ class TestAccountsController(FrappeTestCase):
def test_93_dimension_inheritance_on_advance(self):
self.setup_dimensions()
dpt = "Research & Development"
dpt = "Research & Development - _TC"
adv = self.create_payment_entry(amount=1, source_exc_rate=85)
adv.department = dpt

View File

@@ -375,7 +375,7 @@
"depends_on": "eval:!doc.__islocal",
"fieldname": "notes_tab",
"fieldtype": "Tab Break",
"label": "Comments"
"label": "Notes"
},
{
"collapsible": 1,
@@ -514,7 +514,7 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2023-12-01 18:46:49.468526",
"modified": "2025-01-31 13:40:08.094759",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",

View File

@@ -32,6 +32,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = {
fieldname: "status",
label: __("Status"),
fieldtype: "MultiSelectList",
options: ["Open", "Converted", "Quotation", "Replied"],
get_data: function () {
return [
{ value: "Open", description: "Status" },

View File

@@ -1,211 +1,78 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "field:gateway_name",
"beta": 0,
"creation": "2018-02-06 16:11:10.028249",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"gateway_name",
"section_break_2",
"access_token",
"webhooks_secret",
"use_sandbox",
"header_img"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Gateway Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "access_token",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Access Token",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "webhooks_secret",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Webhooks Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"label": "Webhooks Secret"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "use_sandbox",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Use Sandbox",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"label": "Use Sandbox"
},
{
"fieldname": "header_img",
"fieldtype": "Attach Image",
"label": "Header Image"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2022-02-12 14:18:47.209114",
"links": [],
"modified": "2024-07-22 12:34:26.791274",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "GoCardless Settings",
"name_case": "",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
"states": [],
"track_changes": 1
}

View File

@@ -4,7 +4,7 @@ app_publisher = "Frappe Technologies Pvt. Ltd."
app_description = """ERP made simple"""
app_icon = "fa fa-th"
app_color = "#e74c3c"
app_email = "info@erpnext.com"
app_email = "hello@frappe.io"
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
@@ -479,7 +479,7 @@ email_brand_image = "assets/erpnext/images/erpnext-logo.jpg"
default_mail_footer = """
<span>
Sent via
<a class="text-muted" href="https://erpnext.com?source=via_email_footer" target="_blank">
<a class="text-muted" href="https://frappe.io/erpnext?source=via_email_footer" target="_blank">
ERPNext
</a>
</span>

View File

@@ -9,6 +9,7 @@ if TYPE_CHECKING:
import frappe
from frappe.model.document import Document
from frappe.utils import date_diff, get_datetime, now
class BOMUpdateTool(Document):
@@ -38,13 +39,21 @@ def auto_update_latest_price_in_all_boms() -> None:
if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
wip_log = frappe.get_all(
"BOM Update Log",
{"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]},
fields=["creation", "status"],
filters={"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]},
limit_page_length=1,
order_by="creation desc",
)
if not wip_log:
if not wip_log or is_older_log(wip_log[0]):
create_bom_update_log(update_type="Update Cost")
def is_older_log(log: dict) -> bool:
no_of_days = date_diff(get_datetime(now()), get_datetime(log.creation))
return no_of_days > 10
def create_bom_update_log(
boms: dict[str, str] | None = None,
update_type: Literal["Replace BOM", "Update Cost"] = "Replace BOM",

View File

@@ -201,7 +201,7 @@
"description": "In the case of 'Use Multi-Level BOM' in a work order, if the user wishes to add sub-assembly costs to Finished Goods items without using a job card as well the scrap items, then this option needs to be enable.",
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
"fieldtype": "Check",
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
"label": "Set Operating Cost / Scrap Items From Sub-assemblies"
}
],
"icon": "icon-wrench",
@@ -226,4 +226,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -92,7 +92,7 @@ class WorkOrder(Document):
if self.source_warehouse:
self.set_warehouses()
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
validate_uom_is_integer(self, "stock_uom", ["required_qty"])
self.set_required_items(reset_only_qty=len(self.get("required_items")))

View File

@@ -57,6 +57,7 @@ frappe.query_reports["Job Card Summary"] = {
label: __("Work Orders"),
fieldname: "work_order",
fieldtype: "MultiSelectList",
options: "Work Order",
get_data: function (txt) {
return frappe.db.get_link_options("Work Order", txt);
},
@@ -65,6 +66,7 @@ frappe.query_reports["Job Card Summary"] = {
label: __("Production Item"),
fieldname: "production_item",
fieldtype: "MultiSelectList",
options: "Item",
get_data: function (txt) {
return frappe.db.get_link_options("Item", txt);
},

View File

@@ -42,7 +42,7 @@ frappe.query_reports["Production Planning Report"] = {
fieldname: "docnames",
label: __("Document Name"),
fieldtype: "MultiSelectList",
options: "Sales Order",
options: "based_on",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -50,7 +50,11 @@ def get_returned_materials(work_orders):
raw_materials = frappe.get_all(
"Stock Entry",
fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"],
fields=[
"`tabStock Entry`.`work_order`",
"`tabStock Entry Detail`.`item_code`",
"`tabStock Entry Detail`.`qty`",
],
filters=[
["Stock Entry", "is_return", "=", 1],
["Stock Entry Detail", "docstatus", "=", 1],
@@ -59,12 +63,14 @@ def get_returned_materials(work_orders):
)
for d in raw_materials:
raw_materials_qty[d.item_code] += d.qty
key = (d.work_order, d.item_code)
raw_materials_qty[key] += d.qty
for row in work_orders:
row.returned_qty = 0.0
if raw_materials_qty.get(row.raw_material_item_code):
row.returned_qty = raw_materials_qty.get(row.raw_material_item_code)
key = (row.parent, row.raw_material_item_code)
if raw_materials_qty.get(key):
row.returned_qty = raw_materials_qty.get(key)
def get_fields():

View File

@@ -43,6 +43,7 @@ frappe.query_reports["Work Order Summary"] = {
label: __("Sales Orders"),
fieldname: "sales_order",
fieldtype: "MultiSelectList",
options: "Sales Order",
get_data: function (txt) {
return frappe.db.get_link_options("Sales Order", txt);
},
@@ -51,6 +52,7 @@ frappe.query_reports["Work Order Summary"] = {
label: __("Production Item"),
fieldname: "production_item",
fieldtype: "MultiSelectList",
options: "Item",
get_data: function (txt) {
return frappe.db.get_link_options("Item", txt);
},

View File

@@ -368,3 +368,7 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1
erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter
erpnext.patches.v14_0.update_stock_uom_in_work_order_item
erpnext.patches.v14_0.disable_add_row_in_gross_profit
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
erpnext.patches.v14_0.update_posting_datetime
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes

View File

@@ -0,0 +1,5 @@
import frappe
def execute():
frappe.db.set_value("Report", "Gross Profit", "add_total_row", 0)

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