Compare commits

..

657 Commits

Author SHA1 Message Date
Ankush Menat
401f276334 perf: Drop cost center index
This was only added to allow renaming for one user (?) can be added if
there's a more serious use case than that.
2025-04-21 22:40:30 +05:30
Ankush Menat
f25524ff95 perf: Drop paty_type + party index
party alone provides almost all of cardinality.

party_type might be separately useful in some cases but I don't see
anything here.
2025-04-21 22:40:30 +05:30
Ankush Menat
34896f1cd1 perf: remove voucher_type + voucher_no index
Voucher_no is real source of cardinality here... voucher_type will just
be 10-20 unique types, not really helping much here.
2025-04-21 22:40:30 +05:30
Ankush Menat
4cbc92649f perf: Only created company indexes in multi-company setups
A vast vast majority of installations don't have multiple companies, so
this unnecessary index is an overhead for them.
2025-04-21 22:40:30 +05:30
Ankush Menat
1b85fbf0e5 perf: Drop party_type index
It's covered by party_type + party index. So this is not required at all.

Party type is also a rarely used as most selective filter => By nature it has very low cardinality.
2025-04-21 22:40:27 +05:30
rohitwaghchaure
a599632f8a Merge pull request #47186 from rohitwaghchaure/fixed-github-47112
fix: disabled UOM showing in the list
2025-04-21 22:12:26 +05:30
Rohit Waghchaure
3745825052 fix: disbaled UOM showing in the list 2025-04-21 21:24:58 +05:30
rohitwaghchaure
5835502937 Merge pull request #47144 from rohitwaghchaure/fixed-bin-recalculate-qty
fix: provision to recalculate the qty in the Bin
2025-04-21 21:18:07 +05:30
Frappe PR Bot
c7e5442565 fix: sync translations from crowdin (#47163) 2025-04-21 17:26:25 +02:00
Diptanil Saha
f3838c0b61 Merge pull request #47182 from diptanilsaha/fix-pos-item-selector-cart-ui
fix: pos item selector cart ui
2025-04-21 18:30:35 +05:30
diptanilsaha
83cf17f07c fix: row gap in item details form 2025-04-21 18:16:45 +05:30
diptanilsaha
52d73de6b6 fix: cart item toggle highlight 2025-04-21 17:57:40 +05:30
ruthra kumar
c5db4ca43a Merge pull request #47179 from frappe/broken_markdown
chore: broken markdown
2025-04-21 17:01:15 +05:30
ruthra kumar
9244ecdaff chore: broken markdown 2025-04-21 16:59:33 +05:30
ruthra kumar
21df80b750 Merge pull request #47106 from yashhhYB/yashhhYB-patch-1
docs(readme): add Frappe School badge for learning ERPNext
2025-04-21 16:41:25 +05:30
Yash Bodade
911c7b969c docs(readme): add Frappe School badge for learning ERPNext
This PR adds a badge linking to [Frappe School](https://frappe.school) to promote learning resources directly from the README. It helps newcomers discover structured tutorials and improves onboarding.

### Changes Made

- Added a Frappe School badge below the title in `README.md`

### Screenshot

[![Learn on Frappe School](https://img.shields.io/badge/Frappe%20School-Learn%20ERPNext-blue?style=flat-square)](https://frappe.school)

---
2025-04-21 16:39:55 +05:30
diptanilsaha
e035120f41 fix: item with no image 2025-04-21 16:17:24 +05:30
ruthra kumar
a7bf999a4f Merge pull request #47019 from Z4nzu/fix_italy_localization
fix(italy): Add tax-related custom fields to POS Invoice Item
2025-04-21 16:08:47 +05:30
ruthra kumar
b9fc173a98 Merge pull request #47067 from ljain112/fix-error-message
fix: correct error message in validate_internal_transfer_qty
2025-04-21 15:57:56 +05:30
diptanilsaha
374da68829 fix: added gap in item details form 2025-04-21 15:53:29 +05:30
diptanilsaha
3e9fc558a8 fix: item images on pos 2025-04-21 15:43:15 +05:30
Diptanil Saha
cb2ad4acdb fix: update country wise fiscal year (#47141) 2025-04-21 14:19:24 +05:30
Diptanil Saha
f52cbf6165 fix: pos disable customer selection at payment (#47169) 2025-04-21 11:53:03 +05:30
Ankush Menat
8b602c4608 chore: translations link (#47168) 2025-04-21 05:54:34 +00:00
Frappe PR Bot
32d5597d23 chore: update POT file (#47165) 2025-04-20 13:45:26 +02:00
Frappe PR Bot
d19b197524 fix: sync translations from crowdin (#47139)
* fix: Chinese Simplified translations

* fix: Thai translations

* fix: Portuguese translations
2025-04-19 21:42:26 +02:00
Rohit Waghchaure
36081413d8 fix: provision to recalculate the qty in the Bin 2025-04-19 16:26:57 +05:30
Sagar Vora
eac96cb186 Merge pull request #47154 from sagarvora/fix-dimension-copying 2025-04-19 12:51:08 +05:30
Sagar Vora
7dbe27da19 fix: respect mapped accounting dimensions 2025-04-19 11:16:48 +05:30
Raffael Meyer
baa0c8bd5f chore: migrate pre-commit config (#47132) 2025-04-17 11:59:48 +00:00
Raffael Meyer
e945b4cc06 fix: create default warehouse (#47125) 2025-04-17 13:17:02 +02:00
ruthra kumar
048997f809 Merge pull request #47118 from aerele/tb-group-by-account
fix: add group by after user permission condition
2025-04-17 16:05:19 +05:30
Frappe PR Bot
e5eafc49ee fix: sync translations from crowdin (#47035) 2025-04-17 11:41:55 +02:00
venkat102
756d496235 fix: add group by after user permission condition 2025-04-17 00:12:43 +05:30
rohitwaghchaure
5d35e31281 Merge pull request #46944 from rohitwaghchaure/fixed-production=plan-lables
fix: production plan label description
2025-04-16 23:17:55 +05:30
Rohit Waghchaure
2de61e955a fix: production plan label description 2025-04-16 23:03:58 +05:30
rohitwaghchaure
659119adc2 Merge pull request #47050 from mihir-kandoi/st35798
fix: consider per_ordered instead of per_billed when creating PO from MR
2025-04-16 16:28:00 +05:30
Shariq Ansari
f4c4a0cc0f Merge pull request #47108 from shariquerik/revert-disable-customer 2025-04-16 10:21:06 +05:30
Shariq Ansari
fc16199a49 revert: disable customer if creating from opportunity 2025-04-16 10:18:29 +05:30
ruthra kumar
0b22f87db9 Merge pull request #46770 from ljain112/fix-dunnig
fix: correct outstanding amount for invoice in dunning
2025-04-15 17:50:25 +05:30
ruthra kumar
7728126b1f Merge pull request #46996 from Sanket322/contact_adress_in_add_column
fix: Allow adding Address/Contact fields to `Address And Contacts` report
2025-04-15 17:45:27 +05:30
rohitwaghchaure
88b5d40202 Merge pull request #46973 from rtdany10/reservation-precision
fix: precision issue on qty_to_be_reserved
2025-04-15 17:36:39 +05:30
ruthra kumar
67600b76e8 Merge pull request #47001 from aerele/inter-company-order-echange-rate
fix: fetch exchange rate while creating inter-company order and invoice
2025-04-15 17:35:58 +05:30
ruthra kumar
39174f9dc0 chore: translatable labels 2025-04-15 17:32:46 +05:30
Marica
445263bb85 Merge pull request #47007 from HUMENTH/patch-2
chore: Fix typo "Item Wise Tax Detail "
2025-04-15 17:01:04 +05:30
Khushi Rawat
e41720f1a3 fix: enabled allow on submit for asset name field (#47093) 2025-04-15 16:59:16 +05:30
marination
be556167b1 fix: Modify .json from desk to change modified 2025-04-15 13:15:50 +02:00
ruthra kumar
573809cece Merge pull request #46951 from cogk/fix-use-party_name-instead-of-customer-in-set_query-of-quotation-items
fix: Use `party_name` instead of `customer` in `set_query` of Quotation items
2025-04-15 15:45:27 +05:30
ruthra kumar
a2b326a988 Merge pull request #46640 from ljain112/fix-ledger-summary
perf: refactored customer ledger summary for performance
2025-04-15 11:52:15 +05:30
ruthra kumar
eb1b401653 Merge pull request #46789 from aerele/order-tax-table
fix: map tax table while creating purchase order from sales order
2025-04-15 11:41:15 +05:30
ruthra kumar
71f0f7a0b5 test: basic supplier ledger summary 2025-04-15 11:37:59 +05:30
ruthra kumar
9a3a80dfd3 test: basic output of customer ledger summary report 2025-04-15 11:37:57 +05:30
ljain112
3b613c44a6 chore: added test for Fetch Overdue Payments in dunning 2025-04-15 11:36:19 +05:30
ruthra kumar
9bae57e2b4 Merge pull request #46754 from muruthigitau/create-payment-gateway-account
feat: add optional company parameter to create_payment_gateway_account
2025-04-15 11:07:49 +05:30
muruthigitau
6608735006 refactor: optional company param to create_payment_gateway_account 2025-04-15 10:55:26 +05:30
Raffael Meyer
fb9cb779f4 test(Payment Entry): account type is set (#47071) 2025-04-14 19:06:25 +02:00
Raffael Meyer
a854beeb40 fix(Payment Entry): set account type if missing (#47069) 2025-04-14 18:32:19 +02:00
ljain112
5063f1174e fix: correct error message in validate_internal_transfer_qty 2025-04-14 19:06:22 +05:30
rohitwaghchaure
e3f1677786 Merge pull request #47057 from rohitwaghchaure/fixed-github-46675
fix: not able to complete work order with reserved qty
2025-04-14 18:08:52 +05:30
rohitwaghchaure
083a666188 Merge pull request #47058 from rohitwaghchaure/fixed-support-36030
fix: consider negative stock qty in stock reconciliation
2025-04-14 18:08:30 +05:30
ruthra kumar
95686fc255 Merge pull request #46782 from iamkhanraheel/fix-customer-payment-autofetch
fix: fetch customer related data in making payment entry from customer
2025-04-14 17:32:49 +05:30
Rohit Waghchaure
15272d0e56 fix: consider negative stock qty in stock reco 2025-04-14 14:37:04 +05:30
Rohit Waghchaure
27d674d54a fix: not able to complete work order with reserved qty 2025-04-14 14:33:51 +05:30
ruthra kumar
46783c1377 Merge pull request #47049 from frappe/revert-46900-fix-against-voucher
Revert "fix: remove against_voucher and against_voucher_type column from General Ledger Report"
2025-04-14 13:18:36 +05:30
ruthra kumar
adb331ef71 fix: revert #46900 - against_voucher filter in general ledger 2025-04-14 13:01:40 +05:30
Mihir Kandoi
5a524854de fix: consider per_ordered instead of per_billed when creating PO from MR 2025-04-14 12:36:59 +05:30
rohitwaghchaure
61a3e01bd3 Merge pull request #46620 from mihir-kandoi/46618
fix: add fetch from to currency and set default to empty in item price
2025-04-14 12:13:07 +05:30
rohitwaghchaure
ea917f946c Merge pull request #46938 from mihir-kandoi/st35392
fix: group sub assemblies in production plan
2025-04-14 12:10:36 +05:30
Mihir Kandoi
8df18762a9 fix: test cases error 2025-04-14 11:09:45 +05:30
Sagar Vora
205443a9e0 Merge pull request #47016 from barredterra/employee-user-perms 2025-04-14 10:27:19 +05:30
Deepesh Garg
acfa2465df Merge pull request #47043 from deepeshgarg007/trial_balance_memory
fix: Group GLs by account for TB generation
2025-04-13 19:26:29 +05:30
barredterra
08f21c7905 fix: remove hook that does nothing 2025-04-13 15:29:38 +02:00
Deepesh Garg
f894c6d275 fix: Group GLs by account for TB generation 2025-04-13 18:53:11 +05:30
Frappe PR Bot
eff2f34048 chore: update POT file (#47042) 2025-04-13 12:35:01 +02:00
ruthra kumar
8ef31c8fc0 Merge pull request #47012 from ljain112/fix-pur-resigister-item
fix: correct doctype in item_wise_purchase register
2025-04-12 07:10:32 +05:30
rohitwaghchaure
b4be1503e3 Merge pull request #47026 from rohitwaghchaure/fixed-removed-unused-code
fix: batchwise valuation for MA item
2025-04-11 21:56:30 +05:30
Rohit Waghchaure
504b8c0a68 fix: batchwise valuation for MA item 2025-04-11 21:20:05 +05:30
Frappe PR Bot
78583ac632 fix: sync translations from crowdin (#47009) 2025-04-11 16:41:50 +02:00
rohitwaghchaure
dc8616d2ab Merge pull request #47022 from rohitwaghchaure/fixed-condition-for-use_batchwise_valuation
fix: condition for use_batchwise_valuation
2025-04-11 19:34:45 +05:30
Mihir Kandoi
a7394329ca fix: test cases 2025-04-11 19:30:35 +05:30
Rohit Waghchaure
cc171d9706 fix: condition for use_batchwise_valuation 2025-04-11 19:17:47 +05:30
rohitwaghchaure
e2a7856a44 Merge pull request #47020 from rohitwaghchaure/fixed-display-depends-on
fix: removed display depends on
2025-04-11 19:14:57 +05:30
Rohit Waghchaure
e0bf45e03b fix: removed display depends on 2025-04-11 18:58:09 +05:30
Hardik Zinzuvadiya
02bb63a5f7 fix(italy): Add missing tax fields to POS Invoice Item 2025-04-11 12:57:25 +00:00
rohitwaghchaure
890040db86 Merge pull request #47015 from rohitwaghchaure/fixed-support-35994-1
fix: allow to use batch-wise valuation for moving average items
2025-04-11 17:07:20 +05:30
Rohit Waghchaure
65ba79bb85 fix: allow to use batchwise valuation for moving average items 2025-04-11 16:48:41 +05:30
barredterra
046bcfa606 fix(Employee): add/delete user permission 2025-04-11 13:14:52 +02:00
Mihir Kandoi
f071255340 fix: logic and added test case 2025-04-11 16:22:28 +05:30
ljain112
b8b8dce733 fix: correct doctype in item_wise_purchase register 2025-04-11 15:06:03 +05:30
Himanshu Shivhare
9624d56abd chore: Fix typo "Item Wise Tax Detail " 2025-04-10 23:18:43 +05:30
rohitwaghchaure
4abc39ef62 Merge pull request #46997 from rohitwaghchaure/fixed-support-35003
fix: update the modified date in for SLEs and GLs after rename
2025-04-10 17:39:35 +05:30
rohitwaghchaure
8b0fd7b00f Merge pull request #47002 from rohitwaghchaure/fixed-support-34140
feat: Allow to Make Quality Inspection after Purchase / Delivery
2025-04-10 17:39:04 +05:30
Rohit Waghchaure
8eaa2afeb7 feat: Allow to Make Quality Inspection after Purchase / Delivery 2025-04-10 16:28:23 +05:30
venkat102
145a6c5e2a fix: fetch exchange rate while creating inter-company order and invoice 2025-04-10 16:07:20 +05:30
Rohit Waghchaure
dc5a5ef258 fix: update the modified date in for SLEs and GLs after rename 2025-04-10 15:53:06 +05:30
Sanket322
d9ca7e755f fix: add Address and Contact in Add Column 2025-04-10 13:03:11 +05:30
rohitwaghchaure
511a01c26c Merge pull request #46965 from rohitwaghchaure/fixed-reposting-error-recreate-ledgers
fix: Recreate Stock Ledgers issue
2025-04-10 11:42:51 +05:30
rohitwaghchaure
2ef8b581cd Merge pull request #46978 from rohitwaghchaure/fixed-support-32497
fix: bypass validation during reposting
2025-04-10 11:42:20 +05:30
Rohit Waghchaure
3697b9fd9b fix: bypass validation during reposting 2025-04-10 11:26:44 +05:30
Frappe PR Bot
8383c826c3 Merge pull request #46963 from frappe/l10n_develop
fix: sync translations from crowdin
2025-04-09 16:47:24 +02:00
Dany Robert
860699ee7b fix: precision issue on qty_to_be_reserved 2025-04-09 18:39:36 +05:30
rohitwaghchaure
358e3581c2 Merge pull request #46972 from rtdany10/reservation-precision
fix: precision loss causing reservation error
2025-04-09 18:09:37 +05:30
Dany Robert
e3c0dbe79f fix: precision loss causing reservation error 2025-04-09 17:38:00 +05:30
Diptanil Saha
67fcc172d7 Merge pull request #46964 from diptanilsaha/pos-profile-add-project-field
fix: added missing project field on pos profile
2025-04-09 15:34:26 +05:30
Marica
2fee4958d7 Merge pull request #46933 from rohitwaghchaure/fixed-support-32029
fix: item code not showing in the error message
2025-04-09 15:32:21 +05:30
Rohit Waghchaure
229a4cef45 fix: Recreate Stock Ledgers issue 2025-04-09 12:37:02 +05:30
diptanilsaha
821d64241a fix: added missing project field on pos profile 2025-04-09 11:37:51 +05:30
Raffael Meyer
147852a5af Merge pull request #46959 from barredterra/refactor-due-date
fix: interaction with due date / payment terms / payment schedule
2025-04-08 21:00:41 +02:00
barredterra
c00f62d54a chore: add context 2025-04-08 20:54:52 +02:00
barredterra
57be8a85d6 fix: clarify confirmation message 2025-04-08 20:53:29 +02:00
barredterra
8a4db69581 fix: use the actual field label 2025-04-08 20:53:04 +02:00
barredterra
c55c77f4e9 fix: recognize trigger from child table 2025-04-08 20:40:50 +02:00
barredterra
87c21a89fe refactor: use doc parameter instead of this.frm.doc 2025-04-08 20:39:14 +02:00
Corentin Forler
a16fd451ee fix: Use party_name instead of customer in Quotation set_query 2025-04-08 15:33:22 +02:00
Raffael Meyer
fa719d8695 fix: make report's "printed on" translatable (#46913)
* fix: make report's "printed on" translatable

* fix: go for lower case "on" because we already have translations for that

* fix: remove redundant letter head

* fix(General Ledger): make table header translatable
2025-04-08 15:22:13 +02:00
barredterra
6dc65f1a19 fix(General Ledger): make table header translatable 2025-04-08 15:19:46 +02:00
barredterra
5837f257bc fix: remove redundant letter head 2025-04-08 15:08:40 +02:00
barredterra
b0d5642785 fix: go for lower case "on" because we already have translations for that 2025-04-08 15:01:21 +02:00
rohitwaghchaure
495428ba4e Merge pull request #46383 from mihir-kandoi/st29657-2
feat: available serial no report
2025-04-08 17:46:12 +05:30
rohitwaghchaure
984bb7c7ea Merge pull request #46942 from mihir-kandoi/st35203
fix: ignore backflush setting on subcontracting return
2025-04-08 17:44:21 +05:30
ruthra kumar
6f89f7e945 Merge pull request #46892 from mihir-kandoi/st35406
fix: use get instead of dot operator to access dict value to prevent no attribute error
2025-04-08 17:03:22 +05:30
Mihir Kandoi
7479e1ec32 fix: ignore backflush setting on subcontracting return 2025-04-08 15:10:42 +05:30
Mihir Kandoi
f58abed935 fix: group sub assemblies in production plan 2025-04-08 15:02:12 +05:30
ruthra kumar
c8d6968a66 Merge pull request #46631 from aerele/fix/update_outstanding_for_self
fix: update outstanding for self
2025-04-08 14:27:13 +05:30
rohitwaghchaure
72846dc64f Merge pull request #46171 from JK-1117/fix-reconciliation-inventory-dimension
Fix validation mismatch in inventory dimension fields
2025-04-08 14:26:25 +05:30
Rohit Waghchaure
86dee69c2f fix: item code not showing in the error message 2025-04-08 13:30:12 +05:30
ruthra kumar
ea8ab256ad Merge pull request #46626 from ljain112/fix-pr-amount
fix: correct payment request amount
2025-04-08 13:18:35 +05:30
ruthra kumar
673e3c7849 Merge pull request #46821 from ljain112/cust-query
fix: removed customer_group query in customer.js
2025-04-08 12:57:02 +05:30
Shariq Ansari
5dfc0d32d6 Merge pull request #46903 from shariquerik/restrict-customer-change
fix: restrict customer change if creating from opportunity
2025-04-08 12:06:10 +05:30
ruthra kumar
c58e7c22f5 Merge pull request #46819 from cogk/fix-fieldtype-unreconcile-dialog
fix: Fix fieldtype in UnReconcile dialog
2025-04-08 11:25:35 +05:30
ruthra kumar
b1051dadd5 Merge pull request #46709 from ljain112/user-permission
fix: user permissions in sales and purchase report
2025-04-08 11:09:54 +05:30
ruthra kumar
49822c553a Merge pull request #46804 from ljain112/fix-os-precision
fix: update outstanding with precision
2025-04-08 09:58:44 +05:30
ruthra kumar
4f758701fe Merge pull request #46917 from frappe/l10n_develop
fix: sync translations from crowdin
2025-04-08 09:55:42 +05:30
Frappe PR Bot
b5d7fe6734 fix: Persian translations 2025-04-08 08:51:17 +05:30
rohitwaghchaure
6ee6d3508a Merge pull request #46893 from rohitwaghchaure/fixed-support-35192
fix: inventory dimensions columns visibility depends on filter
2025-04-08 08:49:33 +05:30
rohitwaghchaure
903ff67a22 Merge pull request #46898 from rohitwaghchaure/fixed-support-33798-1
fix: removed hardcoded search fields to fix performance issue
2025-04-08 08:49:10 +05:30
barredterra
186d7abf0a fix: make report's "printed on" translatable 2025-04-08 02:28:46 +02:00
Diptanil Saha
6fae98afda fix: pos opening entry's status not getting updated on cancel (#46909) 2025-04-07 20:44:47 +05:30
Diptanil Saha
3de1b22480 fix: validate if pos is opened before pos invoice creation (#46907)
* fix: validate if pos is opened before pos invoice creation

* fix: added title on throw dialog

* test: fixed failing test
2025-04-07 20:33:27 +05:30
ruthra kumar
79bd731188 Merge pull request #46895 from ljain112/fix-filter-gl
fix: empty party filter on change of party type in General Ledger Report
2025-04-07 17:42:02 +05:30
Shariq Ansari
dc4819e897 fix: restrict customer change if creating from opportunity 2025-04-07 17:17:42 +05:30
ruthra kumar
274cb898ea Merge pull request #46900 from ljain112/fix-against-voucher
fix: remove against_voucher and against_voucher_type column from General Ledger Report
2025-04-07 17:10:50 +05:30
ljain112
6d1f119a0f fix: remove against_voucher from General Ledger Report 2025-04-07 16:28:52 +05:30
Frappe PR Bot
56eaa504ec fix: sync translations from crowdin (#46872)
* fix: Persian translations

* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations
2025-04-07 12:13:55 +02:00
Rohit Waghchaure
216bf2456e fix: removed hardcoded search fields to fix performance issue 2025-04-07 14:46:16 +05:30
ruthra kumar
15d632f8de Merge pull request #46728 from rtdany10/inv_date_issue
fix: update posting date before running validations
2025-04-07 14:30:39 +05:30
ljain112
9c68bc22fa fix: empty party filter on change of party type in General Ledger Report. 2025-04-07 14:09:52 +05:30
Rohit Waghchaure
2b411fb7f5 fix: inventory dimensions columns visibility depends on filter 2025-04-07 11:47:27 +05:30
Mihir Kandoi
7fb75f0482 fix: use get instead of dot operator to access dict value 2025-04-07 11:06:19 +05:30
ruthra kumar
7f0af3c667 Merge pull request #46637 from aerele/allocate-payment-term
fix(payment term): allocate payment amount when payment term is fetched from order
2025-04-07 11:03:24 +05:30
ruthra kumar
8759824aa7 Merge pull request #46784 from mohsinalimat/develop
fix: correct mapping(schedule_date) sales order to material request
2025-04-07 10:43:26 +05:30
ruthra kumar
0d72d58860 Merge pull request #46743 from aerele/auto-bank-reconcile
fix: include auto_reconcile_vouchers flag in background job
2025-04-07 10:19:59 +05:30
rohitwaghchaure
9c685c5af0 Merge pull request #46836 from mihir-kandoi/st35124
fix: remove all serial/batch fields when use button is unselected
2025-04-07 10:19:16 +05:30
ruthra kumar
82bf88635c Merge pull request #46727 from aerele/apply_docstatus
fix: use docstatus for status filter
2025-04-07 10:13:00 +05:30
Diptanil Saha
21954b9f9c fix: pos closed dialog on pos closing entry (#46881) 2025-04-06 21:26:14 +05:30
Frappe PR Bot
b4430a14a6 chore: update POT file (#46876) 2025-04-06 13:50:52 +02:00
rohitwaghchaure
5786596fc0 Merge pull request #46845 from rohitwaghchaure/fixed-support-35337
fix: slow query
2025-04-06 16:10:28 +05:30
rohitwaghchaure
c9cc5122dd Merge pull request #46875 from TurkerTunali/patch-13
perf: Stock entry cancel is slow
2025-04-06 16:09:08 +05:30
ruthra kumar
2948a33f40 Merge pull request #46716 from barredterra/dunning-customer-dashboard
feat(Customer): add Dunning to dashboard
2025-04-06 15:50:45 +05:30
ruthra kumar
a55a4efb22 Merge pull request #46658 from SruthysCode/fix_accounts_payable
chore: adjusted dimension placement in Accounts Payable
2025-04-06 15:39:22 +05:30
Türker Tunalı
ddbb44c6a2 perf: Stock entry cancel is slow
Some queries still use "timestamp" function instead of "posting_datetime". In my instance single stock entry cancel ends with request timeout. Using "posting_datetime" field directly improves the situation.

cont: https://github.com/frappe/erpnext/pull/46293
2025-04-06 12:25:42 +03:00
Rohit Waghchaure
f82c8ea5eb fix: slow query 2025-04-06 13:41:14 +05:30
rohitwaghchaure
6fd15abd15 Merge pull request #46853 from rohitwaghchaure/fixed-support-35243
fix: stock entry repack amount calculation
2025-04-06 09:44:59 +05:30
Sagar Vora
4ef5f6a392 Merge pull request #46870 from sagarvora/perf-validate
perf: reduce query when validating any doc
2025-04-05 23:38:30 +05:30
Sagar Vora
b863296e53 perf: reduce query when validating any doc 2025-04-05 23:06:52 +05:30
Raffael Meyer
04df09cfca fix(Dunning): undefined variable (#46868) 2025-04-05 15:19:23 +00:00
Raffael Meyer
e25517a3ce refactor(Payment Entry): reduce indentation (#46864) 2025-04-05 16:41:23 +02:00
Raffael Meyer
7d12e9afd4 fix: make message translatable (#46863) 2025-04-05 15:56:33 +02:00
Frappe PR Bot
27deed6d94 fix: sync translations from crowdin (#46858)
* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Chinese Simplified translations

* fix: Bosnian translations

* fix: Esperanto translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations
2025-04-05 12:33:37 +02:00
ruthra kumar
ae1f6912f8 Merge pull request #46625 from Abdeali099/bank-transaction-entries
refactor: Update Bank Transaction `set_query` implemetation
2025-04-05 07:04:26 +05:30
Rohit Waghchaure
544ceb93cd fix: stock entry repack amount calculation 2025-04-04 13:38:45 +05:30
Frappe PR Bot
89f58f7822 fix: sync translations from crowdin (#46849)
* fix: Spanish translations

* fix: French translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Croatian translations
2025-04-04 00:45:09 +02:00
ruthra kumar
4cb6b70048 Merge pull request #46818 from cogk/fix-translate-unreconcile-dialog
fix: Translate UnReconcile dialog title
2025-04-03 06:51:59 +05:30
Md Hussain Nagaria
ef4f662c31 chore: update links to Frappe School (#46823) 2025-04-02 12:15:10 +05:30
rohitwaghchaure
7b5406b940 Merge pull request #46832 from rohitwaghchaure/fixed-support-33851-3
fix: set draft QC in purchase document on creation of qc
2025-04-01 19:46:36 +05:30
ljain112
038355f87b fix: correct function name 2025-04-01 17:43:11 +05:30
ljain112
fca46e0b2d fix: child values for tree doctypes and query refactor 2025-04-01 17:18:01 +05:30
Rohit Waghchaure
2553dea78e fix: set draft QC in purchase document on creation of qc 2025-04-01 15:11:03 +05:30
Mihir Kandoi
22ffdb9e77 fix: remove all serial/batch fields when use button is unselected 2025-04-01 14:15:47 +05:30
rohitwaghchaure
8dae5047c2 Merge pull request #46831 from rohitwaghchaure/fixed-support-35187
fix: current batch qty showing zero in the stock reconciliation
2025-04-01 14:08:54 +05:30
Sagar Vora
db0c5ae48d Merge pull request #46829 from vishakhdesai/gt-diff
fix: use `grand_total_diff` instead of `rounding_adjustment` in `taxes_and_totals`
2025-04-01 13:30:48 +05:30
Rohit Waghchaure
a5c62f8623 fix: current batch qty showing zero in the stock reconciliation 2025-04-01 13:15:06 +05:30
vishakhdesai
fd252da6b1 fix: use grand_total_diff instead of rounding_adjustment in taxes_and_totals 2025-04-01 12:43:34 +05:30
Diptanil Saha
5d5b6acc79 fix: pos checking opened entry closed or not (#46726)
* fix: pos checking opened entry closed or not

* fix: linter issue

* fix: linter issue
2025-04-01 12:40:47 +05:30
Abdeali Chharchhoda
4ae11d4384 chore: formatting 2025-04-01 12:12:37 +05:30
Abdeali Chharchhoda
fb054d4904 Merge branch 'develop' into bank-transaction-entries 2025-04-01 12:12:07 +05:30
rohitwaghchaure
63ef0a5a99 Merge pull request #46808 from rohitwaghchaure/fixed-support-34914
fix: condition to update the last purchase rate
2025-03-31 20:44:28 +05:30
ljain112
f49adfdd98 fix: removed customer_group query in customer.js 2025-03-31 18:06:33 +05:30
Corentin Forler
665645721b fix: Fix fieldtype in UnReconcile dialog 2025-03-31 13:54:51 +02:00
Corentin Forler
f2cfb03c2c fix: Translate UnReconcile dialog title 2025-03-31 13:51:47 +02:00
rohitwaghchaure
c7db13c275 Merge pull request #46815 from rohitwaghchaure/fixed-support-35177
feat: allow UOMs to select for which conversion rate defined in item
2025-03-31 16:22:17 +05:30
Rohit Waghchaure
b1dfbbe85e feat: allow UOMs to select for which converstion rate defined in item master 2025-03-31 15:53:55 +05:30
Vishakh Desai
3a9dca0563 Merge pull request #46812 from vishakhdesai/fix-taxes-and-totals
fix: revert resetting `rounding_adjustment`
2025-03-31 10:23:13 +00:00
Vishakh Desai
f25bf6dbd2 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: Sagar Vora <sagar@resilient.tech>
2025-03-31 09:25:09 +00:00
Rohit Waghchaure
bad901e7da fix: condition to update the last puurchase rate 2025-03-31 14:23:47 +05:30
rohitwaghchaure
1e780945e8 Merge pull request #46806 from rohitwaghchaure/fixed-suupport-35020
feat: option to recreate Stock Ledger Entries against stock transactions
2025-03-31 14:13:35 +05:30
Marc Ramser
21b8ad6aa5 feat(regional): Address Template for Germany & Add Switzerland Template (#46737)
* Add Address template for Switzerland

* Fix address template for germany

If an ERPNext instance is set to German and used by a business outside Germany (e.g., in Switzerland or Austria), customer addresses in Germany are displayed in their national format. However, for postal services, the international format (including the country) is required.".

This is just a workaround. IMHO the correct fix would be to check where the company is located and based on that use the national or the international template.
2025-03-31 10:32:36 +02:00
Rohit Waghchaure
218dbd6911 feat: option to recreate Stock Ledger Entries against stock transactions 2025-03-31 13:48:21 +05:30
ljain112
aadda9f606 fix: update outstanding with precision 2025-03-31 12:37:28 +05:30
Vishakh Desai
646cf54679 fix: multiple Bank Reconciliation Tool issues (#46644)
* fix: bank reconciliation tool issue

* refactor: separate Bank Transaction linking from other logic

* fix: delink old pe on update_after_submit in bank transaction

* fix: failing test case fixed

* fix: changes as per review

* refactor: rename `gles` to `gl_entries`

---------

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2025-03-31 05:55:41 +00:00
rohitwaghchaure
9aff191bb1 Merge pull request #46801 from rohitwaghchaure/fixed-ux-for-job-card
fix: UX for partial job card completion
2025-03-30 22:51:30 +05:30
Rohit Waghchaure
8e8a724d82 fix: UX for partial job card completion 2025-03-30 19:01:15 +05:30
Frappe PR Bot
6df932f789 chore: update POT file (#46798) 2025-03-30 13:25:09 +02:00
iamkhanraheel
19279dffea fix: change cur_frm to frm 2025-03-29 16:41:28 +05:30
Sugesh393
a393195866 test: add unit test to validate tax values in Purchase Order from Sales Order 2025-03-29 15:35:01 +05:30
Sugesh393
1e18569be7 fix: map tax table while creating purchase order from sales order 2025-03-29 15:34:22 +05:30
MohsinAli
732e950265 fix: correct mapping(schedule_date) sales order to material request 2025-03-29 11:03:24 +05:30
iamkhanraheel
4284701442 refactor: remove print func 2025-03-29 06:15:22 +05:30
iamkhanraheel
3a4ba0c790 fix: fetch customer related data in making payment entry from customer 2025-03-29 03:59:57 +05:30
rohitwaghchaure
f55e8d1f4a Merge pull request #46777 from rohitwaghchaure/fixed-support-33851-1
fix: incorrect condition
2025-03-28 23:48:07 +05:30
rohitwaghchaure
b93957197e Merge pull request #46775 from rohitwaghchaure/fixed-support-34440
fix: for deadlock issue keep status as In Progress
2025-03-28 23:45:25 +05:30
rohitwaghchaure
dc75a385b7 Merge pull request #46760 from rohitwaghchaure/fixed-support-29324
fix: valuation rate not updating for raw materials
2025-03-28 23:44:42 +05:30
Rohit Waghchaure
0c1a8e9c58 fix: incorrect condition 2025-03-28 23:43:25 +05:30
Abdeali Chharchhodawala
8c9d630ee4 Merge pull request #46683 from Abdeali099/set-employee-contact-details
fix: Set complete contact details for `Employee` in PE
2025-03-28 18:17:47 +05:30
ljain112
c2bdd30e6d fix: correct outstanding amount for invoice in dunning 2025-03-28 13:26:28 +05:30
Rohit Waghchaure
e6ff7f0e9f fix: for deadlock issue keep status as In Progress 2025-03-28 13:10:12 +05:30
Diptanil Saha
45b45efee5 fix: pos button and on hover color (#46762)
* fix: pos button and on hover color

Changed the POS 'New Order' button color to frappe's color scheme and fixed the on hover text color on 'Toggle Recent Orders'

* fix: on hover text unreadable in dark mode
2025-03-28 08:54:51 +05:30
venkat102
7bf1a39861 fix: update payment amount if automatically_fetch_payment_terms is enabled 2025-03-27 22:28:20 +05:30
Rohit Waghchaure
5af8378471 fix: valuation rate not updating for raw materials 2025-03-27 21:32:38 +05:30
Mihir Kandoi
036af54d54 refactor: split and clean execute function to be more readable 2025-03-27 21:10:03 +05:30
Mihir Kandoi
668fdc8f92 fix: remove incorrect default field 2025-03-27 20:56:05 +05:30
ruthra kumar
11800e3cd8 Merge pull request #46719 from ljain112/fix-val-contact
fix: do not use self object for setting party and party type
2025-03-27 11:07:41 +05:30
venkat102
35fbbc2057 fix: include auto_reconcile_vouchers flag in background job 2025-03-27 01:04:38 +05:30
rohitwaghchaure
6275b236ee Merge pull request #46739 from rohitwaghchaure/fixed-support-34851
fix: slow query
2025-03-26 22:08:53 +05:30
Rohit Waghchaure
5ddb36af87 fix: slow query 2025-03-26 21:50:18 +05:30
Frappe PR Bot
d35ae3ef5e fix: sync translations from crowdin (#46738)
* fix: French translations

* fix: Chinese Simplified translations
2025-03-26 16:43:53 +01:00
rohitwaghchaure
b9d70e2ea4 Merge pull request #46732 from rohitwaghchaure/fixed-job-card-hours-issue
fix: not able to set hours manually in job card
2025-03-26 15:54:31 +05:30
rohitwaghchaure
cf2d67f145 Merge pull request #46733 from rohitwaghchaure/fixed-support-34788
fix: decimal values causing incorrect batch picking
2025-03-26 15:42:47 +05:30
Rohit Waghchaure
338b09debb fix: not able to set hours manually in job card 2025-03-26 15:38:44 +05:30
Rohit Waghchaure
7bfe703b04 fix: decimal values causing incorrect batch picking 2025-03-26 15:20:21 +05:30
Mihir Kandoi
26de902496 perf: take query out of loop 2025-03-26 14:39:45 +05:30
rohitwaghchaure
1d8c252ee7 Merge pull request #46710 from rohitwaghchaure/fixed-support-34516
fix: expense account in the stock entry
2025-03-26 11:59:14 +05:30
Dany Robert
d04dbd8ed9 fix: update posting date before running validations 2025-03-26 11:37:35 +05:30
rethik
31e59354c9 fix: use docstatus for status filter 2025-03-26 11:10:08 +05:30
Frappe PR Bot
9d2f9ad4c0 fix: sync translations from crowdin (#46724)
* fix: Swedish translations

* fix: Chinese Simplified translations
2025-03-25 18:21:18 +01:00
ljain112
80b746d4dd fix: do not use self object for setting party and party type 2025-03-25 17:51:41 +05:30
rohitwaghchaure
e2412072d9 Merge pull request #46718 from rohitwaghchaure/fixed-github-46708
fix: disable use multi level BOM for track Semi Finished Goods
2025-03-25 17:29:08 +05:30
Rohit Waghchaure
bf3b0d607f fix: disable use multi level BOM for track Semi Finished Goods 2025-03-25 17:24:48 +05:30
barredterra
638d825d8c feat(Customer): add Dunning to dashboard 2025-03-25 11:12:35 +01:00
ljain112
f4bc1dfd00 fix: user permissions in sales and purchase report 2025-03-25 14:15:24 +05:30
ruthra kumar
435bb1c7b0 Merge pull request #46616 from ljain112/conversion-rate
fix: do not validate if conversion rate is 1 for different currency
2025-03-25 14:11:21 +05:30
Rohit Waghchaure
89569d4b32 fix: expense account in the stock entry 2025-03-25 13:41:27 +05:30
ruthra kumar
a88259591b Merge pull request #46617 from ljain112/fix-ui-sales
refactor: removed redundant message display for each item row cost center update
2025-03-25 12:15:43 +05:30
ruthra kumar
a8ed8b26f1 Merge pull request #46622 from ljain112/feat-pr-acc-repost
feat: repost accounting ledger for purchase receipt
2025-03-25 11:48:33 +05:30
Frappe PR Bot
9af130598a fix: sync translations from crowdin (#46698)
* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations
2025-03-24 16:54:40 +01:00
Raffael Meyer
eb350012b0 ci: apply label "skip-release-notes" based on PR title (#46694)
Workflow copied from frappe/frappe
2025-03-24 15:22:17 +00:00
Raffael Meyer
70d9db7557 ci: invert language mapping (#46693)
* ci: invert language mapping

* ci: add skip-release-notes on crowdin PRs
2025-03-24 16:17:51 +01:00
barredterra
56a0c4642f ci: add skip-release-notes on crowdin PRs 2025-03-24 16:05:13 +01:00
barredterra
41f2b136d5 ci: invert language mapping 2025-03-24 16:04:17 +01:00
Raffael Meyer
9a8f0f3aef ci: fix language code mapping (#46692)
* ci: fix language code mapping

Keys are expected to be not the locale_with_underscore but the codes according to https://support.crowdin.com/developer/language-codes/

* chore: remove redundant translation files
2025-03-24 13:35:35 +00:00
Sagar Vora
d35a533d8b Merge pull request #46669 from vishakhdesai/bank-clearance-fix
fix: don't filter payment entries on Bank Account in Payment Clearance
2025-03-24 17:49:02 +05:30
ruthra kumar
ea224e131c Merge pull request #46577 from ljain112/jv-ccl
fix: customer credit limit check based on `bypass_credit_limit_check` in Journal Entry
2025-03-24 13:30:42 +05:30
ruthra kumar
8f647b4172 Merge pull request #46574 from ljain112/fix-order-payment-reco
fix: correct invoice order in payment reconcillaiton
2025-03-24 12:23:22 +05:30
ruthra kumar
9c82f4bb0f Merge pull request #46440 from aerele/payable-receivable-report
fix: add base_outstanding and base_paid_amount in payment schedule table
2025-03-24 11:43:57 +05:30
vishakhdesai
fa2fd5bf88 fix: don't filter payment entries on Bank Account in Payment Clearance 2025-03-24 11:23:53 +05:30
ruthra kumar
eda0e2152d Merge pull request #46569 from barredterra/language_map
ci: configure language mapping for crowdin
2025-03-24 10:39:34 +05:30
ruthra kumar
becb53ba5d Merge pull request #46518 from barredterra/so-set-deliver_date
fix(Sales Order): make `set_missing_values` set delivery date in item rows
2025-03-24 10:09:40 +05:30
ruthra kumar
03fde2c039 Merge pull request #46386 from FHenry/dev_whitelist_getchart
feat(accounting/regional): allow chart_of_account.get_chart to be whilelist
2025-03-24 09:59:07 +05:30
Khushi Rawat
eec2e7e833 fix: correct accumulated depreciation calculation for disposed assets (#46660) 2025-03-23 18:38:06 +05:30
Frappe PR Bot
93bd9a40cd chore: update POT file (#46659) 2025-03-23 11:05:00 +01:00
Sruthy
361a55a703 chore: adjusted dimension placement in Accounts Payable 2025-03-23 05:50:03 +00:00
HarryPaulo
b0dccadd26 fix: decimal garbage on variable total (#46621)
* fix: decimal dirty on variable total

* fix: linters

* fix: decimal garbage, code improvement
2025-03-21 14:14:16 +01:00
rohitwaghchaure
d3f8263890 Merge pull request #46642 from rohitwaghchaure/fixed-patch-table-issue
fix: patch
2025-03-21 18:08:17 +05:30
rohitwaghchaure
91c899f3f9 Merge pull request #46641 from rohitwaghchaure/fixed-rename-costing-timeout
perf: timeout while renaming cost center
2025-03-21 18:07:36 +05:30
Md Hussain Nagaria
e0f8e72c4d Merge pull request #46636 from frappe/fix-arg-usage
fix: unwired order_by argument in get_transaction_list
2025-03-21 15:47:25 +05:30
Rohit Waghchaure
e73570910c fix: patch 2025-03-21 15:31:20 +05:30
Rohit Waghchaure
92be7cbbbf perf: timeout while renaming cost center 2025-03-21 15:30:23 +05:30
ljain112
e84e49345a perf: refactored customer ledger summary for performance 2025-03-21 15:18:18 +05:30
venkat102
7785296573 test: validate payment schedule based on invoice amount 2025-03-21 13:18:19 +05:30
venkat102
5618859bd8 fix(payment term): allocate payment amount when payment term is fetched from order 2025-03-21 13:17:32 +05:30
Hussain Nagaria
2c1077d332 fix: unwired order_by argument
* lol on how it was updated from modified in both the places (version 15), but wasn't fixed
2025-03-21 11:38:41 +05:30
Raffael Meyer
2fd1d4d3ab Merge pull request #46627 from mihir-kandoi/date_added_to_wrong_patch
fix: date added to wrong patch
2025-03-20 19:25:05 +01:00
Bhavan23
7b0882600a test: add unit test to validate outstanding amount for update_outstanding_for_self checkbox enabled 2025-03-20 19:20:41 +05:30
Bhavan23
222f1834f1 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
2025-03-20 19:19:47 +05:30
ljain112
913c60d77b fix: correct payment request amount 2025-03-20 19:10:36 +05:30
Mihir Kandoi
dc45c3b39c fix: date added to wrong patch 2025-03-20 15:55:33 +05:30
Abdeali Chharchhoda
85dd1dd4c7 fix: add Not Cancelled filter for payment_entry in Bank Transaction 2025-03-20 15:19:01 +05:30
Mihir Kandoi
80c17cc005 fix: remove get_items query.run outside of if condition 2025-03-20 15:17:59 +05:30
Abdeali Chharchhoda
257802aeda refactor: move payment_document query to setup 2025-03-20 15:12:06 +05:30
ljain112
b36e356469 feat: repost accounting ledger for purchase receipt 2025-03-20 13:25:53 +05:30
Mihir Kandoi
393434dfd9 fix: add fetch from to currency and set default to empty in item price 2025-03-19 20:32:27 +05:30
ljain112
4376ca5f1d refactor: removed redundant message display for each item row cost center update 2025-03-19 17:20:25 +05:30
ljain112
e8a66d03bc fix: do not validate if conversion rate is 1 for different currency 2025-03-19 16:52:13 +05:30
ruthra kumar
21e22b577a Merge pull request #46591 from ruthra-kumar/revert_to_json_for_test_records
Revert "chore(tests): move to compact, human readable, commentable to…
2025-03-19 16:50:14 +05:30
rohitwaghchaure
f2da369a07 Merge pull request #46608 from mihir-kandoi/st34183
fix: fetch bom_no when updating items in sales order
2025-03-19 15:44:22 +05:30
Mihir Kandoi
386df968c2 fix: remove duplicate 2025-03-19 15:25:50 +05:30
rohitwaghchaure
97debcb27b Merge pull request #46573 from mihir-kandoi/fix_set_landed_cost_based_on_pi
Fix set landed cost based on pi
2025-03-19 15:20:45 +05:30
Marc Ramser
f4aba561ce feat(projects): add option to hide timesheets for project users (#46173)
* feat: add option to hide timesheets for project users

* Added a new "Hide timesheets" checkbox field to Project User doctype that allows to control timesheet visibility for specific users. When enabled, the timesheets section will not be displayed on the project page for that user.

* Update projects.html
2025-03-19 13:34:15 +05:30
rohitwaghchaure
eb7cf2608c Merge pull request #46595 from rohitwaghchaure/fixed-support-34185
fix: not able to make PR against stand alone Debit Note
2025-03-19 13:34:01 +05:30
ruthra kumar
c2cf1d14e6 Merge pull request #46605 from yatridholakia/fix-broken-links
fix: broken Frappe School links
2025-03-19 13:12:43 +05:30
Mihir Kandoi
b3c400f998 fix: take function call outside loop 2025-03-19 13:04:08 +05:30
Mihir Kandoi
508727a57a fix: fetch bom_no when updating items in sales order 2025-03-19 12:52:34 +05:30
ruthra kumar
b1f15358dd Merge pull request #46596 from rohitwaghchaure/fixed-support-32083-2
fix: debit in transaction currency
2025-03-19 11:55:52 +05:30
Yatri Dholakia
343940a9f3 fix: broken Frappe School links 2025-03-19 04:20:20 +00:00
Rohit Waghchaure
e4acf20a62 fix: debit in transaction currency 2025-03-18 21:26:25 +05:30
Rohit Waghchaure
6a52c30591 fix: not able to make PR against stand alone Debit Note 2025-03-18 21:20:40 +05:30
rohitwaghchaure
0985441fc3 Merge pull request #46593 from rohitwaghchaure/fixed-batch-valuation-test-case
test: test case for FIFO batch valuation
2025-03-18 19:19:38 +05:30
Rohit Waghchaure
ad9ac1f058 test: test case for FIFO batch valuation 2025-03-18 18:41:57 +05:30
ruthra kumar
4683e03353 chore: include company in test record 2025-03-18 17:50:44 +05:30
rohitwaghchaure
57d7ac4954 Merge pull request #46588 from rohitwaghchaure/fixed-support-34111
fix: SABB validation for packed items
2025-03-18 17:31:39 +05:30
ruthra kumar
54d96f34f2 Revert "chore(tests): move to compact, human readable, commentable toml layout for test records"
This reverts commit bb917207c2.
2025-03-18 17:27:01 +05:30
rohitwaghchaure
327af0ceac Merge pull request #46579 from rohitwaghchaure/fixed-valuation-for-batch-ma
fix: valuation for moving average with batches
2025-03-18 16:33:36 +05:30
Rohit Waghchaure
3756bf231b fix: SABB validation for packed items 2025-03-18 16:28:31 +05:30
Khushi Rawat
29d77aa19f fix: repost future sle and gle after capitalization (#46576) 2025-03-18 15:59:58 +05:30
rohitwaghchaure
182b4ef266 Merge pull request #46554 from mihir-kandoi/fix_rename_sc_fields
fix: add validation to rename_subcontracting_fields patch
2025-03-18 15:59:09 +05:30
rohitwaghchaure
59f2302b77 Merge pull request #46575 from mihir-kandoi/st34161
fix: fetch quality inspection parameter group
2025-03-18 15:56:18 +05:30
Rohit Waghchaure
cdfbc73f4c fix: valuation for moving average with batches 2025-03-18 15:54:23 +05:30
Smit Vora
1e2f5940fd Merge pull request #46515 from vorasmit/fix-qty-conversion
fix: ensure qty conversion when creating production plan from SO
2025-03-18 14:24:49 +05:30
ljain112
8a84faebed fix: customer credit limit check based on bypass_credit_limit_check in Journal Entry 2025-03-18 14:16:35 +05:30
Mihir Kandoi
0a482c7ea8 fix: fetch quality inspection parameter group 2025-03-18 13:08:48 +05:30
ljain112
5c34a5aaed fix: correct invoice order in payment reconcillaiton 2025-03-18 12:49:29 +05:30
Mihir Kandoi
7e669c0728 fix: patch 2025-03-18 11:43:16 +05:30
Mihir Kandoi
75ab5f2bd0 fix: set landed cost based on purchase invoice rate 2025-03-18 11:22:46 +05:30
ruthra kumar
e1ba08a28d Merge pull request #46566 from ruthra-kumar/correct_account_currency_in_offset_account
fix: set correct currency for offset account gl entries
2025-03-18 11:14:04 +05:30
ruthra kumar
3eaa9bfca4 Merge pull request #46508 from ruthra-kumar/use_base_total_for_uae_report
fix: use base currency total for UAE VAT 201 report
2025-03-18 10:58:58 +05:30
barredterra
c6cfbd7a65 chore: remove redundant translation files 2025-03-17 23:52:34 +01:00
barredterra
efeab38199 ci: configure language mapping for crowdin 2025-03-17 23:42:47 +01:00
ruthra kumar
c32e11e69d fix: set correct currency for offset account gl entries 2025-03-17 20:28:34 +05:30
Ejaaz Khan
dd214fedd8 Merge pull request #46564 from iamejaaz/remove-default-print
refactor: remove default print format from sales invoice
2025-03-17 20:07:07 +05:30
Ejaaz Khan
f10d1f2b1f refactor: remove default print format from sales invoice 2025-03-17 19:34:04 +05:30
ruthra kumar
a70354a0a4 Merge pull request #46563 from frappe/l10n_develop
fix: sync translations from crowdin
2025-03-17 17:08:42 +05:30
Frappe PR Bot
c7ad7eecbb fix: Croatian translations 2025-03-17 16:56:17 +05:30
Frappe PR Bot
4687ba22d4 fix: Bosnian translations 2025-03-17 16:56:04 +05:30
Frappe PR Bot
b25e0bd2b0 fix: Persian translations 2025-03-17 16:55:59 +05:30
Frappe PR Bot
ca1eec355c fix: Swedish translations 2025-03-17 16:55:50 +05:30
Frappe PR Bot
addc26fb2b fix: French translations 2025-03-17 16:55:33 +05:30
ruthra kumar
88ec083e4c Merge pull request #46557 from aerele/transaction-deletion-record
fix(Transaction Deletion Record): sql syntax error while deleting lead address
2025-03-17 16:37:41 +05:30
rohitwaghchaure
798664dfbf Merge pull request #46555 from rohitwaghchaure/fixed-support-33798
fix: performance issue for item list view
2025-03-17 16:36:53 +05:30
rohitwaghchaure
368892a82f Merge pull request #46553 from rohitwaghchaure/fixed-bom-uom
fix: not able to select the item in the BOM
2025-03-17 16:35:19 +05:30
rohitwaghchaure
173a954d8c Merge pull request #46552 from rohitwaghchaure/fixed-support-32083-1
fix: Debit and Credit not equal for Purchase Invoice
2025-03-17 16:34:51 +05:30
venkat102
af0d6eeae8 fix(Transaction Deletion Record): sql syntax error while fetching lead address 2025-03-17 16:13:42 +05:30
ruthra kumar
b666b8bbd4 Merge pull request #46539 from frappe/l10n_develop
fix: sync translations from crowdin
2025-03-17 15:13:45 +05:30
Rohit Waghchaure
d758fde881 fix: performance issue for item list view 2025-03-17 13:48:14 +05:30
Mihir Kandoi
6c3117dc0d fix: add validation to rename_subcontracting_fields patch 2025-03-17 13:11:46 +05:30
Rohit Waghchaure
96d0cd23f1 fix: not able to select the item in the BOM 2025-03-17 13:00:32 +05:30
Rohit Waghchaure
ecb31b7c9f fix: Debit and Credit not equal for Purchase Invoice 2025-03-17 12:57:44 +05:30
Ankush Menat
e47a87839b perf: faster count estimation (#46550)
These count queries themselves take quite a long time. `estimate_count`
uses info_schema stats to guess the time.

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-17 07:09:21 +00:00
rohitwaghchaure
90b6e00237 Merge pull request #46504 from rohitwaghchaure/fixed-support-33851
fix: dashboard link for QC from PR
2025-03-17 10:06:09 +05:30
Frappe PR Bot
ba50d01f6b fix: Croatian translations 2025-03-16 16:37:39 +05:30
Frappe PR Bot
5c5b29745b fix: Thai translations 2025-03-16 16:37:36 +05:30
Frappe PR Bot
350c4e5d9f fix: Portuguese, Brazilian translations 2025-03-16 16:37:33 +05:30
Frappe PR Bot
b68627240c fix: Esperanto translations 2025-03-16 16:37:31 +05:30
Frappe PR Bot
06732da687 fix: German translations 2025-03-16 16:37:28 +05:30
Frappe PR Bot
3fbbf5a609 fix: Bosnian translations 2025-03-16 16:37:24 +05:30
Frappe PR Bot
0e593fddf4 fix: Persian translations 2025-03-16 16:37:21 +05:30
Frappe PR Bot
a0d50e146e fix: Chinese Simplified translations 2025-03-16 16:37:17 +05:30
Frappe PR Bot
a3f3724fc6 fix: Turkish translations 2025-03-16 16:37:15 +05:30
Frappe PR Bot
0c58b2cf7e fix: Swedish translations 2025-03-16 16:37:12 +05:30
Frappe PR Bot
a536400a0c fix: Russian translations 2025-03-16 16:37:09 +05:30
Frappe PR Bot
825e8325eb fix: Polish translations 2025-03-16 16:37:06 +05:30
Frappe PR Bot
84889718ca fix: Hungarian translations 2025-03-16 16:37:03 +05:30
Frappe PR Bot
a30a5ce645 fix: Arabic translations 2025-03-16 16:37:00 +05:30
Frappe PR Bot
eaa386da2f fix: Spanish translations 2025-03-16 16:36:57 +05:30
Frappe PR Bot
63129cecf3 fix: French translations 2025-03-16 16:36:54 +05:30
Raffael Meyer
43791abdd3 Merge pull request #46545 from frappe/pot_develop_2025-03-16
chore: update POT file
2025-03-16 11:49:00 +01:00
frappe-pr-bot
40a84a3dfb chore: update POT file 2025-03-16 09:34:53 +00:00
Frappe PR Bot
0821a46302 fix: Bosnian translations 2025-03-15 16:33:06 +05:30
Frappe PR Bot
b21a823cc4 fix: Persian translations 2025-03-15 16:33:02 +05:30
Sagar Vora
e5fdbeefc3 Merge pull request #46536 from sagarvora/remove-init
chore: remove unnecessary init method
2025-03-15 11:55:36 +05:30
Sagar Vora
52ef4f38fe chore: remove unnecessary init method 2025-03-15 11:41:40 +05:30
Sagar Vora
dd8956a3cf Merge pull request #46531 from sagarvora/perf/mariadb-2
ci: use `mysqlclient` in tests
2025-03-15 10:01:22 +05:30
Sagar Vora
25623325ce ci: use mysqlclient in tests 2025-03-15 09:48:40 +05:30
Sagar Vora
70aa9e5f50 Merge pull request #46533 from sagarvora/fix-dupl-se
fix: exclude current doc when checking for duplicate
2025-03-15 00:06:33 +05:30
Sagar Vora
d8ef5e4d58 fix: exclude current doc when checking for duplicate 2025-03-14 23:51:30 +05:30
Raffael Meyer
3be570b44a Merge pull request #45924 from PatrickDEissler/develop
fix(Employee): remove User Permissions if create_user_permission is unchecked
2025-03-14 13:57:42 +01:00
Shariq Ansari
3c7583495e Merge pull request #46529 from shariquerik/handle-party-type-erpenxt-crm-integration
fix: also consider CRM Deal as party type for ERPNext CRM Integration
2025-03-14 16:03:03 +05:30
Shariq Ansari
f59620fd55 Merge branch 'develop' into handle-party-type-erpenxt-crm-integration 2025-03-14 16:00:42 +05:30
Shariq Ansari
04edbf7efe fix: also consider CRM Deal as party type for ERPNext CRM Integration 2025-03-14 15:54:03 +05:30
Mihir Kandoi
c7e7da6500 Merge pull request #46513 from mihir-kandoi/46043
fix: get bom_no from sales order item and material request item
2025-03-14 13:05:36 +05:30
Mihir Kandoi
1b560ce949 Merge pull request #46514 from mihir-kandoi/46060
fix: UOM conversion error when creating pick list from material trans…
2025-03-14 12:42:17 +05:30
Mihir Kandoi
8bb2a15732 Merge pull request #46512 from mihir-kandoi/hide_subcontracted_qty_field
fix: hide subcontracted qty field if PO is not subcontracted
2025-03-14 12:41:22 +05:30
ruthra kumar
e80129627a test: report ouput on foreign currency PI 2025-03-14 11:50:16 +05:30
ruthra kumar
229147a434 Merge pull request #46497 from Sanket322/use_party_explicitly
fix: use `party` explicitly instead of party_field
2025-03-14 10:21:35 +05:30
ruthra kumar
784e61e3e9 Merge pull request #46488 from aerele/validate-address-contact-permission
refactor: replace get_list with get_all for dynamic link child access
2025-03-14 09:51:03 +05:30
ruthra kumar
e6a3a19641 Merge pull request #46519 from frappe/l10n_develop
fix: sync translations from crowdin
2025-03-14 09:28:15 +05:30
Frappe PR Bot
3adb9eefd7 fix: Persian translations 2025-03-14 07:17:18 +05:30
barredterra
fc279c85bd fix(Sales Order): make set_missing_values set delivery date in item rows 2025-03-13 18:56:59 +01:00
Mihir Kandoi
8411e2e01f fix: wrong field mapping 2025-03-13 20:19:47 +05:30
Mihir Kandoi
840ea070a9 fix: UOM conversion error when creating pick list from material transfer request 2025-03-13 18:33:39 +05:30
Smit Vora
75882cc81c fix: ensure qty conversion when creating production plan from SO 2025-03-13 18:33:22 +05:30
Mihir Kandoi
ac354505ef fix: get bom_no from sales order item and material request item 2025-03-13 18:11:20 +05:30
Mihir Kandoi
6e8521d761 fix: hide subcontracted qty field if PO is not subcontracted 2025-03-13 17:28:48 +05:30
ruthra kumar
46f4babcd0 fix: use base currency total 2025-03-13 14:23:51 +05:30
Diptanil Saha
0552209310 refactor: print receipt on order complete on pos (#46501) 2025-03-13 14:22:05 +05:30
Rohit Waghchaure
551f89f14b fix: dashboard link for QC from PR 2025-03-13 13:50:25 +05:30
mergify[bot]
378a554ea2 fix: add parenttype condition to payment schedule query in accounts receivable report (backport #46370) 2025-03-13 12:00:29 +05:30
Sanket322
5057e3fe30 fix: use party explicitly 2025-03-13 11:29:20 +05:30
ruthra kumar
6572fba435 Merge pull request #46494 from frappe/l10n_develop
fix: sync translations from crowdin
2025-03-13 08:42:29 +05:30
ruthra kumar
f2a709f953 Merge pull request #46491 from cogk/fix-disable-and-rename-project-template
feat: Allow disabling and renaming Project Template
2025-03-13 08:42:19 +05:30
Frappe PR Bot
0ba9712935 fix: Croatian translations 2025-03-13 06:56:54 +05:30
Frappe PR Bot
cc2ce30bd4 fix: Bosnian translations 2025-03-13 06:56:48 +05:30
Frappe PR Bot
d9662959d2 fix: Persian translations 2025-03-13 06:56:44 +05:30
Sanket Shah
38955af802 fix: using in for lookup in list instead of directly assigning (#46492)
fix: using in for lookup in list instead of assigning

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-03-12 22:37:08 +05:30
Corentin Forler
43303f2cf6 feat: Allow disabling and renaming Project Template 2025-03-12 14:27:44 +01:00
Sugesh393
8f7f0b81f6 refactor: replace get_list with get_all for dynamic link child access 2025-03-12 17:54:39 +05:30
ruthra kumar
95b8355cff Merge pull request #46251 from aerele/fix/bank-reconciliation-timeout
fix(bank-reconciliation): run bank reconciliation as a background job
2025-03-12 17:20:06 +05:30
Sugesh G
f8c659d8d5 fix: pricing rule not ignored in Sales Order (#46248)
* fix: pricing rule not ignored in Sales Order

* test: update parameter do_not_submit to do_not_save
2025-03-12 16:27:48 +05:30
Mihir Kandoi
08c7e8a602 feat: add is_transporter filter to supplier field in purchase invoice… (#46430)
* feat: add is_transporter filter to supplier field in purchase invoice and receipt

* fix: add filter to purchase order as well
2025-03-12 16:24:57 +05:30
Priyansh Shah
d371236684 fix: validations and account type filter for Tax Withholding Category (#46207)
fix: validations and account type filter for tax withholding category
2025-03-12 16:21:12 +05:30
Bhavansathru
ba96c86576 fix(invoice):validate return invoice qty (#46451)
* fix(invoice): validate return quantity when update stock is unchecked

* test: add unit test for validating fully returned invoice quantity
2025-03-12 16:19:24 +05:30
Sugesh G
0bdb81db53 fix: use shipping_address_name for address validation in sales invoice (#46473)
* fix: validate address and contact related to party

* fix: solve unboundlocal error

* refactor: improve variable scope

* refactor: translatable strings

* fix: use shipping_address_name for address validation in sales invoice

* test: add new unit test for address and contact validation

* chore: to avoid keyerror

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-03-12 16:13:35 +05:30
Sudharsanan Ashok
9e808c832f fix: calculate due date based on payment term (#46416) 2025-03-12 16:10:14 +05:30
Mihir Kandoi
41c93c8832 fix: return None if document does not have status field in get_status… (#46415)
* fix: return None if document does not have status field in get_status function

* chore: add comment
2025-03-12 16:05:08 +05:30
Mihir Kandoi
67e9389a02 fix: error when creating delivery note from pick list (#46417) 2025-03-12 15:27:47 +05:30
Mihir Kandoi
5a72edcb82 fix: enable no copy for serial no field (#46381)
* fix: enable no copy for serial no field

* fix: remove no copy from serial no field of purchase receipt item

* fix: remove no copy from serial no field of delivery note item

* Revert "fix: remove no copy from serial no field of purchase receipt item"

This reverts commit 5d1752030b.

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-12 15:11:55 +05:30
rohitwaghchaure
b6c0448075 Merge pull request #46470 from rohitwaghchaure/fixed-support-33560
fix: do not recalculate qty for batch items during reposting
2025-03-12 12:17:07 +05:30
Frappe PR Bot
b270140e61 fix: sync translations from crowdin (#46435)
* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations

* fix: Chinese Simplified translations

* fix: Bosnian translations
2025-03-12 10:49:50 +05:30
Rohit Waghchaure
0753c018d2 fix: do not recalculate qty for batch items during reposting 2025-03-12 10:49:21 +05:30
Venkatesh
b72f6f5a3d fix(payment entry): fetch default bank account based on company (#46379) 2025-03-12 10:49:13 +05:30
Diptanil Saha
8ba4ac3b86 fix: credit note creation during pos invoice consolidation (#46277)
* fix: credit note creation during pos invoice consolidation

* fix: added check to skip merging empty list of return pos invoices

* fix: sql query

* fix: using return invoice name instead of return invoice object

* fix: added pos invoice field in sales invoice item
2025-03-12 10:44:21 +05:30
Dany Robert
0a5ca0c35f fix: validate last_gl_update exists before comparing (#46464) 2025-03-12 10:36:50 +05:30
Mihir Kandoi
8bce42e633 fix: rename sla fields patch (#46465)
* fix: rename sla fields patch

* fix: rerun patch
2025-03-12 10:34:20 +05:30
Mihir Kandoi
6f010915fe Merge pull request #46424 from mihir-kandoi/45440
fix: show remaining qty on 'Complete Job' button instead of full qty
2025-03-12 00:30:55 +05:30
Mihir Kandoi
52b76736d3 Merge pull request #46455 from mihir-kandoi/fix_production_analytics
fix: error in production analytics report
2025-03-12 00:29:41 +05:30
Mihir Kandoi
f5d5508035 Merge pull request #46460 from mihir-kandoi/fix_rename_sle_fields_2
fix: rename sla fields patch
2025-03-12 00:26:35 +05:30
Mihir Kandoi
b6c18849c5 fix: rename sla fields patch 2025-03-12 00:03:08 +05:30
rohitwaghchaure
c1a8de9fcd Merge pull request #46452 from mihir-kandoi/fix_sla_fields_patch
fix: sla fields patch
2025-03-11 23:37:10 +05:30
Raffael Meyer
f1398a50e7 Merge pull request #46453 from barredterra/ignore-release-label
ci: ignore PRs labeled with "skip-release-notes" when generating release notes
2025-03-11 17:34:09 +01:00
Mihir Kandoi
03e66468f6 fix: error in production analytics report 2025-03-11 22:02:23 +05:30
barredterra
57007bf937 ci: ignore PRs labeled with "skip-release-notes" when generating release notes 2025-03-11 17:11:35 +01:00
Mihir Kandoi
d653899372 fix: sla fields patch 2025-03-11 21:18:18 +05:30
ruthra kumar
74208a3e06 Merge pull request #46448 from frappe/revert-45958-validate-party-address
Revert "fix: validate address and contact related to party"
2025-03-11 18:12:42 +05:30
ruthra kumar
b81ad55acd Merge pull request #46398 from aerele/coa-name-fix
chore: rename print and stationery account
2025-03-11 17:32:20 +05:30
ruthra kumar
24e4d92202 Revert "Merge pull request #45958 from aerele/validate-party-address"
This reverts commit 18fdd17e94.
2025-03-11 17:30:52 +05:30
ruthra kumar
44835126db Merge pull request #46344 from aerele/e-invoicing-regional-validation
fix: make 'company_tax_id' and 'company_fiscal_code' as mandatory
2025-03-11 17:11:26 +05:30
ruthra kumar
121798ba85 chore: translatable strings 2025-03-11 16:55:04 +05:30
ruthra kumar
a2881b5be0 Merge pull request #45818 from vishakhdesai/set-correct-account-currency-deferred-expense
fix: set correct account currency for deferred expense account
2025-03-11 16:35:19 +05:30
vishakhdesai
398083853c fix: set correct account currency for deferred expence account in PI 2025-03-11 15:33:36 +05:30
Sugesh G
18fdd17e94 Merge pull request #45958 from aerele/validate-party-address
fix: validate address and contact related to party
2025-03-11 14:28:23 +05:30
rohitwaghchaure
9b7e936169 Merge pull request #46436 from rohitwaghchaure/fixed-support-32871-1
fix: filter batches that going to be zero
2025-03-11 13:31:40 +05:30
Sugesh393
7e92e4967a fix: add patch to update base_outstanding and base_paid_amount 2025-03-11 11:52:42 +05:30
Sugesh393
6c2f9a563e fix: add base_outstanding and base_paid_amount in payment schedule table 2025-03-11 11:49:35 +05:30
ruthra kumar
66d5aab8ac Merge pull request #46372 from aerele/update-account-number
fix(account): update account number from parent company
2025-03-11 11:01:08 +05:30
ruthra kumar
9dd801ff3b Merge pull request #46426 from asmitahase/employee-tree
fix: clear cashe on employee hierarchy change
2025-03-11 10:41:46 +05:30
Rohit Waghchaure
aba512c1c6 fix: filter batches that going to be zero 2025-03-11 10:13:34 +05:30
Raffael Meyer
c962b2e97b Merge pull request #45955 from barredterra/buying-translatability
refactor: improve translatability in buying controller
2025-03-11 02:07:12 +01:00
Raffael Meyer
5caef24335 chore: fix file naming for Crowdin integration (#46434) 2025-03-11 01:03:56 +00:00
rohitwaghchaure
7a584d3228 Merge pull request #46427 from frappe/mergify/bp/develop/pr-46414
fix: not able to save work order with alternative item (backport #46414)
2025-03-10 23:14:05 +05:30
Frappe PR Bot
04e9134016 fix: sync translations from crowdin (#46394)
* fix: Croatian translations

* fix: Bosnian translations

* fix: Croatian translations

* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations
2025-03-10 13:43:58 +01:00
Rohit Waghchaure
ac7fc608aa fix: not able to save work order with alternative item
(cherry picked from commit 6ca1f9bc73)
2025-03-10 12:10:54 +00:00
Asmita Hase
6789578b27 fix: clear cashe on employee hierarchy change to reflect updated permissions 2025-03-10 17:06:03 +05:30
ruthra kumar
3376f37012 Merge pull request #46423 from frappe/revert-46172-credit_note_column
Revert "fix: Show Credit Note amount in credit note column"
2025-03-10 16:56:31 +05:30
ruthra kumar
9aacd90633 Merge pull request #46407 from mahsem/typo_sales_invoice_print
fix: typo in sales_invoice_print
2025-03-10 16:47:27 +05:30
Mihir Kandoi
db93302a53 fix: show remaining qty on 'Complete Job' button instead of full qty 2025-03-10 16:44:16 +05:30
Akhil Narang
f9cac1c186 fix(README): recommend using .localhost instead of .dev (#46422)
All `.dev` domains have HSTS enforced by default and so require SSL to access.

`.localhost` hosts automatically resolve without requiring edits to hosts files

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-03-10 16:27:01 +05:30
ruthra kumar
5a9767ca67 Revert "fix: Show Credit Note amount in credit note column" 2025-03-10 16:24:43 +05:30
rohitwaghchaure
9026b67333 Merge pull request #46418 from rohitwaghchaure/fixed-support-33273
fix: stock balance in and out value
2025-03-10 15:34:45 +05:30
Rohit Waghchaure
e917bd5334 fix: stock balance in and out value 2025-03-10 14:44:29 +05:30
ruthra kumar
98690c42b0 Merge pull request #46412 from ruthra-kumar/revert_mergify_and_lock_thread_changes
ci: re-enable label backports and decrease lock frequency
2025-03-10 08:47:05 +05:30
ruthra kumar
97733c8974 ci: re-enable label backports and decrease lock frequency 2025-03-10 08:31:22 +05:30
Frappe PR Bot
0afcf59ba1 chore: update POT file (#46406) 2025-03-09 11:50:11 +01:00
mahsem
f7bcae83e4 fix: typo in sales_invoice_print 2025-03-09 11:11:54 +01:00
rohitwaghchaure
0bd80f98a8 Merge pull request #46395 from mihir-kandoi/st33499
fix: consider account freeze date in recalculate_amount_difference_fi…
2025-03-08 20:20:52 +05:30
ruthra kumar
262798e319 Merge pull request #46401 from ruthra-kumar/fix_typo
chore: fix typo
2025-03-08 15:11:00 +05:30
ruthra kumar
a4c913a794 chore: fix typo 2025-03-08 14:54:11 +05:30
ruthra kumar
60379241d8 Merge pull request #46400 from ruthra-kumar/lock_frequently
ci: increase conversion lock frequency
2025-03-08 14:40:02 +05:30
ruthra kumar
ab8134aec6 ci: lock frequently 2025-03-08 14:21:11 +05:30
ruthra kumar
eca5a8e6ab Merge pull request #46399 from ruthra-kumar/temp_disable_label_backports
ci: temporarily disable label based backports
2025-03-08 13:37:06 +05:30
ruthra kumar
d2732e376c ci: temporarily disable label based backports 2025-03-08 13:20:44 +05:30
chethank1407
615997b774 chore: rename print and stationery account 2025-03-08 13:04:50 +05:30
ruthra kumar
f5d061d2dd Merge pull request #46064 from ruthra-kumar/transaction_currency_from_parent_document
fix: Debit and Credit mismatch on transaction currency debit and credit values
2025-03-08 12:05:13 +05:30
ruthra kumar
f1d8feec15 refactor: internal transfer gl 2025-03-08 11:45:12 +05:30
Mihir Kandoi
cd72532789 fix: consider stock freeze date in recalculate_amount_difference_field patch 2025-03-08 11:38:28 +05:30
Mihir Kandoi
696f931678 fix: consider account freeze date in recalculate_amount_difference_field patch 2025-03-08 11:31:33 +05:30
ruthra kumar
55d0636123 test: assert total debit and credit for trx currency 2025-03-08 09:35:08 +05:30
ruthra kumar
455a55b2ce refactor: handle rounding diff for trx currency dr and cr 2025-03-08 09:35:08 +05:30
ruthra kumar
4cd3f3531c refactor: trx currency dr and cr for tax rows and item rows 2025-03-08 09:35:08 +05:30
ruthra kumar
7528ef147a refactor: convert tax amount using exchange rate 2025-03-08 09:35:08 +05:30
ruthra kumar
6545467aec fix: incorrect category in list 2025-03-08 09:35:08 +05:30
ruthra kumar
bc792c61e9 chore: typo 2025-03-08 09:35:08 +05:30
ruthra kumar
b348aa3b37 refactor: isolate to specific doctypes 2025-03-08 09:35:08 +05:30
ruthra kumar
23d465805b refactor(test): save first to let the tax table populate 2025-03-08 09:35:08 +05:30
ruthra kumar
a31770d122 fix(test): incorrect transaction exchange rate in test case 2025-03-08 09:35:08 +05:30
ruthra kumar
ceca5b4c72 refactor: set transaction currency and rate before gl map 2025-03-08 09:35:08 +05:30
ruthra kumar
5c86e3ce85 refactor: handle payment entry 2025-03-08 09:35:08 +05:30
ruthra kumar
9f3847c0f8 refactor: handle Journal entries 2025-03-08 09:35:08 +05:30
ruthra kumar
d1d06885dc refactor: move utility method to controller 2025-03-08 09:35:08 +05:30
ruthra kumar
3e292ef2cb refactor: set transaction currency dr/cr in sales invoice 2025-03-08 09:35:08 +05:30
ruthra kumar
ee93ed8c97 refactor: handle stocked items 2025-03-08 09:35:08 +05:30
ruthra kumar
7ff3977394 refactor: handle stocked items 2025-03-08 09:35:08 +05:30
ruthra kumar
e9af567033 refactor: set tr currency dr & cr directly on parent document 2025-03-08 09:35:08 +05:30
ruthra kumar
b115bf2e2a refactor: use highest precision for storing exc rate 2025-03-08 09:35:08 +05:30
ruthra kumar
f5f138ef9d Merge pull request #46393 from ruthra-kumar/fix_error_on_internal_transfer_po_so
fix: attribute error on internal so and po
2025-03-08 09:30:14 +05:30
ruthra kumar
f585fd6b88 fix: attribute error on internal so and po 2025-03-08 09:14:36 +05:30
Florian HENRY
49dcd96909 feat(accounting): allow chart_of_account.get_chart to be whilelist 2025-03-07 12:29:51 +01:00
Mihir Kandoi
501f07803e refactor: import functions in new report instead of redundant code 2025-03-07 12:05:31 +05:30
Mihir Kandoi
5592d8e87f feat: available serial no report 2025-03-07 11:54:15 +05:30
maasanto
faa340c8b5 feat: Add first and last name fields to quick entry customer creation (#46281)
* feat: Add First and Last Name Fields to Quick Entry Customer Creation Form

* fix: added first and last_name fields to customer dt

* chore: linter issues

* chore: linter issue

* chore: linter issue

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-07 10:31:45 +05:30
Mihir Kandoi
98111dac17 Merge pull request #46374 from mihir-kandoi/33265
fix: rare precision issue preventing submission of subcontracting order
2025-03-06 22:15:18 +05:30
Mihir Kandoi
33b71544db fix: rare precision issue preventing submission of subcontracting order 2025-03-06 20:58:08 +05:30
venkat102
4a4894bc01 fix(account): update account number from parent company 2025-03-06 18:33:53 +05:30
mergify[bot]
18832c057c fix: auto email report creation (backport #46343) (#46364)
fix: auto email report creation (#46343)

* fix(financial_statements): mandatory based on filter_based_on value

* fix(financial_statements.js): include options for multiselect

(cherry picked from commit 5cc251a172)

Co-authored-by: Justine Jay <jjaycaneza@gmail.com>
2025-03-06 18:12:42 +05:30
Bhavansathru
7b6ebad9e6 fix: validate accounting dimension company in Journal Entry & Stock Entry (#46204)
* fix: validate accounting dimension company in journal entry and stock entry

* test: update test cases to validate company-based accounting dimension

* fix(test): ensure 'Pick List' company matches 'Delivery Note' to prevent test failures

* chore: remove redundant lines of code
2025-03-06 17:51:20 +05:30
Ninad Parikh
d01367eefe fix: base_net_rate Required to Check Valid Range (#46332)
fix: base_net_rate required to check valid range
2025-03-06 17:34:25 +05:30
ruthra kumar
ba2f887be9 Merge pull request #46363 from ruthra-kumar/incorrect_yaml_format
ci: fix incorrect yaml format
2025-03-06 17:33:09 +05:30
Sanket Shah
606dcb0ad1 fix: change fieldname for cash_flow to export (#46353)
fix: change fieldname for cash_flow

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-03-06 17:20:12 +05:30
Shariq Ansari
884709deb8 fix: Allow rename prospect doctype (#46352)
fix: allow rename prospect doctype
2025-03-06 17:19:14 +05:30
ruthra kumar
81d460e4bd ci: fix incorrect yaml format 2025-03-06 17:17:37 +05:30
ruthra kumar
87e7f0f4d7 Merge pull request #46349 from frappe/l10n_develop
fix: sync translations from crowdin
2025-03-06 17:04:28 +05:30
rohitwaghchaure
2e207d651d Merge pull request #46354 from mihir-kandoi/46351
fix: recalculate_amount_difference_field patch
2025-03-06 13:27:43 +05:30
rohitwaghchaure
93779c88d2 Merge pull request #46355 from mihir-kandoi/46348
fix: rename_sla_fields patch
2025-03-06 13:18:01 +05:30
Mihir Kandoi
95d1976931 fix: check if set_landed_cost_based_on_purchase_invoice_rate is enabled before running patch 2025-03-06 13:11:31 +05:30
Mihir Kandoi
0492b941ff fix: recalculate_amount_difference_field patch 2025-03-06 12:58:53 +05:30
Mihir Kandoi
e8d4a487c6 fix: rename_sla_fields patch 2025-03-06 12:57:27 +05:30
Frappe PR Bot
40c92b0988 fix: Bosnian translations 2025-03-06 09:53:15 +05:30
rohitwaghchaure
137090ca77 Merge pull request #46339 from rohitwaghchaure/fixed-report-incorrect-sabb
fix: doctype name
2025-03-05 21:16:49 +05:30
Bhavan23
abd044eb0d fix: make 'company_tax_id' and 'company_fiscal_code' as mandatory 2025-03-05 20:09:21 +05:30
Rohit Waghchaure
d039310d80 fix: doctype name 2025-03-05 18:07:17 +05:30
Ejaaz Khan
38aa7cab8a feat: create sales invoice print format (#45403)
* feat: create sales invoice print format

* fix: linter issue

* style: remove border from table

* refactor: change label to uppercase and show taxes

* refactor: format date and add translation on label

* refactor: remove default header and format labels

* refactor: change label style and small fix

* chore: Qty in title case

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-05 12:36:59 +00:00
Sugesh G
8caf7f275e fix(pos): get parent item group without user permission (#46020)
* fix(pos): get parent item group without user permission

* feat: add item group filter based on user permission

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
2025-03-05 18:06:42 +05:30
Diptanil Saha
35512d40bb fix: replacing serial and batch bundle on pos with auto fetch serial nos (#46236)
* fix: replacing serial and batch bundle on pos with auto fetch serial nos

* fix: reserved serial no

added a check to look for serial no in reserved serial nos list before removing it as there might be a situation where an item is returned which was already consolidated.
2025-03-05 17:41:43 +05:30
Khushi Rawat
f50d479bfd fix: exclude already consumed purchase receipt items from asset capitalization (#46329)
* feat: link purchase receipt row item to capitalization

* fix: avoid fetching already consumed stock and asset items during capitalization

* fix(patch): added patch to link purchase receipt item to stock item child table

* fix: added nosemgrep

* refactor: rename  to
2025-03-05 17:39:24 +05:30
rohitwaghchaure
cb004709ff Merge pull request #46330 from rohitwaghchaure/fixed-support-33209
fix: Accounting Period validation throwing for different companies
2025-03-05 16:40:07 +05:30
Prateek Karamchandani
4f559b6df2 fix: allow variant uom to differ from template uom (#45850) 2025-03-05 16:37:42 +05:30
Mihir Kandoi
58ed697ba5 fix: uom reverts to default upon selecting do not explode (#45693)
* fix: uom reverts to default upon selecting do not explode

* fix: logical error failing tests
2025-03-05 16:33:57 +05:30
Venkatesh
4ef7e6424a fix: set stock adjustment account in difference account (#45606)
* fix: set stock adjustment account in difference account

* fix(stock entry): fetch stock difference account for manufacture entry
2025-03-05 16:32:03 +05:30
rohitwaghchaure
6c0b18b19f Merge pull request #46313 from mihir-kandoi/st33100
fix: production analytics report
2025-03-05 16:23:13 +05:30
Rohit Waghchaure
b1508efca2 fix: Accounting Period validation throwing for different companies 2025-03-05 16:19:47 +05:30
mergify[bot]
82c0995f2e fix: Close and Reopen buttons dissapear after saving changes (backport #46048) (#46325)
fix: Close and Reopen buttons dissapear after saving changes (#46048)

* fix: Close and Reopen buttons dissapear after saving changes

* style: linter issue

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
(cherry picked from commit 506dd3c6b9)

Co-authored-by: Ben Kebdani <benkebdani@users.noreply.github.com>
2025-03-05 16:18:56 +05:30
Diptanil Saha
c4eb577a76 chore: erpnext demo url update (#46327) 2025-03-05 15:47:20 +05:30
Corentin Forler
a700345cee refactor: Remove duplicate inherited method (#46030)
The method add_taxes_from_item_tax_template is defined identically in the erpnext.taxes_and_totals class, that erpnext.TransactionController extends.
2025-03-05 15:38:05 +05:30
0xD0M1M0
104f60cc57 fix: Naming of Purchase Amount (#46051)
* fix: Naming of Purchase Amount

* fix: linters
2025-03-05 15:24:23 +05:30
Mihir Kandoi
6cc3d67835 feat: add new Closed and Stopped rows 2025-03-05 15:14:54 +05:30
Diptanil Saha
926e4ecc4f fix: adding cost center on pos invoice items while applying product discount (#46082) 2025-03-05 15:14:31 +05:30
Lakshit Jain
1f685efcaf fix: consolidate gl entries by project in General Ledger Report (#46314) 2025-03-05 15:08:52 +05:30
Sugesh G
425fb12e91 fix: consider journal entry and return invoice in paid_amount calculation (#46129)
* fix: consider journal entry and return invoice in paid_amount calculation

* test: add new unit test to consider journal entry and return invoice in paid_amount calculation
2025-03-05 14:47:22 +05:30
Fab
c5f90c823d Merge pull request #45840 from gms-electronics/40254-italian-einvoice
fix:[regional] Italian einvoice xml generated with wrong prices (#40254)
2025-03-05 14:45:54 +05:30
rohitwaghchaure
70128881a8 Merge pull request #45947 from mihir-kandoi/st31369
fix: set landed cost based on purchase invoice rate
2025-03-05 14:31:31 +05:30
rohitwaghchaure
694098a578 Merge pull request #45761 from rohitwaghchaure/fixed-support-31024
fix: incorrect batch picked in the pick list
2025-03-05 14:19:53 +05:30
Mihir Kandoi
961258a4ce fix: use else instead of unnecessary elif 2025-03-05 13:56:10 +05:30
Mihir Kandoi
772e9ecfaa fix: production analytics report 2025-03-05 13:48:34 +05:30
Raffael Meyer
94547188bf feat(Sales Invoice): add items row via "Fetch Timesheet" (#46071) 2025-03-05 13:21:40 +05:30
Diógenes Souza
89bcdd6fa5 fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) (#46146)
* fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic)

* style: after run pre-commit
2025-03-05 13:16:45 +05:30
Venkatesh
2d26bff870 fix: use value from currency exchange when exchange api is disabled (#46137) 2025-03-05 13:15:20 +05:30
Mihir Kandoi
3bb6a311de Merge branch 'develop' into st31369 2025-03-05 12:48:19 +05:30
rohitwaghchaure
27446a68b7 Merge pull request #45987 from mihir-kandoi/st31052-3
fix: rename some sla fields
2025-03-05 12:45:53 +05:30
Nabin Hait
5513e24b00 fix(workspace): enable is_query_report on purchase reports (#46249)
* fix(workspace): enable is_query_report on purchase reports

* fix: resolved conflict

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
2025-03-05 12:30:09 +05:30
Khushi Rawat
4a542b22a4 fix: depreciation and balances report correction (#46259) 2025-03-05 12:29:23 +05:30
Lakshit Jain
88fcdbb81e fix: only include submitted docs for internal received quantity validation (#46262) 2025-03-05 12:27:47 +05:30
ruthra kumar
0f6062efee Merge pull request #46294 from ruthra-kumar/temporary_exclude_old_prs
ci: dont lock really old pull requests
2025-03-05 12:26:22 +05:30
Sudharsanan Ashok
6117706ab5 feat(received items to be billed): add company and date filters (#46271)
* feat(received items to be billed): add company and date filters

* feat(delivered to be billed): add company and date filters

* feat: add company and date conditions

* chore: remove debugger
2025-03-05 12:25:43 +05:30
rohitwaghchaure
65a229abb1 Merge pull request #46226 from mihir-kandoi/refactor_sc_feilds
refactor: rename subcontracting fields
2025-03-05 12:25:08 +05:30
rohitwaghchaure
93fe9203da Merge pull request #46258 from mihir-kandoi/46196
fix: delivery note from sales order uom conversion mistake
2025-03-05 12:20:59 +05:30
Frappe PR Bot
4c810a878c fix: sync translations from crowdin (#46287)
* fix: French translations

* fix: Arabic translations

* fix: Persian translations

* fix: Bosnian translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations
2025-03-05 12:19:53 +05:30
Kunhi
a8d1cbc1c3 fix: Add company filter at get_invoice method (#46238) 2025-03-05 12:18:17 +05:30
Mihir Kandoi
01b5fb2f91 Merge branch 'develop' into st31052-3 2025-03-05 12:14:01 +05:30
Mihir Kandoi
ceaf217341 Merge branch 'develop' into st31369 2025-03-05 12:13:13 +05:30
rohitwaghchaure
62837099fc Merge pull request #46046 from mihir-kandoi/st32237-2
perf: optimize query in project.py
2025-03-05 12:10:16 +05:30
rohitwaghchaure
2b703c233b Merge pull request #46068 from mihir-kandoi/st32081
fix: use valuation method from settings in stock ageing report
2025-03-05 12:07:46 +05:30
Priyansh Shah
59e99f167d fix: Include additional account types for Expense Account in LCV (#46206)
fix: additional account types in filters for the Expense account selection
2025-03-05 12:07:25 +05:30
ruthra kumar
bb12551599 ci: dont lock really old pull requests
temporary change. mergify incorrectly triggers backport for really old
pull requests
2025-03-05 12:01:43 +05:30
Ankush Menat
41fe30ea6e chore: erpnext.com -> frappe.io/erpnext (#46288) 2025-03-05 05:32:21 +00:00
rohitwaghchaure
55de0176e8 Merge pull request #46279 from rohitwaghchaure/fixed-support-32979
fix: rate changing on the deliver note
2025-03-04 21:41:31 +05:30
rohitwaghchaure
1bdfd44d4c Merge pull request #46208 from rohitwaghchaure/fixed-support-32747
fix: stock reservation issue while making Purchase Invoice
2025-03-04 20:09:48 +05:30
Rohit Waghchaure
6f40849d55 fix: rate changing on the deliver note 2025-03-04 20:01:50 +05:30
Smit Vora
f006fd99d2 Merge pull request #46039 from Sanket322/address_template_for_usa
fix: Ensure new line is added regardless of postal code presence
2025-03-04 18:05:16 +05:30
Smit Vora
f8dd4e0961 Merge pull request #46010 from Sanket322/check_pos_permission
fix: Add permission check in POS's `Toggle Recent Orders`
2025-03-04 18:03:27 +05:30
Smit Vora
60247eeed5 Merge pull request #46260 from ljain112/fix-billed-items-report
fix: do not include opening invoices in billed items to be received report
2025-03-04 16:48:35 +05:30
Smit Vora
1332ea02d1 Merge pull request #45896 from ljain112/fix-pe-allocate
fix: auto allocation for negative amount outstanding for Customers in Payment Entry
2025-03-04 16:47:51 +05:30
ruthra kumar
6cbce114a6 Merge pull request #45682 from cogk/fix-filters-posting_date-not-always-string
fix(stock): Cast filters.to_date before string concatenation
2025-03-04 15:58:06 +05:30
ruthra kumar
24f684c58f Merge pull request #45751 from aerele/invoice-tds-link
fix: change voucher_type and voucher_name field type to data
2025-03-04 15:39:12 +05:30
ruthra kumar
6151f9c912 Merge pull request #46172 from Sanket322/credit_note_column
fix: Show Credit Note amount in credit note column
2025-03-04 13:27:33 +05:30
ljain112
c1ddf444c6 fix: do not include opening invoices in billed items to be received report 2025-03-04 12:49:48 +05:30
Mihir Kandoi
49a43d355d fix: delivery note from sales order uom conversion mistake 2025-03-04 11:48:28 +05:30
ruthra kumar
0d44b5d3ce Merge pull request #46257 from frappe/l10n_develop
fix: sync translations from crowdin
2025-03-04 10:05:56 +05:30
Frappe PR Bot
e6514eda50 fix: Bosnian translations 2025-03-04 09:40:48 +05:30
Sanket Shah
e4b0ab6656 fix: Convert tuple of tuples to list of dicts for dot notation access (#46062)
fix: use as_dict to convert tuples into list of dict

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-03-03 21:38:11 +05:30
Bhavan23
a29c6a5aea fix: run bank reconciliation as a background job to prevent request timeout 2025-03-03 18:10:59 +05:30
barredterra
8db1fcd19c refactor: improve translatability in buying controller (2)
Added full stops and support for custom fieldnames. Improved wording.
2025-03-03 13:22:46 +01:00
Smit Vora
17eb57efdc Merge pull request #46192 from Ninad1306/budget_variance_report_fix
fix: Exclude Cancelled GL Entries
2025-03-03 16:58:09 +05:30
Smit Vora
117b847de7 Merge pull request #45972 from Ninad1306/update_taxes
fix: Set Taxes Before Calculating Taxes and Totals
2025-03-03 16:57:01 +05:30
Frappe PR Bot
03fc1d7a00 fix: sync translations from crowdin (#46234)
* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations
2025-03-03 12:24:13 +01:00
rohitwaghchaure
25d39a38f1 Merge pull request #46231 from rohitwaghchaure/fixed-support-32871
fix: incorrect batch picked
2025-03-03 14:48:48 +05:30
rohitwaghchaure
d3a1fbbfef Merge pull request #46239 from rohitwaghchaure/fixed-support-32836
fix: incorrectly billed amount in the purchase receipt
2025-03-03 14:44:13 +05:30
ruthra kumar
efd7e05bb6 Merge pull request #46070 from nirmal2010/batch_price_update_billed
fix: Batch Price gets updated only if it is a billed item
2025-03-03 14:27:05 +05:30
ruthra kumar
0a2193e458 chore: linter fix 2025-03-03 14:05:41 +05:30
Nirmalrajaa K
1a56b83054 fix: Batch Price gets updated only if it is a billed item 2025-03-03 14:05:40 +05:30
Nirmalrajaa K
9597b1a69e fix: Batch Price gets updated only if it is a billed item 2025-03-03 14:05:26 +05:30
Rohit Waghchaure
a5271fdb2e fix: incorrectly billed amount in the purchase receipt 2025-03-03 13:55:52 +05:30
Khushi Rawat
4bf84f5c84 fix(asset depreciation schedules): enable auto commit
Merge pull request #46201 from aerele/asset-depreciation-patch
2025-03-03 12:11:08 +05:30
rohitwaghchaure
47648329ed Merge pull request #46223 from mihir-kandoi/st30672-3
fix: dont update rate of free item when batch is updated
2025-03-03 10:15:01 +05:30
Diptanil Saha
62c3915ecb refactor: using function to unset grand total to default mode of payment in pos (#46228) 2025-03-03 01:56:23 +05:30
Frappe PR Bot
9078e3798d fix: sync translations from crowdin (#46222)
fix: Bosnian translations
2025-03-02 19:04:06 +01:00
Frappe PR Bot
e803ed72e6 chore: update POT file (#46225) 2025-03-02 13:50:04 +01:00
Mihir Kandoi
b4f65154f5 refactor: rename subcontracting fields 2025-03-02 17:16:17 +05:30
Rohit Waghchaure
d2564cad68 fix: incorrect batch picked 2025-03-02 12:36:17 +05:30
rohitwaghchaure
5bee30ff2f Merge pull request #46219 from rohitwaghchaure/fixed-support-32834
fix: stock qty not recalculate on changing of the qty
2025-03-02 12:15:32 +05:30
Mihir Kandoi
7c9c0c7776 fix: error 2025-03-02 12:07:31 +05:30
Mihir Kandoi
a3596f717b fix: dont update rate of free item when batch is updated 2025-03-02 11:29:39 +05:30
Rohit Waghchaure
464e3339fe fix: stock qty not recalculate on changing of the qty 2025-03-01 21:34:06 +05:30
Frappe PR Bot
912f9bfd8e fix: sync translations from crowdin (#46216)
* fix: French translations

* fix: Chinese Simplified translations
2025-03-01 09:05:40 +01:00
rohitwaghchaure
1c124b3c90 Merge pull request #46202 from rohitwaghchaure/fixed-fields-stock-settings
fix: rearrange stock settings fields
2025-03-01 12:26:32 +05:30
Diptanil Saha
d2fad44e89 fix: pos item detail serial no field (#46211) 2025-02-28 22:36:24 +05:30
Ankush Menat
999f1cf96d fix: don't allow renaming account while system is actively in use (#46176) 2025-02-28 19:39:50 +05:30
Rohit Waghchaure
64985bffe0 fix: stock reservation issue while making Purchase Invoice 2025-02-28 19:28:20 +05:30
Diptanil Saha
8fb09decd2 fix: pos item selection using serial no (#46200) 2025-02-28 15:38:21 +05:30
Rohit Waghchaure
93f461c6f3 fix: rearrange stock settings fields 2025-02-28 15:30:42 +05:30
venkat102
a4b24f7451 fix(asset depreciation schedules): enable auto commit 2025-02-28 15:14:25 +05:30
Frappe PR Bot
48f9769c05 fix: sync translations from crowdin (#46170)
* fix: Persian translations

* fix: Bosnian translations

* fix: Swedish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-28 10:25:48 +01:00
ruthra kumar
e965c44908 Merge pull request #46169 from frappe/mergify/ruthra-kumar/config-update
ci(Mergify): configuration update
2025-02-28 13:34:29 +05:30
ruthra kumar
0c1e8bbfbb Merge pull request #46186 from rohitwaghchaure/fixed-support-32566
fix: removed mandatory property for the cost center field
2025-02-28 13:33:00 +05:30
Ninad1306
3251a331dd fix: exclude cancelled gl entries 2025-02-28 11:08:30 +05:30
Sagar Vora
453bcd7513 Merge pull request #46177 from vishakhdesai/payment-entry-fix 2025-02-28 10:42:26 +05:30
Ankush Menat
ded0aab680 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.
2025-02-28 04:57:57 +00:00
Rohit Waghchaure
079cf772aa fix: removed mandatory property for the cost center field 2025-02-28 09:48:19 +05:30
vishakhdesai
2dbef23244 fix: payment entry exchange gain loss issue 2025-02-27 18:18:49 +05:30
Sanket322
6719bbeb10 fix: test case for debit note 2025-02-27 17:14:29 +05:30
Sanket322
9b2b477ae0 fix: fixing test case 2025-02-27 16:52:13 +05:30
Sanket322
ccb4bdbe4c fix: if invoice is return then add amount in proper column 2025-02-27 13:17:57 +05:30
JK1117
daa5bebdd0 fix: use source_fieldname to validate inventory dimension 2025-02-27 15:17:39 +08:00
JK1117
4e63ee1a70 feat: fetch source_fieldname for inventory dimension 2025-02-27 15:17:10 +08:00
ruthra kumar
967f5ac7f3 ci(Mergify): configuration update
Signed-off-by: ruthra kumar <null>
2025-02-27 06:54:24 +05:30
ruthra kumar
fcf374928f Merge pull request #46155 from frappe/l10n_develop
fix: sync translations from crowdin
2025-02-26 13:52:06 +05:30
ruthra kumar
ac640a22b7 Merge pull request #46156 from aerele/autofetch-timesheet
fix: no permission to get project settings in sales invoice
2025-02-26 13:51:01 +05:30
venkat102
221f1468cb fix: no permission to get project settings in sales invoice 2025-02-26 11:01:40 +05:30
Frappe PR Bot
da4b28bf2f fix: Bosnian translations 2025-02-26 08:32:13 +05:30
rohitwaghchaure
5a6e06a140 Merge pull request #46097 from rohitwaghchaure/fixed-valuation-for-batch-sabb
fix: valuation rate for batch
2025-02-25 23:14:00 +05:30
ruthra kumar
45d72cbefc Merge pull request #45908 from aerele/autofetch-timesheet
fix(projects settings): add checkbox to auto fetch timesheet in sales invoice
2025-02-25 22:26:16 +05:30
Smit Vora
fd3fec3912 Merge pull request #46117 from Ninad1306/add_status_in_po_analysis_report 2025-02-25 18:36:41 +05:30
Ninad1306
936d7d4342 fix(report): filter sales / purchase orders based on date filters 2025-02-25 17:13:09 +05:30
Ninad1306
2394e76e7d fix(report): allow Closed sales orders to be visible 2025-02-25 15:59:46 +05:30
Ankush Menat
f62aa8fc57 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.
2025-02-25 09:43:53 +00:00
Frappe PR Bot
8284efdf66 fix: sync translations from crowdin (#46111)
* fix: Spanish translations

* fix: Swedish translations

* fix: Bosnian translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-25 10:21:09 +01:00
Patrick Eissler
3876cf0c2b refactor: make linter happy 2025-02-24 08:51:36 +01:00
Patrick Eissler
85f46d6e32 fix: only update User Permissions if a relevant field has changed 2025-02-24 08:47:17 +01:00
Ninad1306
3b2879d3a1 fix(report): allow Closed purchase orders to be visible 2025-02-24 12:42:05 +05:30
Mihir Kandoi
6073f5a6f9 test: added test 2025-02-23 22:26:52 +05:30
Rohit Waghchaure
b88305a95f fix: valuation rate for batch 2025-02-23 22:04:52 +05:30
Mihir Kandoi
da09c278c8 fix: use valuation method from settings in stock ageing report 2025-02-21 19:08:12 +05:30
Mihir Kandoi
f7594e2ff9 fix: revamp logic (split parent and child) 2025-02-21 11:04:14 +05:30
Mihir Kandoi
2f1e253e19 fix: syntax error 2025-02-20 15:28:13 +05:30
Mihir Kandoi
5e66231ca4 perf: replace if function in query 2025-02-20 15:03:19 +05:30
Sanket322
746adfd057 refactor: add new line ragardless of postal code 2025-02-20 11:33:53 +05:30
Mihir Kandoi
0c465b0f32 Merge branch 'develop' into st31369 2025-02-19 16:04:26 +05:30
Sanket322
a08bc6b913 fix: use get_list to check permissions 2025-02-19 14:02:11 +05:30
Mihir Kandoi
dcec446e55 fix: patch path 2025-02-19 13:10:13 +05:30
Mihir Kandoi
1230127d24 fix: patch 2025-02-19 13:03:45 +05:30
Mihir Kandoi
019303dd12 fix: tests 2025-02-18 22:02:13 +05:30
Mihir Kandoi
1b831e9abd fix: tests 2025-02-18 21:30:24 +05:30
Mihir Kandoi
baa0dd1235 fix: rename some sla fields 2025-02-18 21:30:21 +05:30
Ninad1306
196ef7ac4e test: validate fetching of taxes based on taxes and charges template 2025-02-18 14:03:35 +05:30
Ninad1306
0fd0695bbb fix: set taxes before calculating taxes and totals 2025-02-18 12:52:33 +05:30
Mihir Kandoi
154e9813c4 fix: revert last commit 2025-02-17 21:52:49 +05:30
Mihir Kandoi
7cf8e498c4 fix: fiscal year error 2025-02-17 18:44:33 +05:30
Mihir Kandoi
a41024813b perf: patch 2025-02-17 18:08:31 +05:30
barredterra
b9450288f5 refactor: improve translatability in buying controller 2025-02-17 12:16:39 +01:00
venkat102
5880f1d5c6 fix: enable fetch_timesheet_in_sales_invoice in test 2025-02-17 14:49:32 +05:30
Mihir Kandoi
17d415b105 fix: set landed cost based on purchase invoice rate 2025-02-17 14:36:15 +05:30
Patrick Eissler
92f63a026b chore: use existing utility function 2025-02-14 16:28:40 +01:00
Patrick Eissler
dee46c6954 fix(Employee): remove User Permissions if create_user_permission is unchecked 2025-02-14 15:14:03 +01:00
venkat102
43b13b91be fix: check value as int 2025-02-13 23:24:05 +05:30
venkat102
914ad357fd fix(sales invoice): check fetch_timesheet_in_sales_invoice enabled before fetching the timesheet 2025-02-13 23:07:45 +05:30
venkat102
876082ea2f fix(project settings): add checkbox to auto fetch timesheet in sales invoice 2025-02-13 23:05:34 +05:30
ljain112
6275b44a0b fix: auto allocation for negative amount outstanding for Customers in Payment Entry 2025-02-13 14:56:11 +05:30
Rohit Waghchaure
e1b7688a17 fix: incorrect batch picked in the pick list 2025-02-11 15:13:13 +05:30
Sugesh393
f8ab021920 fix: change voucher_type and voucher_no field type to data 2025-02-05 18:40:21 +05:30
Corentin Forler
b7d801d571 fix(stock): Cast filters.to_date before string concatenation 2025-02-03 11:32:03 +01:00
422 changed files with 313735 additions and 163587 deletions

View File

@@ -6,7 +6,7 @@ cd ~ || exit
sudo apt update
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client
sudo apt install libcups2-dev redis-server mariadb-client libmariadb-dev
pip install frappe-bench

View File

@@ -8,6 +8,7 @@
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"use_mysqlclient": 1,
"root_login": "root",
"root_password": "root",
"host_name": "http://test_site:8000",

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

@@ -2,29 +2,27 @@ pull_request_rules:
- name: Auto-close PRs on stable branch
conditions:
- and:
- and:
- author!=surajshetty3416
- author!=gavindsouza
- author!=rohitwaghchaure
- author!=nabinhait
- author!=ankush
- author!=deepeshgarg007
- author!=frappe-pr-bot
- author!=mergify[bot]
- or:
- base=version-13
- base=version-12
- base=version-14
- base=version-15
- base=version-16
- and:
- author!=surajshetty3416
- author!=gavindsouza
- author!=rohitwaghchaure
- author!=nabinhait
- author!=ankush
- author!=deepeshgarg007
- author!=frappe-pr-bot
- author!=mergify[bot]
- or:
- base=version-13
- base=version-12
- base=version-14
- base=version-15
- base=version-16
actions:
close:
comment:
message: |
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
message: |
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
- name: backport to develop
conditions:
- label="backport develop"
@@ -34,7 +32,6 @@ pull_request_rules:
- develop
assignees:
- "{{ author }}"
- name: backport to version-14-hotfix
conditions:
- label="backport version-14-hotfix"
@@ -44,7 +41,6 @@ pull_request_rules:
- version-14-hotfix
assignees:
- "{{ author }}"
- name: backport to version-15-hotfix
conditions:
- label="backport version-15-hotfix"
@@ -54,18 +50,6 @@ pull_request_rules:
- version-15-hotfix
assignees:
- "{{ author }}"
- name: backport to version-13-hotfix
conditions:
- label="backport version-13-hotfix"
actions:
backport:
branches:
- version-13-hotfix
assignees:
- "{{ author }}"
- name: Automatic merge on CI success and review
conditions:
- status-success=linters
@@ -96,6 +80,6 @@ pull_request_rules:
merge:
method: squash
commit_message_template: |
{{ title }} (#{{ number }})
{{ title }} (#{{ number }})
{{ body }}
{{ body }}

View File

@@ -1,5 +1,5 @@
exclude: 'node_modules|.git'
default_stages: [commit]
default_stages: [pre-commit]
fail_fast: false

View File

@@ -1,5 +1,5 @@
<div align="center">
<a href="https://erpnext.com">
<a href="https://frappe.io/erpnext">
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>
</a>
<h2>ERPNext</h2>
@@ -7,6 +7,7 @@
<p>Powerful, Intuitive and Open-Source ERP</p>
</p>
[![Learn on Frappe School](https://img.shields.io/badge/Frappe%20School-Learn%20ERPNext-blue?style=flat-square)](https://frappe.school)<br><br>
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)
@@ -17,11 +18,11 @@
</div>
<div align="center">
<a href="https://erpnext-demo.frappe.cloud/app/home">Live Demo</a>
<a href="https://erpnext-demo.frappe.cloud/api/method/erpnext_demo.erpnext_demo.auth.login_demo">Live Demo</a>
-
<a href="https://erpnext.com">Website</a>
<a href="https://frappe.io/erpnext">Website</a>
-
<a href="https://docs.erpnext.com">Documentation</a>
<a href="https://docs.frappe.io/erpnext/">Documentation</a>
</div>
## ERPNext
@@ -114,26 +115,23 @@ To setup the repository locally follow the steps mentioned below:
2. In a separate terminal window, run the following commands:
```
# Create a new site
bench new-site erpnext.dev
# Map your site to localhost
bench --site erpnext.dev add-to-hosts
bench new-site erpnext.localhost
```
3. Get the ERPNext app and install it
```
# Get the ERPNext app
bench get-app https://github.com/frappe/erpnext
# Install the app
bench --site erpnext.dev install-app erpnext
bench --site erpnext.localhost install-app erpnext
```
4. Open the URL `http://erpnext.dev:8000/app` in your browser, you should see the app running
4. Open the URL `http://erpnext.localhost:8000/app` in your browser, you should see the app running
## Learning and community
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers.
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users.
@@ -144,6 +142,7 @@ To setup the repository locally follow the steps mentioned below:
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
2. [Translations](https://crowdin.com/project/frappe)
## Logo and Trademark Policy

View File

@@ -4,7 +4,11 @@ files:
pull_request_title: "fix: sync translations from crowdin"
pull_request_labels:
- translation
- skip-release-notes
pull_request_reviewers:
- barredterra # change to your GitHub username if you copied this file
commit_message: "fix: %language% translations"
append_commit_message: false
languages_mapping:
two_letters_code:
pt-BR: pt_BR

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
@@ -479,6 +479,7 @@ def get_account_autoname(account_number, account_name, company):
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False):
_ensure_idle_system()
account = frappe.get_cached_doc("Account", name)
if not account:
return
@@ -499,7 +500,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")
@@ -540,6 +541,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)
@@ -593,3 +595,31 @@ 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
last_gl_update = None
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 not last_gl_update:
return
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

@@ -116,6 +116,7 @@ def identify_is_group(child):
return is_group
@frappe.whitelist()
def get_chart(chart_template, existing_company=None):
chart = {}
if existing_company:

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

@@ -0,0 +1,6 @@
[
{
"doctype": "Account",
"name": "_Test Account 1"
}
]

View File

@@ -1,3 +0,0 @@
[[Account]]
name = "_Test Account 1"

View File

@@ -160,9 +160,6 @@ def get_payment_entries_for_bank_clearance(
as_dict=1,
)
if bank_account:
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
f"""
select
@@ -184,7 +181,6 @@ def get_payment_entries_for_bank_clearance(
"account": account,
"from": from_date,
"to": to_date,
"bank_account": bank_account,
},
as_dict=1,
)

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
from erpnext import get_default_cost_center
@@ -373,10 +374,37 @@ def auto_reconcile_vouchers(
from_reference_date=None,
to_reference_date=None,
):
frappe.flags.auto_reconcile_vouchers = True
reconciled, partially_reconciled = set(), set()
bank_transactions = get_bank_transactions(bank_account)
if len(bank_transactions) > 10:
frappe.enqueue(
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
queue="long",
bank_transactions=bank_transactions,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=filter_by_reference_date,
from_reference_date=from_reference_date,
to_reference_date=to_reference_date,
)
frappe.msgprint(_("Auto Reconciliation has started in the background"))
else:
start_auto_reconcile(
bank_transactions,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
def start_auto_reconcile(
bank_transactions, from_date, to_date, filter_by_reference_date, from_reference_date, to_reference_date
):
frappe.flags.auto_reconcile_vouchers = True
reconciled, partially_reconciled = set(), set()
for transaction in bank_transactions:
linked_payments = get_linked_payments(
transaction.name,
@@ -414,7 +442,6 @@ def auto_reconcile_vouchers(
frappe.msgprint(title=_("Auto Reconciliation"), msg=alert_message, indicator=indicator)
frappe.flags.auto_reconcile_vouchers = False
return reconciled, partially_reconciled
def get_auto_reconcile_message(partially_reconciled, reconciled):
@@ -491,16 +518,23 @@ def subtract_allocations(gl_account, vouchers):
voucher_allocated_amounts = get_total_allocated_amount(voucher_docs)
for voucher in vouchers:
rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) or []
filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows))
if amount := None if not filtered_row else filtered_row[0]["total"]:
if amount := get_allocated_amount(voucher_allocated_amounts, voucher, gl_account):
voucher["paid_amount"] -= amount
copied.append(voucher)
return copied
def get_allocated_amount(voucher_allocated_amounts, voucher, gl_account):
if not (voucher_details := voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name")))):
return
if not (row := voucher_details.get(gl_account)):
return
return row.get("total")
def check_matching(
bank_account,
company,
@@ -770,26 +804,20 @@ def get_je_matching_query(
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
ref_condition = je.cheque_no == transaction.reference_number
ref_rank = frappe.qb.terms.Case().when(ref_condition, 1).else_(0)
amount_field = f"{cr_or_dr}_in_account_currency"
amount_equality = getattr(jea, amount_field) == transaction.unallocated_amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
filter_by_date = je.posting_date.between(from_date, to_date)
if cint(filter_by_reference_date):
filter_by_date = je.cheque_date.between(from_reference_date, to_reference_date)
query = (
subquery = (
frappe.qb.from_(jea)
.join(je)
.on(jea.parent == je.name)
.select(
(ref_rank + amount_rank + 1).as_("rank"),
Sum(getattr(jea, amount_field)).as_("paid_amount"),
ConstantColumn("Journal Entry").as_("doctype"),
je.name,
getattr(jea, amount_field).as_("paid_amount"),
je.cheque_no.as_("reference_no"),
je.cheque_date.as_("reference_date"),
je.pay_to_recd_from.as_("party"),
@@ -801,13 +829,26 @@ def get_je_matching_query(
.where(je.voucher_type != "Opening Entry")
.where(je.clearance_date.isnull())
.where(jea.account == common_filters.bank_account)
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(filter_by_date)
.groupby(je.name)
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
)
if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition)
subquery = subquery.where(je.cheque_no == transaction.reference_number)
ref_rank = frappe.qb.terms.Case().when(subquery.reference_no == transaction.reference_number, 1).else_(0)
amount_equality = subquery.paid_amount == transaction.unallocated_amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
query = (
frappe.qb.from_(subquery)
.select(
"*",
(ref_rank + amount_rank + 1).as_("rank"),
)
.where(amount_equality if exact_match else subquery.paid_amount > 0.0)
)
return query

View File

@@ -2,27 +2,6 @@
// For license information, please see license.txt
frappe.ui.form.on("Bank Transaction", {
onload(frm) {
frm.set_query("payment_document", "payment_entries", function () {
const payment_doctypes = frm.events.get_payment_doctypes(frm);
return {
filters: {
name: ["in", payment_doctypes],
},
};
});
},
refresh(frm) {
if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) {
frm.add_custom_button(__("Unreconcile Transaction"), () => {
frm.call("remove_payment_entries").then(() => frm.refresh());
});
}
},
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
setup: function (frm) {
frm.set_query("party_type", function () {
return {
@@ -31,6 +10,41 @@ frappe.ui.form.on("Bank Transaction", {
},
};
});
frm.set_query("bank_account", function () {
return {
filters: { is_company_account: 1 },
};
});
frm.set_query("payment_document", "payment_entries", function () {
const payment_doctypes = frm.events.get_payment_doctypes(frm);
return {
filters: {
name: ["in", payment_doctypes],
},
};
});
frm.set_query("payment_entry", "payment_entries", function () {
return {
filters: {
docstatus: ["!=", 2],
},
};
});
},
refresh(frm) {
if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) {
frm.add_custom_button(__("Unreconcile Transaction"), () => {
frm.call("remove_payment_entries").then(() => frm.refresh());
});
}
},
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
get_payment_doctypes: function () {
@@ -39,31 +53,6 @@ frappe.ui.form.on("Bank Transaction", {
},
});
frappe.ui.form.on("Bank Transaction Payments", {
payment_entries_remove: function (frm, cdt, cdn) {
update_clearance_date(frm, cdt, cdn);
},
});
const update_clearance_date = (frm, cdt, cdn) => {
if (frm.doc.docstatus === 1) {
frappe
.xcall("erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", {
doctype: cdt,
docname: cdn,
bt_name: frm.doc.name,
})
.then((e) => {
if (e == "success") {
frappe.show_alert({
message: __("Document {0} successfully uncleared", [e]),
indicator: "green",
});
}
});
}
};
function set_bank_statement_filter(frm) {
frm.set_query("bank_statement", function () {
return {

View File

@@ -5,7 +5,7 @@ import frappe
from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document
from frappe.utils import flt
from frappe.utils import flt, getdate
class BankTransaction(Document):
@@ -84,16 +84,16 @@ class BankTransaction(Document):
if not self.payment_entries:
return
pe = []
references = set()
for row in self.payment_entries:
reference = (row.payment_document, row.payment_entry)
if reference in pe:
if reference in references:
frappe.throw(
_("{0} {1} is allocated twice in this Bank Transaction").format(
row.payment_document, row.payment_entry
)
)
pe.append(reference)
references.add(reference)
def update_allocated_amount(self):
allocated_amount = (
@@ -104,6 +104,19 @@ class BankTransaction(Document):
self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
def delink_old_payment_entries(self):
if self.flags.updating_linked_bank_transaction:
return
old_doc = self.get_doc_before_save()
payment_entry_names = set(pe.name for pe in self.payment_entries)
for old_pe in old_doc.payment_entries:
if old_pe.name in payment_entry_names:
continue
self.delink_payment_entry(old_pe)
def before_submit(self):
self.allocate_payment_entries()
self.set_status()
@@ -113,13 +126,14 @@ class BankTransaction(Document):
def before_update_after_submit(self):
self.validate_duplicate_references()
self.allocate_payment_entries()
self.update_allocated_amount()
self.delink_old_payment_entries()
self.allocate_payment_entries()
self.set_status()
def on_cancel(self):
for payment_entry in self.payment_entries:
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.delink_payment_entry(payment_entry)
self.set_status()
@@ -152,43 +166,55 @@ class BankTransaction(Document):
- 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date
"""
if self.flags.updating_linked_bank_transaction or not self.payment_entries:
return
remaining_amount = self.unallocated_amount
to_remove = []
payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries]
pe_bt_allocations = get_total_allocated_amount(payment_entry_docs)
gl_entries = get_related_bank_gl_entries(payment_entry_docs)
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
self,
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry))
or [],
for payment_entry in list(self.payment_entries):
if payment_entry.allocated_amount != 0:
continue
allocable_amount, should_clear, clearance_date = get_clearance_details(
self,
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry)) or {},
gl_entries.get((payment_entry.payment_document, payment_entry.payment_entry)) or {},
gl_bank_account,
)
if allocable_amount < 0:
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(allocable_amount))
if remaining_amount <= 0:
self.remove(payment_entry)
continue
if allocable_amount == 0:
if should_clear:
self.clear_linked_payment_entry(payment_entry, clearance_date=clearance_date)
self.remove(payment_entry)
continue
should_clear = should_clear and allocable_amount <= remaining_amount
payment_entry.allocated_amount = min(allocable_amount, remaining_amount)
remaining_amount = flt(
remaining_amount - payment_entry.allocated_amount,
self.precision("unallocated_amount"),
)
if payment_entry.payment_document == "Bank Transaction":
self.update_linked_bank_transaction(
payment_entry.payment_entry, payment_entry.allocated_amount
)
elif should_clear:
self.clear_linked_payment_entry(payment_entry, clearance_date=clearance_date)
if 0.0 == unallocated_amount:
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
to_remove.append(payment_entry)
elif remaining_amount <= 0.0:
to_remove.append(payment_entry)
elif 0.0 < unallocated_amount <= remaining_amount:
payment_entry.allocated_amount = unallocated_amount
remaining_amount -= unallocated_amount
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
elif 0.0 < unallocated_amount:
payment_entry.allocated_amount = remaining_amount
remaining_amount = 0.0
elif 0.0 > unallocated_amount:
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
for payment_entry in to_remove:
self.remove(payment_entry)
self.update_allocated_amount()
@frappe.whitelist()
def remove_payment_entries(self):
@@ -199,14 +225,64 @@ class BankTransaction(Document):
def remove_payment_entry(self, payment_entry):
"Clear payment entry and clearance"
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.delink_payment_entry(payment_entry)
self.remove(payment_entry)
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
clearance_date = None if for_cancel else self.date
set_voucher_clearance(
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
)
def delink_payment_entry(self, payment_entry):
if payment_entry.payment_document == "Bank Transaction":
self.update_linked_bank_transaction(payment_entry.payment_entry, allocated_amount=None)
else:
self.clear_linked_payment_entry(payment_entry, clearance_date=None)
def clear_linked_payment_entry(self, payment_entry, clearance_date=None):
doctype = payment_entry.payment_document
docname = payment_entry.payment_entry
# might be a bank transaction
if doctype not in get_doctypes_for_bank_reconciliation():
return
if doctype == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doctype, parent=docname),
"clearance_date",
clearance_date,
)
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
def update_linked_bank_transaction(self, bank_transaction_name, allocated_amount=None):
"""For when a second bank transaction has fixed another, e.g. refund"""
bt = frappe.get_doc(self.doctype, bank_transaction_name)
if allocated_amount:
bt.append(
"payment_entries",
{
"payment_document": self.doctype,
"payment_entry": self.name,
"allocated_amount": allocated_amount,
},
)
else:
pe = next(
(
pe
for pe in bt.payment_entries
if pe.payment_document == self.doctype and pe.payment_entry == self.name
),
None,
)
if not pe:
return
bt.flags.updating_linked_bank_transaction = True
bt.remove(pe)
bt.save()
def auto_set_party(self):
from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty
@@ -238,71 +314,107 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes")
def get_clearance_details(transaction, payment_entry, bt_allocations):
def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries, gl_bank_account):
"""
There should only be one bank gle for a voucher.
Could be none for a Bank Transaction.
But if a JE, could affect two banks.
Should only clear the voucher if all bank gles are allocated.
There should only be one bank gl entry for a voucher, except for JE.
For JE, there can be multiple bank gl entries for the same account.
In this case, the allocable_amount will be the sum of amounts of all gl entries of the account.
There will be no gl entry for a Bank Transaction so return the unallocated amount.
Should only clear the voucher if all bank gl entries are allocated.
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
unallocated_amount = min(
transaction.unallocated_amount,
get_paid_amount(payment_entry, transaction.currency, gl_bank_account),
)
unmatched_gles = len(gles)
latest_transaction = transaction
for gle in gles:
if gle["gl_account"] == gl_bank_account:
if gle["amount"] <= 0.0:
frappe.throw(
_("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"])
transaction_date = getdate(transaction.date)
if payment_entry.payment_document == "Bank Transaction":
bt = frappe.db.get_value(
"Bank Transaction",
payment_entry.payment_entry,
("unallocated_amount", "bank_account"),
as_dict=True,
)
if bt.bank_account != gl_bank_account:
frappe.throw(
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
bt.bank_account, payment_entry.payment_entry, gl_bank_account
)
)
unmatched_gles -= 1
unallocated_amount = gle["amount"]
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"]:
unallocated_amount = gle["amount"] - a["total"]
if frappe.utils.getdate(transaction.date) < a["latest_date"]:
latest_transaction = frappe.get_doc("Bank Transaction", a["latest_name"])
else:
# Must be a Journal Entry affecting more than one bank
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"] and a["total"] == gle["amount"]:
unmatched_gles -= 1
return abs(bt.unallocated_amount), True, transaction_date
return unallocated_amount, unmatched_gles == 0, latest_transaction
if gl_bank_account not in gl_entries:
frappe.throw(
_("{} {} is not affecting bank account {}").format(
payment_entry.payment_document, payment_entry.payment_entry, gl_bank_account
)
)
allocable_amount = gl_entries.pop(gl_bank_account) or 0
if allocable_amount <= 0.0:
frappe.throw(
_("Invalid amount in accounting entries of {} {} for Account {}: {}").format(
payment_entry.payment_document, payment_entry.payment_entry, gl_bank_account, allocable_amount
)
)
matching_bt_allocaion = bt_allocations.pop(gl_bank_account, {})
allocable_amount = flt(
allocable_amount - matching_bt_allocaion.get("total", 0), transaction.precision("unallocated_amount")
)
should_clear = all(
gl_entries[gle_account] == bt_allocations.get(gle_account, {}).get("total", 0)
for gle_account in gl_entries
)
bt_allocation_date = matching_bt_allocaion.get("latest_date", None)
clearance_date = transaction_date if not bt_allocation_date else max(transaction_date, bt_allocation_date)
return allocable_amount, should_clear, clearance_date
def get_related_bank_gl_entries(doctype, docname):
def get_related_bank_gl_entries(docs):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
return frappe.db.sql(
if not docs:
return {}
result = frappe.db.sql(
"""
SELECT
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
gle.account AS gl_account
FROM
`tabGL Entry` gle
LEFT JOIN
`tabAccount` ac ON ac.name=gle.account
WHERE
ac.account_type = 'Bank'
AND gle.voucher_type = %(doctype)s
AND gle.voucher_no = %(docname)s
AND is_cancelled = 0
""",
dict(doctype=doctype, docname=docname),
SELECT
gle.voucher_type AS doctype,
gle.voucher_no AS docname,
gle.account AS gl_account,
SUM(ABS(gle.credit_in_account_currency - gle.debit_in_account_currency)) AS amount
FROM
`tabGL Entry` gle
LEFT JOIN
`tabAccount` ac ON ac.name = gle.account
WHERE
ac.account_type = 'Bank'
AND (gle.voucher_type, gle.voucher_no) IN %(docs)s
AND gle.is_cancelled = 0
GROUP BY
gle.voucher_type, gle.voucher_no, gle.account
""",
{"docs": docs},
as_dict=True,
)
entries = {}
for row in result:
key = (row["doctype"], row["docname"])
if key not in entries:
entries[key] = {}
entries[key][row["gl_account"]] = row["amount"]
return entries
def get_total_allocated_amount(docs):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
along with the latest bank transaction date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
if not docs:
@@ -311,11 +423,10 @@ def get_total_allocated_amount(docs):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT total, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account,
btp.payment_document,
@@ -338,104 +449,14 @@ def get_total_allocated_amount(docs):
payment_allocation_details = {}
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row)
row["latest_date"] = getdate(row["latest_date"])
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), {})[
row["gl_account"]
] = row
return payment_allocation_details
def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
if doc.payment_type == "Receive":
paid_amount_field = (
"received_amount" if doc.paid_to_account_currency == currency else "base_received_amount"
)
elif doc.payment_type == "Pay":
paid_amount_field = (
"paid_amount" if doc.paid_from_account_currency == currency else "base_paid_amount"
)
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, paid_amount_field
)
elif payment_entry.payment_document == "Journal Entry":
return abs(
frappe.db.get_value(
"Journal Entry Account",
{"parent": payment_entry.payment_entry, "account": gl_bank_account},
"sum(debit_in_account_currency-credit_in_account_currency)",
)
or 0
)
elif payment_entry.payment_document == "Expense Claim":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed"
)
elif payment_entry.payment_document == "Loan Disbursement":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount"
)
elif payment_entry.payment_document == "Loan Repayment":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value(
"Bank Transaction", payment_entry.payment_entry, ("deposit", "withdrawal")
)
return abs(flt(wth) - flt(dep))
else:
frappe.throw(
f"Please reconcile {payment_entry.payment_document}: {payment_entry.payment_entry} manually"
)
def set_voucher_clearance(doctype, docname, clearance_date, self):
if doctype in get_doctypes_for_bank_reconciliation():
if (
doctype == "Payment Entry"
and frappe.db.get_value("Payment Entry", docname, "payment_type") == "Internal Transfer"
and len(get_reconciled_bank_transactions(doctype, docname)) < 2
):
return
if doctype == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doctype, parent=docname),
"clearance_date",
clearance_date,
)
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund
bt = frappe.get_doc(doctype, docname)
if clearance_date:
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
bt.add_payment_entries(vouchers)
bt.save()
else:
for pe in bt.payment_entries:
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
bt.remove(pe)
bt.save()
break
def get_reconciled_bank_transactions(doctype, docname):
return frappe.get_all(
"Bank Transaction Payments",
@@ -444,13 +465,6 @@ def get_reconciled_bank_transactions(doctype, docname):
)
@frappe.whitelist()
def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt)
return docname
def remove_from_bank_transaction(doctype, docname):
"""Remove a (cancelled) voucher from all Bank Transactions."""
for bt_name in get_reconciled_bank_transactions(doctype, docname):

View File

@@ -0,0 +1,23 @@
[
{
"company": "_Test Company",
"cost_center_name": "_Test Cost Center",
"doctype": "Cost Center",
"is_group": 0,
"parent_cost_center": "_Test Company - _TC"
},
{
"company": "_Test Company",
"cost_center_name": "_Test Cost Center 2",
"doctype": "Cost Center",
"is_group": 0,
"parent_cost_center": "_Test Company - _TC"
},
{
"company": "_Test Company",
"cost_center_name": "_Test Write Off Cost Center",
"doctype": "Cost Center",
"is_group": 0,
"parent_cost_center": "_Test Company - _TC"
}
]

View File

@@ -1,18 +0,0 @@
[["Cost Center"]]
company = "_Test Company"
cost_center_name = "_Test Cost Center"
is_group = 0
parent_cost_center = "_Test Company - _TC"
[["Cost Center"]]
company = "_Test Company"
cost_center_name = "_Test Cost Center 2"
is_group = 0
parent_cost_center = "_Test Company - _TC"
[["Cost Center"]]
company = "_Test Company"
cost_center_name = "_Test Write Off Cost Center"
is_group = 0
parent_cost_center = "_Test Company - _TC"

View File

@@ -128,7 +128,7 @@ class TestCouponCode(IntegrationTestCase):
item_code="_Test Tesla Car",
rate=5000,
qty=1,
do_not_submit=True,
do_not_save=True,
)
self.assertEqual(so.items[0].rate, 5000)

View File

@@ -230,6 +230,7 @@ def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str |
if not language:
language = doc.get("language")
letter_text = None
if language:
letter_text = frappe.db.get_value(
DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1

View File

@@ -1,6 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import json
import frappe
from frappe.model import mapper
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.utils import add_days, nowdate, today
@@ -77,6 +80,36 @@ class TestDunning(IntegrationTestCase):
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.

View File

@@ -0,0 +1,36 @@
[
{
"doctype": "Dunning Type",
"dunning_type": "_Test First Notice",
"company": "_Test Company",
"is_default": 1,
"dunning_fee": 0.0,
"rate_of_interest": 0.0,
"dunning_letter_text": [
{
"language": "en",
"body_text": "We have still not received payment for our invoice",
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
}
],
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC"
},
{
"doctype": "Dunning Type",
"dunning_type": "_Test Second Notice",
"company": "_Test Company",
"is_default": 0,
"dunning_fee": 10.0,
"rate_of_interest": 10.0,
"dunning_letter_text": [
{
"language": "en",
"body_text": "We have still not received payment for our invoice",
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
}
],
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC"
}
]

View File

@@ -1,28 +0,0 @@
[["Dunning Type"]]
dunning_type = "_Test First Notice"
company = "_Test Company"
is_default = 1
dunning_fee = 0.0
rate_of_interest = 0.0
income_account = "Sales - _TC"
cost_center = "_Test Cost Center - _TC"
[["Dunning Type".dunning_letter_text]]
language = "en"
body_text = "We have still not received payment for our invoice"
closing_text = "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
[["Dunning Type"]]
dunning_type = "_Test Second Notice"
company = "_Test Company"
is_default = 0
dunning_fee = 10.0
rate_of_interest = 10.0
income_account = "Sales - _TC"
cost_center = "_Test Cost Center - _TC"
[["Dunning Type".dunning_letter_text]]
language = "en"
body_text = "We have still not received payment for our invoice"
closing_text = "We kindly request that you pay the outstanding amount immediately, including interest and late fees."

View File

@@ -86,8 +86,7 @@
"fieldname": "party_type",
"fieldtype": "Link",
"label": "Party Type",
"options": "DocType",
"search_index": 1
"options": "DocType"
},
{
"fieldname": "party",
@@ -242,8 +241,7 @@
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"search_index": 1
"options": "Company"
},
{
"fieldname": "finance_book",
@@ -279,7 +277,8 @@
{
"fieldname": "transaction_exchange_rate",
"fieldtype": "Float",
"label": "Transaction Exchange Rate"
"label": "Transaction Exchange Rate",
"precision": "9"
},
{
"fieldname": "debit_in_transaction_currency",
@@ -357,7 +356,7 @@
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2024-08-22 13:03:39.997475",
"modified": "2025-04-21 22:37:16.349564",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
@@ -388,8 +387,9 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "voucher_no,account,posting_date,against_voucher",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

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 (
@@ -438,9 +438,14 @@ def update_against_account(voucher_type, voucher_no):
def on_doctype_update():
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
frappe.db.add_index("GL Entry", ["posting_date", "company"])
frappe.db.add_index("GL Entry", ["party_type", "party"])
add_company_indexes()
def add_company_indexes():
"""Only add company indexes if more than one company exists."""
if frappe.db.count("Company", {"name": ("not like", "%(Demo)%")}) > 1:
frappe.db.add_index("GL Entry", ["posting_date", "company"])
frappe.db.add_index("GL Entry", ["company"])
def rename_gle_sle_docs():
@@ -456,7 +461,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

@@ -0,0 +1,79 @@
[
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 10",
"company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 10,
"tax_type": "_Test Account Excise Duty - _TC"
}
]
},
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 12",
"company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 12,
"tax_type": "_Test Account Excise Duty - _TC"
}
]
},
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 15",
"company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 15,
"tax_type": "_Test Account Excise Duty - _TC"
}
]
},
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 20",
"company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 20,
"tax_type": "_Test Account Excise Duty - _TC"
}
]
},
{
"doctype": "Item Tax Template",
"title": "_Test Item Tax Template 1",
"company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 5,
"tax_type": "_Test Account Excise Duty - _TC"
},
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 10,
"tax_type": "_Test Account Education Cess - _TC"
},
{
"doctype": "Item Tax Template Detail",
"parentfield": "taxes",
"tax_rate": 15,
"tax_type": "_Test Account S&H Education Cess - _TC"
}
]
}
]

View File

@@ -1,62 +0,0 @@
[["Item Tax Template"]]
title = "_Test Account Excise Duty @ 10"
company = "_Test Company"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 10
tax_type = "_Test Account Excise Duty - _TC"
[["Item Tax Template"]]
title = "_Test Account Excise Duty @ 12"
company = "_Test Company"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 12
tax_type = "_Test Account Excise Duty - _TC"
[["Item Tax Template"]]
title = "_Test Account Excise Duty @ 15"
company = "_Test Company"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 15
tax_type = "_Test Account Excise Duty - _TC"
[["Item Tax Template"]]
title = "_Test Account Excise Duty @ 20"
company = "_Test Company"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 20
tax_type = "_Test Account Excise Duty - _TC"
[["Item Tax Template"]]
title = "_Test Item Tax Template 1"
company = "_Test Company"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 5
tax_type = "_Test Account Excise Duty - _TC"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 10
tax_type = "_Test Account Education Cess - _TC"
[["Item Tax Template".taxes]]
doctype = "Item Tax Template Detail"
parentfield = "taxes"
tax_rate = 15
tax_type = "_Test Account S&H Education Cess - _TC"

View File

@@ -141,6 +141,7 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
self.validate_company_in_accounting_dimension()
self.validate_advance_accounts()
if self.docstatus == 0:
@@ -578,8 +579,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"]:
@@ -827,14 +842,13 @@ class JournalEntry(AccountsController):
"Debit Note",
"Credit Note",
]:
invoice = frappe.db.get_value(
reference_type, reference_name, ["docstatus", "outstanding_amount"], as_dict=1
)
invoice = frappe.get_doc(reference_type, reference_name)
if invoice.docstatus != 1:
frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name))
if total and flt(invoice.outstanding_amount) < total:
precision = invoice.precision("outstanding_amount")
if total and flt(invoice.outstanding_amount, precision) < flt(total, precision):
frappe.throw(
_("Payment against {0} {1} cannot be greater than Outstanding Amount {2}").format(
reference_type, reference_name, invoice.outstanding_amount
@@ -1062,14 +1076,15 @@ class JournalEntry(AccountsController):
gl_map = []
company_currency = erpnext.get_company_currency(self.company)
self.transaction_currency = company_currency
self.transaction_exchange_rate = 1
if self.multi_currency:
for row in self.get("accounts"):
if row.account_currency != company_currency:
self.currency = row.account_currency
self.conversion_rate = row.exchange_rate
# Journal assumes the first foreign currency as transaction currency
self.transaction_currency = row.account_currency
self.transaction_exchange_rate = row.exchange_rate
break
else:
self.currency = company_currency
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
@@ -1094,6 +1109,18 @@ class JournalEntry(AccountsController):
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"transaction_currency": self.transaction_currency,
"transaction_exchange_rate": self.transaction_exchange_rate,
"debit_in_transaction_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
"credit_in_transaction_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,

View File

@@ -583,7 +583,7 @@ class TestJournalEntry(IntegrationTestCase):
order_by="account",
)
expected = [
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 1.0},
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 85.0},
{"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0},
]
self.assertEqual(expected, actual)
@@ -599,13 +599,14 @@ def make_journal_entry(
save=True,
submit=False,
project=None,
company=None,
):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
jv = frappe.new_doc("Journal Entry")
jv.posting_date = posting_date or nowdate()
jv.company = "_Test Company"
jv.company = company or "_Test Company"
jv.user_remark = "test"
jv.multi_currency = 1
jv.set(

View File

@@ -0,0 +1,94 @@
[
{
"cheque_date": "2013-03-14",
"cheque_no": "33",
"company": "_Test Company",
"doctype": "Journal Entry",
"accounts": [
{
"account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 400.0,
"debit_in_account_currency": 0.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "_Test Cost Center - _TC"
},
{
"account": "_Test Bank - _TC",
"credit_in_account_currency": 0.0,
"debit_in_account_currency": 400.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "_Test Cost Center - _TC"
}
],
"naming_series": "_T-Journal Entry-",
"posting_date": "2013-02-14",
"user_remark": "test",
"voucher_type": "Bank Entry"
},
{
"cheque_date": "2013-02-14",
"cheque_no": "33",
"company": "_Test Company",
"doctype": "Journal Entry",
"accounts": [
{
"account": "_Test Payable - _TC",
"party_type": "Supplier",
"party": "_Test Supplier",
"credit_in_account_currency": 0.0,
"debit_in_account_currency": 400.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "_Test Cost Center - _TC"
},
{
"account": "_Test Bank - _TC",
"credit_in_account_currency": 400.0,
"debit_in_account_currency": 0.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "_Test Cost Center - _TC"
}
],
"naming_series": "_T-Journal Entry-",
"posting_date": "2013-02-14",
"user_remark": "test",
"voucher_type": "Bank Entry"
},
{
"cheque_date": "2013-02-14",
"cheque_no": "33",
"company": "_Test Company",
"doctype": "Journal Entry",
"accounts": [
{
"account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 0.0,
"debit_in_account_currency": 400.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "_Test Cost Center - _TC"
},
{
"account": "Sales - _TC",
"credit_in_account_currency": 400.0,
"debit_in_account_currency": 0.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "_Test Cost Center - _TC"
}
],
"naming_series": "_T-Journal Entry-",
"posting_date": "2013-02-14",
"user_remark": "test",
"voucher_type": "Bank Entry"
}
]

View File

@@ -1,81 +0,0 @@
[["Journal Entry"]]
cheque_date = "2013-03-14"
cheque_no = "33"
company = "_Test Company"
naming_series = "_T-Journal Entry-"
posting_date = "2013-02-14"
user_remark = "test"
voucher_type = "Bank Entry"
[["Journal Entry".accounts]]
account = "Debtors - _TC"
party_type = "Customer"
party = "_Test Customer"
credit_in_account_currency = 400.0
debit_in_account_currency = 0.0
doctype = "Journal Entry Account"
parentfield = "accounts"
cost_center = "_Test Cost Center - _TC"
[["Journal Entry".accounts]]
account = "_Test Bank - _TC"
credit_in_account_currency = 0.0
debit_in_account_currency = 400.0
doctype = "Journal Entry Account"
parentfield = "accounts"
cost_center = "_Test Cost Center - _TC"
[["Journal Entry"]]
cheque_date = "2013-02-14"
cheque_no = "33"
company = "_Test Company"
naming_series = "_T-Journal Entry-"
posting_date = "2013-02-14"
user_remark = "test"
voucher_type = "Bank Entry"
[["Journal Entry".accounts]]
account = "_Test Payable - _TC"
party_type = "Supplier"
party = "_Test Supplier"
credit_in_account_currency = 0.0
debit_in_account_currency = 400.0
doctype = "Journal Entry Account"
parentfield = "accounts"
cost_center = "_Test Cost Center - _TC"
[["Journal Entry".accounts]]
account = "_Test Bank - _TC"
credit_in_account_currency = 400.0
debit_in_account_currency = 0.0
doctype = "Journal Entry Account"
parentfield = "accounts"
cost_center = "_Test Cost Center - _TC"
[["Journal Entry"]]
cheque_date = "2013-02-14"
cheque_no = "33"
company = "_Test Company"
naming_series = "_T-Journal Entry-"
posting_date = "2013-02-14"
user_remark = "test"
voucher_type = "Bank Entry"
[["Journal Entry".accounts]]
account = "Debtors - _TC"
party_type = "Customer"
party = "_Test Customer"
credit_in_account_currency = 0.0
debit_in_account_currency = 400.0
doctype = "Journal Entry Account"
parentfield = "accounts"
cost_center = "_Test Cost Center - _TC"
[["Journal Entry".accounts]]
account = "Sales - _TC"
credit_in_account_currency = 400.0
debit_in_account_currency = 0.0
doctype = "Journal Entry Account"
parentfield = "accounts"
cost_center = "_Test Cost Center - _TC"

View File

@@ -0,0 +1,44 @@
[{
"doctype": "Monthly Distribution",
"distribution_id": "_Test Distribution",
"fiscal_year": "_Test Fiscal Year 2013",
"percentages": [
{
"month": "January",
"percentage_allocation": "8"
}, {
"month": "February",
"percentage_allocation": "8"
}, {
"month": "March",
"percentage_allocation": "8"
}, {
"month": "April",
"percentage_allocation": "8"
}, {
"month": "May",
"percentage_allocation": "8"
}, {
"month": "June",
"percentage_allocation": "8"
}, {
"month": "July",
"percentage_allocation": "8"
}, {
"month": "August",
"percentage_allocation": "8"
}, {
"month": "September",
"percentage_allocation": "8"
}, {
"month": "October",
"percentage_allocation": "8"
}, {
"month": "November",
"percentage_allocation": "10"
}, {
"month": "December",
"percentage_allocation": "10"
}
]
}]

View File

@@ -1,52 +0,0 @@
[["Monthly Distribution"]]
distribution_id = "_Test Distribution"
fiscal_year = "_Test Fiscal Year 2013"
[["Monthly Distribution".percentages]]
month = "January"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "February"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "March"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "April"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "May"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "June"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "July"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "August"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "September"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "October"
percentage_allocation = "8"
[["Monthly Distribution".percentages]]
month = "November"
percentage_allocation = "10"
[["Monthly Distribution".percentages]]
month = "December"
percentage_allocation = "10"

View File

@@ -258,6 +258,10 @@ frappe.ui.form.on("Payment Entry", {
frappe.flags.allocate_payment_amount = true;
},
validate: async function (frm) {
await frm.events.set_exchange_gain_loss_deduction(frm);
},
validate_company: (frm) => {
if (!frm.doc.company) {
frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") });
@@ -1837,8 +1841,6 @@ function prompt_for_missing_account(frm, account) {
(values) => resolve(values?.[account]),
__("Please Specify Account")
);
dialog.on_hide = () => resolve("");
});
}

View File

@@ -200,14 +200,14 @@
"fieldtype": "Column Break"
},
{
"depends_on": "party",
"depends_on": "eval: doc.party && doc.party_type !== \"Employee\"",
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact",
"options": "Contact"
},
{
"depends_on": "contact_person",
"depends_on": "eval: (doc.contact_person || doc.party_type === \"Employee\") && doc.contact_email",
"fieldname": "contact_email",
"fieldtype": "Data",
"label": "Email",
@@ -777,7 +777,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2025-01-31 11:24:58.076393",
"modified": "2025-03-24 16:18:19.920701",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -7,6 +7,7 @@ from functools import reduce
import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe.model.meta import get_field_precision
from frappe.query_builder import Tuple
from frappe.query_builder.functions import Count
from frappe.utils import cint, comma_or, flt, getdate, nowdate
@@ -37,7 +38,11 @@ from erpnext.accounts.general_ledger import (
make_reverse_gl_entries,
process_gl_map,
)
from erpnext.accounts.party import complete_contact_details, get_party_account, set_contact_details
from erpnext.accounts.party import (
complete_contact_details,
get_default_contact,
get_party_account,
)
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
@@ -334,16 +339,18 @@ class PaymentEntry(AccountsController):
reference_names.add(key)
def set_bank_account_data(self):
if self.bank_account:
bank_data = get_bank_account_details(self.bank_account)
if not self.bank_account:
return
field = "paid_from" if self.payment_type == "Pay" else "paid_to"
bank_data = get_bank_account_details(self.bank_account)
self.bank = bank_data.bank
self.bank_account_no = bank_data.bank_account_no
field = "paid_from" if self.payment_type == "Pay" else "paid_to"
if not self.get(field):
self.set(field, bank_data.account)
self.bank = bank_data.bank
self.bank_account_no = bank_data.bank_account_no
if not self.get(field):
self.set(field, bank_data.account)
def validate_payment_type_with_outstanding(self):
total_outstanding = sum(d.allocated_amount for d in self.get("references"))
@@ -361,15 +368,16 @@ class PaymentEntry(AccountsController):
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
return
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_as_per_payment_request(self):
"""
@@ -407,91 +415,89 @@ class PaymentEntry(AccountsController):
return False
def validate_allocated_amount_with_latest_data(self):
if self.references:
uniq_vouchers = set([(x.reference_doctype, x.reference_name) for x in self.references])
vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers]
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
"vouchers": vouchers,
"book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account,
},
validate=True,
)
if not self.references:
return
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
uniq_vouchers = {(x.reference_doctype, x.reference_name) for x in self.references}
vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers]
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
"vouchers": vouchers,
"book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account,
},
validate=True,
)
for idx, d in enumerate(self.get("references"), start=1):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
).format(frappe.bold(d.reference_name), frappe.bold(idx))
)
for idx, d in enumerate(self.get("references"), start=1):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
latest = latest.get(d.payment_term) or latest.get(None)
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(
_(d.reference_doctype), d.reference_name
)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
and d.payment_term == ""
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(_(d.reference_doctype), d.reference_name)
)
# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
).format(frappe.bold(d.reference_name), frappe.bold(idx))
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
latest = latest.get(d.payment_term) or latest.get(None)
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
and d.payment_term == ""
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(_(d.reference_doctype), d.reference_name)
)
if (
d.payment_term
and (
(flt(d.allocated_amount)) > 0
and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
)
and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
)
):
frappe.throw(
_(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
if (
d.payment_term
and (
(flt(d.allocated_amount)) > 0
and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
)
and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
):
frappe.throw(
_(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
)
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self):
for reference in self.references:
@@ -524,25 +530,27 @@ class PaymentEntry(AccountsController):
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
if not self.contact_person:
set_contact_details(
self, party=frappe._dict({"name": self.party}), party_type=self.party_type
)
else:
complete_contact_details(self)
if self.party_type == "Employee":
self.contact_person = None
elif not self.contact_person:
self.contact_person = get_default_contact(self.party_type, self.party)
complete_contact_details(self)
if not self.party_account:
party_account = get_party_account(self.party_type, self.party, self.company)
self.set(self.party_account_field, party_account)
self.party_account = party_account
if self.paid_from and not self.paid_from_account_currency:
if self.paid_from and (not self.paid_from_account_currency 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_type = acc.account_type
if self.paid_to and not self.paid_to_account_currency:
if self.paid_to and (not self.paid_to_account_currency 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_type = acc.account_type
self.party_account_currency = (
self.paid_from_account_currency
@@ -557,51 +565,52 @@ class PaymentEntry(AccountsController):
reference_exchange_details: dict | None = None,
) -> None:
for d in self.get("references"):
if d.allocated_amount:
if (
update_ref_details_only_for
and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
):
if not d.allocated_amount:
continue
if (
update_ref_details_only_for
and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
):
continue
ref_details = get_reference_details(
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
)
# Only update exchange rate when the reference is Journal Entry
if (
reference_exchange_details
and d.reference_doctype == reference_exchange_details.reference_doctype
and d.reference_name == reference_exchange_details.reference_name
):
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
# for cases where gain/loss is booked into invoice
# exchange_gain_loss is calculated from invoice & populated
# and row.exchange_rate is already set to payment entry's exchange rate
# refer -> `update_reference_in_payment_entry()` in utils.py
continue
ref_details = get_reference_details(
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
)
# Only update exchange rate when the reference is Journal Entry
if (
reference_exchange_details
and d.reference_doctype == reference_exchange_details.reference_doctype
and d.reference_name == reference_exchange_details.reference_name
):
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
# for cases where gain/loss is booked into invoice
# exchange_gain_loss is calculated from invoice & populated
# and row.exchange_rate is already set to payment entry's exchange rate
# refer -> `update_reference_in_payment_entry()` in utils.py
continue
if field == "exchange_rate" or not d.get(field) or force:
if self.get("_action") in ("submit", "cancel"):
d.db_set(field, value)
else:
d.set(field, value)
if field == "exchange_rate" or not d.get(field) or force:
if self.get("_action") in ("submit", "cancel"):
d.db_set(field, value)
else:
d.set(field, value)
def validate_payment_type(self):
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
frappe.throw(_("Payment Type must be one of Receive, Pay and Internal Transfer"))
def validate_party_details(self):
if self.party:
if not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
if self.party and not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
@@ -611,12 +620,8 @@ class PaymentEntry(AccountsController):
if self.paid_from:
if self.paid_from_account_currency == self.company_currency:
self.source_exchange_rate = 1
else:
if ref_doc:
if self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get(
"conversion_rate"
)
elif ref_doc and self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if not self.source_exchange_rate:
self.source_exchange_rate = get_exchange_rate(
@@ -627,9 +632,8 @@ class PaymentEntry(AccountsController):
if self.paid_from_account_currency == self.paid_to_account_currency:
self.target_exchange_rate = self.source_exchange_rate
elif self.paid_to and not self.target_exchange_rate:
if ref_doc:
if self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if ref_doc and self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if not self.target_exchange_rate:
self.target_exchange_rate = get_exchange_rate(
@@ -660,63 +664,61 @@ class PaymentEntry(AccountsController):
elif d.reference_name:
if not frappe.db.exists(d.reference_doctype, d.reference_name):
frappe.throw(_("{0} {1} does not exist").format(d.reference_doctype, d.reference_name))
else:
ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name)
if d.reference_doctype != "Journal Entry":
if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw(
_("{0} {1} is not associated with {2} {3}").format(
_(d.reference_doctype), d.reference_name, _(self.party_type), self.party
)
)
else:
self.validate_journal_entry()
ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name)
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name)
or ref_doc.debit_to
)
elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
if (
ref_party_account != self.party_account
and not self.book_advance_payments_in_separate_party_account
):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
_(d.reference_doctype),
d.reference_name,
ref_party_account,
self.party_account,
)
)
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw(
_("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
title=_("Invalid Purchase Invoice"),
)
if ref_doc.docstatus != 1:
if d.reference_doctype != "Journal Entry":
if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw(
_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name)
_("{0} {1} is not associated with {2} {3}").format(
_(d.reference_doctype), d.reference_name, _(self.party_type), self.party
)
)
else:
self.validate_journal_entry()
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name)
or ref_doc.debit_to
)
elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
if (
ref_party_account != self.party_account
and not self.book_advance_payments_in_separate_party_account
):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
_(d.reference_doctype),
d.reference_name,
ref_party_account,
self.party_account,
)
)
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw(
_("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
title=_("Invalid Purchase Invoice"),
)
if ref_doc.docstatus != 1:
frappe.throw(
_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name)
)
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
elif self.party_type in ["Shareholder", "Employee"]:
return ("Journal Entry",)
elif self.party_type == "Supplier":
return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
return ("Journal Entry",)
def validate_paid_invoices(self):
no_oustanding_refs = {}
@@ -782,37 +784,39 @@ class PaymentEntry(AccountsController):
invoice_paid_amount_map = {}
for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
if not ref.payment_term or not ref.reference_name:
continue
if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
if not (term.discount_type and term.discount):
continue
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
if not (term.discount_type and term.discount):
continue
if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1):
if not invoice_paid_amount_map.get(key):
@@ -825,16 +829,39 @@ class PaymentEntry(AccountsController):
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
conversion_rate = frappe.db.get_value(key[2], {"name": key[1]}, "conversion_rate")
base_paid_amount_precision = get_field_precision(
frappe.get_meta("Payment Schedule").get_field("base_paid_amount")
)
base_outstanding_precision = get_field_precision(
frappe.get_meta("Payment Schedule").get_field("base_outstanding")
)
base_paid_amount = flt(
(allocated_amount - discounted_amt) * conversion_rate, base_paid_amount_precision
)
base_outstanding = flt(allocated_amount * conversion_rate, base_outstanding_precision)
if cancel:
frappe.db.sql(
"""
UPDATE `tabPayment Schedule`
SET
paid_amount = `paid_amount` - %s,
base_paid_amount = `base_paid_amount` - %s,
discounted_amount = `discounted_amount` - %s,
outstanding = `outstanding` + %s
outstanding = `outstanding` + %s,
base_outstanding = `base_outstanding` - %s
WHERE parent = %s and payment_term = %s""",
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
(
allocated_amount - discounted_amt,
base_paid_amount,
discounted_amt,
allocated_amount,
base_outstanding,
key[1],
key[0],
),
)
else:
if allocated_amount > outstanding:
@@ -850,10 +877,20 @@ class PaymentEntry(AccountsController):
UPDATE `tabPayment Schedule`
SET
paid_amount = `paid_amount` + %s,
base_paid_amount = `base_paid_amount` + %s,
discounted_amount = `discounted_amount` + %s,
outstanding = `outstanding` - %s
outstanding = `outstanding` - %s,
base_outstanding = `base_outstanding` - %s
WHERE parent = %s and payment_term = %s""",
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
(
allocated_amount - discounted_amt,
base_paid_amount,
discounted_amt,
allocated_amount,
base_outstanding,
key[1],
key[0],
),
)
def get_allocated_amount_in_transaction_currency(
@@ -1026,14 +1063,14 @@ class PaymentEntry(AccountsController):
applicable_tax = 0
base_applicable_tax = 0
for tax in self.get("taxes"):
if not tax.included_in_paid_amount:
amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount
base_amount = (
-1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount
)
if tax.included_in_paid_amount:
continue
applicable_tax += amount
base_applicable_tax += base_amount
amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount
base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount
applicable_tax += amount
base_applicable_tax += base_amount
self.paid_amount_after_tax = flt(
flt(self.paid_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax")
@@ -1311,15 +1348,22 @@ class PaymentEntry(AccountsController):
self.set("remarks", "\n".join(remarks))
def set_transaction_currency_and_rate(self):
company_currency = erpnext.get_company_currency(self.company)
self.transaction_currency = company_currency
self.transaction_exchange_rate = 1
if self.paid_from_account_currency != company_currency:
self.transaction_currency = self.paid_from_account_currency
self.transaction_exchange_rate = self.source_exchange_rate
elif self.paid_to_account_currency != company_currency:
self.transaction_currency = self.paid_to_account_currency
self.transaction_exchange_rate = self.target_exchange_rate
def build_gl_map(self):
if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
self.setup_party_account_field()
company_currency = erpnext.get_company_currency(self.company)
if self.paid_from_account_currency != company_currency:
self.currency = self.paid_from_account_currency
elif self.paid_to_account_currency != company_currency:
self.currency = self.paid_to_account_currency
self.set_transaction_currency_and_rate()
gl_entries = []
self.add_party_gl_entries(gl_entries)
@@ -1400,6 +1444,9 @@ class PaymentEntry(AccountsController):
"cost_center": cost_center,
dr_or_cr + "_in_account_currency": d.allocated_amount,
dr_or_cr: allocated_amount_in_company_currency,
dr_or_cr + "_in_transaction_currency": d.allocated_amount
if self.transaction_currency == self.party_account_currency
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
},
item=self,
)
@@ -1444,6 +1491,9 @@ class PaymentEntry(AccountsController):
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr + "_in_transaction_currency": self.unallocated_amount
if self.party_account_currency == self.transaction_currency
else base_unallocated_amount / self.transaction_exchange_rate,
dr_or_cr: base_unallocated_amount,
},
item=self,
@@ -1461,6 +1511,7 @@ class PaymentEntry(AccountsController):
def make_advance_gl_entries(
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
):
self.set_transaction_currency_and_rate()
gl_entries = []
self.add_advance_gl_entries(gl_entries, entry)
@@ -1540,9 +1591,16 @@ class PaymentEntry(AccountsController):
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
base_allocated_amount = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict["account"] = account
args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict[dr_or_cr] = base_allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_transaction_currency"] = (
invoice.allocated_amount
if self.party_account_currency == self.transaction_currency
else base_allocated_amount / self.transaction_exchange_rate
)
args_dict.update(
{
"against_voucher_type": invoice.reference_doctype,
@@ -1560,8 +1618,13 @@ class PaymentEntry(AccountsController):
args_dict[dr_or_cr + "_in_account_currency"] = 0
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
args_dict["account"] = self.party_account
args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict[dr_or_cr] = base_allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_transaction_currency"] = (
invoice.allocated_amount
if self.party_account_currency == self.transaction_currency
else base_allocated_amount / self.transaction_exchange_rate
)
args_dict.update(
{
"against_voucher_type": "Payment Entry",
@@ -1583,6 +1646,9 @@ class PaymentEntry(AccountsController):
"account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type == "Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount,
"credit_in_transaction_currency": self.paid_amount
if self.paid_from_account_currency == self.transaction_currency
else self.base_paid_amount / self.transaction_exchange_rate,
"credit": self.base_paid_amount,
"cost_center": self.cost_center,
"post_net_value": True,
@@ -1598,6 +1664,9 @@ class PaymentEntry(AccountsController):
"account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type == "Receive" else self.paid_from,
"debit_in_account_currency": self.received_amount,
"debit_in_transaction_currency": self.received_amount
if self.paid_to_account_currency == self.transaction_currency
else self.base_received_amount / self.transaction_exchange_rate,
"debit": self.base_received_amount,
"cost_center": self.cost_center,
},
@@ -1633,6 +1702,8 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency
else d.tax_amount,
dr_or_cr + "_in_transaction_currency": base_tax_amount
/ self.transaction_exchange_rate,
"cost_center": d.cost_center,
"post_net_value": True,
},
@@ -1658,6 +1729,8 @@ class PaymentEntry(AccountsController):
rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency
else d.tax_amount,
rev_dr_or_cr + "_in_transaction_currency": base_tax_amount
/ self.transaction_exchange_rate,
"cost_center": self.cost_center,
"post_net_value": True,
},
@@ -1668,24 +1741,27 @@ class PaymentEntry(AccountsController):
def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"):
if d.amount:
account_currency = get_account_currency(d.account)
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
if not d.amount:
continue
gl_entries.append(
self.get_gl_dict(
{
"account": d.account,
"account_currency": account_currency,
"against": self.party or self.paid_from,
"debit_in_account_currency": d.amount,
"debit": d.amount,
"cost_center": d.cost_center,
},
item=d,
)
account_currency = get_account_currency(d.account)
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
gl_entries.append(
self.get_gl_dict(
{
"account": d.account,
"account_currency": account_currency,
"against": self.party or self.paid_from,
"debit_in_account_currency": d.amount,
"debit_in_transaction_currency": d.amount / self.transaction_exchange_rate,
"debit": d.amount,
"cost_center": d.cost_center,
},
item=d,
)
)
def get_party_account_for_taxes(self):
if self.payment_type == "Receive":
@@ -1702,15 +1778,17 @@ class PaymentEntry(AccountsController):
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
if self.payment_type not in ("Receive", "Pay") or not self.party:
return
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name
@@ -1942,7 +2020,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(
@@ -2950,7 +3028,9 @@ 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")
pe.bank_account = frappe.db.get_value(
"Bank Account", {"is_company_account": 1, "is_default": 1, "company": doc.company}, "name"
)
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
pe.project = doc.get("project") or reduce(

View File

@@ -58,6 +58,8 @@ class TestPaymentEntry(IntegrationTestCase):
pe.insert()
pe.submit()
self.assertEqual(pe.paid_to_account_type, "Cash")
expected_gle = dict(
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
)
@@ -569,6 +571,8 @@ class TestPaymentEntry(IntegrationTestCase):
pe.insert()
pe.submit()
self.assertEqual(pe.paid_from_account_type, "Bank")
outstanding_amount, status = frappe.db.get_value(
"Purchase Invoice", pi.name, ["outstanding_amount", "status"]
)

View File

@@ -1,9 +1,9 @@
import json
import frappe
from frappe import _, qb
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Abs, Sum
from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate
from frappe.utils.background_jobs import enqueue
@@ -12,7 +12,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_company_defaults,
get_payment_entry,
)
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
@@ -122,16 +121,14 @@ class PaymentRequest(Document):
title=_("Invalid Amount"),
)
existing_payment_request_amount = flt(
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
)
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
if not ref_amount:
frappe.throw(_("Payment Entry is already created"))
existing_payment_request_amount = flt(get_existing_payment_request_amount(ref_doc))
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
frappe.throw(
_("Total Payment Request amount cannot be greater than {0} amount").format(
@@ -554,19 +551,8 @@ def make_payment_request(**args):
ref_doc.db_update()
grand_total = grand_total - loyalty_amount
bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else ""
)
draft_payment_request = frappe.db.get_value(
"Payment Request",
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
)
# fetches existing payment request `grand_total` amount
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
existing_paid_amount = get_existing_paid_amount(ref_doc.doctype, ref_doc.name)
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount):
grand_total -= existing_payment_request_amount
@@ -578,7 +564,7 @@ def make_payment_request(**args):
if args.order_type == "Shopping Cart":
# If Payment Request is in an advanced stage, then create for remaining amount.
if get_existing_payment_request_amount(
ref_doc.doctype, ref_doc.name, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"]
ref_doc, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"]
):
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
else:
@@ -587,14 +573,10 @@ def make_payment_request(**args):
else:
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
if existing_paid_amount:
if ref_doc.party_account_currency == ref_doc.currency:
if ref_doc.conversion_rate:
grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate)
else:
grand_total -= flt(existing_paid_amount)
else:
grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate)
draft_payment_request = frappe.db.get_value(
"Payment Request",
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
)
if draft_payment_request:
frappe.db.set_value(
@@ -602,6 +584,11 @@ def make_payment_request(**args):
)
pr = frappe.get_doc("Payment Request", draft_payment_request)
else:
bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party"))
if args.get("party_type")
else ""
)
pr = frappe.new_doc("Payment Request")
if not args.get("payment_request_type"):
@@ -681,22 +668,35 @@ def make_payment_request(**args):
def get_amount(ref_doc, payment_account=None):
"""get amount based on doctype"""
grand_total = 0
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - ref_doc.advance_paid
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
if (
dt == "Sales Invoice"
and ref_doc.is_pos
and ref_doc.payments
and any(
[
payment.type == "Phone" and payment.account == payment_account
for payment in ref_doc.payments
]
)
):
grand_total = sum(
[
payment.amount
for payment in ref_doc.payments
if payment.type == "Phone" and payment.account == payment_account
]
)
else:
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total)
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(
flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate
)
elif dt == "Sales Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
grand_total = pay.amount
break
grand_total = flt(flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate)
elif dt == "POS Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
@@ -705,10 +705,7 @@ def get_amount(ref_doc, payment_account=None):
elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
if grand_total > 0:
return flt(grand_total, get_currency_precision())
else:
frappe.throw(_("Payment Entry is already created"))
return flt(grand_total, get_currency_precision()) if grand_total > 0 else 0
def get_irequest_status(payment_requests: None | list = None) -> list:
@@ -751,7 +748,7 @@ def cancel_old_payment_requests(ref_dt, ref_dn):
frappe.db.set_value("Integration Request", ireq.name, "status", "Cancelled")
def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = None) -> list:
def get_existing_payment_request_amount(ref_doc, statuses: list | None = None) -> list:
"""
Return the total amount of Payment Requests against a reference document.
"""
@@ -759,9 +756,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
query = (
frappe.qb.from_(PR)
.select(Sum(PR.grand_total))
.where(PR.reference_doctype == ref_dt)
.where(PR.reference_name == ref_dn)
.select(Sum(PR.outstanding_amount))
.where(PR.reference_doctype == ref_doc.doctype)
.where(PR.reference_name == ref_doc.name)
.where(PR.docstatus == 1)
)
@@ -770,33 +767,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
response = query.run()
return response[0][0] if response[0] else 0
os_amount_in_transaction_currency = flt(response[0][0] if response[0] else 0)
if ref_doc.currency != ref_doc.party_account_currency:
os_amount_in_transaction_currency = flt(os_amount_in_transaction_currency / ref_doc.conversion_rate)
def get_existing_paid_amount(doctype, name):
PL = frappe.qb.DocType("Payment Ledger Entry")
PER = frappe.qb.DocType("Payment Entry Reference")
query = (
frappe.qb.from_(PL)
.left_join(PER)
.on(
(PL.against_voucher_type == PER.reference_doctype)
& (PL.against_voucher_no == PER.reference_name)
& (PL.voucher_type == PER.parenttype)
& (PL.voucher_no == PER.parent)
)
.select(Abs(Sum(PL.amount)).as_("total_paid_amount"))
.where(PL.against_voucher_type.eq(doctype))
.where(PL.against_voucher_no.eq(name))
.where(PL.amount < 0)
.where(PL.delinked == 0)
.where(PER.docstatus == 1)
.where(PER.payment_request.isnull())
)
response = query.run()
return response[0][0] if response[0] else 0
return os_amount_in_transaction_currency
def get_gateway_details(args): # nosemgrep

View File

@@ -465,6 +465,16 @@ class TestPaymentRequest(IntegrationTestCase):
self.assertEqual(pr.outstanding_amount, 800)
self.assertEqual(pr.grand_total, 1000)
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
make_payment_request,
dt="Sales Order",
dn=so.name,
mute_email=1,
submit_doc=1,
return_doc=1,
)
# complete payment
pe = pr.create_payment_entry()
@@ -484,7 +494,7 @@ class TestPaymentRequest(IntegrationTestCase):
# creating a more payment Request must not allowed
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
re.compile(r"Payment Entry is already created"),
make_payment_request,
dt="Sales Order",
dn=so.name,
@@ -516,6 +526,17 @@ class TestPaymentRequest(IntegrationTestCase):
self.assertEqual(pr.party_account_currency, "INR")
self.assertEqual(pr.status, "Initiated")
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
make_payment_request,
dt="Purchase Invoice",
dn=pi.name,
mute_email=1,
submit_doc=1,
return_doc=1,
)
# to make partial payment
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 2000
@@ -544,7 +565,7 @@ class TestPaymentRequest(IntegrationTestCase):
# creating a more payment Request must not allowed
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
re.compile(r"Payment Entry is already created"),
make_payment_request,
dt="Purchase Invoice",
dn=pi.name,
@@ -748,6 +769,34 @@ class TestPaymentRequest(IntegrationTestCase):
pi.load_from_db()
self.assertEqual(pr_2.grand_total, pi.outstanding_amount)
def test_consider_journal_entry_and_return_invoice(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
si = create_sales_invoice(currency="INR", qty=5, rate=500)
je = make_journal_entry("_Test Cash - _TC", "Debtors - _TC", 500, save=False)
je.accounts[1].party_type = "Customer"
je.accounts[1].party = si.customer
je.accounts[1].reference_type = "Sales Invoice"
je.accounts[1].reference_name = si.name
je.accounts[1].credit_in_account_currency = 500
je.submit()
pe = get_payment_entry("Sales Invoice", si.name)
pe.paid_amount = 500
pe.references[0].allocated_amount = 500
pe.save()
pe.submit()
cr_note = create_sales_invoice(qty=-1, rate=500, is_return=1, return_against=si.name, do_not_save=1)
cr_note.update_outstanding_for_self = 0
cr_note.save()
cr_note.submit()
si.load_from_db()
pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1)
self.assertEqual(pr.grand_total, si.outstanding_amount)
def test_partial_paid_invoice_with_submitted_payment_entry(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)

View File

@@ -24,7 +24,9 @@
"paid_amount",
"discounted_amount",
"column_break_3",
"base_payment_amount"
"base_payment_amount",
"base_outstanding",
"base_paid_amount"
],
"fields": [
{
@@ -155,18 +157,34 @@
"fieldtype": "Currency",
"label": "Payment Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fieldname": "base_outstanding",
"fieldtype": "Currency",
"label": "Outstanding (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"depends_on": "base_paid_amount",
"fieldname": "base_paid_amount",
"fieldtype": "Currency",
"label": "Paid Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:11.356171",
"modified": "2025-03-11 11:06:51.792982",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],

View File

@@ -14,6 +14,8 @@ class PaymentSchedule(Document):
if TYPE_CHECKING:
from frappe.types import DF
base_outstanding: DF.Currency
base_paid_amount: DF.Currency
base_payment_amount: DF.Currency
description: DF.SmallText | None
discount: DF.Float

View File

@@ -0,0 +1,34 @@
[
{
"doctype":"Payment Term",
"due_date_based_on":"Day(s) after invoice date",
"payment_term_name":"_Test N30",
"description":"_Test Net 30 Days",
"invoice_portion":50,
"credit_days":30
},
{
"doctype":"Payment Term",
"due_date_based_on":"Day(s) after invoice date",
"payment_term_name":"_Test COD",
"description":"_Test Cash on Delivery",
"invoice_portion":50,
"credit_days":0
},
{
"doctype":"Payment Term",
"due_date_based_on":"Month(s) after the end of the invoice month",
"payment_term_name":"_Test EONM",
"description":"_Test End of Next Month",
"invoice_portion":100,
"credit_months":1
},
{
"doctype":"Payment Term",
"due_date_based_on":"Day(s) after invoice date",
"payment_term_name":"_Test N30 1",
"description":"_Test Net 30 Days",
"invoice_portion":100,
"credit_days":30
}
]

View File

@@ -1,28 +0,0 @@
[["Payment Term"]]
due_date_based_on = "Day(s) after invoice date"
payment_term_name = "_Test N30"
description = "_Test Net 30 Days"
invoice_portion = 50
credit_days = 30
[["Payment Term"]]
due_date_based_on = "Day(s) after invoice date"
payment_term_name = "_Test COD"
description = "_Test Cash on Delivery"
invoice_portion = 50
credit_days = 0
[["Payment Term"]]
due_date_based_on = "Month(s) after the end of the invoice month"
payment_term_name = "_Test EONM"
description = "_Test End of Next Month"
invoice_portion = 100
credit_months = 1
[["Payment Term"]]
due_date_based_on = "Day(s) after invoice date"
payment_term_name = "_Test N30 1"
description = "_Test Net 30 Days"
invoice_portion = 100
credit_days = 30

View File

@@ -0,0 +1,60 @@
[
{
"doctype":"Payment Terms Template",
"terms":[
{
"doctype":"Payment Terms Template Detail",
"due_date_based_on":"Day(s) after invoice date",
"idx":1,
"description":"Cash on Delivery",
"invoice_portion":50,
"credit_days":0,
"credit_months":0,
"payment_term":"_Test COD"
},
{
"doctype":"Payment Terms Template Detail",
"due_date_based_on":"Day(s) after invoice date",
"idx":2,
"description":"Net 30 Days ",
"invoice_portion":50,
"credit_days":30,
"credit_months":0,
"payment_term":"_Test N30"
}
],
"template_name":"_Test Payment Term Template"
},
{
"doctype":"Payment Terms Template",
"terms":[
{
"doctype":"Payment Terms Template Detail",
"due_date_based_on":"Month(s) after the end of the invoice month",
"idx":1,
"description":"_Test End of Next Months",
"invoice_portion":100,
"credit_days":0,
"credit_months":1,
"payment_term":"_Test EONM"
}
],
"template_name":"_Test Payment Term Template 1"
},
{
"doctype":"Payment Terms Template",
"terms":[
{
"doctype":"Payment Terms Template Detail",
"due_date_based_on":"Day(s) after invoice date",
"idx":1,
"description":"_Test Net Within 30 days",
"invoice_portion":100,
"credit_days":30,
"credit_months":0,
"payment_term":"_Test N30 1"
}
],
"template_name":"_Test Payment Term Template 3"
}
]

View File

@@ -1,49 +0,0 @@
[["Payment Terms Template"]]
template_name = "_Test Payment Term Template"
[["Payment Terms Template".terms]]
doctype = "Payment Terms Template Detail"
due_date_based_on = "Day(s) after invoice date"
idx = 1
description = "Cash on Delivery"
invoice_portion = 50
credit_days = 0
credit_months = 0
payment_term = "_Test COD"
[["Payment Terms Template".terms]]
doctype = "Payment Terms Template Detail"
due_date_based_on = "Day(s) after invoice date"
idx = 2
description = "Net 30 Days "
invoice_portion = 50
credit_days = 30
credit_months = 0
payment_term = "_Test N30"
[["Payment Terms Template"]]
template_name = "_Test Payment Term Template 1"
[["Payment Terms Template".terms]]
doctype = "Payment Terms Template Detail"
due_date_based_on = "Month(s) after the end of the invoice month"
idx = 1
description = "_Test End of Next Months"
invoice_portion = 100
credit_days = 0
credit_months = 1
payment_term = "_Test EONM"
[["Payment Terms Template"]]
template_name = "_Test Payment Term Template 3"
[["Payment Terms Template".terms]]
doctype = "Payment Terms Template Detail"
due_date_based_on = "Day(s) after invoice date"
idx = 1
description = "_Test Net Within 30 days"
invoice_portion = 100
credit_days = 30
credit_months = 0
payment_term = "_Test N30 1"

View File

@@ -139,7 +139,7 @@ class PeriodClosingVoucher(AccountsController):
self.cancel_gl_entries()
def make_gl_entries(self):
if self.get_gle_count_in_selected_period() > 5000:
if frappe.db.estimate_count("GL Entry") > 100_000:
frappe.enqueue(
process_gl_and_closing_entries,
doc=self,
@@ -154,16 +154,6 @@ class PeriodClosingVoucher(AccountsController):
else:
process_gl_and_closing_entries(self)
def get_gle_count_in_selected_period(self):
return frappe.db.count(
"GL Entry",
{
"posting_date": ["between", [self.period_start_date, self.period_end_date]],
"company": self.company,
"is_cancelled": 0,
},
)
def get_pcv_gl_entries(self):
self.pl_accounts_reverse_gle = []
self.closing_account_gle = []

View File

@@ -26,6 +26,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
company=company,
save=False,
)
jv1.company = company
@@ -38,6 +39,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
account1="Cost of Goods Sold - TPC",
account2="Cash - TPC",
cost_center=cost_center,
company=company,
save=False,
)
jv2.company = company
@@ -155,6 +157,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
amount=400,
cost_center=cost_center,
posting_date="2021-03-15",
company=company,
)
jv.company = company
jv.finance_book = create_finance_book().name
@@ -197,6 +200,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
company=company,
save=False,
)
jv1.company = company
@@ -219,6 +223,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center1,
company=company,
save=False,
)
jv1.company = company
@@ -231,6 +236,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
company=company,
save=False,
)
jv2.company = company
@@ -260,6 +266,7 @@ class TestPeriodClosingVoucher(IntegrationTestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
company=company,
save=False,
)

View File

@@ -124,6 +124,11 @@ class POSClosingEntry(StatusUpdater):
def on_submit(self):
consolidate_pos_invoices(closing_entry=self)
frappe.publish_realtime(
f"poe_{self.pos_opening_entry}_closed",
self,
docname=f"POS Opening Entry/{self.pos_opening_entry}",
)
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)

View File

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

View File

@@ -196,6 +196,7 @@ class POSInvoice(SalesInvoice):
# run on validate method of selling controller
super(SalesInvoice, self).validate()
self.validate_pos_opening_entry()
self.validate_auto_set_posting_time()
self.validate_mode_of_payment()
self.validate_uom_is_integer("stock_uom", "stock_qty")
@@ -327,6 +328,18 @@ class POSInvoice(SalesInvoice):
_("Payment related to {0} is not completed").format(pay.mode_of_payment)
)
def validate_pos_opening_entry(self):
opening_entries = frappe.get_list(
"POS Opening Entry", filters={"pos_profile": self.pos_profile, "status": "Open", "docstatus": 1}
)
if len(opening_entries) == 0:
frappe.throw(
title=_("POS Opening Entry Missing"),
msg=_("No open POS Opening Entry found for POS Profile {0}.").format(
frappe.bold(self.pos_profile)
),
)
def validate_stock_availablility(self):
if self.is_return:
return

View File

@@ -28,6 +28,12 @@ class TestPOSInvoice(IntegrationTestCase):
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`")
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
cls.test_user, cls.pos_profile = init_user_and_profile()
create_opening_entry(cls.pos_profile, cls.test_user)
def tearDown(self):
if frappe.session.user != "Administrator":
frappe.set_user("Administrator")

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc
from frappe.query_builder import DocType
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
from frappe.utils.background_jobs import enqueue, is_job_enqueued
from frappe.utils.scheduler import is_scheduler_inactive
@@ -119,17 +120,18 @@ class POSInvoiceMergeLog(Document):
returns = [d for d in pos_invoice_docs if d.get("is_return") == 1]
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
sales_invoice, credit_note = "", ""
sales_invoice, credit_notes = "", {}
sales_invoice_doc = None
if sales:
sales_invoice_doc = self.process_merging_into_sales_invoice(sales)
sales_invoice = sales_invoice_doc.name
if returns:
credit_note = self.process_merging_into_credit_note(returns, sales_invoice_doc)
distinguished_returns = self.distinguish_return_pos_invoices(returns, sales_invoice_doc)
credit_notes = self.process_merging_into_credit_notes(distinguished_returns)
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_notes)
def on_cancel(self):
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
@@ -159,34 +161,50 @@ class POSInvoiceMergeLog(Document):
return sales_invoice
def process_merging_into_credit_note(self, data, sales_invoice_doc=None):
credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1
def process_merging_into_credit_notes(self, data):
credit_notes = {}
for key, value in data.items():
if not value:
continue
credit_note = self.merge_pos_invoice_into(credit_note, data)
referenes = {}
credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1
if sales_invoice_doc:
credit_note.return_against = sales_invoice_doc.name
credit_note = self.merge_pos_invoice_into(credit_note, value)
credit_note.return_against = key
for d in sales_invoice_doc.items:
referenes[d.item_code] = d.name
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date)
credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
for d in credit_note.items:
d.sales_invoice_item = referenes.get(d.item_code)
self.consolidated_credit_note = credit_note.name
credit_notes[credit_note.name] = [d.name for d in value]
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date)
credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
return credit_notes
self.consolidated_credit_note = credit_note.name
def distinguish_return_pos_invoices(self, data, sales_invoice_doc=None):
return_invoices = {}
return credit_note.name
return_invoices[sales_invoice_doc.name if sales_invoice_doc else None] = []
for doc in data:
sales_invoices_of_return_against = frappe.db.get_value(
"POS Invoice", doc.return_against, "consolidated_invoice"
)
if sales_invoices_of_return_against:
if sales_invoices_of_return_against in return_invoices:
return_invoices[sales_invoices_of_return_against].append(doc)
else:
return_invoices[sales_invoices_of_return_against] = [doc]
else:
return_invoices[sales_invoice_doc.name if sales_invoice_doc else None].append(doc)
return return_invoices
def merge_pos_invoice_into(self, invoice, data):
items, payments, taxes = [], [], []
@@ -212,33 +230,20 @@ class POSInvoiceMergeLog(Document):
loyalty_amount_sum += doc.loyalty_amount
for item in doc.get("items"):
found = False
for i in items:
if (
i.item_code == item.item_code
and not i.serial_and_batch_bundle
and not i.serial_no
and not i.batch_no
and i.uom == item.uom
and i.net_rate == item.net_rate
and i.warehouse == item.warehouse
):
found = True
i.qty = i.qty + item.qty
i.amount = i.amount + item.net_amount
i.net_amount = i.amount
i.base_amount = i.base_amount + item.base_net_amount
i.base_net_amount = i.base_amount
if not found:
item.rate = item.net_rate
item.amount = item.net_amount
item.base_amount = item.base_net_amount
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
if item.serial_and_batch_bundle:
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
items.append(si_item)
item.rate = item.net_rate
item.amount = item.net_amount
item.base_amount = item.base_net_amount
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
si_item.pos_invoice = doc.name
si_item.pos_invoice_item = item.name
if doc.is_return:
si_item.sales_invoice_item = get_sales_invoice_item(
doc.return_against, item.pos_invoice_item
)
if item.serial_and_batch_bundle:
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
items.append(si_item)
for tax in doc.get("taxes"):
found = False
@@ -328,16 +333,16 @@ class POSInvoiceMergeLog(Document):
return sales_invoice
def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_note=""):
def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_notes=None):
for doc in invoice_docs:
doc.load_from_db()
doc.update(
{
"consolidated_invoice": None
if self.docstatus == 2
else (credit_note if doc.is_return else sales_invoice)
}
)
inv = sales_invoice
if doc.is_return:
for key, value in credit_notes.items():
if doc.name in value:
inv = key
break
doc.update({"consolidated_invoice": None if self.docstatus == 2 else inv})
doc.set_status(update=True)
doc.save()
@@ -628,3 +633,26 @@ def get_error_message(message) -> str:
return message["message"]
except Exception:
return str(message)
def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
try:
SalesInvoice = DocType("Sales Invoice")
SalesInvoiceItem = DocType("Sales Invoice Item")
query = (
frappe.qb.from_(SalesInvoice)
.from_(SalesInvoiceItem)
.select(SalesInvoiceItem.name)
.where(
(SalesInvoice.name == SalesInvoiceItem.parent)
& (SalesInvoice.is_return == 0)
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
)
)
result = query.run(as_dict=True)
return result[0].name if result else None
except Exception:
return None

View File

@@ -70,3 +70,6 @@ class POSOpeningEntry(StatusUpdater):
def on_submit(self):
self.set_status(update=True)
def on_cancel(self):
self.set_status(update=True)

View File

@@ -58,7 +58,8 @@
"apply_discount_on",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
"dimension_col_break",
"project"
],
"fields": [
{
@@ -406,6 +407,14 @@
"fieldname": "disable_grand_total_to_default_mop",
"fieldtype": "Check",
"label": "Disable auto setting Grand Total to default Payment Mode"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Project"
}
],
"icon": "icon-cog",
@@ -433,7 +442,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2025-01-29 13:12:30.796630",
"modified": "2025-04-09 11:35:13.779613",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
@@ -459,7 +468,8 @@
"role": "Accounts User"
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _, msgprint, scrub, unscrub
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.document import Document
from frappe.utils import get_link_to_form, now
@@ -52,6 +53,7 @@ class POSProfile(Document):
payments: DF.Table[POSPaymentMethod]
print_format: DF.Link | None
print_receipt_on_order_complete: DF.Check
project: DF.Link | None
select_print_heading: DF.Link | None
selling_price_list: DF.Link | None
tax_category: DF.Link | None
@@ -206,17 +208,41 @@ class POSProfile(Document):
def get_item_groups(pos_profile):
item_groups = []
pos_profile = frappe.get_cached_doc("POS Profile", pos_profile)
permitted_item_groups = get_permitted_nodes("Item Group")
if pos_profile.get("item_groups"):
# Get items based on the item groups defined in the POS profile
for data in pos_profile.get("item_groups"):
item_groups.extend(
["%s" % frappe.db.escape(d.name) for d in get_child_nodes("Item Group", data.item_group)]
[
"%s" % frappe.db.escape(d.name)
for d in get_child_nodes("Item Group", data.item_group)
if not permitted_item_groups or d.name in permitted_item_groups
]
)
if not item_groups and permitted_item_groups:
item_groups = ["%s" % frappe.db.escape(d) for d in permitted_item_groups]
return list(set(item_groups))
def get_permitted_nodes(group_type):
nodes = []
permitted_nodes = get_permitted_documents(group_type)
if not permitted_nodes:
return nodes
for node in permitted_nodes:
if frappe.db.get_value(group_type, node, "is_group"):
nodes.extend([d.name for d in get_child_nodes(group_type, node)])
else:
nodes.append(node)
return nodes
def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql(

View File

@@ -454,8 +454,7 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
if pricing_rule.coupon_code_based == 1:
if not args.coupon_code:
return item_details
continue
coupon_code = frappe.db.get_value(
doctype="Coupon Code", filters={"pricing_rule": pricing_rule.name}, fieldname="name"
)

View File

@@ -68,6 +68,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger("supplier");
}
this.frm.set_query("supplier", function () {
return {
filters: {
is_transporter: 0,
},
};
});
}
refresh(doc) {

View File

@@ -873,6 +873,7 @@ class PurchaseInvoice(BuyingController):
self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
self.set_transaction_currency_and_rate_in_gl_map(gl_entries)
return gl_entries
def check_asset_cwip_enabled(self):
@@ -918,6 +919,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
else grand_total,
"credit_in_transaction_currency": grand_total,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"project": self.project,
@@ -953,7 +955,7 @@ class PurchaseInvoice(BuyingController):
valuation_tax_accounts = [
d.account_head
for d in self.get("taxes")
if d.category in ("Valuation", "Total and Valuation")
if d.category in ("Valuation", "Valuation and Total")
and flt(d.base_tax_amount_after_discount_amount)
]
@@ -969,7 +971,6 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
if item.item_code:
frappe.get_cached_value("Item", item.item_code, "asset_category")
@@ -978,6 +979,7 @@ class PurchaseInvoice(BuyingController):
and self.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
account_currency = get_account_currency(item.expense_account)
# warehouse account
warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency
@@ -993,6 +995,7 @@ class PurchaseInvoice(BuyingController):
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": item.net_amount,
},
warehouse_account[item.warehouse]["account_currency"],
item=item,
@@ -1013,6 +1016,7 @@ class PurchaseInvoice(BuyingController):
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
},
warehouse_account[item.from_warehouse]["account_currency"],
item=item,
@@ -1027,6 +1031,7 @@ class PurchaseInvoice(BuyingController):
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project,
@@ -1044,6 +1049,10 @@ class PurchaseInvoice(BuyingController):
"account": item.expense_account,
"against": self.supplier,
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": flt(
warehouse_debit_amount / self.conversion_rate,
item.precision("net_amount"),
),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project or self.project,
@@ -1056,7 +1065,9 @@ class PurchaseInvoice(BuyingController):
# Amount added through landed-cost-voucher
if landed_cost_entries:
if (item.item_code, item.name) in landed_cost_entries:
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
for account, base_amount in landed_cost_entries[
(item.item_code, item.name)
].items():
gl_entries.append(
self.get_gl_dict(
{
@@ -1064,8 +1075,9 @@ class PurchaseInvoice(BuyingController):
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount["base_amount"]),
"credit_in_account_currency": flt(amount["amount"]),
"credit": flt(base_amount["base_amount"]),
"credit_in_account_currency": flt(base_amount["amount"]),
"credit_in_transaction_currency": item.net_amount,
"project": item.project or self.project,
},
item=item,
@@ -1088,6 +1100,7 @@ class PurchaseInvoice(BuyingController):
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost),
"credit_in_transaction_currency": item.net_amount,
},
warehouse_account[self.supplier_warehouse]["account_currency"],
item=item,
@@ -1101,7 +1114,8 @@ class PurchaseInvoice(BuyingController):
else item.deferred_expense_account
)
dummy, amount = self.get_amount_and_base_amount(item, None)
account_currency = get_account_currency(expense_account)
amount, base_amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
self.make_provisional_gl_entry(gl_entries, item)
@@ -1112,7 +1126,8 @@ class PurchaseInvoice(BuyingController):
{
"account": expense_account,
"against": self.supplier,
"debit": amount,
"debit": base_amount,
"debit_in_transaction_currency": amount,
"cost_center": item.cost_center,
"project": item.project or self.project,
},
@@ -1186,6 +1201,10 @@ class PurchaseInvoice(BuyingController):
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"debit_in_transaction_currency": flt(
item.item_tax_amount / self.conversion_rate,
item.precision("item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
"project": item.project or self.project,
@@ -1305,6 +1324,7 @@ class PurchaseInvoice(BuyingController):
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
@@ -1316,6 +1336,38 @@ class PurchaseInvoice(BuyingController):
warehouse_debit_amount = stock_amount
elif self.is_return and self.update_stock and self.is_internal_supplier and warehouse_debit_amount:
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
stock_amount = (
net_rate
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("amount_difference_with_purchase_invoice"))
)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
return warehouse_debit_amount
def make_tax_gl_entries(self, gl_entries):
@@ -1338,6 +1390,7 @@ class PurchaseInvoice(BuyingController):
dr_or_cr + "_in_account_currency": base_amount
if account_currency == self.company_currency
else amount,
dr_or_cr + "_in_transaction_currency": amount,
"cost_center": tax.cost_center,
},
account_currency,
@@ -1384,6 +1437,10 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": applicable_amount,
"credit_in_transaction_currency": flt(
applicable_amount / self.conversion_rate,
frappe.get_precision("Purchase Invoice Item", "item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
},
item=tax,
@@ -1402,6 +1459,10 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": valuation_tax[tax.name],
"credit_in_transaction_currency": flt(
valuation_tax[tax.name] / self.conversion_rate,
frappe.get_precision("Purchase Invoice Item", "item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
},
item=tax,
@@ -1417,6 +1478,7 @@ class PurchaseInvoice(BuyingController):
"account": self.unrealized_profit_loss_account,
"against": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_transaction_currency": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center,
},
@@ -1466,6 +1528,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.base_paid_amount
if self.party_account_currency == self.company_currency
else self.paid_amount,
"debit_in_transaction_currency": self.paid_amount,
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
@@ -1487,6 +1550,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount
if bank_account_currency == self.company_currency
else self.paid_amount,
"credit_in_transaction_currency": self.paid_amount,
"cost_center": self.cost_center,
},
bank_account_currency,
@@ -1511,6 +1575,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.base_write_off_amount
if self.party_account_currency == self.company_currency
else self.write_off_amount,
"debit_in_transaction_currency": self.write_off_amount,
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
@@ -1531,6 +1596,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount
if write_off_account_currency == self.company_currency
else self.write_off_amount,
"credit_in_transaction_currency": self.write_off_amount,
"cost_center": self.cost_center or self.write_off_cost_center,
},
item=self,

View File

@@ -2101,7 +2101,7 @@ class TestPurchaseInvoice(IntegrationTestCase, 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(
@@ -2481,6 +2481,76 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_adjust_incoming_rate_from_pi_with_multi_currency_and_partial_billing(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
pr = make_purchase_receipt(
qty=10, rate=10, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
)
pr.conversion_rate = 5300
pr.save()
pr.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
self.assertEqual(incoming_rate, 53000) # Asserting to confirm if the default calculation is correct
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 1 : Incoming rate should not change as only the qty has changed and not the rate (this was not the case before)
self.assertEqual(incoming_rate, 53000)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 9
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 2 : Rate in new PI is lower than PR, so incoming rate should also be lower
self.assertEqual(incoming_rate, 50350)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 12
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 3 : Rate in new PI is higher than PR, so incoming rate should also be higher
self.assertEqual(incoming_rate, 54766.667)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_opening_invoice_rounding_adjustment_validation(self):
pi = make_purchase_invoice(do_not_save=1)
pi.items[0].rate = 99.98
@@ -2586,6 +2656,122 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", original_value
)
def test_trx_currency_debit_credit_for_high_precision(self):
exc_rate = 0.737517516
pi = make_purchase_invoice(
currency="USD", conversion_rate=exc_rate, qty=1, rate=2000, do_not_save=True
)
pi.supplier = "_Test Supplier USD"
pi.save().submit()
expected = (
("_Test Account Cost for Goods Sold - _TC", 1475.04, 0.0, 2000.0, 0.0, "USD", exc_rate),
("_Test Payable USD - _TC", 0.0, 1475.04, 0.0, 2000.0, "USD", exc_rate),
)
actual = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pi.name},
fields=[
"account",
"debit",
"credit",
"debit_in_transaction_currency",
"credit_in_transaction_currency",
"transaction_currency",
"transaction_exchange_rate",
],
order_by="account",
as_list=1,
)
self.assertEqual(actual, expected)
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
invoice = make_purchase_invoice(qty=10)
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = -10
return_doc.save().submit()
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = 0
self.assertRaises(StockOverReturnError, return_doc.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 set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -0,0 +1,209 @@
[
{
"bill_no": "NA",
"buying_price_list": "_Test Price List",
"company": "_Test Company",
"conversion_rate": 1,
"credit_to": "_Test Payable - _TC",
"currency": "INR",
"doctype": "Purchase Invoice",
"items": [
{
"amount": 500,
"base_amount": 500,
"base_rate": 50,
"conversion_factor": 1.0,
"cost_center": "_Test Cost Center - _TC",
"doctype": "Purchase Invoice Item",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"parentfield": "items",
"qty": 10,
"rate": 50,
"uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC"
},
{
"amount": 750,
"base_amount": 750,
"base_rate": 150,
"conversion_factor": 1.0,
"cost_center": "_Test Cost Center - _TC",
"doctype": "Purchase Invoice Item",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 200",
"item_name": "_Test Item Home Desktop 200",
"parentfield": "items",
"qty": 5,
"rate": 150,
"uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC"
}
],
"grand_total": 0,
"naming_series": "T-PINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
"add_deduct_tax": "Add",
"category": "Valuation and Total",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"tax_amount": 100
},
{
"account_head": "_Test Account Customs Duty - _TC",
"add_deduct_tax": "Add",
"category": "Valuation",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 10
},
{
"account_head": "_Test Account Excise Duty - _TC",
"add_deduct_tax": "Add",
"category": "Total",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Education Cess - _TC",
"add_deduct_tax": "Add",
"category": "Total",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "Education Cess",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 2,
"row_id": 3
},
{
"account_head": "_Test Account S&H Education Cess - _TC",
"add_deduct_tax": "Add",
"category": "Total",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "S&H Education Cess",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 1,
"row_id": 3
},
{
"account_head": "_Test Account CST - _TC",
"add_deduct_tax": "Add",
"category": "Total",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "CST",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 2,
"row_id": 5
},
{
"account_head": "_Test Account VAT - _TC",
"add_deduct_tax": "Add",
"category": "Total",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 12.5
},
{
"account_head": "_Test Account Discount - _TC",
"add_deduct_tax": "Deduct",
"category": "Total",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Discount",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"rate": 10,
"row_id": 7
}
],
"supplier": "_Test Supplier",
"supplier_name": "_Test Supplier"
},
{
"bill_no": "NA",
"buying_price_list": "_Test Price List",
"company": "_Test Company",
"conversion_rate": 1.0,
"credit_to": "_Test Payable - _TC",
"currency": "INR",
"doctype": "Purchase Invoice",
"items": [
{
"conversion_factor": 1.0,
"cost_center": "_Test Cost Center - _TC",
"doctype": "Purchase Invoice Item",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item",
"item_name": "_Test Item",
"parentfield": "items",
"qty": 10.0,
"rate": 50.0,
"uom": "_Test UOM"
}
],
"grand_total": 0,
"naming_series": "T-PINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
"add_deduct_tax": "Add",
"category": "Valuation and Total",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"tax_amount": 100.0
},
{
"account_head": "_Test Account VAT - _TC",
"add_deduct_tax": "Add",
"category": "Total",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"tax_amount": 120.0
},
{
"account_head": "_Test Account Customs Duty - _TC",
"add_deduct_tax": "Add",
"category": "Valuation",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty",
"doctype": "Purchase Taxes and Charges",
"parentfield": "taxes",
"tax_amount": 150.0
}
],
"supplier": "_Test Supplier",
"supplier_name": "_Test Supplier"
}
]

View File

@@ -1,194 +0,0 @@
[["Purchase Invoice"]]
bill_no = "NA"
buying_price_list = "_Test Price List"
company = "_Test Company"
conversion_rate = 1
credit_to = "_Test Payable - _TC"
currency = "INR"
grand_total = 0
naming_series = "T-PINV-"
supplier = "_Test Supplier"
supplier_name = "_Test Supplier"
[["Purchase Invoice".items]]
amount = 500
base_amount = 500
base_rate = 50
conversion_factor = 1.0
cost_center = "_Test Cost Center - _TC"
doctype = "Purchase Invoice Item"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item Home Desktop 100"
item_name = "_Test Item Home Desktop 100"
item_tax_template = "_Test Account Excise Duty @ 10 - _TC"
parentfield = "items"
qty = 10
rate = 50
uom = "_Test UOM"
warehouse = "_Test Warehouse - _TC"
[["Purchase Invoice".items]]
amount = 750
base_amount = 750
base_rate = 150
conversion_factor = 1.0
cost_center = "_Test Cost Center - _TC"
doctype = "Purchase Invoice Item"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item Home Desktop 200"
item_name = "_Test Item Home Desktop 200"
parentfield = "items"
qty = 5
rate = 150
uom = "_Test UOM"
warehouse = "_Test Warehouse - _TC"
[["Purchase Invoice".taxes]]
account_head = "_Test Account Shipping Charges - _TC"
add_deduct_tax = "Add"
category = "Valuation and Total"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "Shipping Charges"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
tax_amount = 100
[["Purchase Invoice".taxes]]
account_head = "_Test Account Customs Duty - _TC"
add_deduct_tax = "Add"
category = "Valuation"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Customs Duty"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 10
[["Purchase Invoice".taxes]]
account_head = "_Test Account Excise Duty - _TC"
add_deduct_tax = "Add"
category = "Total"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Excise Duty"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 12
[["Purchase Invoice".taxes]]
account_head = "_Test Account Education Cess - _TC"
add_deduct_tax = "Add"
category = "Total"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "Education Cess"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 2
row_id = 3
[["Purchase Invoice".taxes]]
account_head = "_Test Account S&H Education Cess - _TC"
add_deduct_tax = "Add"
category = "Total"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "S&H Education Cess"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 1
row_id = 3
[["Purchase Invoice".taxes]]
account_head = "_Test Account CST - _TC"
add_deduct_tax = "Add"
category = "Total"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "CST"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 2
row_id = 5
[["Purchase Invoice".taxes]]
account_head = "_Test Account VAT - _TC"
add_deduct_tax = "Add"
category = "Total"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "VAT"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 12.5
[["Purchase Invoice".taxes]]
account_head = "_Test Account Discount - _TC"
add_deduct_tax = "Deduct"
category = "Total"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "Discount"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
rate = 10
row_id = 7
[["Purchase Invoice"]]
bill_no = "NA"
buying_price_list = "_Test Price List"
company = "_Test Company"
conversion_rate = 1.0
credit_to = "_Test Payable - _TC"
currency = "INR"
grand_total = 0
naming_series = "T-PINV-"
supplier = "_Test Supplier"
supplier_name = "_Test Supplier"
[["Purchase Invoice".items]]
conversion_factor = 1.0
cost_center = "_Test Cost Center - _TC"
doctype = "Purchase Invoice Item"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item"
item_name = "_Test Item"
parentfield = "items"
qty = 10.0
rate = 50.0
uom = "_Test UOM"
[["Purchase Invoice".taxes]]
account_head = "_Test Account Shipping Charges - _TC"
add_deduct_tax = "Add"
category = "Valuation and Total"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "Shipping Charges"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
tax_amount = 100.0
[["Purchase Invoice".taxes]]
account_head = "_Test Account VAT - _TC"
add_deduct_tax = "Add"
category = "Total"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "VAT"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
tax_amount = 120.0
[["Purchase Invoice".taxes]]
account_head = "_Test Account Customs Duty - _TC"
add_deduct_tax = "Add"
category = "Valuation"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "Customs Duty"
doctype = "Purchase Taxes and Charges"
parentfield = "taxes"
tax_amount = 150.0

View File

@@ -462,7 +462,8 @@
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No"
"label": "Serial No",
"no_copy": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
@@ -979,16 +980,18 @@
"options": "currency"
}
],
"grid_page_length": 50,
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-10-28 15:06:19.246141",
"modified": "2025-03-07 10:21:59.960021",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []

View File

@@ -199,7 +199,7 @@
"fieldname": "item_wise_tax_detail",
"fieldtype": "Code",
"hidden": 1,
"label": "Item Wise Tax Detail ",
"label": "Item Wise Tax Detail",
"oldfieldname": "item_wise_tax_detail",
"oldfieldtype": "Small Text",
"print_hide": 1,
@@ -267,18 +267,20 @@
"report_hide": 1
}
],
"grid_page_length": 50,
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-11-22 19:17:02.377473",
"modified": "2025-04-15 13:14:48.936047",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -8,6 +8,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):
# begin: auto-generated types
@@ -97,6 +99,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()
@@ -177,6 +182,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, IntegrationTestCase):
@@ -209,9 +211,81 @@ class TestRepostAccountingLedger(AccountsTestMixin, IntegrationTestCase):
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

@@ -895,8 +895,16 @@ frappe.ui.form.on("Sales Invoice", {
project: function (frm) {
if (frm.doc.project) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project,
frappe.call({
method: "is_auto_fetch_timesheet_enabled",
doc: frm.doc,
callback: function (r) {
if (cint(r.message)) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project,
});
}
},
});
}
},
@@ -912,9 +920,25 @@ frappe.ui.form.on("Sales Invoice", {
}
const timesheets = await frm.events.get_timesheet_data(frm, kwargs);
if (kwargs.item_code) {
frm.events.add_timesheet_item(frm, kwargs.item_code, timesheets);
}
return frm.events.set_timesheet_data(frm, timesheets);
},
add_timesheet_item: function (frm, item_code, timesheets) {
const row = frm.add_child("items");
frappe.model.set_value(row.doctype, row.name, "item_code", item_code);
frappe.model.set_value(
row.doctype,
row.name,
"qty",
timesheets.reduce((a, b) => a + (b["billing_hours"] || 0.0), 0.0)
);
},
async get_timesheet_data(frm, kwargs) {
return frappe
.call({
@@ -1012,6 +1036,22 @@ frappe.ui.form.on("Sales Invoice", {
fieldtype: "Date",
reqd: 1,
},
{
label: __("Item Code"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
get_query: () => {
return {
query: "erpnext.controllers.queries.item_query",
filters: {
is_sales_item: 1,
customer: frm.doc.customer,
has_variants: 0,
},
};
},
},
{
fieldtype: "Column Break",
fieldname: "col_break_1",
@@ -1036,6 +1076,7 @@ frappe.ui.form.on("Sales Invoice", {
from_time: data.from_time,
to_time: data.to_time,
project: data.project,
item_code: data.item_code,
});
d.hide();
},

View File

@@ -2201,6 +2201,7 @@
"print_hide": 1
}
],
"grid_page_length": 50,
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
@@ -2211,7 +2212,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-02-06 15:59:54.636202",
"modified": "2025-03-17 19:32:31.809658",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2257,6 +2258,7 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "creation",
@@ -2266,4 +2268,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -273,8 +273,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()
@@ -679,7 +679,13 @@ class SalesInvoice(SellingController):
"Account", self.debit_to, "account_currency", cache=True
)
if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
self.due_date = get_due_date(
self.posting_date,
"Customer",
self.customer,
self.company,
template_name=self.payment_terms_template,
)
super().set_missing_values(for_validate)
@@ -1096,16 +1102,17 @@ class SalesInvoice(SellingController):
timesheet.billing_amount = ts_doc.total_billable_amount
def update_timesheet_billing_for_project(self):
if self.timesheets:
if not self.timesheets and self.project and self.is_auto_fetch_timesheet_enabled():
self.add_timesheet_data()
else:
self.calculate_billing_amount_for_timesheet()
@frappe.whitelist(methods=["PUT"])
def add_timesheet_data(self):
if not self.timesheets and self.project:
self._add_timesheet_data()
self.save()
@frappe.whitelist()
def is_auto_fetch_timesheet_enabled(self):
return frappe.db.get_single_value("Projects Settings", "fetch_timesheet_in_sales_invoice")
def _add_timesheet_data(self):
@frappe.whitelist()
def add_timesheet_data(self):
self.set("timesheets", [])
if self.project:
for data in get_projectwise_timesheet_data(self.project):
@@ -1237,6 +1244,7 @@ class SalesInvoice(SellingController):
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
self.set_transaction_currency_and_rate_in_gl_map(gl_entries)
return gl_entries
def make_customer_gl_entry(self, gl_entries):
@@ -1270,6 +1278,7 @@ class SalesInvoice(SellingController):
"debit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
else grand_total,
"debit_in_transaction_currency": grand_total,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1301,6 +1310,9 @@ class SalesInvoice(SellingController):
if account_currency == self.company_currency
else flt(amount, tax.precision("tax_amount_after_discount_amount"))
),
"credit_in_transaction_currency": flt(
amount, tax.precision("tax_amount_after_discount_amount")
),
"cost_center": tax.cost_center,
},
account_currency,
@@ -1318,6 +1330,7 @@ class SalesInvoice(SellingController):
"against": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"debit_in_transaction_currency": flt(self.total_taxes_and_charges),
"cost_center": self.cost_center,
},
account_currency,
@@ -1416,6 +1429,7 @@ class SalesInvoice(SellingController):
if account_currency == self.company_currency
else flt(amount, item.precision("net_amount"))
),
"credit_in_transaction_currency": flt(amount, item.precision("net_amount")),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
@@ -1467,6 +1481,7 @@ class SalesInvoice(SellingController):
+ cstr(self.loyalty_redemption_account)
+ " for the Loyalty Program",
"credit": self.loyalty_amount,
"credit_in_transaction_currency": self.loyalty_amount,
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1481,6 +1496,7 @@ class SalesInvoice(SellingController):
"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
"against": self.customer,
"debit": self.loyalty_amount,
"debit_in_transaction_currency": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer",
},
item=self,
@@ -1514,6 +1530,7 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency
else payment_mode.amount,
"credit_in_transaction_currency": payment_mode.amount,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1533,6 +1550,7 @@ class SalesInvoice(SellingController):
"debit_in_account_currency": payment_mode.base_amount
if payment_mode_account_currency == self.company_currency
else payment_mode.amount,
"debit_in_transaction_currency": payment_mode.amount,
"cost_center": self.cost_center,
},
payment_mode_account_currency,
@@ -1561,6 +1579,7 @@ class SalesInvoice(SellingController):
"debit_in_account_currency": flt(self.base_change_amount)
if self.party_account_currency == self.company_currency
else flt(self.change_amount),
"debit_in_transaction_currency": flt(self.change_amount),
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
@@ -1576,6 +1595,7 @@ class SalesInvoice(SellingController):
"account": self.account_for_change_amount,
"against": self.customer,
"credit": self.base_change_amount,
"credit_in_transaction_currency": self.change_amount,
"cost_center": self.cost_center,
},
item=self,
@@ -1605,6 +1625,9 @@ class SalesInvoice(SellingController):
if self.party_account_currency == self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))
),
"credit_in_transaction_currency": flt(
self.write_off_amount, self.precision("write_off_amount")
),
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1625,6 +1648,9 @@ class SalesInvoice(SellingController):
if write_off_account_currency == self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))
),
"debit_in_transaction_currency": flt(
self.write_off_amount, self.precision("write_off_amount")
),
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center,
},
write_off_account_currency,
@@ -1669,6 +1695,9 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment")
),
"credit_in_transaction_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment")
),
"credit": flt(
self.base_rounding_adjustment, self.precision("base_rounding_adjustment")
),
@@ -1933,13 +1962,16 @@ def is_overdue(doc, total):
"base_payment_amount" if doc.party_account_currency != doc.currency else "payment_amount"
)
payable_amount = sum(
payment.get(payment_amount_field)
for payment in doc.payment_schedule
if getdate(payment.due_date) < today
payable_amount = flt(
sum(
payment.get(payment_amount_field)
for payment in doc.payment_schedule
if getdate(payment.due_date) < today
),
doc.precision("outstanding_amount"),
)
return (total - outstanding_amount) < payable_amount
return flt(total - outstanding_amount, doc.precision("outstanding_amount")) < payable_amount
def get_discounting_status(sales_invoice):
@@ -2685,9 +2717,11 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False):
target.closing_text = letter_text.get("closing_text")
target.language = letter_text.get("language")
# update outstanding
# update outstanding from doc
if source.payment_schedule and len(source.payment_schedule) == 1:
target.overdue_payments[0].outstanding = source.get("outstanding_amount")
for row in target.overdue_payments:
if row.payment_schedule == source.payment_schedule[0].name:
row.outstanding = source.get("outstanding_amount")
target.validate()

View File

@@ -0,0 +1,401 @@
[
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"cost_center": "_Test Cost Center - _TC",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
"debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"items": [
{
"amount": 500.0,
"base_amount": 500.0,
"base_rate": 500.0,
"cost_center": "_Test Cost Center - _TC",
"description": "138-CMS Shoe",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "138-CMS Shoe",
"item_name": "138-CMS Shoe",
"parentfield": "items",
"qty": 1.0,
"rate": 500.0,
"uom": "_Test UOM",
"conversion_factor": 1,
"stock_uom": "_Test UOM"
}
],
"base_grand_total": 561.8,
"grand_total": 561.8,
"is_pos": 0,
"naming_series": "T-SINV-",
"base_net_total": 500.0,
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 6
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 6.36
}
],
"plc_conversion_rate": 1.0,
"price_list_currency": "INR",
"sales_team": [
{
"allocated_percentage": 65.5,
"doctype": "Sales Team",
"parentfield": "sales_team",
"sales_person": "_Test Sales Person 1"
},
{
"allocated_percentage": 34.5,
"doctype": "Sales Team",
"parentfield": "sales_team",
"sales_person": "_Test Sales Person 2"
}
],
"selling_price_list": "_Test Price List",
"territory": "_Test Territory"
},
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
"debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
{
"amount": 500.0,
"base_amount": 500.0,
"base_rate": 500.0,
"cost_center": "_Test Cost Center - _TC",
"description": "_Test Item",
"doctype": "Sales Invoice Item",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"income_account": "Sales - _TC",
"item_code": "_Test Item",
"item_name": "_Test Item",
"parentfield": "items",
"price_list_rate": 500.0,
"qty": 1.0,
"uom": "_Test UOM",
"conversion_factor": 1,
"stock_uom": "_Test UOM"
}
],
"base_grand_total": 630.0,
"grand_total": 630.0,
"is_pos": 0,
"naming_series": "T-SINV-",
"base_net_total": 500.0,
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 16
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 10
}
],
"plc_conversion_rate": 1.0,
"price_list_currency": "INR",
"selling_price_list": "_Test Price List",
"territory": "_Test Territory"
},
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
"debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
{
"cost_center": "_Test Cost Center - _TC",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"parentfield": "items",
"price_list_rate": 50,
"qty": 10,
"rate": 50,
"uom": "_Test UOM 1",
"conversion_factor": 1,
"stock_uom": "_Test UOM 1"
},
{
"cost_center": "_Test Cost Center - _TC",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 200",
"item_name": "_Test Item Home Desktop 200",
"parentfield": "items",
"price_list_rate": 150,
"qty": 5,
"uom": "_Test UOM",
"conversion_factor": 1,
"rate": 150,
"stock_uom": "_Test UOM"
}
],
"grand_total": 0,
"is_pos": 0,
"naming_series": "T-SINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"tax_amount": 100
},
{
"account_head": "_Test Account Customs Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 10
},
{
"account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Education Cess - _TC",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "Education Cess",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 2,
"row_id": 3
},
{
"account_head": "_Test Account S&H Education Cess - _TC",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "S&H Education Cess",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 1,
"row_id": 3
},
{
"account_head": "_Test Account CST - _TC",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "CST",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 2,
"row_id": 5
},
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 12.5
},
{
"account_head": "_Test Account Discount - _TC",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Discount",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": -10,
"row_id": 7
}
],
"plc_conversion_rate": 1.0,
"price_list_currency": "INR",
"selling_price_list": "_Test Price List",
"territory": "_Test Territory"
},
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
"debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
{
"cost_center": "_Test Cost Center - _TC",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"parentfield": "items",
"price_list_rate": 62.5,
"qty": 10,
"uom": "_Test UOM 1",
"conversion_factor": 1,
"stock_uom": "_Test UOM 1"
},
{
"cost_center": "_Test Cost Center - _TC",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 200",
"item_name": "_Test Item Home Desktop 200",
"parentfield": "items",
"price_list_rate": 190.66,
"qty": 5,
"uom": "_Test UOM",
"conversion_factor": 1,
"stock_uom": "_Test UOM"
}
],
"grand_total": 0,
"is_pos": 0,
"naming_series": "T-SINV-",
"taxes": [
{
"account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Sales Taxes and Charges",
"idx": 1,
"included_in_print_rate": 1,
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Education Cess - _TC",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "Education Cess",
"doctype": "Sales Taxes and Charges",
"idx": 2,
"included_in_print_rate": 1,
"parentfield": "taxes",
"rate": 2,
"row_id": 1
},
{
"account_head": "_Test Account S&H Education Cess - _TC",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "S&H Education Cess",
"doctype": "Sales Taxes and Charges",
"idx": 3,
"included_in_print_rate": 1,
"parentfield": "taxes",
"rate": 1,
"row_id": 1
},
{
"account_head": "_Test Account CST - _TC",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "CST",
"doctype": "Sales Taxes and Charges",
"idx": 4,
"included_in_print_rate": 1,
"parentfield": "taxes",
"rate": 2,
"row_id": 3
},
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"idx": 5,
"included_in_print_rate": 1,
"parentfield": "taxes",
"rate": 12.5
},
{
"account_head": "_Test Account Customs Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty",
"doctype": "Sales Taxes and Charges",
"idx": 6,
"parentfield": "taxes",
"rate": 10
},
{
"account_head": "_Test Account Shipping Charges - _TC",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges",
"doctype": "Sales Taxes and Charges",
"idx": 7,
"parentfield": "taxes",
"tax_amount": 100
},
{
"account_head": "_Test Account Discount - _TC",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Discount",
"doctype": "Sales Taxes and Charges",
"idx": 8,
"parentfield": "taxes",
"rate": -10,
"row_id": 7
}
],
"plc_conversion_rate": 1.0,
"price_list_currency": "INR",
"selling_price_list": "_Test Price List",
"territory": "_Test Territory"
}
]

View File

@@ -1,377 +0,0 @@
[["Sales Invoice"]]
company = "_Test Company"
conversion_rate = 1.0
currency = "INR"
cost_center = "_Test Cost Center - _TC"
customer = "_Test Customer"
customer_name = "_Test Customer"
debit_to = "Debtors - _TC"
base_grand_total = 561.8
grand_total = 561.8
is_pos = 0
naming_series = "T-SINV-"
base_net_total = 500.0
plc_conversion_rate = 1.0
price_list_currency = "INR"
selling_price_list = "_Test Price List"
territory = "_Test Territory"
[["Sales Invoice".items]]
amount = 500.0
base_amount = 500.0
base_rate = 500.0
cost_center = "_Test Cost Center - _TC"
description = "138-CMS Shoe"
doctype = "Sales Invoice Item"
income_account = "Sales - _TC"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "138-CMS Shoe"
item_name = "138-CMS Shoe"
parentfield = "items"
qty = 1.0
rate = 500.0
uom = "_Test UOM"
conversion_factor = 1
stock_uom = "_Test UOM"
[["Sales Invoice".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
description = "VAT"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
cost_center = "_Test Cost Center - _TC"
rate = 6
[["Sales Invoice".taxes]]
account_head = "_Test Account Service Tax - _TC"
charge_type = "On Net Total"
description = "Service Tax"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
cost_center = "_Test Cost Center - _TC"
rate = 6.36
[["Sales Invoice".sales_team]]
allocated_percentage = 65.5
doctype = "Sales Team"
parentfield = "sales_team"
sales_person = "_Test Sales Person 1"
[["Sales Invoice".sales_team]]
allocated_percentage = 34.5
doctype = "Sales Team"
parentfield = "sales_team"
sales_person = "_Test Sales Person 2"
[["Sales Invoice"]]
company = "_Test Company"
conversion_rate = 1.0
currency = "INR"
customer = "_Test Customer"
customer_name = "_Test Customer"
debit_to = "Debtors - _TC"
cost_center = "_Test Cost Center - _TC"
base_grand_total = 630.0
grand_total = 630.0
is_pos = 0
naming_series = "T-SINV-"
base_net_total = 500.0
plc_conversion_rate = 1.0
price_list_currency = "INR"
selling_price_list = "_Test Price List"
territory = "_Test Territory"
[["Sales Invoice".items]]
amount = 500.0
base_amount = 500.0
base_rate = 500.0
cost_center = "_Test Cost Center - _TC"
description = "_Test Item"
doctype = "Sales Invoice Item"
expense_account = "_Test Account Cost for Goods Sold - _TC"
income_account = "Sales - _TC"
item_code = "_Test Item"
item_name = "_Test Item"
parentfield = "items"
price_list_rate = 500.0
qty = 1.0
uom = "_Test UOM"
conversion_factor = 1
stock_uom = "_Test UOM"
[["Sales Invoice".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
description = "VAT"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
cost_center = "_Test Cost Center - _TC"
rate = 16
[["Sales Invoice".taxes]]
account_head = "_Test Account Service Tax - _TC"
charge_type = "On Net Total"
description = "Service Tax"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
cost_center = "_Test Cost Center - _TC"
rate = 10
[["Sales Invoice"]]
company = "_Test Company"
conversion_rate = 1.0
currency = "INR"
customer = "_Test Customer"
customer_name = "_Test Customer"
debit_to = "Debtors - _TC"
cost_center = "_Test Cost Center - _TC"
grand_total = 0
is_pos = 0
naming_series = "T-SINV-"
plc_conversion_rate = 1.0
price_list_currency = "INR"
selling_price_list = "_Test Price List"
territory = "_Test Territory"
[["Sales Invoice".items]]
cost_center = "_Test Cost Center - _TC"
doctype = "Sales Invoice Item"
income_account = "Sales - _TC"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item Home Desktop 100"
item_name = "_Test Item Home Desktop 100"
item_tax_template = "_Test Account Excise Duty @ 10 - _TC"
parentfield = "items"
price_list_rate = 50
qty = 10
rate = 50
uom = "_Test UOM 1"
conversion_factor = 1
stock_uom = "_Test UOM 1"
[["Sales Invoice".items]]
cost_center = "_Test Cost Center - _TC"
doctype = "Sales Invoice Item"
income_account = "Sales - _TC"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item Home Desktop 200"
item_name = "_Test Item Home Desktop 200"
parentfield = "items"
price_list_rate = 150
qty = 5
uom = "_Test UOM"
conversion_factor = 1
rate = 150
stock_uom = "_Test UOM"
[["Sales Invoice".taxes]]
account_head = "_Test Account Shipping Charges - _TC"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "Shipping Charges"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
tax_amount = 100
[["Sales Invoice".taxes]]
account_head = "_Test Account Customs Duty - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Customs Duty"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 10
[["Sales Invoice".taxes]]
account_head = "_Test Account Excise Duty - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Excise Duty"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 12
[["Sales Invoice".taxes]]
account_head = "_Test Account Education Cess - _TC"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "Education Cess"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 2
row_id = 3
[["Sales Invoice".taxes]]
account_head = "_Test Account S&H Education Cess - _TC"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "S&H Education Cess"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 1
row_id = 3
[["Sales Invoice".taxes]]
account_head = "_Test Account CST - _TC"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "CST"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 2
row_id = 5
[["Sales Invoice".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "VAT"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 12.5
[["Sales Invoice".taxes]]
account_head = "_Test Account Discount - _TC"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "Discount"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = -10
row_id = 7
[["Sales Invoice"]]
company = "_Test Company"
conversion_rate = 1.0
currency = "INR"
customer = "_Test Customer"
customer_name = "_Test Customer"
debit_to = "Debtors - _TC"
cost_center = "_Test Cost Center - _TC"
grand_total = 0
is_pos = 0
naming_series = "T-SINV-"
plc_conversion_rate = 1.0
price_list_currency = "INR"
selling_price_list = "_Test Price List"
territory = "_Test Territory"
[["Sales Invoice".items]]
cost_center = "_Test Cost Center - _TC"
doctype = "Sales Invoice Item"
income_account = "Sales - _TC"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item Home Desktop 100"
item_name = "_Test Item Home Desktop 100"
item_tax_template = "_Test Account Excise Duty @ 10 - _TC"
parentfield = "items"
price_list_rate = 62.5
qty = 10
uom = "_Test UOM 1"
conversion_factor = 1
stock_uom = "_Test UOM 1"
[["Sales Invoice".items]]
cost_center = "_Test Cost Center - _TC"
doctype = "Sales Invoice Item"
income_account = "Sales - _TC"
expense_account = "_Test Account Cost for Goods Sold - _TC"
item_code = "_Test Item Home Desktop 200"
item_name = "_Test Item Home Desktop 200"
parentfield = "items"
price_list_rate = 190.66
qty = 5
uom = "_Test UOM"
conversion_factor = 1
stock_uom = "_Test UOM"
[["Sales Invoice".taxes]]
account_head = "_Test Account Excise Duty - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Excise Duty"
doctype = "Sales Taxes and Charges"
idx = 1
included_in_print_rate = 1
parentfield = "taxes"
rate = 12
[["Sales Invoice".taxes]]
account_head = "_Test Account Education Cess - _TC"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "Education Cess"
doctype = "Sales Taxes and Charges"
idx = 2
included_in_print_rate = 1
parentfield = "taxes"
rate = 2
row_id = 1
[["Sales Invoice".taxes]]
account_head = "_Test Account S&H Education Cess - _TC"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "S&H Education Cess"
doctype = "Sales Taxes and Charges"
idx = 3
included_in_print_rate = 1
parentfield = "taxes"
rate = 1
row_id = 1
[["Sales Invoice".taxes]]
account_head = "_Test Account CST - _TC"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "CST"
doctype = "Sales Taxes and Charges"
idx = 4
included_in_print_rate = 1
parentfield = "taxes"
rate = 2
row_id = 3
[["Sales Invoice".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "VAT"
doctype = "Sales Taxes and Charges"
idx = 5
included_in_print_rate = 1
parentfield = "taxes"
rate = 12.5
[["Sales Invoice".taxes]]
account_head = "_Test Account Customs Duty - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Customs Duty"
doctype = "Sales Taxes and Charges"
idx = 6
parentfield = "taxes"
rate = 10
[["Sales Invoice".taxes]]
account_head = "_Test Account Shipping Charges - _TC"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "Shipping Charges"
doctype = "Sales Taxes and Charges"
idx = 7
parentfield = "taxes"
tax_amount = 100
[["Sales Invoice".taxes]]
account_head = "_Test Account Discount - _TC"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "Discount"
doctype = "Sales Taxes and Charges"
idx = 8
parentfield = "taxes"
rate = -10
row_id = 7

View File

@@ -1827,17 +1827,6 @@ class TestSalesInvoice(IntegrationTestCase):
for field in expected_gle:
self.assertEqual(expected_gle[field], gle[field])
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
@@ -4305,6 +4294,31 @@ class TestSalesInvoice(IntegrationTestCase):
doc = frappe.get_doc("Project", project.name)
self.assertEqual(doc.total_billed_amount, si.grand_total)
def test_total_billed_amount_with_different_projects(self):
# This test case is for checking the scenario where project is set at document level and for **some** child items only, not all
from copy import copy
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()
si.project = project.name
si.items.append(copy(si.items[0]))
si.items.append(copy(si.items[0]))
si.items[0].project = project.name
si.items[1].project = project.name
# Not setting project on last item
si.items[1].insert()
si.items[2].insert()
si.submit()
project.reload()
self.assertIsNone(si.items[2].project)
self.assertEqual(project.total_billed_amount, 300)
def test_pos_returns_with_party_account_currency(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
@@ -4329,6 +4343,49 @@ class TestSalesInvoice(IntegrationTestCase):
pos_return = make_sales_return(pos.name)
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
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 test_prevents_fully_returned_invoice_with_zero_quantity(self):
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
invoice = create_sales_invoice(qty=10)
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = -10
return_doc.save().submit()
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = 0
self.assertRaises(StockOverReturnError, return_doc.save)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -106,6 +106,9 @@
"delivery_note",
"dn_detail",
"delivered_qty",
"column_break_vwhb",
"pos_invoice",
"pos_invoice_item",
"internal_transfer_section",
"purchase_order",
"column_break_92",
@@ -631,6 +634,7 @@
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1,
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text"
},
@@ -952,18 +956,42 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "pos_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "POS Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_vwhb",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_invoice",
"fieldtype": "Link",
"label": "POS Invoice",
"no_copy": 1,
"options": "POS Invoice",
"print_hide": 1,
"search_index": 1
}
],
"grid_page_length": 50,
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-11-25 16:27:33.287341",
"modified": "2025-03-07 10:25:30.275246",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []

View File

@@ -71,6 +71,8 @@ class SalesInvoiceItem(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
pos_invoice: DF.Link | None
pos_invoice_item: DF.Data | None
price_list_rate: DF.Currency
pricing_rules: DF.SmallText | None
project: DF.Link | None

View File

@@ -0,0 +1,209 @@
[
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 6
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 6.36
}
],
"title": "_Test Sales Taxes and Charges Template"
},
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
"charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"tax_amount": 100
},
{
"account_head": "_Test Account Customs Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 10
},
{
"account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Education Cess - _TC",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "Education Cess",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 2,
"row_id": 3
},
{
"account_head": "_Test Account S&H Education Cess - _TC",
"charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC",
"description": "S&H Education Cess",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 1,
"row_id": 3
},
{
"account_head": "_Test Account CST - _TC",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "CST",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 2,
"row_id": 5
},
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": 12.5
},
{
"account_head": "_Test Account Discount - _TC",
"charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Discount",
"doctype": "Sales Taxes and Charges",
"parentfield": "taxes",
"rate": -10,
"row_id": 7
}
],
"title": "_Test India Tax Master"
},
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template - Rest of the World"
},
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template 1"
},
{
"company": "_Test Company",
"doctype": "Sales Taxes and Charges Template",
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 12
},
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
"cost_center": "Main - _TC",
"parentfield": "taxes",
"rate": 4
}
],
"title": "_Test Sales Taxes and Charges Template 2"
},
{
"doctype" : "Sales Taxes and Charges Template",
"title": "_Test Tax 1",
"company": "_Test Company",
"taxes":[{
"charge_type": "Actual",
"account_head": "Sales Expenses - _TC",
"cost_center": "Main - _TC",
"description": "Test Shopping cart taxes with Tax Rule",
"tax_amount": 1000
}]
},
{
"doctype" : "Sales Taxes and Charges Template",
"title": "_Test Tax 2",
"company": "_Test Company",
"taxes":[{
"charge_type": "Actual",
"account_head": "Sales Expenses - _TC",
"cost_center": "Main - _TC",
"description": "Test Shopping cart taxes with Tax Rule",
"tax_amount": 200
}]
}
]

View File

@@ -1,190 +0,0 @@
[["Sales Taxes and Charges Template"]]
company = "_Test Company"
title = "_Test Sales Taxes and Charges Template"
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
description = "VAT"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 6
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Service Tax - _TC"
charge_type = "On Net Total"
description = "Service Tax"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 6.36
[["Sales Taxes and Charges Template"]]
company = "_Test Company"
title = "_Test India Tax Master"
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Shipping Charges - _TC"
charge_type = "Actual"
cost_center = "_Test Cost Center - _TC"
description = "Shipping Charges"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
tax_amount = 100
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Customs Duty - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Customs Duty"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 10
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Excise Duty - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "Excise Duty"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 12
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Education Cess - _TC"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "Education Cess"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 2
row_id = 3
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account S&H Education Cess - _TC"
charge_type = "On Previous Row Amount"
cost_center = "_Test Cost Center - _TC"
description = "S&H Education Cess"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 1
row_id = 3
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account CST - _TC"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "CST"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 2
row_id = 5
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
cost_center = "_Test Cost Center - _TC"
description = "VAT"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = 12.5
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Discount - _TC"
charge_type = "On Previous Row Total"
cost_center = "_Test Cost Center - _TC"
description = "Discount"
doctype = "Sales Taxes and Charges"
parentfield = "taxes"
rate = -10
row_id = 7
[["Sales Taxes and Charges Template"]]
company = "_Test Company"
title = "_Test Sales Taxes and Charges Template - Rest of the World"
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
description = "VAT"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 12
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Service Tax - _TC"
charge_type = "On Net Total"
description = "Service Tax"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 4
[["Sales Taxes and Charges Template"]]
company = "_Test Company"
title = "_Test Sales Taxes and Charges Template 1"
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
description = "VAT"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 12
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Service Tax - _TC"
charge_type = "On Net Total"
description = "Service Tax"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 4
[["Sales Taxes and Charges Template"]]
company = "_Test Company"
title = "_Test Sales Taxes and Charges Template 2"
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account VAT - _TC"
charge_type = "On Net Total"
description = "VAT"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 12
[["Sales Taxes and Charges Template".taxes]]
account_head = "_Test Account Service Tax - _TC"
charge_type = "On Net Total"
description = "Service Tax"
doctype = "Sales Taxes and Charges"
cost_center = "Main - _TC"
parentfield = "taxes"
rate = 4
[["Sales Taxes and Charges Template"]]
title = "_Test Tax 1"
company = "_Test Company"
[["Sales Taxes and Charges Template".taxes]]
charge_type = "Actual"
account_head = "Sales Expenses - _TC"
cost_center = "Main - _TC"
description = "Test Shopping cart taxes with Tax Rule"
tax_amount = 1000
[["Sales Taxes and Charges Template"]]
title = "_Test Tax 2"
company = "_Test Company"
[["Sales Taxes and Charges Template".taxes]]
charge_type = "Actual"
account_head = "Sales Expenses - _TC"
cost_center = "Main - _TC"
description = "Test Shopping cart taxes with Tax Rule"
tax_amount = 200

View File

@@ -0,0 +1,10 @@
[
{
"doctype": "Share Type",
"title": "Class A"
},
{
"doctype": "Share Type",
"title": "Class B"
}
]

View File

@@ -1,6 +0,0 @@
[["Share Type"]]
title = "Class A"
[["Share Type"]]
title = "Class B"

View File

@@ -0,0 +1,20 @@
[
{
"doctype": "Shareholder",
"naming_series": "SH-",
"title": "Iron Man",
"company": "_Test Company"
},
{
"doctype": "Shareholder",
"naming_series": "SH-",
"title": "Thor",
"company": "_Test Company"
},
{
"doctype": "Shareholder",
"naming_series": "SH-",
"title": "Hulk",
"company": "_Test Company"
}
]

View File

@@ -1,15 +0,0 @@
[[Shareholder]]
naming_series = "SH-"
title = "Iron Man"
company = "_Test Company"
[[Shareholder]]
naming_series = "SH-"
title = "Thor"
company = "_Test Company"
[[Shareholder]]
naming_series = "SH-"
title = "Hulk"
company = "_Test Company"

View File

@@ -0,0 +1,108 @@
[
{
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule",
"name": "_Test Shipping Rule",
"shipping_rule_type": "Selling",
"conditions": [
{
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"to_value": 100
},
{
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"to_value": 200
},
{
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 200.0
}
],
"countries": [
{"country": "India"}
],
"worldwide_shipping": 1
},
{
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - India",
"name": "_Test Shipping Rule - India",
"conditions": [
{
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"to_value": 100
},
{
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"to_value": 200
},
{
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 0.0
}
],
"countries": [
{"country": "India"}
]
},
{
"account": "_Test Account Shipping Charges - _TC",
"calculate_based_on": "Net Total",
"company": "_Test Company",
"cost_center": "_Test Cost Center - _TC",
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - Rest of the World",
"name": "_Test Shipping Rule - Rest of the World",
"shipping_rule_type": "Buying",
"conditions": [
{
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 500.0,
"to_value": 1000
},
{
"doctype": "Shipping Rule Condition",
"from_value": 1001,
"parentfield": "conditions",
"shipping_amount": 1000.0,
"to_value": 2000
},
{
"doctype": "Shipping Rule Condition",
"from_value": 2001,
"parentfield": "conditions",
"shipping_amount": 1500.0
}
],
"worldwide_shipping": 1,
"countries": [
{"country": "Germany"}
]
}
]

View File

@@ -1,97 +0,0 @@
[["Shipping Rule"]]
account = "_Test Account Shipping Charges - _TC"
calculate_based_on = "Net Total"
company = "_Test Company"
cost_center = "_Test Cost Center - _TC"
label = "_Test Shipping Rule"
name = "_Test Shipping Rule"
shipping_rule_type = "Selling"
worldwide_shipping = 1
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 0
parentfield = "conditions"
shipping_amount = 50.0
to_value = 100
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 101
parentfield = "conditions"
shipping_amount = 100.0
to_value = 200
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 201
parentfield = "conditions"
shipping_amount = 200.0
[["Shipping Rule".countries]]
country = "India"
[["Shipping Rule"]]
account = "_Test Account Shipping Charges - _TC"
calculate_based_on = "Net Total"
company = "_Test Company"
cost_center = "_Test Cost Center - _TC"
label = "_Test Shipping Rule - India"
name = "_Test Shipping Rule - India"
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 0
parentfield = "conditions"
shipping_amount = 50.0
to_value = 100
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 101
parentfield = "conditions"
shipping_amount = 100.0
to_value = 200
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 201
parentfield = "conditions"
shipping_amount = 0.0
[["Shipping Rule".countries]]
country = "India"
[["Shipping Rule"]]
account = "_Test Account Shipping Charges - _TC"
calculate_based_on = "Net Total"
company = "_Test Company"
cost_center = "_Test Cost Center - _TC"
label = "_Test Shipping Rule - Rest of the World"
name = "_Test Shipping Rule - Rest of the World"
shipping_rule_type = "Buying"
worldwide_shipping = 1
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 0
parentfield = "conditions"
shipping_amount = 500.0
to_value = 1000
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 1001
parentfield = "conditions"
shipping_amount = 1000.0
to_value = 2000
[["Shipping Rule".conditions]]
doctype = "Shipping Rule Condition"
from_value = 2001
parentfield = "conditions"
shipping_amount = 1500.0
[["Shipping Rule".countries]]
country = "Germany"

View File

@@ -0,0 +1,14 @@
[
{
"doctype": "Tax Category",
"title": "_Test Tax Category 1"
},
{
"doctype": "Tax Category",
"title": "_Test Tax Category 2"
},
{
"doctype": "Tax Category",
"title": "_Test Tax Category 3"
}
]

View File

@@ -1,9 +0,0 @@
[["Tax Category"]]
title = "_Test Tax Category 1"
[["Tax Category"]]
title = "_Test Tax Category 2"
[["Tax Category"]]
title = "_Test Tax Category 3"

View File

@@ -0,0 +1,28 @@
[
{
"doctype": "Tax Rule",
"tax_type" : "Sales",
"sales_tax_template": "_Test Tax 1 - _TC",
"use_for_shopping_cart": 1,
"billing_city": "_Test City",
"billing_state": "Test State",
"billing_country": "India",
"shipping_city": "_Test City",
"shipping_country": "India",
"priority": 1,
"company": "_Test Company"
},
{
"doctype": "Tax Rule",
"tax_type" : "Sales",
"sales_tax_template": "_Test Tax 2 - _TC",
"use_for_shopping_cart": 0,
"billing_city": "_Test City",
"billing_country": "India",
"shipping_city": "_Test City",
"shipping_state": "Test State",
"shipping_country": "India",
"priority": 2,
"company": "_Test Company"
}
]

View File

@@ -1,24 +0,0 @@
[["Tax Rule"]]
tax_type = "Sales"
sales_tax_template = "_Test Tax 1 - _TC"
use_for_shopping_cart = 1
billing_city = "_Test City"
billing_state = "Test State"
billing_country = "India"
shipping_city = "_Test City"
shipping_country = "India"
priority = 1
company = "_Test Company"
[["Tax Rule"]]
tax_type = "Sales"
sales_tax_template = "_Test Tax 2 - _TC"
use_for_shopping_cart = 0
billing_city = "_Test City"
billing_country = "India"
shipping_city = "_Test City"
shipping_state = "Test State"
shipping_country = "India"
priority = 2
company = "_Test Company"

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": "2024-03-27 13:10:52.307012",
"modified": "2025-02-05 16:39:14.863698",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",

View File

@@ -18,8 +18,8 @@ class TaxWithheldVouchers(Document):
parentfield: DF.Data
parenttype: DF.Data
taxable_amount: DF.Currency
voucher_name: DF.DynamicLink | None
voucher_type: DF.Link | None
voucher_name: DF.Data | None
voucher_type: DF.Data | None
# end: auto-generated types
pass

View File

@@ -10,6 +10,7 @@ frappe.ui.form.on("Tax Withholding Category", {
filters: {
company: child.company,
root_type: ["in", ["Asset", "Liability"]],
is_group: 0,
},
};
}

View File

@@ -36,27 +36,38 @@ class TaxWithholdingCategory(Document):
def validate(self):
self.validate_dates()
self.validate_accounts()
self.validate_companies_and_accounts()
self.validate_thresholds()
def validate_dates(self):
last_date = None
for d in self.get("rates"):
last_to_date = None
rates = sorted(self.get("rates"), key=lambda d: getdate(d.from_date))
for d in rates:
if getdate(d.from_date) >= getdate(d.to_date):
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
# validate overlapping of dates
if last_date and getdate(d.to_date) < getdate(last_date):
if last_to_date and getdate(d.from_date) < getdate(last_to_date):
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
def validate_accounts(self):
existing_accounts = []
last_to_date = d.to_date
def validate_companies_and_accounts(self):
existing_accounts = set()
companies = set()
for d in self.get("accounts"):
# validate duplicate company
if d.get("company") in companies:
frappe.throw(_("Company {0} added multiple times").format(frappe.bold(d.get("company"))))
companies.add(d.get("company"))
# validate duplicate account
if d.get("account") in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
validate_account_head(d.idx, d.get("account"), d.get("company"))
existing_accounts.append(d.get("account"))
existing_accounts.add(d.get("account"))
def validate_thresholds(self):
for d in self.get("rates"):
@@ -436,6 +447,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
tax_details.get("tax_withholding_category"),
company,
),
as_dict=1,
)
for d in journal_entries_details:

View File

@@ -529,7 +529,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
payment = get_payment_entry(order.doctype, order.name)
payment.apply_tax_withholding_amount = 1
payment.tax_withholding_category = "Cumulative Threshold TDS"
payment.submit()
payment.save().submit()
self.assertEqual(payment.taxes[0].tax_amount, 4000)
def test_multi_category_single_supplier(self):

View File

@@ -82,6 +82,10 @@ def make_acc_dimensions_offsetting_entry(gl_map):
"credit_in_account_currency": credit,
"remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}",
"against_voucher": None,
"account_currency": dimension.account_currency,
# Party Type and Party are restricted to Receivable and Payable accounts
"party_type": None,
"party": None,
}
)
offsetting_entry["against_voucher_type"] = None
@@ -109,6 +113,9 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
accounting_dimensions_to_offset = []
for acc_dimension in acc_dimensions:
values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
acc_dimension.account_currency = frappe.get_cached_value(
"Account", acc_dimension.offsetting_account, "account_currency"
)
if len(values) > 1:
accounting_dimensions_to_offset.append(acc_dimension)
@@ -431,7 +438,7 @@ def process_debit_credit_difference(gl_map):
voucher_no = gl_map[0].voucher_no
allowance = get_debit_credit_allowance(voucher_type, precision)
debit_credit_diff = get_debit_credit_difference(gl_map, precision)
debit_credit_diff, trx_cur_debit_credit_diff = get_debit_credit_difference(gl_map, precision)
if abs(debit_credit_diff) > allowance:
if not (
@@ -442,9 +449,9 @@ def process_debit_credit_difference(gl_map):
raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
make_round_off_gle(gl_map, debit_credit_diff, precision)
make_round_off_gle(gl_map, debit_credit_diff, trx_cur_debit_credit_diff, precision)
debit_credit_diff = get_debit_credit_difference(gl_map, precision)
debit_credit_diff, trx_cur_debit_credit_diff = get_debit_credit_difference(gl_map, precision)
if abs(debit_credit_diff) > allowance:
if not (
voucher_type == "Journal Entry"
@@ -456,14 +463,23 @@ def process_debit_credit_difference(gl_map):
def get_debit_credit_difference(gl_map, precision):
debit_credit_diff = 0.0
trx_cur_debit_credit_diff = 0
for entry in gl_map:
entry.debit = flt(entry.debit, precision)
entry.credit = flt(entry.credit, precision)
debit_credit_diff += entry.debit - entry.credit
debit_credit_diff = flt(debit_credit_diff, precision)
entry.debit_in_transaction_currency = flt(entry.debit_in_transaction_currency, precision)
entry.credit_in_transaction_currency = flt(entry.credit_in_transaction_currency, precision)
trx_cur_debit_credit_diff += (
entry.debit_in_transaction_currency - entry.credit_in_transaction_currency
)
return debit_credit_diff
debit_credit_diff = flt(debit_credit_diff, precision)
trx_cur_debit_credit_diff = flt(trx_cur_debit_credit_diff, precision)
return debit_credit_diff, trx_cur_debit_credit_diff
def get_debit_credit_allowance(voucher_type, precision):
@@ -490,7 +506,7 @@ def has_opening_entries(gl_map: list) -> bool:
return False
def make_round_off_gle(gl_map, debit_credit_diff, precision):
def make_round_off_gle(gl_map, debit_credit_diff, trx_cur_debit_credit_diff, precision):
round_off_account, round_off_cost_center, round_off_for_opening = get_round_off_account_and_cost_center(
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
)
@@ -535,6 +551,12 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
"credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
"debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
"credit": debit_credit_diff if debit_credit_diff > 0 else 0,
"debit_in_transaction_currency": abs(trx_cur_debit_credit_diff)
if trx_cur_debit_credit_diff < 0
else 0,
"credit_in_transaction_currency": trx_cur_debit_credit_diff
if trx_cur_debit_credit_diff > 0
else 0,
"cost_center": round_off_cost_center,
"party_type": None,
"party": None,

View File

@@ -280,32 +280,50 @@ def get_regional_address_details(party_details, doctype, company):
def complete_contact_details(party_details):
if not party_details.contact_person:
party_details.update(
{
"contact_person": None,
"contact_display": None,
"contact_email": None,
"contact_mobile": None,
"contact_phone": None,
"contact_designation": None,
"contact_department": None,
}
contact_details = frappe._dict()
if party_details.party_type == "Employee":
contact_details = frappe.db.get_value(
"Employee",
party_details.party,
[
"employee_name as contact_display",
"prefered_email as contact_email",
"cell_number as contact_mobile",
"designation as contact_designation",
"department as contact_department",
],
as_dict=True,
)
contact_details.update({"contact_person": None, "contact_phone": None})
elif party_details.contact_person:
contact_details = frappe.db.get_value(
"Contact",
party_details.contact_person,
[
"name as contact_person",
"full_name as contact_display",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
],
as_dict=True,
)
else:
fields = [
"name as contact_person",
"full_name as contact_display",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = {
"contact_person": None,
"contact_display": None,
"contact_email": None,
"contact_mobile": None,
"contact_phone": None,
"contact_designation": None,
"contact_department": None,
}
contact_details = frappe.db.get_value("Contact", party_details.contact_person, fields, as_dict=True)
party_details.update(contact_details)
party_details.update(contact_details)
def set_contact_details(party_details, party, party_type):
@@ -577,12 +595,13 @@ def validate_party_accounts(doc):
@frappe.whitelist()
def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
def get_due_date(posting_date, party_type, party, company=None, bill_date=None, template_name=None):
"""Get due date from `Payment Terms Template`"""
due_date = None
if (bill_date or posting_date) and party:
due_date = bill_date or posting_date
template_name = get_payment_terms_template(party, party_type, company)
if not template_name:
template_name = get_payment_terms_template(party, party_type, company)
if template_name:
due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
@@ -779,9 +798,9 @@ def validate_account_party_type(self):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type and (account_type not in ["Receivable", "Payable", "Equity"]):
frappe.throw(
_(
"Party Type and Party can only be set for Receivable / Payable account<br><br>" "{0}"
).format(self.account)
_("Party Type and Party can only be set for Receivable / Payable account<br><br>{0}").format(
self.account
)
)

View File

@@ -0,0 +1,161 @@
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
{% if print_heading_template %}
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
{% else %}
{% endif %}
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
<div class="text-center" document-status="cancelled">
<h4 style="margin: 0px;">{{ _("CANCELLED") }}</h4>
</div>
{%- endif -%}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
<style>
.taxes-section .order-taxes.mt-5{
margin-top: 0px !important;
}
.taxes-section .order-taxes .border-btm.pb-5{
padding-bottom: 0px !important;
}
.print-format label{
color: #74808b;
font-size: 12px;
margin-bottom: 4px;
}
</style>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
<div class="row section-break" style="margin-bottom: 10px;">
<div class="col-xs-6 p-0">
<div class="col-xs-12 value text-uppercase"><b>{{ doc.customer }}</b></div>
<div class="col-xs-12">
{{ doc.address_display }}
</div>
<div class="col-xs-12">
{{ _("Contact: ")+doc.contact_display if doc.contact_display else '' }}
</div>
<div class="col-xs-12">
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
</div>
</div>
<div class="col-xs-3"></div>
<div class="col-xs-3" style="padding-left: 5px;">
<div>
<div><label>{{ _("Invoice ID") }}</label></div>
<div>{{ doc.name }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Invoice Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.posting_date) }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Due Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.due_date) }}</div>
</div>
</div>
</div>
<div class="section-break">
<table class="table table-bordered table-condensed mb-0" style="width: 100%; border-collapse: collapse; font-size: 12px;">
<colgroup>
<col style="width: 5%">
<col style="width: 45%">
<col style="width: 10%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr>
<th class="text-uppercase" style="text-align:center">{{ _("Sr") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Details") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Qty") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Rate") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Amount") }}</th>
</tr>
</thead>
{% for item in doc.items %}
<tr>
<td style="text-align:center">{{ loop.index }}</td>
<td>
<b>{{ item.item_code }}: {{ item.item_name }}</b>
{% if (item.description != item.item_name) %}
<br>{{ item.description }}
{% endif %}
</td>
<td style="text-align: center;">
{{ item.get_formatted("qty", 0) }}
{{ item.get_formatted("uom", 0) }}
</td>
<td style="text-align: right;">{{ item.get_formatted("net_rate", doc) }}</td>
<td style="text-align: right;">{{ item.get_formatted("net_amount", doc) }}</td>
</tr>
{% endfor %}
</table>
<!-- total -->
<div class="row">
<div class="col-xs-6">
<div>
<label>{{ _("Amount in Words") }}</label>
{{ doc.in_words }}
</div>
<div style="margin-top: 20px;">
<label>{{ _("Payment Status") }}</label>
{{ doc.status }}
</div>
</div>
<div class="col-xs-6">
<div class="row section-break">
<div class="col-xs-7"><div>{{ _("Sub Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("net_total", doc) }}</div>
</div>
<div>
{% for d in doc.taxes %}
{% if d.tax_amount %}
<div class="row">
<div class="col-xs-8"><div>{{ _(d.description) }}</div></div>
<div class="col-xs-4" style="text-align: right;">{{ d.get_formatted("tax_amount") }}</div>
</div>
{% endif %}
{% endfor %}
</div>
<div class="row">
<div class="col-xs-7"><div>{{ _("Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("grand_total", doc) }}</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row important data-field">
<div class="col-xs-12"><label>{{ _("Terms and Conditions") }}: </label></div>
<div class="col-xs-12">{{ doc.terms if doc.terms else '' }}</div>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@@ -0,0 +1,32 @@
{
"absolute_value": 0,
"align_labels_right": 0,
"creation": "2025-01-22 16:23:51.012200",
"css": "",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "",
"font_size": 14,
"idx": 0,
"line_breaks": 0,
"margin_bottom": 0.0,
"margin_left": 0.0,
"margin_right": 0.0,
"margin_top": 0.0,
"modified": "2025-01-22 16:23:51.012200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Print",
"owner": "Administrator",
"page_number": "Hide",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

View File

@@ -164,7 +164,7 @@ frappe.query_reports["Accounts Payable"] = {
},
};
erpnext.utils.add_dimensions("Accounts Payable", 9);
erpnext.utils.add_dimensions("Accounts Payable", 10);
function get_party_type_options() {
let options = [];

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

@@ -517,10 +517,10 @@ class ReceivablePayableReport:
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount,
ps.description, ps.paid_amount, ps.discounted_amount
ps.description, ps.paid_amount, ps.base_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
@@ -540,20 +540,24 @@ class ReceivablePayableReport:
# Deduct that from paid amount pre allocation
row.paid -= flt(payment_terms_details[0].total_advance)
company_currency = frappe.get_value("Company", self.filters.get("company"), "default_currency")
# If single payment terms, no need to split the row
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
self.append_payment_term(row, payment_terms_details[0], original_row)
self.append_payment_term(row, payment_terms_details[0], original_row, company_currency)
return
for d in payment_terms_details:
term = frappe._dict(original_row)
self.append_payment_term(row, d, term)
self.append_payment_term(row, d, term, company_currency)
def append_payment_term(self, row, d, term):
if d.currency == d.party_account_currency:
def append_payment_term(self, row, d, term, company_currency):
invoiced = d.base_payment_amount
paid_amount = d.base_paid_amount
if company_currency == d.party_account_currency or self.filters.get("in_party_currency"):
invoiced = d.payment_amount
else:
invoiced = d.base_payment_amount
paid_amount = d.paid_amount
row.payment_terms.append(
term.update(
@@ -562,15 +566,15 @@ class ReceivablePayableReport:
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
"payment_term": d.description or d.payment_term,
"paid": d.paid_amount + d.discounted_amount,
"paid": paid_amount + d.discounted_amount,
"credit_note": 0.0,
"outstanding": invoiced - d.paid_amount - d.discounted_amount,
"outstanding": invoiced - paid_amount - d.discounted_amount,
}
)
)
if d.paid_amount:
row["paid"] -= d.paid_amount + d.discounted_amount
if paid_amount:
row["paid"] -= paid_amount + d.discounted_amount
def allocate_closing_to_term(self, row, term, key):
if row[key]:
@@ -729,11 +733,13 @@ class ReceivablePayableReport:
"company": self.filters.company,
"update_outstanding_for_self": 0,
}
or_filters = {}
for party_type in self.party_type:
if party_type := self.filters.party_type:
party_field = scrub(party_type)
if self.filters.get(party_field):
or_filters.update({party_field: self.filters.get(party_field)})
if parties := self.filters.get("party"):
or_filters.update({party_field: ["in", parties]})
self.return_entries = frappe._dict(
frappe.get_all(
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1

View File

@@ -21,7 +21,7 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
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, IntegrationTestCase):
rate=100,
price_list_rate=100,
do_not_save=1,
**args,
)
if not no_payment_schedule:
si.append(
@@ -108,7 +109,7 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
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,
@@ -145,11 +146,15 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
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,
[
@@ -162,6 +167,99 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
],
)
def test_accounts_receivable_without_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
"report_date": today(),
"range": "30, 60, 90, 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(),
"range": "30, 60, 90, 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,

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