Compare commits

..

853 Commits

Author SHA1 Message Date
frappe-pr-bot
945b390d25 chore: update POT file 2025-07-27 09:38:10 +00:00
ruthra kumar
ac448988ca Merge pull request #48780 from frappe/l10n_develop
fix: sync translations from crowdin
2025-07-25 19:41:13 +05:30
MochaMind
cc48cfaa5d fix: Indonesian translations 2025-07-25 18:49:01 +05:30
rohitwaghchaure
46e6e48495 Merge pull request #48773 from ShreyasTheNewbie/create_job_card
fix: create job card for selected operations only
2025-07-25 10:05:36 +05:30
Shreyas Sojitra
27e5344188 fix: create job card for selected operations only 2025-07-24 10:05:03 +00:00
Kitti U. @ Ecosoft
ed79adebc4 refactor: call hooks after gle & sle rename (#48706) 2025-07-23 11:18:06 +05:30
ruthra kumar
d45d20e4db Merge pull request #48650 from aerele/payment-entry-outstanding-amount
fix: update outstanding amount on payment reconcillation
2025-07-23 06:52:29 +05:30
ruthra kumar
f41c6c037b Merge pull request #48732 from aerele/dimension-wise-acc-balance-report
fix: resolve sql syntax on accounting dimension
2025-07-23 06:43:33 +05:30
MochaMind
313913b329 fix: sync translations from crowdin (#48724) 2025-07-22 23:22:26 +02:00
ruthra kumar
56ddb16186 Merge pull request #48733 from aerele/item-wise-sales-purchase-register
fix: add alias for order by field
2025-07-22 17:53:41 +05:30
ruthra kumar
4f1acc9349 Merge pull request #48665 from aerele/exchange-gain-or-loss-account
fix: show amount for exchange gain or loss account
2025-07-22 12:13:02 +05:30
rohitwaghchaure
ecff9dfdd8 Merge pull request #48723 from aerele/company-filter-job-card
fix(job card): company filter
2025-07-22 09:48:10 +05:30
rohitwaghchaure
73d2878e08 Merge pull request #48720 from rohitwaghchaure/fixed-support-44556
fix: valuation for rejected materials
2025-07-22 09:47:26 +05:30
l0gesh29
feaf39a812 fix: add alias for order by field 2025-07-22 01:23:19 +05:30
l0gesh29
1662b7c311 fix: resolve sql syntax on accounting dimension 2025-07-21 23:58:19 +05:30
pugazhendhivelu
4edbe77f67 chore: rename function set company filters 2025-07-21 20:10:08 +05:30
rohitwaghchaure
fa228da29c Merge pull request #48382 from karm1000/feat/stock-balance-multiselect-items-warehouses
feat: stock balance and stock ledger report with multi-select items and warehouses
2025-07-21 20:02:25 +05:30
rohitwaghchaure
9aea4ba51a Merge pull request #48704 from aerele/production-plan-sub-assembly-warehouse
fix(production plan): add company filter to sub assembly warehouse
2025-07-21 19:56:35 +05:30
Rohit Waghchaure
b7039cc506 fix: valuation for rejected materials 2025-07-21 19:53:04 +05:30
ruthra kumar
1e44e3c1f6 Merge pull request #48690 from aerele/sales-order-item-schedule-date
fix: set delivery date if missing
2025-07-21 19:43:04 +05:30
ruthra kumar
a8e2386daa Merge pull request #48671 from aerele/journal-entry-print-settings
revert: do not set pay_to_recd_from to None
2025-07-21 19:39:23 +05:30
ravibharathi656
7e12332ea5 test: add test for pay_to_recd_from 2025-07-21 18:45:31 +05:30
ravibharathi656
03d6550db3 revert: do not set pay_to_recd_from to None 2025-07-21 18:45:30 +05:30
pugazhendhivelu
b1311ceb30 fix(job card): company filter 2025-07-21 17:34:49 +05:30
MochaMind
5089cf2155 fix: sync translations from crowdin (#48685) 2025-07-21 12:57:24 +02:00
Khushi Rawat
204de4934a Merge pull request #48649 from khushi8112/post-gl-entry-on-completion-date-of-asset-repair
fix: post gl entry on completion date instead of current date
2025-07-21 15:14:14 +05:30
MochaMind
8f3ed909c3 chore: update POT file (#48712) 2025-07-20 11:59:11 +02:00
ravibharathi656
1728a95111 fix(production plan): add company filter to sub assembly warehouse 2025-07-19 08:18:22 +05:30
Mihir Kandoi
4174269091 fix: job card linter error (#47561) 2025-07-18 14:51:41 +00:00
ravibharathi656
cf6913891a fix: set delivery date if missing 2025-07-18 18:37:29 +05:30
Diptanil Saha
7591656491 feat: tabs in pos invoice doctype (#48683) 2025-07-18 17:12:13 +05:30
khushi8112
766c5bbe2b fix: make completion mandatory and fix test case 2025-07-18 17:10:58 +05:30
Mihir Kandoi
446264e496 fix: patch to set default buying price list in material request (#48680)
* fix: patch to set default buying price list in material request
2025-07-18 11:13:27 +00:00
Mihir Kandoi
e01ff50833 Merge pull request #48653 from mihir-kandoi/st43557-2
feat: button to recalculate costing and billing fields in project
2025-07-18 16:25:04 +05:30
Karm Soni
8a97b39028 fix: update get_data function to use item_query 2025-07-18 16:10:32 +05:30
Mihir Kandoi
f6e16c1180 chore: rename recalculating to updating 2025-07-18 16:08:04 +05:30
Mihir Kandoi
dd23d4c81b feat: option to recalculate costing and billing fields in project 2025-07-18 16:08:04 +05:30
Mihir Kandoi
dfd115cee5 Merge pull request #48631 from mihir-kandoi/st43869-2
feat: consider process less when calculating pending qty in work order
2025-07-18 15:53:00 +05:30
Diptanil Saha
37a964c300 Merge pull request #48677 from diptanilsaha/fix_coa_company_view_ledger_btn
fix: view ledger button of company on chart of accounts
2025-07-18 15:41:53 +05:30
diptanilsaha
98eb115746 fix: view ledger button of company on chart of accounts 2025-07-18 15:28:57 +05:30
Diptanil Saha
9dc583ffcb fix: remove incorrect report conditions and unset sales partner on consolidated sales invoice (#48669)
* fix: undo query changes for sales partner related reports

* fix: patch to remove sales partner from consolidated sales invoice
2025-07-18 00:30:04 +05:30
Diptanil Saha
971024ab99 Merge pull request #48667 from diptanilsaha/fix_pos_sales_partner
fix: sales partner on pos invoice
2025-07-17 20:33:01 +05:30
l0gesh29
b6da350c20 fix: add validation for account key 2025-07-17 18:52:41 +05:30
l0gesh29
4f90f50eb2 fix: show amount for exchange gain or loss account 2025-07-17 18:15:06 +05:30
diptanilsaha
84b9a2aefb fix: unset commission and sales partner on consolidated sales invoice 2025-07-17 18:09:11 +05:30
diptanilsaha
e70416c78c fix: fixed the query for sales invoice in sale partner related reports 2025-07-17 17:53:54 +05:30
diptanilsaha
4fb1202c30 fix: added POS Invoice option on sales partner related reports 2025-07-17 17:53:54 +05:30
diptanilsaha
2245731fc8 fix: prepare sales partner commission report from pos invoice and sales invoice 2025-07-17 17:53:54 +05:30
diptanilsaha
2b87de1000 fix: pos invoice fetch commission_rate from sales_partner doctype on empty 2025-07-17 17:53:14 +05:30
Karm Soni
063c4e9720 refactor: revert indentation 2025-07-17 15:39:12 +05:30
rohitwaghchaure
cc2ca58721 Merge pull request #48655 from rohitwaghchaure/fixed-support-44001
fix: precision issue for Sales Incoming Rate
2025-07-17 15:34:31 +05:30
Rohit Waghchaure
7b99275ceb fix: precision issue for Sales Incoming Rate 2025-07-17 15:10:06 +05:30
ravibharathi656
478766c600 fix: update outstanding amount on payment reconcillation 2025-07-17 14:48:56 +05:30
rohitwaghchaure
0ae080723c Merge pull request #48616 from rohitwaghchaure/fixed-support-44081
fix: stand-alone credit note gl entries
2025-07-17 13:57:52 +05:30
Rohit Waghchaure
f3d6a64156 fix: stand-alone credit note gl entries 2025-07-17 13:33:10 +05:30
khushi8112
da8f7b29c1 fix: post gl entry on completion date instead of current date 2025-07-17 13:10:50 +05:30
Mihir Kandoi
8900744fc4 Merge pull request #48645 from mihir-kandoi/st43688
fix: do not consider cancelled SLEs in report
2025-07-17 11:38:33 +05:30
Mihir Kandoi
71578cb2ef fix: do not consider cancelled SLEs in report 2025-07-17 11:20:05 +05:30
ruthra kumar
8d091f6821 Merge pull request #48637 from frappe/l10n_develop
fix: sync translations from crowdin
2025-07-16 20:28:46 +05:30
ruthra kumar
9c7c22ed20 Merge pull request #48612 from aerele/pcv-forex-accounts
fix(period closing voucher): closing account head debit and debit in account currency should be equal
2025-07-16 20:13:06 +05:30
MochaMind
f9a78e9b45 fix: Persian translations 2025-07-16 17:02:01 +05:30
MochaMind
13afd3301f fix: Portuguese translations 2025-07-16 17:01:57 +05:30
rohitwaghchaure
a27f3f737f Merge pull request #48633 from rohitwaghchaure/fixed-support-42610
fix: performance issue while submitting the purchase invoice
2025-07-16 16:45:36 +05:30
Karm Soni
bc46045cc7 refactor: remove unused imports in stock_balance.py 2025-07-16 16:12:43 +05:30
Rohit Waghchaure
47979871de fix: performance issue while submitting the purchase invoice 2025-07-16 15:55:21 +05:30
Diptanil Saha
c9675b3f7d feat: account name and number columns on financial statements report on export (#48630)
* feat: account name and number columns on trial balance report on export

* feat: account name and number columns on financial statement reports on export

* test: fixed test_profit_and_loss_output_and_summary for new columns
2025-07-16 15:24:43 +05:30
diptanilsaha
79f73ccca1 test: fixed test_profit_and_loss_output_and_summary for new columns 2025-07-16 15:06:28 +05:30
Mihir Kandoi
74c4ca68e5 feat: consider process less when calculating pending qty in work order 2025-07-16 14:46:37 +05:30
Mihir Kandoi
2a186ab8dd Merge pull request #48628 from mihir-kandoi/fix-get-reserved-serial-nos
fix: serial no reserved error when reservation is transferred
2025-07-16 13:22:42 +05:30
diptanilsaha
6b98323806 feat: account name and number columns on financial statement reports on export 2025-07-16 13:22:31 +05:30
Mihir Kandoi
52ac389661 fix: serial no reserved error when reservation is transferred 2025-07-16 12:57:58 +05:30
diptanilsaha
393c1d4bee feat: account name and number columns on trial balance report on export 2025-07-16 12:46:56 +05:30
Diptanil Saha
9b8e0eb5c5 revert: "feat: trial balance report account name and number in separate column" (#48624)
Revert "feat: trial balance report account name and number in separate column"
2025-07-16 11:43:36 +05:30
Diptanil Saha
33f2a23bd8 fix: pos customer selection on new order (#48622) 2025-07-16 11:04:56 +05:30
Mihir Kandoi
d40538968f Merge pull request #48621 from mihir-kandoi/fix-get-batch-against-sales-order-2
fix: possible issue in getting reserved batch against sales order
2025-07-16 10:21:25 +05:30
Mihir Kandoi
28ee5fbf2e fix: possible issue in getting reserved batch against sales order 2025-07-16 10:05:36 +05:30
MochaMind
26f234fdbd fix: sync translations from crowdin (#48605)
* fix: French translations

* fix: Portuguese translations

* fix: Persian translations

* fix: Arabic translations

* fix: Turkish translations

* fix: Polish translations

* fix: Swedish translations

* fix: Vietnamese translations

* fix: Chinese Simplified translations

* fix: Spanish translations

* fix: Czech translations

* fix: German translations

* fix: Hungarian translations

* fix: Italian translations

* fix: Dutch translations

* fix: Russian translations

* fix: Serbian (Cyrillic) translations

* fix: Portuguese, Brazilian translations

* fix: Thai translations

* fix: Croatian translations

* fix: Bosnian translations

* fix: Serbian (Latin) translations

* fix: Esperanto translations
2025-07-15 17:44:34 +02:00
venkat102
d6fd613272 fix(period closing voucher): closing account head debit and debit in account currency should be equal 2025-07-15 18:16:23 +05:30
rohitwaghchaure
480e76d98e Merge pull request #48611 from rohitwaghchaure/fixed-job-card-stock-entry
fix: confirmation dialog for submitting the stock entry
2025-07-15 18:12:10 +05:30
rohitwaghchaure
8b06468490 Merge pull request #48610 from rohitwaghchaure/fixed-support-43674-1
fix: added serial no condition
2025-07-15 18:04:04 +05:30
Rohit Waghchaure
ea3d4ced5e fix: Confirmation dialog for submitting the stock entry 2025-07-15 17:52:08 +05:30
Rohit Waghchaure
bb7ddd11f1 fix: added serial no condition 2025-07-15 17:27:00 +05:30
Diptanil Saha
05e7db2362 Merge pull request #48565 from aerele/fix/qty-change
fix(transaction): recalculate tax and total when quantity changes
2025-07-15 17:01:12 +05:30
Sagar Vora
34e0a939e6 Merge pull request #48607 from ljain112/silly-fix 2025-07-15 11:23:48 +00:00
ljain112
0da8ed2daa fix: fix party account field access 2025-07-15 16:45:58 +05:30
Khushi Rawat
0caa0371dc Merge pull request #48360 from aerele/item-wise-sales-register-mode-of-payment
fix: fetch sales invoice based on mode_of_payment in item-wise sales register
2025-07-15 15:49:46 +05:30
Karm Soni
7a266113ed fix: warehouse filter query by chaining conditions 2025-07-15 15:15:31 +05:30
ruthra kumar
e725780c6d Merge pull request #48535 from ljain112/fix-dda
fix: handle cases where distributed discount amount is not set
2025-07-15 15:07:06 +05:30
rohitwaghchaure
0fb6b4eaf6 Merge pull request #48408 from aerele/make_purchase_order_for_default_supplier
fix: resolve bundle item into line item if againt default supplier ch…
2025-07-15 15:05:48 +05:30
rohitwaghchaure
403220c69a Merge pull request #48595 from rohitwaghchaure/fixed-support-43674
fix: system was allowing credit notes with serial numbers for any customer
2025-07-15 14:56:58 +05:30
rohitwaghchaure
6150106dee Merge pull request #48588 from mihir-kandoi/st43481
perf: optimize code for subcontracting
2025-07-15 14:14:24 +05:30
Rohit Waghchaure
e073075834 fix: system was allowing credit notes with serial numbers for any customer 2025-07-15 14:00:04 +05:30
Karm Soni
fca9843fc2 fix: handle empty warehouse condition in get_warehouse_condition function; typo; 2025-07-15 13:39:39 +05:30
Karm Soni
169caaf66f fix: use the item_query for get_data 2025-07-15 13:23:09 +05:30
ruthra kumar
dea3e326ba Merge pull request #48536 from frappe/l10n_develop
fix: sync translations from crowdin
2025-07-15 12:42:06 +05:30
MochaMind
26ecd7fd1b fix: Persian translations 2025-07-15 12:26:11 +05:30
MochaMind
f877f87b01 fix: Swedish translations 2025-07-15 12:26:11 +05:30
MochaMind
d33851367b fix: Persian translations 2025-07-15 12:26:11 +05:30
MochaMind
4c5d753ade fix: Turkish translations 2025-07-15 12:26:11 +05:30
Mihir Kandoi
bc6f69ad54 perf: optimize code for subcontracting 2025-07-15 11:55:03 +05:30
rohitwaghchaure
ee2ea11458 Merge pull request #48582 from barredterra/sabb-translatability
fix: make labels in serial_batch_prompt translatable
2025-07-15 11:53:59 +05:30
MochaMind
e1b2956cdb chore: update POT file (#48583) 2025-07-14 22:25:42 +02:00
barredterra
8757800888 fix: make labels in serial_batch_prompt translatable 2025-07-14 21:47:00 +02:00
Raffael Meyer
d99f258d61 fix(Employee): add context to status in List View (#48576) 2025-07-14 15:37:00 +02:00
Diptanil Saha
b8bf4319ac Merge pull request #48411 from thomasantony12/employee_query
fix: employee search based on the fields mentioned in the employee doctype search fields
2025-07-14 18:58:09 +05:30
mergify[bot]
7ae642e6fa Merge branch 'develop' into employee_query 2025-07-14 13:10:07 +00:00
Mihir Kandoi
70204b4464 Merge pull request #48526 from mihir-kandoi/st43470
fix: gross margin not set in project on submission of stock entry
2025-07-14 17:11:40 +05:30
rohitwaghchaure
e0895be7e9 Merge pull request #48568 from mihir-kandoi/st43465
fix: recalculate qty issue for stock reco
2025-07-14 17:07:15 +05:30
Mihir Kandoi
815220a3c6 Merge pull request #48542 from mihir-kandoi/st43593
fix: field name of price_list in material request
2025-07-14 16:43:31 +05:30
Mihir Kandoi
668574e4f0 fix: incorrect if condition 2025-07-14 16:36:29 +05:30
Mihir Kandoi
adb9a6bc15 fix: field name of price_list in material request 2025-07-14 16:27:14 +05:30
Mihir Kandoi
597d5aff02 fix: incorrect stock reco sle 2025-07-14 16:25:29 +05:30
ruthra kumar
896b21e78b Merge pull request #48321 from ljain112/fix-client-item-tax-template
fix: fetch item tax template after setting `base_net_rate`
2025-07-14 15:01:59 +05:30
ljain112
816b84be02 fix: handle cases where distributed discount amount is not set 2025-07-14 14:56:12 +05:30
ruthra kumar
290a9b7804 Merge pull request #48435 from ljain112/fix-ui-account
fix: prevent creation of root accounts in account tree view
2025-07-14 14:44:50 +05:30
Bhavan23
ac7b6c6a3d fix(transaction): recalculate tax and total when quantity changes 2025-07-14 13:23:56 +05:30
ruthra kumar
3ccb209bfd Merge pull request #48540 from aerele/payable/receivable-summary
feat: add calculate_ageing_with option in summary reports
2025-07-14 11:38:02 +05:30
Diptanil Saha
f2ce84c161 Merge pull request #48539 from diptanilsaha/feat_gh_37933
feat: trial balance report account name and number in separate column
2025-07-14 11:21:53 +05:30
diptanilsaha
52a6856f6c feat: trial balance report account name and number in separate column 2025-07-14 11:05:04 +05:30
rohitwaghchaure
a73c555574 Merge pull request #48550 from ljain112/fix-test-batch
chore: fix flacky test and remove redundant code
2025-07-13 17:20:39 +05:30
rohitwaghchaure
25838ba9b0 Merge pull request #48509 from mihir-kandoi/fix-reservation-on-manufacture-entry
fix: stock reservation on manufacture stock entry
2025-07-13 14:29:25 +05:30
ljain112
e6b9e82b2f chore: return doc if item already exists for test 2025-07-12 13:07:52 +05:30
ljain112
de8c3ba968 chore: fix flacky test and remove redundant code 2025-07-12 12:48:01 +05:30
Thomas antony
de56faf862 chore: add missing key value 2025-07-12 10:18:34 +05:30
Thomas antony
89233d2b87 Merge branch 'frappe:develop' into employee_query 2025-07-12 10:04:35 +05:30
Thomas antony
75a00928b5 chore: included value in "key" for search condition 2025-07-12 09:48:08 +05:30
l0gesh29
a3834eef46 feat: add calculate_ageing_with option in summary reports 2025-07-11 17:54:09 +05:30
Diptanil Saha
b38d472d7c Merge pull request #48495 from ljain112/refactor--acc-settings
perf: use `cached_doc` for Account Settings
2025-07-11 14:41:30 +05:30
Asmita Hase
96bfe7ccb7 Merge pull request #48395 from mahsem/eetr 2025-07-11 11:59:24 +05:30
ruthra kumar
146f98d026 Merge pull request #48508 from frappe/l10n_develop
fix: sync translations from crowdin
2025-07-11 11:49:49 +05:30
Mihir Kandoi
ec578ba231 fix: gross margin not set in project on submission of stock entry 2025-07-11 11:04:29 +05:30
rohitwaghchaure
29d94f71f3 Merge pull request #48512 from rohitwaghchaure/fixed-support-43409
fix: incorrect last sle for no batch wise valuation
2025-07-11 11:01:16 +05:30
Mihir Kandoi
a878dd3837 fix: stock reservation on manufacture stock entry 2025-07-11 10:31:17 +05:30
Rohit Waghchaure
93d3eb662f fix: incorrect last sle for no batch wise valuation 2025-07-11 09:03:04 +05:30
Diptanil Saha
73c08c1ecd Merge pull request #48517 from diptanilsaha/fix_pos_payment_keypress
fix: pos payment method amount using numpad and keyboard
2025-07-10 23:11:07 +05:30
Mihir Kandoi
ea05f81024 Merge pull request #48514 from mihir-kandoi/st43398
fix: no attribute error in old subcontracting flow
2025-07-10 23:02:18 +05:30
Mihir Kandoi
e16014e448 Merge pull request #48513 from mihir-kandoi/st42779
fix: error in available serial no report is no serial no present in company
2025-07-10 22:51:02 +05:30
diptanilsaha
15b1609d88 fix: pos payment method amount using numpad and keyboard 2025-07-10 22:47:10 +05:30
Mihir Kandoi
51751a7a05 fix: no attribute error in old subcontracting flow 2025-07-10 22:46:56 +05:30
Mihir Kandoi
0ae60b8b61 fix: error in available serial no report is no serial no present in company 2025-07-10 22:34:35 +05:30
Mihir Kandoi
7af9fa36d7 Merge pull request #48510 from mihir-kandoi/st42929
fix: missing parameter in precision function
2025-07-10 19:08:16 +05:30
Mihir Kandoi
3886641887 fix: missing parameter in precision function 2025-07-10 18:52:13 +05:30
MochaMind
b651d3f622 fix: Persian translations 2025-07-10 14:26:30 +05:30
MochaMind
d72825e279 fix: Portuguese translations 2025-07-10 14:26:24 +05:30
Diptanil Saha
6e73fbedb0 Merge pull request #48506 from diptanilsaha/fix_st_43111
fix: pos adding item multiple times on applying item group filter
2025-07-10 12:36:06 +05:30
diptanilsaha
e9f99e5a3f fix: pos adding item multiple times on item group filter 2025-07-10 12:31:15 +05:30
Mihir Kandoi
9f44de50eb Merge pull request #48503 from mihir-kandoi/convert-string-to-date
fix: invalid comparison error in sabb.py
2025-07-10 11:04:59 +05:30
Mihir Kandoi
ec1faf02ed fix: invalid comparison error in sabb.py 2025-07-10 10:48:33 +05:30
Mihir Kandoi
2397abaee5 Merge pull request #48499 from mihir-kandoi/st43376
fix: use planned_qty instead of pending_qty to check if WO should be created against PP
2025-07-09 21:03:30 +05:30
Mihir Kandoi
b11bf8eb79 fix: use planned_qty instead of pending_qty to check if WO should be created against PP 2025-07-09 20:59:11 +05:30
l0gesh29
f3460ec840 fix: carry forward the delivered_by_supplier check to PO 2025-07-09 19:26:41 +05:30
rohitwaghchaure
fbe14b79cc Merge pull request #48490 from rohitwaghchaure/fixed-support-39446
feat: update the modified date of the SLE after reposting
2025-07-09 19:20:45 +05:30
Patrick Eißler
ebd45878c3 feat(BOM): improve tree display with item_name and qty (#48176)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-07-09 13:52:12 +02:00
rohitwaghchaure
91881fad6b Merge pull request #48488 from rohitwaghchaure/fixe-support-42043
feat: parent item group support in Stock Projected Qty report
2025-07-09 16:48:17 +05:30
Rohit Waghchaure
c2cd4934e7 feat: update the modified date of the SLE after reposting 2025-07-09 16:45:13 +05:30
rohitwaghchaure
48485c27ec Merge pull request #48487 from rohitwaghchaure/feat-batch-rate-report
feat: batch rate (valuation) in Batch-Wise Balance History report
2025-07-09 16:28:19 +05:30
Rohit Waghchaure
6e80d89d13 feat: parent item group support in Stock Projected Qty report 2025-07-09 16:18:21 +05:30
Rohit Waghchaure
8a2a845a16 feat: batch rate (valuation) in Batch-Wise Balance History report 2025-07-09 16:07:20 +05:30
Diptanil Saha
376dcf50ec feat: filter to display trial balance report without group account (#48486) 2025-07-09 15:44:46 +05:30
ljain112
751f3abd95 perf: use cached_doc for Account Settings 2025-07-09 13:28:37 +05:30
rohitwaghchaure
6e98adecdd Merge pull request #48481 from rohitwaghchaure/fixed-support-43199
fix: stock settings save issue
2025-07-09 12:02:46 +05:30
Rohit Waghchaure
64ae1ec367 fix: stock settings save issue 2025-07-09 11:43:09 +05:30
ruthra kumar
b937b18e3d Merge pull request #47892 from ruthra-kumar/ar_report_procedures_and_sql
refactor: use sql for building voucher balance in Receivable report
2025-07-09 11:02:41 +05:30
ruthra kumar
fc8ca7d82c chore: rename method 2025-07-09 10:45:58 +05:30
ruthra kumar
7efeed54de refactor: build and pass match conditions as qb criterion 2025-07-09 10:43:41 +05:30
ruthra kumar
52c0df24e3 chore: drop unused utility method 2025-07-09 10:42:46 +05:30
ruthra kumar
9d0ebe3427 refactor: dynamic DB field types 2025-07-09 10:42:46 +05:30
ruthra kumar
1a90c0d031 refactor: better variable name 2025-07-09 10:42:46 +05:30
ruthra kumar
c5e35cc330 refactor: prefix-ed names for easy distinction 2025-07-09 10:42:46 +05:30
ruthra kumar
da32bb5f51 refactor: utility to drop existing procedures and include cost center 2025-07-09 10:42:46 +05:30
ruthra kumar
7b7440d44a refactor: order by posting date 2025-07-09 10:42:46 +05:30
ruthra kumar
e90c6a33bd refactor: call procedures based on config 2025-07-09 10:42:46 +05:30
ruthra kumar
8cf8f6abad refactor: introduce sql option for data fetch 2025-07-09 10:42:46 +05:30
ruthra kumar
097e74979f refactor: better readability 2025-07-09 10:42:45 +05:30
ruthra kumar
e5920c57aa refactor: using sql procedures for AR report
- dynamic filters are passed
2025-07-09 10:42:45 +05:30
Diptanil Saha
c714b724da fix: resolve sql error on dimension-wise accounts balance report (#48477) 2025-07-08 23:41:39 +05:30
Soham Kulkarni
f0697d8f27 Merge pull request #48349 from sokumon/remove-leaderboard
fix: remove Leaderboard shortcut
2025-07-08 22:57:20 +05:30
sokumon
27309d6714 fix: remove Leaderboard shortcut 2025-07-08 22:40:19 +05:30
Mihir Kandoi
c0631468db Merge pull request #48475 from mihir-kandoi/st42934 2025-07-08 22:14:52 +05:30
Mihir Kandoi
d10647a592 fix: indicator in material_request_list.js 2025-07-08 21:58:31 +05:30
Mihir Kandoi
269ac78a98 Merge pull request #48471 from mihir-kandoi/st43209
fix: sort available batches based on expiry when merging SLEs with SABB and those without
2025-07-08 19:47:44 +05:30
Mihir Kandoi
5c665c562a fix: test case 2025-07-08 19:31:03 +05:30
Mihir Kandoi
4d784b8fc7 fix: sort available batches based on expiry 2025-07-08 17:52:19 +05:30
ruthra kumar
dd027f09ac Merge pull request #48469 from ruthra-kumar/better_label_for_ignore_err
chore: better label for checkbox
2025-07-08 17:08:34 +05:30
ruthra kumar
2a16353cf6 Merge pull request #47142 from prateekkaramchandani/feat/ledger-preview-stock-entry
feat: Ledger Preview for Stock Entry
2025-07-08 17:03:19 +05:30
ruthra kumar
8c2e40e291 chore: better label for checkbox 2025-07-08 16:52:24 +05:30
Prateek Karamchandani
5f1ca4113d feat: Ledger Preview for Stock Entry 2025-07-08 16:46:44 +05:30
ruthra kumar
846f0350d8 Merge pull request #48427 from ljain112/fix-psoa-validations
fix: Add company validation to company related fields in Process Statement Of Accounts
2025-07-08 16:05:51 +05:30
Khushi Rawat
95a235e239 Merge pull request #48259 from aerele/item-wise-sales-register
fix: add not specified key for None respresented customer_group and t…
2025-07-08 16:01:57 +05:30
rohitwaghchaure
b7bf2fad84 Merge pull request #48378 from aerele/refactor-quality-inspection
fix: update item reference in quality inspection
2025-07-08 16:01:38 +05:30
ruthra kumar
d0537f2ee4 Merge pull request #48359 from aerele/get_fiscal_year_data
fix: get fiscal year based on date
2025-07-08 15:55:50 +05:30
Khushi Rawat
495afae178 Merge pull request #48402 from aerele/journal-entry-title-rename
fix: rename journal entry title on update
2025-07-08 15:54:33 +05:30
ruthra kumar
83f279410c Merge pull request #48450 from frappe/l10n_develop
fix: sync translations from crowdin
2025-07-08 15:46:06 +05:30
Mihir Kandoi
34f51ae0b2 Merge pull request #48456 from mihir-kandoi/st43167
fix: duplicate items being created when fetching items from warehouse in stock reco
2025-07-08 14:30:22 +05:30
ruthra kumar
5b619c7832 Merge pull request #48328 from ljain112/fix-pricing-rule
fix: update condition for blank tree fields in pricing rule
2025-07-08 14:22:15 +05:30
ruthra kumar
376191b31f Merge pull request #48361 from aerele/revert-48162
revert: do not convert exchange gain/loss amount to foreign currency
2025-07-08 14:22:04 +05:30
ruthra kumar
2c507c891c Merge pull request #48418 from ljain112/jv-paid-loan
chore: remove redundant field "paid_loan" from Journal Entry Doctype
2025-07-08 14:09:11 +05:30
Mihir Kandoi
73f6c29559 fix: duplicate items being created when fetching items from warehouse in stock reco 2025-07-08 13:27:10 +05:30
ruthra kumar
6cac0347ae Merge pull request #48416 from ljain112/refactor-payment-entry
refactor: remove duplicate reconciliation date logic
2025-07-08 13:03:25 +05:30
Diptanil Saha
f5de1ea5c8 Merge pull request #48343 from aerele/validate_with_previous_doc
fix: consider empty string in previous doc validation
2025-07-08 12:39:49 +05:30
rohitwaghchaure
161e336d97 Merge pull request #48444 from mihir-kandoi/st42689
refactor: remove do_reposting_for_each_stock_transaction feature
2025-07-08 12:31:06 +05:30
MochaMind
d2a4cebe54 fix: Persian translations 2025-07-08 12:17:56 +05:30
Diptanil Saha
88255d3d3d Merge pull request #48403 from aerele/validate_selling_price
fix: add selling price validation on update item
2025-07-08 12:14:06 +05:30
ruthra kumar
1cd2266da1 Merge pull request #48326 from ljain112/cost-center-advance-pe
fix: cost center for payment entry against advance payment doctypes in accounts payable/rece
2025-07-08 12:05:25 +05:30
Mihir Kandoi
288c3ee9c2 Merge pull request #48332 from mihir-kandoi/st42096
fix: valuation rate of raw materials in subcontracting receipt
2025-07-08 11:57:45 +05:30
Mihir Kandoi
cae34096c7 Merge pull request #48445 from mihir-kandoi/fix-mr-default-price-list
fix: use default buying price list when price list is falsy
2025-07-08 11:55:00 +05:30
Mihir Kandoi
27c73cf9e9 fix: use default buying price list when price list is falsy 2025-07-08 11:50:43 +05:30
Mihir Kandoi
84ea6afd01 fix: valuation rate of raw materials in subcontracting receipt 2025-07-08 11:42:07 +05:30
Mihir Kandoi
e342b1f7bd refactor: remove do_reposting_for_each_stock_transaction feature 2025-07-08 11:35:45 +05:30
rohitwaghchaure
9f32021d07 Merge pull request #48441 from frappe/revert-47918-fixed-support-40162
Revert "fix: stock reco qty with inventory dimension"
2025-07-08 10:36:18 +05:30
rohitwaghchaure
8ba66c9833 Revert "fix: stock reco qty with inventory dimension (#47918)"
This reverts commit 342cebc778.
2025-07-08 09:56:56 +05:30
ruthra kumar
2012045798 Merge pull request #48324 from ljain112/fix-pr-unreconcile
fix: update payment request outstanding on unreconciliation
2025-07-08 09:45:52 +05:30
MochaMind
7ec4d16403 fix: sync translations from crowdin (#48406) 2025-07-07 17:46:49 +02:00
Raffael Meyer
8cc6853c34 fix: make labels in error message translatable (#48327) 2025-07-07 17:44:56 +02:00
ljain112
3600f2f91b fix: prevent creation of root accounts in account tree view 2025-07-07 19:00:42 +05:30
rohitwaghchaure
7ed05e7d2d Merge pull request #48432 from rohitwaghchaure/fixed-support-42759
fix: fetch from parent optional in inventory dimension
2025-07-07 18:51:01 +07:00
Rohit Waghchaure
8aac6a6b18 fix: fetch from parent optional in inventory dimension 2025-07-07 17:01:01 +05:30
Mihir Kandoi
5fc07842eb Merge pull request #48428 from mihir-kandoi/st42480
fix: address not found when creating internal PR from DN
2025-07-07 15:20:14 +05:30
Mihir Kandoi
aa2c56e117 Merge pull request #48425 from mihir-kandoi/st42648-2
feat: add price list field to material request
2025-07-07 15:03:47 +05:30
Mihir Kandoi
97c48ed6d2 fix: address not found when creating internal PR from DN 2025-07-07 15:01:31 +05:30
ljain112
4e45e69247 fix: Add company validation to company related fields in Process Statement Of Accounts 2025-07-07 14:43:04 +05:30
Mihir Kandoi
0e881f2999 Merge pull request #48424 from mihir-kandoi/st42959
fix: item list and project not being set in work order when created from material request
2025-07-07 14:17:39 +05:30
Mihir Kandoi
f4c6bdf204 feat: add price list field to material request 2025-07-07 14:14:16 +05:30
Mihir Kandoi
099a5fbad9 fix: item list and project not being set in work order when created from material request 2025-07-07 13:01:41 +05:30
Karm Soni
e60c711fdc fix: correct query filter assignment in stock ledger and balance reports 2025-07-07 11:06:46 +05:30
MochaMind
0a41fe2541 chore: update POT file (#48417) 2025-07-06 14:57:09 +02:00
ljain112
2ee463fa33 chore: remove redundant field "paid_loan" from Journal Entry Doctype 2025-07-06 15:24:45 +05:30
ljain112
398406082a refactor: remove duplicate reconciliation date logic 2025-07-06 15:03:06 +05:30
Lakshit Jain
277c1101fc fix: multiple fixes related Deferred Accounting 2025-07-06 14:55:45 +05:30
Diptanil Saha
ee0dd462b8 Merge pull request #48357 from aerele/fix-pos-merge-log
fix: add company field on POS Invoice Merge Log
2025-07-06 14:26:50 +05:30
thomasantony12
09541c52e1 fix: employee search based on the fields mentioned in the doctype searchfields 2025-07-06 11:22:24 +05:30
Kavin
9548f341bf test: test company fetching from POS Closing Entry 2025-07-05 21:59:53 +05:30
Kavin
b4b473185f fix: pass company on create_merge_logs 2025-07-05 21:58:07 +05:30
Kavin
d46b68230c fix: patch for updating company name on existing pos merge log records 2025-07-05 13:34:52 +05:30
Kavin
109658731b fix: add company field on POS Invoice Merge Log 2025-07-05 13:34:11 +05:30
l0gesh29
ec07549d5e fix: resolve bundle item into line item if againt default supplier checked 2025-07-05 12:48:50 +05:30
Diptanil Saha
90be3cddf7 fix: pos payment numpad error on currency precision not set (#48407) 2025-07-05 06:53:29 +00:00
Karm Soni
0a71ca6739 fix(test): update tests 2025-07-05 10:04:34 +05:30
Raffael Meyer
2928d39d58 fix(Quotation): hide buttons if user cannot use them (#48115) 2025-07-04 18:03:39 +00:00
Mihir Kandoi
407fdab487 feat: add subject field to project (#48368)
* feat: add subject field to project
2025-07-04 16:09:34 +00:00
ravibharathi656
9e633bddef chore: add none value 2025-07-04 20:03:25 +05:30
l0gesh29
327d067305 fix: add selling price validation on update item 2025-07-04 19:57:22 +05:30
ravibharathi656
acb9829159 fix: rename journal entry title on update 2025-07-04 19:26:55 +05:30
Mihir Kandoi
b0535bff34 Merge pull request #48372 from mihir-kandoi/st42648
fix: rate not being fetched for product bundles in material request
2025-07-04 12:45:34 +05:30
Mihir Kandoi
0da90f8092 Merge pull request #48374 from mihir-kandoi/st42781-d
fix: LCV from PR order mismatch
2025-07-04 12:45:02 +05:30
l0gesh29
efb8e7c0e4 fix: get fiscal year based on date 2025-07-04 12:23:06 +05:30
mahsem
80d6779210 fix: employee_exit_translatability 2025-07-04 08:26:52 +02:00
Mihir Kandoi
32a45cf635 fix: LCV from PR order mismatch 2025-07-04 11:37:15 +05:30
Mihir Kandoi
45c7bac2d0 fix: rate not being fetched for product bundles in material request 2025-07-04 11:36:51 +05:30
MochaMind
59ae667cce fix: sync translations from crowdin (#48316)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-07-04 05:50:27 +00:00
Diptanil Saha
50bf4017d6 fix: pos minor issues (#48384)
* fix: remove tax rate on pos

* fix: use toggle components to load invoices

* refactor: remove resize selector
2025-07-03 22:50:23 +05:30
Diptanil Saha
5f721f01d3 fix: pos recent order display customer code and name (#48379) 2025-07-03 22:49:09 +05:30
Karm Soni
f2afd98725 feat: update stock ledger report to support multi-select for warehouses and items 2025-07-03 18:04:05 +05:30
Karm Soni
2882576479 refactor: use existing functionality 2025-07-03 16:26:58 +05:30
Karm Soni
2ff1dcc391 feat: enhance apply_warehouse_filter to support multiple warehouses in filters 2025-07-03 16:24:27 +05:30
l0gesh29
dd43594ad6 fix: consider empty string in previous doc validation 2025-07-03 16:04:19 +05:30
venkat102
c17ae703c7 revert: do not convert exchange gain/loss amount to foreign currency 2025-07-03 16:03:21 +05:30
pugazhendhivelu
9da5010265 fix: update item reference in quality inspection 2025-07-03 15:44:00 +05:30
ravibharathi656
39cd7a29df fix: fetch sales invoice based on mode_of_payment in item-wise sales register 2025-07-03 15:38:05 +05:30
Diptanil Saha
86b37782fe fix: pos payment section (#48366) 2025-07-03 15:16:07 +05:30
ruthra kumar
1ee8a9f257 Merge pull request #48375 from ljain112/fix-flaky-test-tds
chore: fix flaky test in Tax Withholding Details
2025-07-03 14:21:06 +05:30
ruthra kumar
704223e5d0 fix(test): flaky budget test case 2025-07-03 14:06:09 +05:30
ljain112
7ee2418f60 fix: sort tax withhodling details report by section code and transaction date 2025-07-03 13:34:33 +05:30
ljain112
14a2f98521 chore: fix flaky test in Tax Withholding Details 2025-07-03 13:11:37 +05:30
Mihir Kandoi
7dbc821731 Merge pull request #48333 from mihir-kandoi/fix-job-card-buttons
fix: multiple buttons in job card showing in a single row
2025-07-03 13:07:58 +05:30
Karm Soni
0d2a88bafc feat: update stock balance report to support multi-select for items and warehouses 2025-07-03 12:59:00 +05:30
Akhil Narang
072518ed96 build: bump dependencies (#48122)
* build(deps): drop `pycountry`

Framework includes this

Signed-off-by: Akhil Narang <me@akhilnarang.dev>

* build(deps): bump unidecode

Signed-off-by: Akhil Narang <me@akhilnarang.dev>

* build(deps): bump holidays

Signed-off-by: Akhil Narang <me@akhilnarang.dev>

* build(deps): pin googlemaps

Signed-off-by: Akhil Narang <me@akhilnarang.dev>

* build(deps): bump python-youtube

Signed-off-by: Akhil Narang <me@akhilnarang.dev>

---------

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-07-02 11:44:49 +05:30
rohitwaghchaure
863507ea28 Merge pull request #48173 from mihir-kandoi/st41207
fix: incorrect pending qty when creating PI from PO and PI rates differ from PO
2025-07-01 17:48:12 +05:30
ruthra kumar
0b7f73fa8b Merge pull request #48325 from ljain112/refactor-po-validate
perf: use get_all instead of get_value for validating po dates
2025-07-01 16:23:44 +05:30
ruthra kumar
37727448f6 Merge pull request #48244 from aerele/reconcile-drcr-posting-date
fix: use gain_loss_posting_date instead of today
2025-07-01 15:41:43 +05:30
Mihir Kandoi
74df63a28a fix: multiple button in job card showing in a single row 2025-07-01 11:44:03 +05:30
Jeba Jebas
bb62a01c0d feat: added chart of accounts and tax template for australian localisation (#48208)
* Add Australian Localisation Setup

* feat: added chart of accounts and tax template for australian localisation

* chore: linter fix

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-07-01 10:11:37 +05:30
ljain112
7e0e9db4d2 fix: update condition for blank tree fields in pricing rule 2025-06-30 19:12:49 +05:30
ljain112
48e8e85617 refactor: function to fetch advance payment doctypes 2025-06-30 17:49:56 +05:30
ljain112
8f19f14004 fix: cost center for payment entry against advance payment doctypes in accounts Payable/Receivable report 2025-06-30 17:35:08 +05:30
ljain112
aac4ac0fae perf: use get_all instead of get_value for validating po dates 2025-06-30 15:58:04 +05:30
ljain112
31d12517f0 chore: fix test case for payment request 2025-06-30 15:04:27 +05:30
ljain112
8098229b55 fix: update payment request outstanding on unreconciliation 2025-06-30 14:18:40 +05:30
Diptanil Saha
8ea9cb1d34 Merge pull request #48279 from aerele/pos-search-by-customer-name
fix(pos invoice): search using customer name
2025-06-30 14:13:25 +05:30
ravibharathi656
6a401bcfbb refactor: use or_filters for customer and customer_name 2025-06-30 12:51:14 +05:30
ravibharathi656
20fd071c4e fix(pos invoice): search using customer name 2025-06-30 12:51:14 +05:30
ljain112
db654d5e59 fix: fetch item tax template after setting base_net_rate 2025-06-30 12:31:39 +05:30
rohitwaghchaure
abfff79095 Merge pull request #48298 from FHenry/dev_fix_support_42326
fix: default UOMs by new Stock Entry created by Stock Level section button (when Item is batch or serial)
2025-06-30 11:02:05 +05:30
rohitwaghchaure
8aafd893ed Merge pull request #48240 from mihir-kandoi/st41963
refactor: bom stock report
2025-06-30 11:01:14 +05:30
rohitwaghchaure
21118d5373 Merge pull request #48184 from iamkhanraheel/update-disassembly-items
fix: Disassembly order items calculation in stock entry & track it in work order
2025-06-30 11:00:58 +05:30
ruthra kumar
2c7262b033 Merge pull request #48291 from ljain112/fix-bank-clearance
perf: use lazy doc for updating bank clearance_date
2025-06-30 10:34:09 +05:30
ruthra kumar
dc1be35dbb Merge pull request #48271 from aerele/company-default-currency-for-commission
fix: use company default currency in amount_eligible_for_commission
2025-06-30 10:08:51 +05:30
ruthra kumar
ee3f4c21be Merge pull request #48304 from zaqoutabed/translation-patch-1
chore: fix translation message
2025-06-30 10:02:58 +05:30
Abdallah A. Zaqout
bc002937ad chore: fix translation message 2025-06-30 09:47:30 +05:30
rohitwaghchaure
2af95d2339 Merge pull request #48310 from rohitwaghchaure/fixed-support-40224
fix: accounting entries for standalone credit notes
2025-06-30 09:38:37 +05:30
Khushi Rawat
d69d5b498d Merge pull request #48311 from khushi8112/validate-asset-status-for-repair
fix: validate asset status for repair
2025-06-30 00:55:29 +05:30
khushi8112
cfe04a2aaf test: asset status validation 2025-06-30 00:29:23 +05:30
khushi8112
c6baa34812 fix: validate asset before repair 2025-06-29 22:46:39 +05:30
Rohit Waghchaure
52177cffcd fix: accounting entries for standalone credit notes 2025-06-29 21:45:57 +05:30
MochaMind
344bcf1448 fix: sync translations from crowdin (#48302) 2025-06-29 16:01:36 +02:00
MochaMind
45292700d4 chore: update POT file (#48307) 2025-06-29 13:12:24 +02:00
Diptanil Saha
c742a1dbe9 feat: partly paid pos invoices (#48246)
* fix: partial payment in pos

* fix: show alerts for update failure

* fix: partial payment validation

* fix: remove setting clearance date

* fix: partly paid invoices in pos

* fix: throw error if user tries to make payment for consolidated invoice

* fix: include unpaid invoices in partly paid invoice filter

* refactor: function rename

* feat: button to open form view for partly paid invoices in pos order summary

* fix: payment menu item visible for unpaid invoices

* refactor: update_payments function

* fix: set outstanding amount for pos invoice

* test: partly paid pos invoices

* test: removed frappe.db.commit

* refactor: using before_submit to set outstanding amount
2025-06-28 00:48:23 +05:30
rohitwaghchaure
1cb7d5126c Merge pull request #48296 from rohitwaghchaure/fixed-support-42161
fix: not able to save material request
2025-06-27 16:24:31 +05:30
Florian HENRY
e7da4992f3 fix: default UOMs by new stock Entry created by Stock Level section button 2025-06-27 12:54:28 +02:00
Rohit Waghchaure
c5e36eb323 fix: not able to save material request 2025-06-27 16:07:08 +05:30
rohitwaghchaure
4dbf4a214d Merge pull request #48293 from rohitwaghchaure/fixed-support-42204
fix: option to pick serial / batch for asset repair
2025-06-27 15:28:01 +05:30
Rohit Waghchaure
ae77c609ff fix: option to pick serial / batch for asset repair 2025-06-27 14:53:11 +05:30
MochaMind
195911ce4e fix: sync translations from crowdin (#48268) 2025-06-27 10:45:05 +02:00
ljain112
d2983b977c perf: use lazy doc for updating clearance_date 2025-06-27 13:54:27 +05:30
ljain112
824a86c503 chore: validate all payment entries before updating the clearance_date 2025-06-27 13:53:16 +05:30
rohitwaghchaure
a0a8428483 Merge pull request #48243 from rohitwaghchaure/fixed-status-stock-reservation
fix: multiple fixes related to stock reservation
2025-06-27 13:51:07 +05:30
Rohit Waghchaure
87a472c2d7 fix: multiple fixes related to stock reservation 2025-06-27 12:46:02 +05:30
Soham Kulkarni
a926c7eafd Merge pull request #48288 from sokumon/remove-newsletter
fix: remove newsletter related code
2025-06-27 11:59:40 +05:30
sokumon
b630ccc8e6 fix: remove newsletter related code 2025-06-27 01:21:33 +05:30
Raffael Meyer
0426b37f32 fix: use label "State/Province" for translatability (#48273) 2025-06-26 20:20:17 +02:00
Diptanil Saha
e1d9f863c6 fix: customer section on pos item cart (#48284)
* fix: customer recent transactions

* fix: pos customer section display customer_name instead of customer name
2025-06-26 22:49:18 +05:30
l0gesh29
24cc711a70 fix: add not specified key for None respresented customer_group and territory 2025-06-26 18:32:54 +05:30
ravibharathi656
7c7b392789 fix: use company default currency in amount_eligible_for_commission 2025-06-26 18:09:23 +05:30
Priyansh Shah
fab0f4f337 fix: Multiple Issues in Pick List to Delivery Note Flow (#48206)
* fix: get items from Pick List to DN even if not linked to Sales Order

* refactor: consistently return dn; better place to convert json to doc

* fix: update DN if already created instead of creating new DN when SO is not present in pick list location

* fix: set correct warehouse,batch no and serial no in packed items and allow multiple customer in a pick list

* fix: return 0 for minimum possible bundles if none exist

* fix: test cases

* test: add tests for product bundle items in pick list and handling pick lists with and without sales orders

* fix: minor change to test case

* refactor: simplify pick list creation by using create_pick_list function

* fix: update delivery note creation logic and remove unused function

* test: update pick list test for packed items

* fix: add conditional check for sales_order before setting customer in delivery note

* test: add test case for packed item multiple times in so

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
2025-06-26 17:55:48 +05:30
iamkhanraheel
aee26c3550 test: added test case for disassembly order 2025-06-26 17:21:34 +05:30
Diptanil Saha
1e929e2c6c fix: pos opening and closing validation (#48059)
* fix: pos opening and closing validation

* test: pos opening entry tests

* test: added test for pos opening entry

* fix: patch to set status cancelled on cancelled POS Opening Entry and POS Closing Entry

* fix: error messages
2025-06-26 16:26:53 +05:30
Sagar Vora
664872a07b Merge pull request #48274 from sagarvora/rm-print-heading 2025-06-26 10:45:55 +00:00
Sagar Vora
19a8303c1a fix: remove print heading doctype 2025-06-26 15:13:18 +05:30
0xD0M1M0
1b18105bce fix: customer_group import from lead to customer (#48266)
In case customization happens and the lead has the field "customer_group", the get_mapped_doc function would fail and be overwritten by the default value.
2025-06-25 23:25:32 +02:00
Khushi Rawat
7181e67ad4 Merge pull request #48242 from khushi8112/salvage-value-update-for-cancellation
fix: add missing cancellation handling for salvage value update
2025-06-25 00:21:11 +05:30
rohitwaghchaure
66eeda6410 fix: stock adjustment entry to make stock balance zero (#48245) 2025-06-24 21:58:33 +05:30
ravibharathi656
0585bc5aef fix: use gain_loss_posting_date instead of today 2025-06-24 18:48:22 +05:30
khushi8112
c13df330f3 fix: add missing cancellation handling for salvage value update 2025-06-24 18:31:54 +05:30
Mihir Kandoi
ee4e0c646d refactor: bom stock report 2025-06-24 18:13:38 +05:30
Sagar Vora
a571a5bf70 Merge pull request #48038 from ljain112/fix-test 2025-06-24 11:32:22 +00:00
Sagar Vora
01bfc25161 Merge branch 'develop' into fix-test 2025-06-24 11:30:42 +00:00
Khushi Rawat
48d3e0d0a2 Merge pull request #48228 from khushi8112/update-salvage-value-after-revaluation
fix: update salvage value after value adjustment
2025-06-24 17:00:00 +05:30
ruthra kumar
b99dd34a4b Merge pull request #48226 from aerele/trends_reports
Trends reports
2025-06-24 15:47:51 +05:30
Lakshit Jain
47c3c4808e fix: get already billed amount from current doc instead of database (#48079)
* fix: get already billed amount from current doc instead of database

* fix: throw overbilling validation for all items in single call

* refactor: minor fixes

---------

Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-06-24 15:40:00 +05:30
ljain112
7bc88ffe0e chore: cache clears on custom field and property setter creation/delete 2025-06-24 15:19:40 +05:30
ruthra kumar
635ce0e94a Merge pull request #47627 from ljain112/fix-item--tax-template-taxes
fix: auto append_taxes_from_item_tax_template in backend
2025-06-24 15:16:37 +05:30
khushi8112
dc28fa79a0 test: change in expected value after useful after asset revaluation 2025-06-24 14:36:22 +05:30
Karuppasamy923
b08d66113c fix: Update indexing to populate correct values in trends report chart 2025-06-24 13:30:06 +05:30
Karuppasamy923
b0e201a332 fix: Update transaction currency to company currency to show correct currency symbol 2025-06-24 13:27:52 +05:30
khushi8112
5e672990b3 fix: salvage value after value adjustment 2025-06-24 12:42:34 +05:30
Mihir Kandoi
ea6ff2defe fix: test case 2025-06-24 12:24:58 +05:30
Mihir Kandoi
ab77ee7f5a fix: incorrect pending qty when creating PI from PO and PI rates differ from PO 2025-06-24 12:15:03 +05:30
Mihir Kandoi
c4cd4a660b Merge pull request #48150 from mihir-kandoi/st41475
fix: disallow posting date of purchase receipt and invoice before PO transaction date
2025-06-24 12:09:55 +05:30
Mihir Kandoi
77e82f4a80 fix: disallow posting date of purchase receipt and invoice before PO transaction date 2025-06-24 11:54:02 +05:30
Mihir Kandoi
f40c78f838 Merge pull request #48078 from mihir-kandoi/st41233
fix: fallback expense account and cost center in subcontracting receipt
2025-06-24 11:48:51 +05:30
Mihir Kandoi
d294afa46e Merge pull request #48123 from mihir-kandoi/fix-zero-rate-toast
fix: allow zero valuation rate toast showing even when rate is not 0
2025-06-24 11:45:20 +05:30
Akhil Narang
c8c1c96298 fix(open_opportunity): remove company=null filter (#48222)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-06-24 05:57:54 +00:00
ruthra kumar
23e3883c13 Merge pull request #48180 from aerele/journal-entry-title-change
fix: update journal entry title on amend
2025-06-24 09:32:40 +05:30
ruthra kumar
7c2812b7f2 Merge pull request #48210 from iamkhanraheel/add-item_name
feat: Display Item Name in items table shown while creating Work order from Sales Order
2025-06-24 09:22:41 +05:30
ljain112
4cb1fa2b6b fix: auto append_taxes_from_item_tax_template in backend 2025-06-24 09:16:28 +05:30
MochaMind
5840645663 fix: sync translations from crowdin (#48085) 2025-06-23 18:56:24 +02:00
Diptanil Saha
919684a787 fix: pos item price in get_item and item search (#47925)
* fix: pos get item and item search

* refactor: resolved linter issue and renamed variables

* fix: uom on get_item

* fix: incorrect item quantity on pos selector

* refactor: remove unused import
2025-06-23 22:25:32 +05:30
rohitwaghchaure
ce2b98a38d Merge pull request #48195 from rohitwaghchaure/fixed-support-41648
fix: incoming rate for the stand-alone credit note
2025-06-23 21:09:52 +05:30
rohitwaghchaure
51429b3642 Merge pull request #48204 from rohitwaghchaure/fixed-stock-reservation-status
fix: stock reservation status
2025-06-23 20:57:08 +05:30
Rohit Waghchaure
b06eca8dcb fix: incoming rate for the stand-alone credit note 2025-06-23 20:47:25 +05:30
Rohit Waghchaure
62cf0504d8 fix: stock reservation status 2025-06-23 20:40:12 +05:30
ruthra kumar
7cabf85f74 Merge pull request #48053 from aerele/fetch-barcode-items
fix: add descendants item groups to fetch the barcode items
2025-06-23 19:30:27 +05:30
ruthra kumar
2d3c8dd4f6 Merge pull request #48162 from aerele/gl_entry_exchange_gain_or_loss
fix: add validation for exchange gain/loss entries
2025-06-23 19:29:18 +05:30
iamkhanraheel
ddeeb194cd feat: add item name in items listed while creating work order 2025-06-23 17:44:47 +05:30
ruthra kumar
2dc05a369b Merge pull request #48200 from ruthra-kumar/better_description
chore: better label and desciption for pegged currency flag
2025-06-23 17:43:27 +05:30
ruthra kumar
c5cd7d91c4 chore: better label and desciption for pegged currency flag 2025-06-23 17:27:57 +05:30
ruthra kumar
05fb3a1304 Merge pull request #47761 from frappe/new-sql-functions-syntax-qb-query
fix: update sql function usage syntax
2025-06-23 17:24:58 +05:30
ruthra kumar
289694ed4c chore: resolve linter 2025-06-23 17:09:29 +05:30
rohitwaghchaure
64a83195e9 Merge pull request #48194 from iamkhanraheel/fix-party-specific-exist-filter
fix: filter to validate existing item in Party Specific Item
2025-06-23 16:49:46 +05:30
mergify[bot]
eb4455cb3e Merge branch 'develop' into new-sql-functions-syntax-qb-query 2025-06-23 11:03:08 +00:00
ruthra kumar
08ef197906 Merge pull request #48013 from aerele/mt940-bank-statement-import
feat(bank-statement-import): add support for uploading MT940 format b…
2025-06-23 15:45:27 +05:30
ruthra kumar
d152ab9b88 Merge pull request #48096 from aerele/sales-trend-party-link
fix: add party and party_name columns to trend reports
2025-06-23 15:41:53 +05:30
ruthra kumar
727c5ce323 Merge pull request #48186 from aerele/sales_order_filter_update
fix: add is_group filter for warehouse
2025-06-23 15:35:57 +05:30
ruthra kumar
9ba61b54e3 Merge pull request #47219 from barredterra/find-row
fix: find table row
2025-06-23 15:20:42 +05:30
iamkhanraheel
99bc02d0e0 fix: update fieldname in filter 2025-06-23 14:20:27 +05:30
ruthra kumar
0b23e808c7 Merge pull request #48148 from ruthra-kumar/fix_dormant_test_in_payment_request
chore: activate dormant test case in payment request
2025-06-23 11:46:42 +05:30
ruthra kumar
8289dc6885 chore: activate dormant test case in payment request 2025-06-23 11:32:03 +05:30
iamkhanraheel
ce6ace4b8a fix: func parameters 2025-06-22 21:33:02 +05:30
MochaMind
0769174206 chore: update POT file (#48189) 2025-06-22 11:51:36 +00:00
rohitwaghchaure
9e8d35bf62 Merge pull request #48126 from rohitwaghchaure/feat-periodic-accounting
feat: periodic accounting
2025-06-22 13:10:18 +05:30
Rohit Waghchaure
938be22ae9 feat: periodic accounting 2025-06-22 12:48:57 +05:30
Faris Ansari
74bd07f10b fix: sql function syntax 2025-06-21 16:50:59 +05:30
Faris Ansari
a397c1dea8 fix: update sql function usage syntax 2025-06-21 16:50:59 +05:30
Faris Ansari
300530d35a fix: change sql function string to pypika
sql function string not supported anymore
2025-06-21 16:50:59 +05:30
Faris Ansari
20e2fda183 fix: field can't be a dict 2025-06-21 16:50:59 +05:30
Faris Ansari
6a3fc36979 fix: update sql function usage syntax 2025-06-21 16:50:59 +05:30
Faris Ansari
495bf4b765 chore: set correct payments app branch 2025-06-21 16:50:59 +05:30
Faris Ansari
6bc59d23ea fix: update sql function usage syntax
based on https://github.com/frappe/frappe/pull/32381
2025-06-21 16:50:59 +05:30
Karuppasamy923
a29ae9cf90 fix: add is_group filter for warehouse 2025-06-21 14:06:59 +05:30
iamkhanraheel
3e4d160626 fix: disassemble qty calculation & max calculation to be allowed to create it 2025-06-21 01:14:26 +05:30
rohitwaghchaure
9491f6f83f Merge pull request #48181 from rohitwaghchaure/fixed-support-41516
fix: SABB validation during the LCV
2025-06-20 21:23:44 +05:30
Rohit Waghchaure
e958f886d3 fix: SABB validation during the LCV 2025-06-20 21:06:43 +05:30
ravibharathi656
4a3ee4df29 fix: update journal entry title on amend 2025-06-20 17:47:09 +05:30
Diptanil Saha
4aa4942a17 fix: pos item details fetch uoms on stock settings allow_uom_with_conversion_rate_defined_in_item configuration (#48178) 2025-06-20 16:19:58 +05:30
Sagar Vora
7502a4aba3 Merge pull request #48111 from sagarvora/use-frappe.in_test 2025-06-20 08:24:27 +00:00
Sagar Vora
7e897c4a5d Merge branch 'develop' into use-frappe.in_test 2025-06-20 08:09:35 +00:00
Khushi Rawat
5c24fc3485 Merge pull request #48151 from khushi8112/duplicate-naming-issue-in-contract-doctype
fix: duplicate naming issue in contract doctype
2025-06-20 12:58:10 +05:30
rohitwaghchaure
ed9346a435 Merge pull request #48165 from rohitwaghchaure/fixed-support-40642-1
fix: target inventory dimension for stock entry
2025-06-19 19:52:59 +05:30
Rohit Waghchaure
d65cb56d66 fix: target inventory dimension for stock entry 2025-06-19 19:33:26 +05:30
khushi8112
a1c0727d7b chore: remove unused import 2025-06-19 18:40:37 +05:30
i-am-vimal
5c9eddd31e fix: add validation for exchange gain/loss entries 2025-06-19 18:14:32 +05:30
khushi8112
4a027125bc refactor: remove test case 2025-06-19 18:09:54 +05:30
rohitwaghchaure
9cd44833b3 Merge pull request #48050 from iamkhanraheel/rearrange-quotation-item-fields
feat: Move "Is Free Item", "Is Alternative Item", and "Has Alternative Item" Checkboxes to Item Section in Quotation Child Table
2025-06-19 18:09:42 +05:30
rohitwaghchaure
b11c72958e Merge pull request #48072 from iamkhanraheel/update-schedule-date
fix: update schedule date in items table in purchase order & disable selecting past date in schedule date
2025-06-19 18:09:18 +05:30
khushi8112
0665691b88 feat: add search field for contract doctype 2025-06-19 18:09:10 +05:30
rohitwaghchaure
b56337ecb9 Merge pull request #48089 from iamkhanraheel/add-margin-fields
fix: attribute error because of missing margin_type field in Supplier Quotation
2025-06-19 18:08:46 +05:30
rohitwaghchaure
44b51bdd02 Merge pull request #48137 from rohitwaghchaure/fixed-make-se-in-job-card
fix: make stock entry in job card
2025-06-19 17:55:45 +05:30
Diptanil Saha
2f8893439f fix: coa reset root_type on unchecking is_group on new_node (#48156) 2025-06-19 17:47:28 +05:30
khushi
bf56c73c6c feat: add naming series for Contract Doctype 2025-06-19 17:45:22 +05:30
rohitwaghchaure
f154c70289 Merge pull request #48131 from rohitwaghchaure/fixed-support-41105
fix: permission issue during reposting
2025-06-19 17:42:20 +05:30
rohitwaghchaure
a25356a99e Merge pull request #48130 from rohitwaghchaure/fixed-gl-entries-lcv-for-se-scr
fix: adjustment entries for SE and SCR after LCV
2025-06-19 17:42:07 +05:30
rohitwaghchaure
29ff258ccb Merge pull request #48127 from rohitwaghchaure/fixed-valuation-rate-stock-entry
fix: additional cost distribution
2025-06-19 17:38:24 +05:30
Rohit Waghchaure
abb05f59e2 fix: make stock entry in job card 2025-06-19 17:25:54 +05:30
Rohit Waghchaure
dcc9fc2fec fix: permission issue during reposting 2025-06-19 17:25:13 +05:30
Rohit Waghchaure
2f50ddae85 fix: adjustment entries for SE and SCR after LCV 2025-06-19 17:24:30 +05:30
Rohit Waghchaure
abbf2fac6d fix: additional cost distribution 2025-06-19 17:18:48 +05:30
rohitwaghchaure
d75bdd9f72 Merge pull request #48155 from rohitwaghchaure/fixed-test-case-for-sre-for-so
fix: test case
2025-06-19 17:18:14 +05:30
khushi
a4bb7c4e95 refactor: remove autoname 2025-06-19 17:13:01 +05:30
Rohit Waghchaure
2f0c0c2b59 fix: test case 2025-06-19 16:55:22 +05:30
Smit Vora
527cfe9c7d fix: better integration of Pick List with Delivery Note (#47831)
Co-authored-by: priyanshshah2442 <priyanshshah2442@gmail.com>
2025-06-19 15:42:37 +05:30
khushi
f7e63936a9 chore: linters check 2025-06-19 15:07:44 +05:30
khushi
b55d1e61c7 chore: test contract autoname 2025-06-19 15:04:24 +05:30
khushi
e13e2bffe2 fix: contract autoname 2025-06-19 15:04:24 +05:30
Khushi Rawat
1170c5c7d3 Merge pull request #48152 from khushi8112/fix-failing-test
chore: fix incorrect test
2025-06-19 14:50:17 +05:30
khushi
7e4db0f70e chore: fix incorrect test 2025-06-19 14:32:00 +05:30
Diptanil Saha
c94764ab52 fix: naming series field in bank transaction (#48121)
* fix: naming series field in bank transaction

* fix: default naming_series
2025-06-19 12:37:11 +05:30
Khushi Rawat
8c3ed5eccf Merge pull request #47789 from khushi8112/gl-entry-for-composite-component
fix: correct gl entry when capitalizing composite component
2025-06-18 19:16:24 +05:30
Diptanil Saha
80f992c87f test: purchase invoice provisional accounting entry (#48112)
* test: fixed purchase invoice provisional accounting entry

* test: added tests for multi currency
2025-06-18 18:26:49 +05:30
Diptanil Saha
14f0569a39 fix: setup wizard load chart of accounts and fiscal year on change of country (#48125) 2025-06-18 17:16:03 +05:30
Mihir Kandoi
41b876ff71 fix: allow zero valuation rate toast showing even when rate is not 0 2025-06-18 16:31:53 +05:30
rohitwaghchaure
e2c9e24f66 Merge pull request #47988 from rohitwaghchaure/fixed-support-40403
fix: stock reconciliation validation for serial nos
2025-06-17 23:00:07 +05:30
Rohit Waghchaure
69d54d2e0f fix: stock reconciliation validation for serial and batch 2025-06-17 22:31:40 +05:30
Sagar Vora
77f17c987d chore: ignore commit replacing flag usage 2025-06-17 21:31:14 +05:30
Sagar Vora
7a482a6998 chore: use frappe.in_test 2025-06-17 21:28:21 +05:30
rohitwaghchaure
5d4405c7f2 Merge pull request #48077 from mihir-kandoi/40957
fix: use set_query on sales_order link field in work order
2025-06-17 17:37:38 +05:30
Sagar Vora
d079db16d6 Merge pull request #48104 from ljain112/fix-conditions 2025-06-17 11:25:19 +00:00
ljain112
20565f5f19 fix: incorrect condition for setting party account on change of company 2025-06-17 16:30:56 +05:30
ruthra kumar
5683032434 Merge pull request #48103 from ruthra-kumar/restrictive_github_actions
ci: restrictive permission for GH actions
2025-06-17 16:08:50 +05:30
ruthra kumar
fa82d3fbb1 ci: restrictive permission for gh actions 2025-06-17 15:41:48 +05:30
ruthra kumar
10a83698eb Merge pull request #48098 from ljain112/fix-retunr-doc
fix: do not reset party account for return doc
2025-06-17 15:29:04 +05:30
Mihir Kandoi
6def182e1a fix: use set_query on sales_order link field in work order 2025-06-17 15:18:24 +05:30
Karuppasamy
cec0ffad06 Merge pull request #47898 from aerele/pegged-currency
Pegged currency
2025-06-17 15:08:31 +05:30
ljain112
7e758a9d5b fix: do not reset party account for return doc 2025-06-17 14:32:20 +05:30
Khushi Rawat
01cd3b6712 Merge pull request #47676 from khushi8112/better-ux-in-asset-capitalization
fix(UI): better ui/ux in asset capitalization
2025-06-17 14:10:03 +05:30
ruthra kumar
a69bfc2e78 Merge pull request #48082 from aerele/get-party-account
fix:add validation for party type
2025-06-17 13:54:59 +05:30
ruthra kumar
e4a35137b4 Merge pull request #47879 from aerele/gl_exchange_rate_entry
fix: add validation for exchange gain/loss entries
2025-06-17 13:33:55 +05:30
Karuppasamy923
d05204a960 fix: add party and party_name columns to trend reports 2025-06-17 13:29:08 +05:30
rohitwaghchaure
fa23f1a37e Merge pull request #48076 from iamkhanraheel/restrict-negative-qty-in-bom
fix: Prevent saving negative quantity in BOM
2025-06-17 13:18:41 +05:30
rohitwaghchaure
f4617d77a7 Merge pull request #48033 from rohitwaghchaure/fixed-reservation-entry-based-on
fix: Reservation Based On in the reservation entry
2025-06-17 12:54:05 +05:30
ruthra kumar
6a3e38e6ef Merge pull request #48087 from ruthra-kumar/restrictive_permissions_on_github_actions
ci: restrictive permission on github token
2025-06-17 12:22:13 +05:30
iamkhanraheel
67f96c66e4 fix: add missing margin related fields 2025-06-17 12:19:28 +05:30
l0gesh29
6ea32a8762 test: add test for debit/credit calculations in exchange gain/loss account filter in GL 2025-06-17 12:14:51 +05:30
l0gesh29
d992f67658 fix: add validation for exchange gain/loss entries 2025-06-17 12:13:23 +05:30
Khushi Rawat
7e5274f650 Merge pull request #48086 from khushi8112/update-asset-status-after-revaluation
fix: update asset status after making asset value adjustment record
2025-06-17 12:02:44 +05:30
ruthra kumar
237f9640d5 ci: restrictive permission on github token 2025-06-17 12:02:35 +05:30
Khushi Rawat
da2663b8dc fix: update asset status after making asset value adjustment record 2025-06-17 11:42:00 +05:30
Rohit Waghchaure
57429e5d90 fix: Reservation Based On in the reservation entry 2025-06-17 09:14:40 +05:30
rohitwaghchaure
28642dd9bd Merge pull request #47970 from rohitwaghchaure/feat-lcv-for-wo-scr
feat: LCV for Work Order and Subcontracting Receipt
2025-06-16 20:35:12 +05:30
Rohit Waghchaure
6be6eab6ff feat: LCV for Work Order and Subcontracting Receipt 2025-06-16 20:18:55 +05:30
AlcinSnowlina
7c9d6aaae2 fix: add validation for party type 2025-06-16 18:47:28 +05:30
ljain112
1e4fc043a8 chore: Refactor test cases to use context manager for user setting 2025-06-16 17:56:30 +05:30
Mihir Kandoi
cf1d4362e5 fix: fallback expense account and cost center in subcontracting receipt 2025-06-16 17:11:17 +05:30
Diptanil Saha
c4bdf2a721 fix: budget naming series (#48075) 2025-06-16 16:27:18 +05:30
iamkhanraheel
e52d83e756 fix: prevent saving negative quantity in BOM 2025-06-16 16:17:15 +05:30
iamkhanraheel
94b45d174a fix: code format 2025-06-16 16:07:52 +05:30
iamkhanraheel
f71a0fa9e1 fix: code format 2025-06-16 15:49:50 +05:30
iamkhanraheel
58565a9fb1 fix: add new line to pass pre commit 2025-06-16 15:28:11 +05:30
Sagar Vora
74af9e32f8 Merge pull request #48071 from sagarvora/test++ 2025-06-16 09:49:13 +00:00
Sagar Vora
b527bb771b test: use self.set_user instead of classname 2025-06-16 15:02:39 +05:30
iamkhanraheel
10825af531 fix: update schedule date in items child table, prevent selecting past dates in schedule date 2025-06-16 14:59:49 +05:30
rohitwaghchaure
43395f922f Merge pull request #48069 from rohitwaghchaure/fixed-fitures-perm-issue
fix: permission issue while completing setup wizard
2025-06-16 14:19:36 +05:30
Rohit Waghchaure
666bf65f48 fix: permission issue while completing setup wizard 2025-06-16 13:59:12 +05:30
MochaMind
36ab072072 fix: sync translations from crowdin (#48062)
* fix: Spanish translations

* fix: Arabic translations

* fix: German translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Portuguese translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Portuguese, Brazilian translations

* fix: Persian translations

* fix: Thai translations

* fix: Croatian translations

* fix: Bosnian translations

* fix: Serbian (Latin) translations

* fix: French translations

* fix: Esperanto translations
2025-06-16 09:21:00 +02:00
rohitwaghchaure
cdcc692e01 Merge pull request #47903 from aerele/fix-return-qty
fix: modify query to fetch valid return qty
2025-06-16 10:23:42 +05:30
rohitwaghchaure
7211609725 Merge pull request #48061 from rohitwaghchaure/fixed-support-40585-1
fix: incorrect warehouse set from SO to MR
2025-06-16 10:11:11 +05:30
ruthra kumar
f1b8b7512e Merge pull request #48036 from aerele/accounts-receivable-summary-ageing
fix: add validation for calculate ageing with filter for summary and …
2025-06-16 08:38:38 +05:30
ruthra kumar
07805f17fc Merge pull request #48060 from frappe/pot_develop_2025-06-15
chore: update POT file
2025-06-16 08:12:45 +05:30
Rohit Waghchaure
0da8d9c869 fix: incorrect warehouse set from SO to MR 2025-06-15 19:17:01 +05:30
frappe-pr-bot
f5808f65c1 chore: update POT file 2025-06-15 09:36:44 +00:00
Sagar Vora
765fc27a9f Merge pull request #48048 from Abdeali099/fix-none-in-error-msg 2025-06-13 17:05:21 +00:00
Diptanil Saha
4178d9e2a1 fix: pos invoice consolidation row refer issue (#48057) 2025-06-13 19:52:15 +05:30
pugazhendhivelu
4b82fe2611 fix: add descendants item groups to fetch the barcode items 2025-06-13 18:16:25 +05:30
Akhil Narang
c275c55d6c build(deps): bump rapidfuzz (#47503)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-06-13 15:57:25 +05:30
Diptanil Saha
f37d5f188e fix: company validation for accounts on item tax template (#47964)
* fix: company validation for accounts on item tax template

* fix: syntax error
2025-06-13 12:38:04 +05:30
Diptanil Saha
41509d37af fix: patch to update child table parentfield name in pos closing entry (#48008) 2025-06-13 12:32:51 +05:30
Abdeali Chharchhoda
0a8e42a358 fix: use flt value of bin qty 2025-06-13 12:24:50 +05:30
rohitwaghchaure
576e804dbf Merge pull request #48037 from rohitwaghchaure/fixed-support-40883
fix: float division by zero
2025-06-13 08:19:31 +05:30
iamkhanraheel
5d4d4f40a1 feat: move checkbox related to items in item section 2025-06-12 17:54:26 +05:30
Rohit Waghchaure
59cbe85817 fix: float division by zero 2025-06-12 17:20:22 +05:30
ljain112
e50467a593 chore: Refactor test to use clear_cache instead of reload_doc 2025-06-12 17:15:38 +05:30
l0gesh29
c630aa9fe8 fix: add validation for calculate ageing with filter for summary and other reports 2025-06-12 16:59:52 +05:30
Khushi Rawat
2c54933e3e Merge pull request #48031 from khushi8112/asset-capitalization-from-connection-tab
fix: do not allow capitalization from connection tab for submitted asset
2025-06-12 15:17:02 +05:30
Khushi Rawat
27bec4cde5 fix: do not allow capitalization from connection tab for submitted asset 2025-06-12 15:13:02 +05:30
ruthra kumar
46985e0f3c Merge pull request #48024 from frappe/l10n_develop
fix: sync translations from crowdin
2025-06-12 14:09:28 +05:30
MochaMind
37f9002b84 fix: Turkish translations 2025-06-12 05:58:06 +05:30
MochaMind
4d9d7b243d fix: Swedish translations 2025-06-12 05:58:02 +05:30
rohitwaghchaure
153a70cd70 Merge pull request #48018 from rohitwaghchaure/fixed-support-40869
fix: batch page length
2025-06-11 20:01:50 +05:30
Sagar Vora
66d1771b94 Merge pull request #48019 from priyanshshah2442/fix_unpack_non_iterable_error 2025-06-11 12:48:13 +00:00
priyanshshah2442
7d940faa4f fix: unpack non-iterable NoneType object error 2025-06-11 18:11:54 +05:30
Rohit Waghchaure
338256b799 fix: batch page length 2025-06-11 16:52:58 +05:30
Ankush Menat
d16a6d42a5 perf: Use lazy loaded documents (#48017)
* perf: Use lazy docs for status updaters and similar use cases

* perf: lazy load documents while reposting
2025-06-11 10:27:08 +00:00
Mihir Kandoi
76982fe133 Merge pull request #48015 from mihir-kandoi/zero_valuation_toast_2
feat: show warning toast if rate is 0 and allow zero valuation rate checkbox is not enabled
2025-06-11 15:06:31 +05:30
Mihir Kandoi
b23a2dd8e8 fix: if condition to validate doctype 2025-06-11 14:50:33 +05:30
Mihir Kandoi
7a1cebfa3e fix: show warning toast if rate is 0 and allow zero valuation rate checkbox is not enabled 2025-06-11 14:50:17 +05:30
Mihir Kandoi
5254057b11 Merge pull request #48014 from frappe/revert-47875-zero_valuation_toast
Revert "feat: show warning toast if rate is 0 and allow zero valuation rate c…"
2025-06-11 11:30:20 +05:30
Mihir Kandoi
1502515ed9 Revert "feat: show warning toast if rate is 0 and allow zero valuation rate c…" 2025-06-11 11:13:56 +05:30
Diptanil Saha
8f4a6d248f fix: fetch correct item tax template on item rate update (#47955) 2025-06-11 10:58:18 +05:30
ruthra kumar
a7974451fd Merge pull request #48012 from frappe/l10n_develop
fix: sync translations from crowdin
2025-06-11 09:41:57 +05:30
MochaMind
175200891c fix: Serbian (Latin) translations 2025-06-11 05:47:13 +05:30
MochaMind
ff868aeba8 fix: Swedish translations 2025-06-11 05:46:55 +05:30
Karuppasamy923
7987ba9b7c feat(bank-statement-import): add support for uploading MT940 format bank statements 2025-06-11 05:20:07 +05:30
Khushi Rawat
bd502c11cc Merge pull request #47901 from khushi8112/asset-transfer-and-movement-in-single-asset-movement-record
feat: Asset Transfer and Issue in single asset movement record
2025-06-11 01:16:51 +05:30
Diptanil Saha
3969a75635 fix: populate is_return and return_against fields in pos closing entry (#48009) 2025-06-11 00:00:47 +05:30
ravibharathi656
14646b1952 test: add test for validating sales invoice qty after return 2025-06-10 18:58:34 +05:30
ravibharathi656
161a186bcc fix: modify query to fetch valid return qty 2025-06-10 18:56:29 +05:30
rohitwaghchaure
37e97e1256 Merge pull request #47998 from rohitwaghchaure/fixed-support-40585
fix: incorrect warehouse in MR
2025-06-10 18:10:09 +05:30
Rahul Agrawal
bc87609264 feat: populate Timer dialog project field from Timesheet parent_project (#47971)
* feat: default parent project in timer dialog > project

* chore: fix formatting

* fix: remove unnecessary or condition

---------

Co-authored-by: Rahul Agrawal <deathstarconsole@Rahuls-MacBook-Air.local>
2025-06-10 18:02:05 +05:30
Diptanil Saha
4e537cdb74 refactor: sales invoice integration with pos (#47713)
* fix: invoice doctype selection in accounts settings

* test: change in accounts settings on sales invoice

* test: refactored pos_invoice_merge_log tests

* test: pos closing entry and pos invoice

* fix: closing voucher details style

* refactor: renamed fields and removed repeated methods

* fix: patch to rename pos closing entry fields

* refactor: replaced get_doc with sql query

* fix: restrict cancelling sales invoice on cancellation of pos closing entry

* fix: removed payment reconciliation summary field and rearranged total section fields

* refactor: set_posting_date_and_time

* test: create_sales_invoice added args for is_created_using_pos

* test: added test for sales invoice creation during pos invoice mode

* test: added test for pos invoice creation during sales invoice mode

* fix: moved invoice type selection in pos settings

* fix: pos additional fields label

* refactor: pos closing entry

rearranged fields, removed rate field from taxes field, fetching payments and taxes details

* test: moved invoice creation in functions

* refactor: using as_dict=1

* fix: wrong table chosen in query

* fix: variable rename

* test: fixed failing tests

* test: fixed pos_closing_entry tests
2025-06-10 17:51:11 +05:30
Rohit Waghchaure
2b9ca79291 fix: incorrect warehouse in MR 2025-06-10 17:36:54 +05:30
Debin Robert
6529b288c2 feat: confirm with user before resetting posting date (#47667)
* feat: confirm with user before resetting posting date

* chore: pre-commit

* changes made as per review

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-06-10 15:20:18 +05:30
Debin Robert
751815745f fix: filter to ignore system generated cr / dr reconciliation journals on customer ledger summary 2025-06-10 15:15:50 +05:30
rohitwaghchaure
d38f3f4fae Merge pull request #47981 from barredterra/wo-query_sales_order
refactor(Work Order): query_sales_order
2025-06-10 14:31:12 +05:30
rohitwaghchaure
97923c3b2b Merge pull request #47969 from rohitwaghchaure/fixed-support-40445
fix: do not create repeat work orders
2025-06-10 14:29:29 +05:30
ruthra kumar
224c525b86 Merge pull request #47923 from aerele/currency_in_trends
fix: update currency based on transaction
2025-06-10 14:25:44 +05:30
Rohit Waghchaure
384f4e120a fix: do not create repeat work orders 2025-06-10 13:59:37 +05:30
Ankush Menat
a123de4884 Merge pull request #47984 from frappe/drop_empty_unit_tests
chore: Drop empty UnitTestCase
2025-06-10 12:59:02 +05:30
Ankush Menat
e8c8f6fb63 test: Use name instead of doc
Previous filters implicitly returned last document.
2025-06-10 12:44:05 +05:30
Sagar Vora
b3bfce56a8 Merge pull request #47989 from priyanshshah2442/fix_calculated_discount_mismatch_report 2025-06-10 07:10:21 +00:00
Ankush Menat
9f0df5ecf3 fix: Correct filter types 2025-06-10 12:40:07 +05:30
priyanshshah2442
4e1abc1814 fix: add draft transactions also in calculated mismatch report 2025-06-10 12:38:07 +05:30
rohitwaghchaure
c62fe1a8db Merge pull request #47942 from rohitwaghchaure/fixed-support-40499
fix: available qty in BOM Stock Report
2025-06-10 12:19:52 +05:30
Sagar Vora
964712301b Merge pull request #47946 from priyanshshah2442/fix_discount_mismatch 2025-06-10 06:00:38 +00:00
Ankush Menat
c222d5fa55 chore: Drop empty UnitTestCase 2025-06-10 10:55:43 +05:30
MochaMind
465ec357aa fix: sync translations from crowdin (#47953) 2025-06-10 05:13:55 +00:00
ruthra kumar
bf9d3eac6a Merge pull request #47983 from frappe/mergify/copy/develop/pr-47934
fix(sales order): error message on creation of work order from sales order (copy #47934)
2025-06-10 10:22:08 +05:30
thomasantony12
2a2af8d986 fix(sales order): error message on creation of work order from sales order
(cherry picked from commit 129cd7ae8a)
2025-06-10 04:51:00 +00:00
Ankush Menat
4861e6a1b8 Merge pull request #47972 from frappe/balance
perf: skip fetching balances in payment entry
2025-06-10 09:04:31 +05:30
Ankush Menat
1343d147bd perf: Skip fetching account balances unless required
This is very expensive operation and not requried when just finding
default account and account info.
2025-06-10 08:50:00 +05:30
Ankush Menat
0a043a4e0b perf: Avoid fetching account balance
This isn't shown anywhere in UI anymore since https://github.com/frappe/erpnext/pull/45630
2025-06-10 08:50:00 +05:30
l0gesh29
c8cec8cedf fix: add .length in list validation (#47974)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-06-10 01:15:31 +02:00
barredterra
2dbdacf905 refactor(Work Order): query_sales_order
- Use `get_list` instead of `db.sql_list`

    The method is used for setting link options in the frontend and the Link field doesn't ignore permissions, so get_list should be fine here.

- Added type hints to enable argument validation
2025-06-09 22:15:41 +02:00
Khushi Rawat
ffa014ecdc Merge pull request #47979 from khushi8112/attribute-error-fix-in-asset-capitalization
fix: AttributeError due to incorrect object
2025-06-10 00:15:18 +05:30
Khushi Rawat
351796bce6 fix: AttributeError due to incorrect object 2025-06-09 23:58:40 +05:30
Aayush Dalal
8b6a8d0c4f fix: throw permission error (#47976)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-06-09 17:28:46 +00:00
Sagar Vora
e91b4f759c Merge branch 'develop' into fix_discount_mismatch 2025-06-09 17:15:41 +00:00
Sagar Vora
54ca0dd004 Merge pull request #47975 from sagarvora/fix-test
test: improve logic for `test_employee_query_with_user_permissions`
2025-06-09 17:12:33 +00:00
Sagar Vora
5e4554d14e test: ensure employees are created before using 2025-06-09 22:27:21 +05:30
Sagar Vora
2a85c401b4 test: improve logic for test_employee_query_with_user_permissions 2025-06-09 22:12:36 +05:30
priyanshshah2442
f781a39dbe fix: fieldtype to Currency for discount amounts 2025-06-09 19:20:40 +05:30
Sagar Vora
9bf9b34ac4 fix: remove currency col 2025-06-09 19:13:53 +05:30
Sagar Vora
33e793354c fix: changes in report 2025-06-09 19:13:03 +05:30
Sagar Vora
e3356ebfb6 Merge branch 'develop' into fix_discount_mismatch 2025-06-09 13:16:02 +00:00
priyanshshah2442
3f0c5be5d9 fix: test case to verify correct setting of discount amount and percentage 2025-06-09 18:43:58 +05:30
priyanshshah2442
9120927a65 fix: add change log for bug fix in Additional Discount functionality 2025-06-09 18:06:16 +05:30
priyanshshah2442
3dcb801a37 fix: ensure proper float conversion for discount values 2025-06-09 17:49:40 +05:30
ruthra kumar
ea68e94665 Merge pull request #47865 from aerele/accounts-receivable/payable-user-permission
fix: consider user permission while populating the data
2025-06-09 15:22:15 +05:30
Sagar Vora
daad6137f8 fix: changes to report and patch 2025-06-09 14:11:19 +05:30
ruthra kumar
1e9c34d4f8 Merge pull request #47626 from debinnn/word-order-date
fix: no validation to compare 'actual start date' and 'actual end date' when creating work order.
2025-06-09 12:12:56 +05:30
ruthra kumar
d83bc7be41 Merge pull request #47963 from frappe/mergify/copy/develop/pr-47590
fix(report): include descendants when filtering by parent item group (copy #47590)
2025-06-09 12:10:49 +05:30
Debin Robert
e1ae650d45 refactor: ensure end date is after start date on work order 2025-06-09 11:57:12 +05:30
FATHIH MOHAMMED
d25021d488 fix(report): include descendants when filtering by parent item group
(cherry picked from commit d21bfa219d)
2025-06-09 06:25:30 +00:00
priyanshshah2442
62dd6df24f feat: report to verify discount amount mismatch 2025-06-09 11:13:59 +05:30
ruthra kumar
8adb5e7e66 Merge pull request #47863 from khushi8112/closing-balance-in-dr-cr
feat: show Dr/Cr in Supplier ledger summary & Customer ledger summary
2025-06-09 10:15:30 +05:30
Ankush Menat
765c7c2bcc refactor: rename get_settings -> get_single_value (#47961) 2025-06-09 04:40:02 +00:00
MochaMind
26abf9f13a chore: update POT file (#47956) 2025-06-08 14:56:06 +02:00
MochaMind
75872925a2 fix: sync translations from crowdin (#47881)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-06-06 20:30:30 +00:00
Ankush Menat
bb693c0a4f perf: Batch GLE/SLE rename commits (#47950) 2025-06-06 14:51:59 +00:00
Ankush Menat
73746e2c71 perf: use cached selling settings (#47949)
* perf: Use cached stock repost settings

* perf: Use cached selling settings
2025-06-06 14:10:12 +00:00
Ankush Menat
7d88d9dd4d perf: use cached Authorization control doc (#47948) 2025-06-06 14:09:47 +00:00
Ankush Menat
45a4beb401 perf: Use cached accounts settings (#47947) 2025-06-06 13:59:18 +00:00
Ankush Menat
ea5c2c454b perf: use cached stock settings (#47945) 2025-06-06 18:54:49 +05:30
priyanshshah2442
f7eda8a156 fix: patch to set discount percentange in case of mismatch 2025-06-06 18:40:06 +05:30
Khushi Rawat
522b6ad49f Merge pull request #47547 from aerele/fix-empty-sequence
fix(asset): make purchase date mandatory
2025-06-06 17:39:10 +05:30
Ankush Menat
a38ed286bd perf: cache item metadata retrieval (#47929)
* perf(get_item_details): use cached doc, avoid full get_doc

* perf: short-circuit common usecase of same UOM

* perf: use cached doc for inspection

* perf: use cached valuation method and other item props

It rarely if ever changes after initial configuration
2025-06-06 11:39:27 +00:00
Rohit Waghchaure
ea689bbe3f fix: available qty in BOM Stock Report 2025-06-06 16:14:43 +05:30
rohitwaghchaure
534f90c33d Merge pull request #47888 from rohitwaghchaure/fixed-support-39929-1
fix: do not remove item which has zero qty and zero valuation
2025-06-06 15:32:03 +05:30
Rohit Waghchaure
86e4a658a5 fix: do not remove item which has zero qty and zero valuation 2025-06-06 13:15:25 +05:30
Khushi Rawat
7d3bec8ef8 fix: failing test case 2025-06-06 12:44:36 +05:30
Khushi Rawat
7e52cb2856 refactor: split set_latest_location_and_custodian_in_asset into smaller functions 2025-06-06 12:44:36 +05:30
Khushi Rawat
07d1a0ed9c fix: saperated validations for each purpose of validation 2025-06-06 12:44:36 +05:30
Khushi Rawat
f5e5146021 feat: added Transfer and Issue option in purpose 2025-06-06 12:44:36 +05:30
Ankush Menat
7bddd64771 perf: Skip fetching non-existing call log (#47930) 2025-06-06 07:13:44 +00:00
Sagar Vora
abad8050a3 Revert "fix: calculate discount percentage if discount amount is specified" (#47927) 2025-06-06 06:06:20 +00:00
Sagar Vora
27dc0f5b70 Revert "fix: calculate discount percentage if discount amount is specified (#…"
This reverts commit bb474f4f42.
2025-06-06 11:35:30 +05:30
Ankush Menat
c9413e0cbb Merge pull request #47899 from frappe/cruft
perf: remove misc random cruft
2025-06-05 22:48:07 +05:30
Ankush Menat
958cc6f8f8 perf: use estimated count on item table 2025-06-05 22:31:36 +05:30
Ankush Menat
8e17054b67 fix: invalid logic for cache hit
if there are no inventory dimensions then `[]` gets treated as a miss.
2025-06-05 22:31:36 +05:30
Ankush Menat
a36daec939 perf: cache pricing rule query 2025-06-05 22:31:36 +05:30
Ankush Menat
4dc2969fa6 perf: avoid duplicate fetching of stock qty 2025-06-05 22:31:35 +05:30
Ankush Menat
c06a361348 fix: get_value doesn't support named plucking 2025-06-05 22:31:35 +05:30
Ankush Menat
9870dd26f7 perf: avoid querying cost center allocation repeatedly 2025-06-05 22:31:33 +05:30
Ankush Menat
013011aa43 perf: cache existence of budgets while validating GL 2025-06-05 22:26:26 +05:30
DHINESH00
fc4f38eed1 fix: update currency based on transaction 2025-06-05 20:04:20 +05:30
rohitwaghchaure
342cebc778 fix: stock reco qty with inventory dimension (#47918) 2025-06-05 17:35:32 +05:30
rohitwaghchaure
8fa3473945 fix: consider expired batches in the stock reco (#47909) 2025-06-05 17:18:46 +05:30
Deepesh Garg
9167f1ff90 Merge pull request #47907 from deepeshgarg007/gl_dict_hook
feat: Add hook to update gl dict by apps
2025-06-05 16:32:51 +05:30
rohitwaghchaure
997ce4eaa7 fix: key-error for COGS By Item Group report (#47914)
fix: keyerror for COGS By Item Group report
2025-06-05 15:59:23 +05:30
Ankush Menat
7720163e4c test: remove manual cleanup of db.value_cache (#47904)
test: clear cache instead of replacing
2025-06-05 09:37:19 +00:00
Deepesh Garg
c4aecb15ce style: Linting issues 2025-06-05 12:48:28 +05:30
Deepesh Garg
10ff369ff2 feat: Add hook to update gl dict by apps 2025-06-05 12:42:16 +05:30
Diptanil Saha
bb903a4bef fix: pos permission error on strict permission (#47896) 2025-06-04 16:07:28 +05:30
Mihir Kandoi
bfff582fea Merge pull request #47875 from mihir-kandoi/zero_valuation_toast
feat: show warning toast if rate is 0 and allow zero valuation rate c…
2025-06-04 12:33:32 +05:30
rohitwaghchaure
dc4f701101 refactor: item tax amount calculation for valuation rate (#47532) 2025-06-04 10:37:54 +05:30
rohitwaghchaure
cbcd580daa fix: stock adjustment entry during reposting (#47878)
fix: stock adjustment entry
2025-06-04 10:37:08 +05:30
Mihir Kandoi
7c57f5a304 feat: show warning toast if rate is 0 and allow zero valuation rate checkbox is not enabled 2025-06-03 18:34:22 +05:30
Mihir Kandoi
67d82894c6 Merge pull request #47874 from mihir-kandoi/st40223
fix: zero division error in purchase receipt
2025-06-03 18:34:17 +05:30
Mihir Kandoi
32229fb646 fix: zero division error in purchase receipt 2025-06-03 18:18:55 +05:30
l0gesh29
1a4bb30923 fix: add user permission while fetching ple 2025-06-03 18:17:11 +05:30
ruthra kumar
3f6a132234 Merge pull request #47872 from nitefury7/fix-purchase-order-analysis-report
fix: typo
2025-06-03 17:38:19 +05:30
Ayush Marhatta
a243abb5fd fix: typo 2025-06-03 17:32:28 +05:45
Khushi Rawat
524ae1d368 fix: removed checkbox 2025-06-03 14:53:21 +05:30
ruthra kumar
b2ff5b4578 Merge pull request #47866 from ruthra-kumar/ci_suppress_compiled_files
ci: suppress compiled files in output
2025-06-03 14:01:57 +05:30
ruthra kumar
e70da0291e ci: suppress compiled files in output 2025-06-03 13:38:12 +05:30
l0gesh29
074dc6d7dd fix: consider user permission while populating the data 2025-06-03 13:28:01 +05:30
ruthra kumar
36f3bd473c Merge pull request #47854 from nitefury7/fix-purchase-order-analysis-report
fix: use user default for company instead of global default in purchase order analysis report
2025-06-03 13:17:13 +05:30
rohitwaghchaure
75b5ba6e67 refactor: track completed app setup wizards and re-run the setup wizard upon new app installation. (#47691) 2025-06-03 13:02:45 +05:30
Khushi Rawat
ff285307c6 chore: wrapped text in translate function 2025-06-03 12:33:46 +05:30
Khushi Rawat
0dbebe74c1 chore: update label 2025-06-03 12:23:56 +05:30
Khushi Rawat
22ea62e92f chore: hide currency column 2025-06-03 12:06:39 +05:30
Khushi Rawat
e7ba420687 feat: added column to show Dr/Cr 2025-06-03 12:05:43 +05:30
Khushi Rawat
d15e3bb52b feat: show Dr/Cr in Supplier ledger summary & Customer ledger summary 2025-06-03 12:04:27 +05:30
Mihir Kandoi
538e0d08e4 Merge pull request #47756 from mihir-kandoi/st38914
feat: specify expense account and cost center for raw materials in Su…
2025-06-03 12:04:04 +05:30
Marc Ramser
0670765baa fix(Timesheet): only update to_time if it's more than 1 second off (#47703)
* Only update to_time if it's more than 1 second off

Before, to_time was updated even when it was almost the same as the expected time (like 17:20 vs 17:19:59.998). This causes problems because of small rounding errors and caused valid times like 17:20 to be reset. Now, to_time is only updated if the difference is greater than 1 second.

To reproduce the current error:

From Time 09:00:00
To Time 17:20:00 Save
To Time is 17:19:59

* Change data to self in timesheet

* Update timesheet_detail.py
2025-06-03 11:58:00 +05:30
Mihir Kandoi
8b42582f3c test: added more assertions 2025-06-03 11:47:38 +05:30
Lakshit Jain
20b87512d1 fix: cash flow report fixes 2025-06-03 11:22:02 +05:30
Diptanil Saha
1c17419a9e fix: pos order summary new order action (#47856) 2025-06-02 18:40:51 +05:30
Diptanil Saha
0624556ef6 fix: pos settings invoice_fields not fetched at pos setup (#47855) 2025-06-02 18:24:06 +05:30
Ayush Marhatta
49f23513e0 fix: use user default for company instead of global default in purchase order analysis report 2025-06-02 18:21:07 +05:45
ruthra kumar
56fc76f180 Merge pull request #47780 from aerele/add-link-field
fix: add internal link field in Sales Order connections for internal …
2025-06-02 16:46:48 +05:30
ruthra kumar
ee8c0daf61 Merge pull request #47665 from mujeerhashmi/patch-3
fix: Project argument is not passed correctly for MR creation
2025-06-02 16:36:31 +05:30
Karuppasamy
3a2b863e7f refactor: add validation for inter company transactions rate
Add validation for inter company transactions rate
2025-06-02 16:34:45 +05:30
Abdeali Chharchhodawala
39f6d8ffb6 fix: Handle duplicate Items qty in Quotation
fix: Handle duplicate Items qty in Quotation
2025-06-02 14:49:15 +05:30
ruthra kumar
f7712c9dc0 Merge pull request #47781 from ljain112/fix-cc-psa
fix: add company filter to cost center and project in process statement of accounts
2025-06-02 13:37:54 +05:30
l0gesh29
c8e052f3c6 Accounts receivable show delivery note (#47821)
* fix: reorder function call

* fix: Add condition to fetch return entries for specific party types
2025-06-02 13:35:23 +05:30
rohitwaghchaure
0dbd9efc91 fix: decimal issue (#47839) 2025-06-02 13:24:41 +05:30
Ankush Menat
f86c3f7f8b perf: Use client cached configuration (#47838)
These are accessed on every document load, reaching to DB is excessive.
2025-06-02 06:14:51 +00:00
Ankush Menat
705b9e0181 perf: cache child warehouses during a single request (#47837)
Gets called for each row right now, warehouses often same on all
items in a document.
2025-06-02 05:36:17 +00:00
MochaMind
910c3cc5cf fix: sync translations from crowdin (#47802) 2025-06-01 21:55:53 +00:00
MochaMind
1c0f164670 chore: update POT file (#47835) 2025-06-01 12:33:11 +02:00
Mihir Kandoi
bb474f4f42 fix: calculate discount percentage if discount amount is specified (#47806) 2025-05-31 20:52:14 +05:30
rohitwaghchaure
e4c4ee79de fix: incorrect valuation for returned materials (#47824) 2025-05-31 18:43:21 +05:30
rohitwaghchaure
0557d4d180 fix: incorrect serial no status after delivery note cancel (#47825) 2025-05-31 18:42:40 +05:30
rohitwaghchaure
b49a835b4c fix: improved indexing for SLE queries. (#47194) 2025-05-30 13:41:14 +05:30
Ankush Menat
a61f065660 Merge pull request #47783 from frappe/perf/stock_reco
perf: speed up stock reconciliation
2025-05-29 18:09:51 +05:30
rohitwaghchaure
c544c3e018 fix: incorrect actual qty in product bundle balance report (#47791) 2025-05-29 18:09:33 +05:30
Ankush Menat
dc8d9dc7fa perf: fix incorrect usage of get_cached_value 2025-05-29 17:54:53 +05:30
Ankush Menat
e1d14d8ac2 perf: Ignore validating links
Framework does this anyway and does it faster.
2025-05-29 17:37:17 +05:30
Ankush Menat
0a1fc3e24f perf: use cached doc for validating item 2025-05-29 17:37:17 +05:30
Ankush Menat
e2920e2536 perf: Fix incorrect cache implementation
`if not {}` results to `True` so if someone doesn't have inventory dimensions, this gets called again and again.
2025-05-29 17:37:17 +05:30
Ankush Menat
076b765cb2 perf: Avoid Querying existence of item code
- This should be ensured by Framework.
- It's also there in validate hook.

So no need to do it again for each item.
2025-05-29 17:37:17 +05:30
Ramdani
4d98094783 Merge pull request #47784 from xhijack/fix_default_indonesia_vat
Fix default indonesia vat
2025-05-29 14:59:02 +05:30
ruthra kumar
45f7db6054 Merge pull request #47794 from ljain112/fix-ar-query
fix: use `query.walk() `for escaping special chars in receiable/payable report
2025-05-29 13:49:55 +05:30
Ankush Menat
6549047c3f fix: Drop duplicate index (#47799) 2025-05-29 12:18:39 +05:30
ljain112
a0a51b5074 fix: use query.walk() for escaping special chars in receiable/payable report 2025-05-29 11:18:08 +05:30
rohitwaghchaure
cb9e6f6655 chore: removed orphaned function (#47796) 2025-05-29 10:14:45 +05:30
Khushi Rawat
7d9379461a test: composite component 2025-05-29 00:15:36 +05:30
Mihir Kandoi
93ebd173cd Merge pull request #47792 from mihir-kandoi/st39718
feat: show item name for raw materials in BOM creator
2025-05-28 19:52:13 +05:30
Ankush Menat
2b4285374f fix: checkbox can't be mandatory (#47793)
fix: checkboxes can't be mandatory
2025-05-28 13:46:58 +00:00
Mihir Kandoi
90ba4ad1e1 feat: show item name for raw materials in BOM creator 2025-05-28 18:13:42 +05:30
Khushi Rawat
83b32f9646 fix: correct gl entry when capitalizing composite component 2025-05-28 17:48:04 +05:30
ljain112
14313b162a fix: add company filter to cost center and project in process statement of accounts 2025-05-28 13:56:42 +05:30
Karuppasamy923
e3e6503076 fix: add internal link field in Sales Order connections for internal transactions 2025-05-28 12:59:49 +05:30
ruthra kumar
82208f8513 Merge pull request #47778 from ruthra-kumar/ui_changes_for_budget_refactor
refactor: UI changes for budget refactor
2025-05-28 11:25:01 +05:30
ruthra kumar
5222e89250 refactor: more user friendly error message 2025-05-28 11:03:26 +05:30
ruthra kumar
5aaa866205 refactor: hide cumulative exp actions if new controller is disabled 2025-05-28 11:03:24 +05:30
ruthra kumar
776688d585 Merge pull request #47775 from frappe/l10n_develop
fix: sync translations from crowdin
2025-05-28 09:25:34 +05:30
MochaMind
aefa5308b7 fix: Serbian (Latin) translations 2025-05-28 02:11:54 +05:30
MochaMind
1026550051 fix: Persian translations 2025-05-28 02:11:44 +05:30
MochaMind
a10b8ecbe5 fix: Swedish translations 2025-05-28 02:11:32 +05:30
Raffael Meyer
3c0efcf7f1 Revert "fix: translate_pos_buttons" (#47773) 2025-05-27 19:08:49 +00:00
ruthra kumar
011084bdf9 Merge pull request #47766 from ljain112/fix-multiselect
fix: handle multiselect filters for tree doctypes in Customer Ledger Summary Report
2025-05-27 19:52:45 +05:30
Mihir Kandoi
4861856b8f test: add test 2025-05-27 18:23:44 +05:30
ruthra kumar
74fab3a539 Merge pull request #47767 from aerele/quotation-lost-reason
fix: add no_copy for lost reasons
2025-05-27 18:02:27 +05:30
ruthra kumar
f50b5b19fe Merge pull request #47765 from ljain112/fix-sls
fix: use pypika object `LiteralValue` for adding match conditions
2025-05-27 18:00:05 +05:30
ljain112
536f7d5ff8 fix: handle multiselect filters for tree doctypes in Customer Ledger Summary Report 2025-05-27 17:27:29 +05:30
l0gesh29
98e889a516 fix: add no_copy for lost reasons 2025-05-27 17:04:41 +05:30
ruthra kumar
d05132ab1d Merge pull request #47763 from aerele/item-tax-template-on-rate
feat: add validation for Item Tax Template on rate change
2025-05-27 16:59:07 +05:30
ljain112
9093e5e363 fix: use pypika object LiteralValue for adding match conditions 2025-05-27 16:51:52 +05:30
Mihir Kandoi
93b33faabf refactor: move logic to validate function 2025-05-27 16:23:42 +05:30
Karuppasamy923
a9a957edc7 feat: add validation for Item Tax Template on rate change 2025-05-27 16:19:43 +05:30
Mihir Kandoi
44bec1f981 feat: GL entries 2025-05-27 15:32:58 +05:30
Mihir Kandoi
0f53cc1922 feat: specify expense account and cost center for raw materials in Subcontracting Receipt for GL entries 2025-05-27 15:32:58 +05:30
Diptanil Saha
4d1d66e579 fix: patch to set status cancelled for already cancelled pos invoices (#47725) 2025-05-27 15:27:45 +05:30
Diptanil Saha
1523b38315 fix: patch to set grand total to default mop if old column exists (#47731)
* fix: patch to set grand total to default mop if old column exists

* chore: patches.txt
2025-05-27 14:48:25 +05:30
ruthra kumar
3bb44b5ef6 Merge pull request #47721 from xhijack/fix_default_indonesia_vat
fix indonesia tax
2025-05-27 14:11:43 +05:30
ruthra kumar
4fc7cc4c15 Merge pull request #47679 from ljain112/fix-pur-piad
fix: setting paid amount to 0 when is_paid is unchecked in purchase invoice
2025-05-27 13:57:20 +05:30
ljain112
e358a9e53f fix: setting paid amount to 0 when is_paid is unchecked in purchase invoice 2025-05-27 13:41:03 +05:30
Ramdani
e1b9f24b69 fix: indonesia tax 2025-05-27 13:18:31 +05:30
ruthra kumar
c3847e4300 Merge pull request #47736 from ljain112/fix-tcs-advance
fix: only include advances within the tcs period
2025-05-27 13:18:01 +05:30
ruthra kumar
bba2bb4561 Merge pull request #47737 from ljain112/fix-party-account
fix: party account based on party type's account type
2025-05-27 13:12:56 +05:30
rohitwaghchaure
d8cb073eaf fix: create Quality Inspection button not showing (#47746) 2025-05-27 12:18:39 +05:30
ruthra kumar
64cb693173 Merge pull request #47659 from mahsem/translate_pos_buttons
fix: translate_pos_buttons
2025-05-27 11:40:56 +05:30
ruthra kumar
114d42d26b Merge pull request #47654 from ljain112/fix-psa-currency
fix: show general ledger in doc currency in Process Statement Of Accounts
2025-05-27 11:39:27 +05:30
ruthra kumar
c40b4c4639 Merge pull request #47531 from Abdeali099/modern-check-for-array
refactor: simplify `source_name` assignment in `map_current_doc`
2025-05-27 11:17:18 +05:30
ruthra kumar
75ae131d81 Merge pull request #47697 from ruthra-kumar/fetch_party_name_for_contract
refactor: Fetch party name for contract
2025-05-26 17:40:32 +05:30
ruthra kumar
8e2221178b refactor: patch old contract with full party name 2025-05-26 17:17:31 +05:30
ruthra kumar
752024e222 refactor: fetch party name on selection 2025-05-26 17:17:11 +05:30
ruthra kumar
016924361a refactor: full name field in contract 2025-05-26 17:17:11 +05:30
maasanto
b7de5398a7 chore: Remove agriculture roles (#47335)
* feat: remove permissions of Agriculture Manager and Agriculture User

* feat: patch to remove agriculture roles

* chore: formatting issue

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-05-26 17:15:46 +05:30
Ankush Menat
def9d8c9e0 perf: speed up customer import (#47738)
* perf: Avoid fetching customer from DB

This is likely code from bygone era where is_new and doc_before_save didn't exist?

I see no reason to do it in this weird manner. This code still appears
to be wrong but I'll leave it as is for now.

* fix: don't validate internal customer for non-internal customer

This was likely missed out while adding validations
2025-05-26 11:35:34 +00:00
ljain112
19b1650522 fix: party account based on party type's account type 2025-05-26 15:55:55 +05:30
Ankush Menat
ff59b89f3e Merge pull request #47734 from frappe/perf_item_import
perf: speedup item import
2025-05-26 15:43:26 +05:30
ljain112
477ec9fdcc fix: only include advances within the tcs period 2025-05-26 15:36:12 +05:30
Ankush Menat
7b811a93b4 perf: don't cleanup plaintext fallback 2025-05-26 15:27:49 +05:30
Ankush Menat
41d56c8e3a perf: fetch cached item group defaults, avoid query 2025-05-26 15:10:03 +05:30
Ankush Menat
31b8eb4922 perf: don't validate for change on new documents 2025-05-26 14:59:02 +05:30
Ankush Menat
a54d965d6d perf: validate variant change only if value changed 2025-05-26 14:57:11 +05:30
Ankush Menat
a52b2d8eb5 perf: don't update price of newly inserted item 2025-05-26 14:46:27 +05:30
ruthra kumar
4faab02e24 Merge pull request #47650 from frappe/l10n_develop
fix: sync translations from crowdin
2025-05-26 13:34:26 +05:30
Deepesh Garg
6174522028 Merge pull request #47709 from diptanilsaha/patch-custom-gl-report
fix: patch to rename group_by filter in custom reports
2025-05-26 12:41:24 +05:30
ruthra kumar
3b2e689957 Merge pull request #47253 from prateekkaramchandani/feat/currency-chart-warehouse-stock-value
fix: display stock value in currency format in chart warehouse wise stock value
2025-05-26 11:19:06 +05:30
MochaMind
7fc017d537 fix: Esperanto translations 2025-05-26 02:05:33 +05:30
MochaMind
a9440d5d43 fix: Portuguese translations 2025-05-26 02:05:29 +05:30
MochaMind
27a99d42df fix: Serbian (Latin) translations 2025-05-26 02:05:27 +05:30
MochaMind
4132e9a283 fix: Bosnian translations 2025-05-26 02:05:23 +05:30
MochaMind
d9c038cd43 fix: Croatian translations 2025-05-26 02:05:20 +05:30
MochaMind
4bc29a1c80 fix: Thai translations 2025-05-26 02:05:16 +05:30
MochaMind
8d00c2f6db fix: Persian translations 2025-05-26 02:05:14 +05:30
MochaMind
910e51005a fix: Portuguese, Brazilian translations 2025-05-26 02:05:10 +05:30
MochaMind
96d053edf7 fix: Chinese Simplified translations 2025-05-26 02:05:07 +05:30
MochaMind
5f9d42b7f5 fix: Turkish translations 2025-05-26 02:05:04 +05:30
MochaMind
6d9afe2bdb fix: Swedish translations 2025-05-26 02:05:01 +05:30
MochaMind
72b811bc6f fix: Russian translations 2025-05-26 02:04:58 +05:30
MochaMind
03e366999a fix: Polish translations 2025-05-26 02:04:55 +05:30
MochaMind
ca9cde986f fix: Hungarian translations 2025-05-26 02:04:52 +05:30
MochaMind
984037eea1 fix: German translations 2025-05-26 02:04:49 +05:30
MochaMind
82e41e79cf fix: Arabic translations 2025-05-26 02:04:46 +05:30
MochaMind
92ab31c54c fix: Spanish translations 2025-05-26 02:04:43 +05:30
MochaMind
1be24f948a fix: French translations 2025-05-26 02:04:40 +05:30
MochaMind
7755e77dfc chore: update POT file (#47720) 2025-05-25 12:45:56 +02:00
rohitwaghchaure
67c86ec028 fix: skip drop ship items (#47670) 2025-05-25 11:36:51 +05:30
rohitwaghchaure
874750f9ce fix: filter of item for manufacture type material request (#47712) 2025-05-25 11:36:02 +05:30
Frappe PR Bot
786963f0be fix: Serbian (Latin) translations 2025-05-25 00:57:00 +05:30
Frappe PR Bot
73b377a9b0 fix: Persian translations 2025-05-25 00:56:55 +05:30
Frappe PR Bot
6cf5e76685 fix: Chinese Simplified translations 2025-05-25 00:56:51 +05:30
rohitwaghchaure
1e8ed22421 fix: absence of rounding causing discrepancy in the valuation rate calculation (#47700) 2025-05-24 17:11:12 +05:30
diptanilsaha
48eccb1f73 fix: using python instead of sql query 2025-05-24 14:53:04 +05:30
diptanilsaha
0d19c18c06 fix: patch to rename group_by filter in custom reports 2025-05-24 12:55:05 +05:30
Diptanil Saha
89115688f7 fix: pos screen ui ux (#47680)
* fix: pos addl info dialog submit form on save

* feat: new invoice and recent order button on page action

* fix: item cart highlighted item scrolling

* fix: using icon instead of text in fullscreen button

* fix: search field clear button alignment

* fix: hide item selector on item details display

* fix: using add_action_icon

* fix: action of 'New Invoice' for unsaved changes

* fix: highlight numpad btns on hover

* fix: pos recent orders filter and list items

* chore: added icons for pos icon buttons

* fix: recent order toggle after invoice submission

* fix: capitalized text in select options
2025-05-23 12:36:21 +05:30
rohitwaghchaure
c3b17024bd fix: skip last purchase rate for free item (#47693) 2025-05-23 08:25:35 +05:30
Frappe PR Bot
70110be24b fix: Bosnian translations 2025-05-23 00:38:57 +05:30
Frappe PR Bot
b4aafd317e fix: Swedish translations 2025-05-23 00:38:47 +05:30
ruthra kumar
30e23a8c74 Merge pull request #47692 from ruthra-kumar/test_refactor_1
refactor(test): drop ignore test dependencies on some empty test modules
2025-05-22 20:29:14 +05:30
ruthra kumar
351f02cea0 refactor(test): project update drop ignore test record dependencies 2025-05-22 20:09:55 +05:30
ruthra kumar
9fa5bbae24 refactor(test): remove IGNORE_TEST_RECORD_DEPENDENCIES 2025-05-22 20:09:55 +05:30
ruthra kumar
10a5815805 chore: remove dead code 2025-05-22 20:09:55 +05:30
ruthra kumar
aae63afec9 Merge pull request #47690 from ruthra-kumar/deterministic_leaf_node_doctypes
refactor(test): remove dependency from sales partner and territory
2025-05-22 19:57:31 +05:30
ruthra kumar
b22efc0bb5 refactor(test): remove dependency from sales partner and territory 2025-05-22 16:59:11 +05:30
Khushi Rawat
6ed97b5fda fix: incorrect valuation rate due to positive qty (#47686) 2025-05-22 16:13:24 +05:30
ruthra kumar
6bd6e7d417 Merge pull request #47675 from ljain112/fix-terrirtory
fix: typo in TREE_DOCTYPES list "Terrirtory" should be "Territory"
2025-05-22 15:56:07 +05:30
ruthra kumar
07e11f94b6 Merge pull request #47656 from ruthra-kumar/new_budget_controller_cumulative_breach
fix: handle cumulative breach for monthly and annual
2025-05-22 15:28:01 +05:30
ruthra kumar
5a9b272f84 test: cumulative actions for budget 2025-05-22 15:04:17 +05:30
ruthra kumar
f077f60344 refactor(test): utlity method to set cumulative actions 2025-05-22 14:03:43 +05:30
ruthra kumar
3eb07fba2a refactor: use cumulative control action on new controller 2025-05-22 13:54:04 +05:30
ruthra kumar
45368f983b refactor: control actions for cumulative expense 2025-05-22 13:47:02 +05:30
Mihir Kandoi
483b572db0 Merge pull request #47658 from mihir-kandoi/st39010
fix: exchange rate not being fetched when creating supplier quotation…
2025-05-22 12:55:37 +05:30
Mihir Kandoi
8d9888b1b6 fix: include rejected amount in PI/PR overbilling validation logic (#47572)
* fix: include rejected amount in PI/PR overbilling validation logic

* fix: add check if amount is 0

* fix: unneccessary condition
2025-05-22 12:52:31 +05:30
Khushi Rawat
9d78485af8 chore: removed commented code 2025-05-22 12:42:08 +05:30
Khushi Rawat
92614cb446 fix: test cases 2025-05-22 12:42:08 +05:30
Khushi Rawat
2aecca50b9 refactor: updated related references 2025-05-22 12:42:08 +05:30
Khushi Rawat
17a5d5f561 refactor: removal of composite asset creation from asset capitalization 2025-05-22 12:42:01 +05:30
ljain112
51162cb1a3 fix: typo in TREE_DOCTYPES list "Terrirtory" should be "Territory" 2025-05-22 12:34:55 +05:30
Frappe PR Bot
0f89eb30ac fix: Serbian (Latin) translations 2025-05-22 00:34:38 +05:30
Frappe PR Bot
bf4350064a fix: Persian translations 2025-05-22 00:34:33 +05:30
Frappe PR Bot
26a1bdd0fe fix: Swedish translations 2025-05-22 00:34:29 +05:30
Frappe PR Bot
3f30404417 fix: French translations 2025-05-22 00:34:25 +05:30
Syed Mujeer Hashmi
9eab434ae8 fix: Project argument is passed correctly for MR creation 2025-05-21 18:56:46 +05:30
Diptanil Saha
b2696bf659 fix: pos additional information fields not updating on removal of data (#47663) 2025-05-21 18:36:26 +05:30
RAVIBHARATHI P C
e6f47be4b0 fix(asset): make purchase date mandatory 2025-05-21 17:57:34 +05:30
mahsem
2839fc9460 fix: prettier 2025-05-21 13:21:55 +02:00
mahsem
50a5b51909 fix: space 2025-05-21 13:04:08 +02:00
mahsem
a442ec4e80 fix: space 2025-05-21 13:01:11 +02:00
mahsem
1953c8489c fix: prettier 2025-05-21 12:59:07 +02:00
mahsem
4a6b5b9993 fix: linter 2025-05-21 12:54:25 +02:00
mahsem
ce45d1664d fix: translate_pos_buttons 2025-05-21 12:46:36 +02:00
Mihir Kandoi
9d12ae071a fix: exchange rate not being fetched when creating supplier quotation from MR 2025-05-21 15:42:51 +05:30
ljain112
22a94d6817 chore: update test case because currency is auto set to system currency 2025-05-21 14:37:47 +05:30
Diptanil Saha
8c86def018 fix: pos invoice status not updating on cancel (#47556) 2025-05-21 14:37:22 +05:30
ruthra kumar
4862ae42d5 fix: handle cumulative breach for monthly and annual
- better method names
2025-05-21 14:30:59 +05:30
ljain112
998f6a29a4 fix: show general ledger in doc currency in Process Statement Of Accounts 2025-05-21 13:18:09 +05:30
Frappe PR Bot
4e3652b1c1 fix: Serbian (Latin) translations 2025-05-21 00:28:48 +05:30
Frappe PR Bot
472c84ecdb fix: Bosnian translations 2025-05-21 00:28:45 +05:30
Frappe PR Bot
fe084d1953 fix: Persian translations 2025-05-21 00:28:38 +05:30
Frappe PR Bot
7196a4b603 fix: German translations 2025-05-21 00:28:34 +05:30
rohitwaghchaure
738cb6a0c1 fix: incorrect inventory dimension for material transfer (#47592) 2025-05-20 19:13:08 +05:30
Khushi Rawat
33ab64dec2 fix: asset cancellation issue (#47639) 2025-05-20 17:06:57 +05:30
Khushi Rawat
0f5be4b245 feat: is composite component checkbox (#47602)
* feat: composite component

* chore: update mandatory depends on according to the new checkbox

* chore: validate disposal date for composite component asset

* fix: updated modified time
2025-05-20 16:51:02 +05:30
ruthra kumar
ee27730b72 Merge pull request #47632 from akhilnarang/quotation-use-small-text
fix(quotation): use `Text Editor` field in alternative items dialog
2025-05-20 14:20:57 +05:30
Akhil Narang
c7ea91073e fix(quotation): use Text Editor field in alternative items dialog for item description
`Data` causes text to overflow - the field is originally a `Text Editor` field

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-05-20 14:03:47 +05:30
ruthra kumar
13dd6272d7 Merge pull request #47629 from ljain112/fix-psa-ar
fix: date formatting in process_statement_of_accounts accounts_receivable print format
2025-05-20 13:52:52 +05:30
ljain112
67c32ce3c9 fix: date formatting in process_statement_of_accounts accounts_receivable print format 2025-05-20 13:19:02 +05:30
ruthra kumar
6f85a74f30 Merge pull request #47580 from aerele/calculate-ageing-with
feat: add option to calculate ageing based on report date or today's date
2025-05-20 13:02:26 +05:30
ruthra kumar
9dc27141c7 Merge pull request #47551 from cogk/fix-escape-uom-in-get_pricing_rules
fix: Escape UOM in pricing rule query
2025-05-20 11:26:00 +05:30
ruthra kumar
15b3ce1beb Merge pull request #47559 from ljain112/fix-billed-amt
fix: include only invoices with update_stock = 0  for billed amt in delivery note.
2025-05-20 11:01:04 +05:30
ruthra kumar
83879715e9 Merge pull request #47486 from aerele/sales-analytics-opening-entry
fix(SalesAnalytics): Ignore opening entries
2025-05-20 10:50:19 +05:30
ruthra kumar
0f27e100b2 Merge pull request #47614 from barredterra/make-return-doc
fix: remove hardcoded doctype in `make_return_doc`
2025-05-20 09:59:12 +05:30
Frappe PR Bot
2206f492ac fix: sync translations from crowdin (#47554) 2025-05-20 03:39:18 +02:00
Khushi Rawat
ff2ccf9bce fix: asset image field updation issue (#47615) 2025-05-20 01:07:14 +05:30
rohitwaghchaure
3e098da01f fix: GL entries for rejected returned materials (#47612) 2025-05-19 22:53:03 +05:30
barredterra
45a5c19dd4 fix: remove hardcoded doctype in make_return_doc 2025-05-19 15:40:57 +02:00
Debin Robert
a912e5ddae fix: wrong source fieldname in lead details report 2025-05-19 15:02:00 +05:30
ruthra kumar
2eea8c9e79 Merge pull request #47505 from aerele/fix/address-link
fix: validate inter company transaction address links
2025-05-19 13:08:51 +05:30
rohitwaghchaure
ca0e53dd78 feat: allow to set valuation rate for Rejected Materials (#47582) 2025-05-19 12:51:32 +05:30
rohitwaghchaure
a058fe7319 fix: incorrect qty during reset (#47593) 2025-05-18 22:50:46 +05:30
Frappe PR Bot
0daeb26e96 chore: update POT file (#47594) 2025-05-18 17:51:30 +02:00
l0gesh29
c67ba2d49b feat: add option to calculate ageing based on report date or today date 2025-05-16 15:50:02 +05:30
Khushi Rawat
7bc974b65a Merge pull request #47573 from khushi8112/asset-capitalization-child-table-row-deletion
fix: alias 'name' and 'parent' to prevent child row mapping issues
2025-05-16 15:34:28 +05:30
Khushi Rawat
1ca51e4f14 fix: removed invalid child param to prevent callback failure 2025-05-16 15:08:16 +05:30
Khushi Rawat
a418e377f4 fix: alias name and parent to prevent child row mapping issues 2025-05-16 15:02:26 +05:30
ruthra kumar
7f9f3027cc Merge pull request #47274 from ruthra-kumar/budget_validation_refactor
refactor: Budget validation
2025-05-16 13:20:29 +05:30
ruthra kumar
3a8075198b chore: patch to force default new controller 2025-05-16 13:03:36 +05:30
ruthra kumar
e1f32df5b3 refactor: make use of frappe._dict 2025-05-16 12:45:13 +05:30
ruthra kumar
6fabedd0da refactor: cleaner code with less verbosity 2025-05-16 12:45:13 +05:30
ruthra kumar
ee3d7db29d refactor(test): tests should use new controller 2025-05-16 12:45:13 +05:30
ruthra kumar
d4ac042d85 chore: make new budget controller configurable 2025-05-16 12:45:13 +05:30
ruthra kumar
58556c82bb refactor: handle exception approver role for budget 2025-05-16 12:45:13 +05:30
ruthra kumar
a7202201f7 refactor: fetch monthly distribution as well 2025-05-16 12:45:13 +05:30
ruthra kumar
3fb5d835f2 refactor: validate budget on cancel as well 2025-05-16 12:45:13 +05:30
ruthra kumar
55cb91ce20 refactor: use meta to identify tree 2025-05-16 12:45:13 +05:30
ruthra kumar
f886b50e7a refactor: handle group nodes 2025-05-16 12:45:13 +05:30
ruthra kumar
11f7c1e49a refactor: validate on GL creation 2025-05-16 12:45:13 +05:30
ruthra kumar
d9d2020b46 refactor: allow for better translation 2025-05-16 12:45:13 +05:30
ruthra kumar
b7e70bb746 refactor: better error message 2025-05-16 12:45:13 +05:30
ruthra kumar
2ecb2fa4af refactor: stateful variables 2025-05-16 12:45:13 +05:30
ruthra kumar
49bb72bcd2 refactor: better query parameters for PO and MR 2025-05-16 12:45:13 +05:30
ruthra kumar
3064646a8f refactor: better error message 2025-05-16 12:45:13 +05:30
ruthra kumar
fcf572e641 refactor: always query booked expenses 2025-05-16 12:45:13 +05:30
ruthra kumar
fc24bbf5ad refactor: handle breach on total expense 2025-05-16 12:45:13 +05:30
ruthra kumar
3e80248cde refactor: PO validation happens after submission 2025-05-16 12:45:13 +05:30
ruthra kumar
0e016a9c47 refactor: replace duplicate validation with single method 2025-05-16 12:45:13 +05:30
ruthra kumar
593729ac2b refactor: code refactor 2025-05-16 12:45:13 +05:30
ruthra kumar
388d901668 refactor: handle monthly distribution limit 2025-05-16 12:45:13 +05:30
ruthra kumar
d52469c51e refactor: handle actual expense 2025-05-16 12:45:13 +05:30
ruthra kumar
791ad16883 refactor: cleaner initialization 2025-05-16 12:45:13 +05:30
ruthra kumar
ec466d024a refactor: minor readbility changes 2025-05-16 12:45:13 +05:30
ruthra kumar
d933d0b478 refactor: handle budget for material request 2025-05-16 12:45:13 +05:30
ruthra kumar
626b345caf refactor: better local variables and contextual error messages 2025-05-16 12:45:13 +05:30
ruthra kumar
7791777d1a refactor: barebones validation 2025-05-16 12:45:13 +05:30
ruthra kumar
1c574561eb refactor: fetch amount booked in material request 2025-05-16 12:45:12 +05:30
ruthra kumar
10c3bb4971 refactor: move validate trigger to controller 2025-05-16 12:45:12 +05:30
ruthra kumar
fb667f5e09 refactor: fetch billed PO amount 2025-05-16 12:45:12 +05:30
ruthra kumar
31ac9a5ea0 refactor: make code more pythonic 2025-05-16 12:45:12 +05:30
ruthra kumar
bd42d09592 refactor: replace get_doc with sql 2025-05-16 12:45:12 +05:30
ruthra kumar
63dae6bd42 refactor: validate only for overlapping keys 2025-05-16 12:45:12 +05:30
ruthra kumar
22150d8175 refactor: budgetvaldiation class 2025-05-16 12:45:12 +05:30
Mihir Kandoi
9fb7490c40 Merge pull request #47570 from mihir-kandoi/enforce_time_logs
feat: add checkbox for validating time logs in job card
2025-05-16 12:06:59 +05:30
Mihir Kandoi
2d9a6a4de8 feat: add checbox for validating time logs in job card 2025-05-16 11:32:22 +05:30
ruthra kumar
b846686971 Merge pull request #47569 from ruthra-kumar/budget_cleanup_test_records
fix(test): clean up data so subsequent test cases don't flake
2025-05-16 10:15:05 +05:30
ruthra kumar
5c15b96d7d fix(test): clean up data so subsequent test cases don't flake 2025-05-16 09:55:56 +05:30
ruthra kumar
9175d0dbbf Merge pull request #47562 from ruthra-kumar/fix_dormant_broken_budget_test_case
fix(test): dormant broken test in Budget
2025-05-15 20:05:28 +05:30
ruthra kumar
9ed69f96e8 fix(test): pass sufficient amount to breach accumulated limit 2025-05-15 16:47:30 +05:30
ruthra kumar
ca07ec79ee fix(test): dormant broken test in Budget 2025-05-15 16:12:55 +05:30
rohitwaghchaure
31c4c1ccf3 fix: sales order qty issue (#47560) 2025-05-15 14:53:32 +05:30
ljain112
6dc459db58 fix: include only invoices with update_stock = 0 for billed amt in delivery note. 2025-05-15 14:06:04 +05:30
rohitwaghchaure
fc554ba599 fix: stop additional item (#47548) 2025-05-15 10:30:46 +05:30
Mihir Kandoi
82161e9cb5 fix: mapping of dispatch address when creating PO from SO (#47552)
* fix: mapping of dispatch address when creating PO from SO

* fix: add to default supplier function as well
2025-05-14 20:35:45 +05:30
Corentin Forler
89f98b835c fix: Escape UOM in pricing rule query 2025-05-14 14:59:09 +02:00
barredterra
61e96f3fb5 fix: find table row 2025-05-13 18:13:26 +02:00
Abdeali Chharchhoda
80320ffa25 refactor: simplify source_name assignment in map_current_doc function 2025-05-13 16:43:13 +05:30
Bhavan23
0caa757dd6 test: add test case to validate inter-company transaction address links 2025-05-12 12:48:27 +05:30
Bhavan23
aed46ad5b9 fix: validate inter-company transaction address links 2025-05-12 12:44:18 +05:30
l0gesh29
6d269b4409 fix(SalesAnalytics): Ignore opening entries 2025-05-09 17:27:48 +05:30
Prateek Karamchandani
7a5cbc759c fix: display stock value in currency format in chart warehouse wise stock value 2025-04-25 06:19:00 +00:00
509 changed files with 392543 additions and 323144 deletions

View File

@@ -42,3 +42,6 @@ a308792ee7fda18a681e9181f4fd00b36385bc23
# noisy typing refactoring of get_item_details
7b7211ac79c248a79ba8a999ff34e734d874c0ae
d827ed21adc7b36047e247cbb0dc6388d048a7f9
# `frappe.flags.in_test` => `frappe.in_test`
7a482a69985c952de0e8193c9d4e086aee65ee6d

View File

@@ -66,7 +66,7 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app payments --branch ${githubbranch%"-hotfix"}
bench get-app payments --branch develop
bench get-app erpnext "${GITHUB_WORKSPACE}"
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi

View File

@@ -5,6 +5,9 @@ on:
- closed
- labeled
permissions:
contents: read
jobs:
main:
runs-on: ubuntu-latest

View File

@@ -2,6 +2,10 @@ name: Trigger Docker build on release
on:
release:
types: [released]
permissions:
contents: read
jobs:
curl:
runs-on: ubuntu-latest

View File

@@ -3,6 +3,9 @@ on:
pull_request:
types: [ opened, synchronize, reopened, edited ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest

View File

@@ -2,6 +2,10 @@
# To add/remove versions just modify the matrix.
name: Create weekly release pull requests
permissions:
contents: read
on:
schedule:
# 9:30 UTC => 3 PM IST Tuesday

View File

@@ -3,6 +3,10 @@ on:
pull_request_target:
types: [opened, reopened]
permissions:
issues: write
pull-requests: write
jobs:
triage:
runs-on: ubuntu-latest

View File

@@ -3,6 +3,9 @@ name: Linters
on:
pull_request: { }
permissions:
contents: read
jobs:
linters:

View File

@@ -10,6 +10,9 @@ on:
- '**.csv'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: patch-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
@@ -36,7 +39,7 @@ jobs:
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
python -m compileall -fq "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1

View File

@@ -11,6 +11,9 @@ on:
- "**.html"
- "**.csv"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest

View File

@@ -3,6 +3,10 @@ on:
push:
branches:
- version-13
permissions:
contents: read
jobs:
release:
name: Release

View File

@@ -7,6 +7,9 @@ concurrency:
group: server-individual-tests-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: false
permissions:
contents: read
jobs:
discover:
runs-on: ubuntu-latest

View File

@@ -10,6 +10,9 @@ on:
- "**.md"
- "**.html"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest

View File

@@ -25,6 +25,9 @@ on:
required: false
type: string
permissions:
contents: read
concurrency:
group: server-mariadb-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
@@ -65,7 +68,7 @@ jobs:
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
python -m compileall -fq "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1

View File

@@ -12,6 +12,9 @@ concurrency:
group: server-postgres-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
if: ${{ contains(github.event.pull_request.labels.*.name, 'postgres') }}
@@ -50,7 +53,7 @@ jobs:
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
python -m compileall -fq "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1

View File

@@ -57,7 +57,7 @@ def get_company_currency(company):
def set_perpetual_inventory(enable=1, company=None):
if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company()
company = "_Test Company" if frappe.in_test else get_default_company()
company = frappe.get_doc("Company", company)
company.enable_perpetual_inventory = enable
@@ -77,7 +77,7 @@ def encode_company_abbr(name, company=None, abbr=None):
def is_perpetual_inventory_enabled(company):
if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company()
company = "_Test Company" if frappe.in_test else get_default_company()
if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {}

View File

@@ -317,7 +317,7 @@ def get_already_booked_amount(doc, item):
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
accounts_frozen_upto = frappe.get_single_value("Accounts Settings", "acc_frozen_upto")
def _book_deferred_revenue_or_expense(
item,
@@ -526,7 +526,7 @@ def make_gl_entries(
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit()
except Exception as e:
if frappe.flags.in_test:
if frappe.in_test:
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
raise e
else:

View File

@@ -92,7 +92,7 @@ class Account(NestedSet):
super().on_update()
def onload(self):
frozen_accounts_modifier = frappe.db.get_single_value("Accounts Settings", "frozen_accounts_modifier")
frozen_accounts_modifier = frappe.get_single_value("Accounts Settings", "frozen_accounts_modifier")
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.set_onload("can_freeze_account", True)
@@ -602,7 +602,7 @@ def _ensure_idle_system():
# 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:
if frappe.in_test:
return
last_gl_update = None

View File

@@ -139,6 +139,11 @@ frappe.treeview_settings["Account"] = {
description: __(
"Further accounts can be made under Groups, but entries can be made against non-Groups"
),
onchange: function () {
if (!this.value) {
this.layout.set_value("root_type", "");
}
},
},
{
fieldtype: "Select",
@@ -231,6 +236,10 @@ frappe.treeview_settings["Account"] = {
root_company,
]);
} else {
const node = treeview.tree.get_selected_node();
if (node.is_root) {
frappe.throw(__("Cannot create root account."));
}
treeview.new_node();
}
},
@@ -249,7 +258,8 @@ frappe.treeview_settings["Account"] = {
].treeview.page.fields_dict.root_company.get_value() ||
frappe.flags.ignore_root_company_validation) &&
node.expandable &&
!node.hide_add
!node.hide_add &&
!node.is_root
);
},
click: function () {
@@ -265,12 +275,14 @@ frappe.treeview_settings["Account"] = {
label: __("View Ledger"),
click: function (node, btn) {
frappe.route_options = {
account: node.label,
from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
company:
frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(),
};
if (node.parent_label) {
frappe.route_options["account"] = node.label;
}
frappe.set_route("query-report", "General Ledger");
},
btnClass: "hidden-xs",

View File

@@ -0,0 +1,817 @@
{
"country_code": "au",
"name": "Australia - Chart of Accounts with Account Numbers",
"tree": {
"Assets": {
"Current Assets": {
"Cash On Hand": {
"Cash On Hand": {
"account_number": "11010",
"account_type": "Cash"
},
"account_number": "110",
"is_group": 1
},
"Cash at Bank": {
"Every Day Bank Account": {
"account_number": "11510",
"account_type": "Bank"
},
"Business Savings Account": {
"account_number": "11520"
},
"Business Term Deposit": {
"account_number": "11530"
},
"account_number": "115",
"is_group": 1
},
"Trade Receivables": {
"Trade Debtors": {
"account_number": "12010",
"account_type": "Receivable"
},
"Provision for Doubtful Debts": {
"account_number": "12020"
},
"Sundry Debtors": {
"account_number": "12030"
},
"Debtor Refund": {
"account_number": "12040"
},
"account_number": "120",
"is_group": 1
},
"Inventory": {
"Stock On Hand": {
"account_number": "13010",
"account_type": "Stock"
},
"WIP - Work In Progress - Manufacturing": {
"account_number": "13020"
},
"account_number": "130",
"is_group": 1
},
"Prepayments": {
"Prepayments": {
"account_number": "14010"
},
"Provisional Tax Paid": {
"account_number": "14020"
},
"account_number": "140",
"is_group": 1
},
"account_number": "11",
"is_group": 1
},
"Non Current Assets": {
"Plant & Equipment": {
"Plant & Equipment": {
"account_number": "16010",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Plant & Equipment": {
"account_number": "16020",
"account_type": "Accumulated Depreciation"
},
"account_number": "160",
"is_group": 1
},
"Motor Vehicle": {
"Motor Vehicle": {
"account_number": "16110",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Motor Vehicle": {
"account_number": "16120",
"account_type": "Accumulated Depreciation"
},
"account_number": "161",
"is_group": 1
},
"Office Equipment": {
"Office Furniture & Equipment": {
"account_number": "16210",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Office Furniture & Equipment": {
"account_number": "16220",
"account_type": "Accumulated Depreciation"
},
"account_number": "162",
"is_group": 1
},
"Computer Equipment": {
"Computer Equipment": {
"account_number": "16310",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Computer Equipment": {
"account_number": "16320",
"account_type": "Accumulated Depreciation"
},
"account_number": "163",
"is_group": 1
},
"Building": {
"Buildings": {
"account_number": "16410",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Buildings": {
"account_number": "16420",
"account_type": "Accumulated Depreciation"
},
"CWIP - Construction Work In Progress": {
"account_number": "16430",
"account_type": "Capital Work in Progress"
},
"Accumulated Depreciation - Others": {
"account_number": "16440",
"account_type": "Accumulated Depreciation"
},
"account_number": "164",
"is_group": 1
},
"Related Party": {
"Loan to Party 1": {
"account_number": "17010"
},
"account_number": "170",
"is_group": 1
},
"Investments & Unlisted Entities": {
"Investment - Entity 1": {
"account_number": "17510"
},
"account_number": "175",
"is_group": 1
},
"Intagible Assets": {
"Goodwill": {
"account_number": "18010"
},
"Opening Balance Temporary ": {
"account_number": "18090",
"account_type": "Temporary"
},
"account_number": "180",
"is_group": 1
},
"account_number": "16",
"is_group": 1
},
"account_number": "1",
"root_type": "Asset"
},
"Liabilities": {
"Current Liabilities": {
"Trade Payables - Current": {
"Trade Creditors": {
"account_number": "21010",
"account_type": "Payable"
},
"Goods Received Not Invoiced": {
"account_number": "21050",
"account_type": "Stock Received But Not Billed"
},
"Service Received Not Invoiced": {
"account_number": "21060"
},
"Asset Received Not Invoiced": {
"account_number": "21070",
"account_type": "Asset Received But Not Billed"
},
"account_number": "210",
"is_group": 1
},
"Other Payables - Current": {
"Accrued Expenses": {
"account_number": "21510"
},
"Payroll - Wages Clearing": {
"account_number": "21550"
},
"Payroll - Superannuation Deductions": {
"account_number": "21555"
},
"Payroll - Misc Deductions": {
"account_number": "21560"
},
"Payroll - Withholding Tax Payable": {
"account_number": "21565"
},
"account_number": "215",
"is_group": 1
},
"GST": {
"GST Payments to ATO": {
"account_number": "22030"
},
"Provision for PAYG Tax": {
"account_number": "22040"
},
"account_number": "220",
"account_type": "Tax",
"is_group": 1
},
"Interest & Non Bearing Liabilities - Current": {
"Credit Card - VISA": {
"account_number": "22510"
},
"account_number": "225",
"is_group": 1
},
"Bank Overdraft": {
"Bank Overdraft Cash at Bank": {
"account_number": "23010"
},
"account_number": "230",
"is_group": 1
},
"Trade Finance": {
"Trade Finance": {
"account_number": "23510"
},
"account_number": "235",
"is_group": 1
},
"Lease Liabilities": {
"Finance Lease - Current": {
"account_number": "24010"
},
"account_number": "240",
"is_group": 1
},
"Provisions": {
"Provision for Long Service Leave": {
"account_number": "24510"
},
"Provision for Holiday Pay": {
"account_number": "24520"
},
"account_number": "245",
"is_group": 1
},
"account_number": "21",
"is_group": 1
},
"Non Current Liabilities": {
"Trade & Other Payables - Non Current": {
"Loan Account - Party 1": {
"account_number": "25010"
},
"account_number": "250",
"is_group": 1
},
"Interest & Non Bearing Liabilities - Non Current": {
"Non Current Liability - Director Loan": {
"account_number": "25510"
},
"account_number": "255",
"is_group": 1
},
"Bank Loans - Non Current": {
"Bank Loan 1 - Non Current": {
"account_number": "26010"
},
"account_number": "260",
"is_group": 1
},
"Lease Liabilities - Non Current": {
"Finance Lease - Non Current": {
"account_number": "27010"
},
"account_number": "270",
"is_group": 1
},
"Provisions - Non Current": {
"Provision for Long Service Leave": {
"account_number": "27510"
},
"Provision for Holiday Pay": {
"account_number": "27520"
},
"account_number": "275",
"is_group": 1
},
"account_number": "25",
"is_group": 1
},
"account_number": "2",
"root_type": "Liability"
},
"Equity": {
"Equity": {
"Owner's/Shareholder's Equity": {
"Owner's/Shareholders Capital": {
"account_number": "31010",
"account_type": "Equity"
},
"Owner's/Shareholders Drawings": {
"account_number": "31020",
"account_type": "Equity"
},
"account_number": "310",
"is_group": 1
},
"Earnings": {
"Current Year Earnings": {
"account_number": "35010",
"account_type": "Equity"
},
"Retained Earnings": {
"account_number": "35020",
"account_type": "Equity"
},
"account_number": "350",
"is_group": 1
},
"account_number": "31",
"is_group": 1
},
"account_number": "3",
"root_type": "Equity"
},
"Revenue": {
"Revenue": {
"Sales Revenue": {
"Sales Income": {
"account_number": "41010",
"account_type": "Income Account"
},
"Freight Income": {
"account_number": "41020",
"account_type": "Income Account"
},
"Other Income": {
"account_number": "41030",
"account_type": "Income Account"
},
"Service Income": {
"account_number": "41040",
"account_type": "Income Account"
},
"account_number": "410",
"is_group": 1
},
"Other Revenue": {
"Commission Received": {
"account_number": "42010"
},
"Discounts Received": {
"account_number": "42020"
},
"Interest received": {
"account_number": "42030"
},
"Profit/Loss on Sales of Assets": {
"account_number": "42040"
},
"Rent Received": {
"account_number": "42050"
},
"Sundry Income": {
"account_number": "42060"
},
"account_number": "420",
"is_group": 1
},
"account_number": "41",
"is_group": 1
},
"account_number": "4",
"root_type": "Income"
},
"Cost of Goods": {
"Cost of Goods": {
"Cost of Goods Sold": {
"Cost of Goods Sold": {
"account_number": "51010",
"account_type": "Cost of Goods Sold"
},
"Freight Expenses (sales related)": {
"account_number": "51020"
},
"Discounts Given": {
"account_number": "51030"
},
"Subcontracting Charges": {
"account_number": "51040"
},
"account_number": "510",
"is_group": 1
},
"Other COGS": {
"Purchases - Miscellaneous": {
"account_number": "52010"
},
"Duty & Customs Fees": {
"account_number": "52020",
"account_type": "Tax"
},
"Freight Inwards": {
"account_number": "52030",
"account_type": "Chargeable"
},
"Stock Adjustment": {
"account_number": "52040",
"account_type": "Stock Adjustment"
},
"Stock Wirte Off": {
"account_number": "52050",
"account_type": "Stock Adjustment"
},
"Stock Valuation Expenses": {
"account_number": "52060",
"account_type": "Expenses Included In Valuation"
},
"Asset Valuation Expenses": {
"account_number": "52070",
"account_type": "Expenses Included In Asset Valuation"
},
"account_number": "520",
"is_group": 1
},
"account_number": "51",
"is_group": 1
},
"account_number": "5",
"root_type": "Expense"
},
"Expenses": {
"Fixed Expenses": {
"Payroll & Related Expenses": {
"Salaries & Wages": {
"account_number": "61010"
},
"Superannuation": {
"account_number": "61015"
},
"Staff Amenities - GST Paid": {
"account_number": "61020"
},
"Staff Amenities - GST Free": {
"account_number": "61025"
},
"Staff Recruitment": {
"account_number": "61030"
},
"Staff Training": {
"account_number": "61035"
},
"Fringe Benefits Tax": {
"account_number": "61040"
},
"Payroll Tax": {
"account_number": "61045"
},
"Workers Compensation": {
"account_number": "61050"
},
"Long Service Leave": {
"account_number": "61060"
},
"Mileage Reimbursement": {
"account_number": "61070"
},
"Overtime": {
"account_number": "61080"
},
"Worksafe Insurance": {
"account_number": "61090"
},
"account_number": "610",
"is_group": 1
},
"Depreciation Expenses": {
"Depreciation - Plant & Equipment": {
"account_number": "62010",
"account_type": "Depreciation"
},
"Depreciation - Motor Vehicle": {
"account_number": "62020",
"account_type": "Depreciation"
},
"Depreciation - Office Equipment": {
"account_number": "62030",
"account_type": "Depreciation"
},
"Depreciation - Computer Equipment": {
"account_number": "62040",
"account_type": "Depreciation"
},
"Depreciation - Building": {
"account_number": "62050",
"account_type": "Depreciation"
},
"Depreciation - Others": {
"account_number": "62510",
"account_type": "Depreciation"
},
"account_number": "620",
"is_group": 1
},
"account_number": "61",
"is_group": 1
},
"Accrued Expenses": {
"Accrued Expenses": {
"Accrued Expenses - Salaries & Wages": {
"account_number": "63010"
},
"Accrued Expenses - Interest": {
"account_number": "63020"
},
"account_number": "630",
"is_group": 1
},
"account_number": "63",
"is_group": 1
},
"Operating Expenses": {
"General and Administrative Expenses": {
"Low Value Assets less than $300": {
"account_number": "64010"
},
"Office Supplies": {
"account_number": "64020"
},
"Postage & Courier": {
"account_number": "64025"
},
"Printing & Stationery": {
"account_number": "64030"
},
"Registration Fees / Filing Fees": {
"account_number": "64040"
},
"Travel & Accommodation - Local": {
"account_number": "64050"
},
"Travel & Accommodation - Overseas": {
"account_number": "64060"
},
"Relocation Costs": {
"account_number": "64070"
},
"Hire Charges": {
"account_number": "64080"
},
"Repairs & Maintenance": {
"account_number": "64210"
},
"Cleaning Expenses": {
"account_number": "64215"
},
"Uniforms": {
"account_number": "64220"
},
"Security": {
"account_number": "64225"
},
"Subscriptions & Licences": {
"account_number": "64510"
},
"Software Expenses": {
"account_number": "64515"
},
"Marketing Expenses": {
"account_number": "64520"
},
"Advertising Expenses": {
"account_number": "64525"
},
"Website Hosting & Domain Expenses": {
"account_number": "64530"
},
"Computer Repairs / Supplies": {
"account_number": "64540"
},
"Conferences": {
"account_number": "64550"
},
"Consultancy /Contract Services": {
"account_number": "64560"
},
"Training Services": {
"account_number": "64570"
},
"Workshop Supplies": {
"account_number": "64580"
},
"Consumables": {
"account_number": "64585"
},
"Entertainment Expenses - Deductible": {
"account_number": "64810"
},
"Entertainment Expenses - Non Deductible": {
"account_number": "64820"
},
"Amortisation Of Goodwill": {
"account_number": "64910"
},
"General / Miscellaneous Expenses": {
"account_number": "64915",
"account_type": "Chargeable"
},
"Donations": {
"account_number": "64920"
},
"Client Gifts": {
"account_number": "64930"
},
"Employee Gifts": {
"account_number": "64935"
},
"account_number": "640",
"is_group": 1
},
"Occupancy Expenses": {
"Rental Expenses": {
"account_number": "65010"
},
"Property Insurance": {
"account_number": "65020"
},
"Electricity Expenses": {
"account_number": "65030"
},
"Water Rates": {
"account_number": "65040"
},
"Gas Expenses": {
"account_number": "65050"
},
"Property Taxes": {
"account_number": "65060"
},
"Rates": {
"account_number": "65070"
},
"account_number": "650",
"is_group": 1
},
"Communication & Vehicle Expenses": {
"Internet Expenses": {
"account_number": "66010"
},
"Mobile Telephone": {
"account_number": "66020"
},
"Telephone Expenses": {
"account_number": "66030"
},
"Motor Vehicle - Fuel Expenses": {
"account_number": "66040"
},
"Motor Vehicle - Parking & Tolls": {
"account_number": "66050"
},
"Motor Vehicle - Registration & Insurance": {
"account_number": "66060"
},
"Motor Vehicle - Service & Repairs": {
"account_number": "66070"
},
"Taxi": {
"account_number": "66080"
},
"account_number": "660",
"is_group": 1
},
"account_number": "64",
"is_group": 1
},
"Non-Operating Expenses": {
"Finance Costs": {
"Interest - Bank Loans": {
"account_number": "67010"
},
"Interest - Finance Leases": {
"account_number": "67020"
},
"Interest - Other Loans": {
"account_number": "67025"
},
"Insurance": {
"account_number": "67030"
},
"Bank Charges": {
"account_number": "67050"
},
"Rounding off": {
"account_number": "67055",
"account_type": "Round Off"
},
"Audit Fees": {
"account_number": "67060"
},
"Accounting Fees": {
"account_number": "67070"
},
"Legal Fees": {
"account_number": "67080"
},
"Management Fees": {
"account_number": "67090"
},
"account_number": "670",
"is_group": 1
},
"Other Costs": {
"Doubtful Debts": {
"account_number": "67510"
},
"Fines": {
"account_number": "67520"
},
"Debt Collection": {
"account_number": "67530"
},
"Bad Debts": {
"account_number": "67540"
},
"account_number": "675",
"is_group": 1
},
"account_number": "67",
"is_group": 1
},
"Variable Expenses": {
"Variable Expenses": {
"Bonus & Commissions Paid": {
"account_number": "68010"
},
"Bonus & Commissions To be Paid": {
"account_number": "68020"
},
"Warranty Claims": {
"account_number": "68030"
},
"account_number": "680",
"is_group": 1
},
"account_number": "68",
"is_group": 1
},
"account_number": "6",
"root_type": "Expense"
},
"Other Income": {
"Other Income": {
"Interest Income": {
"Interest Income": {
"account_number": "71010"
},
"account_number": "710",
"is_group": 1
},
"Asset Disposal Income": {
"Gain on Asset Disposal": {
"account_number": "73010"
},
"account_number": "730",
"is_group": 1
},
"account_number": "71",
"is_group": 1
},
"account_number": "7",
"root_type": "Income"
},
"Other Expenses": {
"Other Expenses": {
"Income Tax Expenses": {
"Income Tax Expenses": {
"account_number": "81010"
},
"account_number": "810",
"is_group": 1
},
"Foreign Exchange Gain/Loss": {
"Exchange Loss/Gain - Realized": {
"account_number": "82010"
},
"account_number": "820",
"is_group": 1
},
"Asset Disposal Expenses": {
"Loss on Asset Disposal": {
"account_number": "83010"
},
"account_number": "830",
"is_group": 1
},
"account_number": "81",
"is_group": 1
},
"account_number": "8",
"root_type": "Expense"
}
}
}

View File

@@ -96,8 +96,20 @@
"account_number": "1132.000"
},
"account_number": "1130.000"
},
},
"Pajak Dibayar di Muka": {
"PPN Masukan": {
"account_number": "1151.001",
"account_type": "Tax"
},
"PPh 23 Dibayar di Muka": {
"account_number": "1152.001",
"account_type": "Tax"
},
"account_number": "1150.000"
},
"account_number": "1100.000"
},
"Aktiva Tetap": {
"Aktiva": {
@@ -557,6 +569,10 @@
"Hutang Pajak": {
"account_number": "2141.000",
"account_type": "Payable"
},
"PPN Keluaran": {
"account_number": "2142.000",
"account_type": "Tax"
},
"account_number": "2140.000"
},

View File

@@ -2,16 +2,7 @@
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
class UnitTestAccountClosingBalance(UnitTestCase):
"""
Unit tests for AccountClosingBalance.
Use this class for testing individual functions and methods.
"""
pass
from frappe.tests import IntegrationTestCase
class TestAccountClosingBalance(IntegrationTestCase):

View File

@@ -83,7 +83,7 @@ class AccountingDimension(Document):
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
def after_insert(self):
if frappe.flags.in_test:
if frappe.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(
@@ -91,7 +91,7 @@ class AccountingDimension(Document):
)
def on_trash(self):
if frappe.flags.in_test:
if frappe.in_test:
delete_accounting_dimension(doc=self)
else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True)
@@ -213,7 +213,7 @@ def delete_accounting_dimension(doc):
@frappe.whitelist()
def disable_dimension(doc):
if frappe.flags.in_test:
if frappe.in_test:
toggle_disabling(doc=doc)
else:
frappe.enqueue(toggle_disabling, doc=doc)

View File

@@ -22,4 +22,32 @@ frappe.ui.form.on("Accounts Settings", {
}
);
},
add_taxes_from_taxes_and_charges_template(frm) {
toggle_tax_settings(frm, "add_taxes_from_taxes_and_charges_template");
},
add_taxes_from_item_tax_template(frm) {
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
},
drop_ar_procedures: function (frm) {
frm.call({
doc: frm.doc,
method: "drop_ar_sql_procedures",
callback: function (r) {
frappe.show_alert(__("Procedures dropped"), 5);
},
});
},
});
function toggle_tax_settings(frm, field_name) {
if (frm.doc[field_name]) {
const other_field =
field_name === "add_taxes_from_item_tax_template"
? "add_taxes_from_taxes_and_charges_template"
: "add_taxes_from_item_tax_template";
frm.set_value(other_field, 0);
}
}

View File

@@ -19,6 +19,7 @@
"column_break_17",
"enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account",
"confirm_before_resetting_posting_date",
"journals_section",
"merge_similar_account_heads",
"deferred_accounting_settings_section",
@@ -31,6 +32,7 @@
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"add_taxes_from_taxes_and_charges_template",
"book_tax_discount_loss",
"round_row_wise_tax",
"print_settings",
@@ -38,8 +40,14 @@
"show_taxes_as_table_in_print",
"column_break_12",
"show_payment_schedule_in_print",
"item_price_settings_section",
"maintain_same_internal_transaction_rate",
"column_break_feyo",
"maintain_same_rate_action",
"role_to_override_stop_action",
"currency_exchange_section",
"allow_stale",
"allow_pegged_currencies_exchange_rates",
"column_break_yuug",
"stale_days",
"section_break_jpd0",
@@ -59,7 +67,6 @@
"pos_setting_section",
"post_change_gl_entries",
"column_break_xrnd",
"use_sales_invoice_in_pos",
"assets_tab",
"asset_settings_section",
"calculate_depr_using_total_days",
@@ -83,10 +90,14 @@
"receivable_payable_remarks_length",
"accounts_receivable_payable_tuning_section",
"receivable_payable_fetch_method",
"column_break_ntmi",
"drop_ar_procedures",
"legacy_section",
"ignore_is_opening_check_for_reporting",
"payment_request_settings",
"create_pr_in_draft_status"
"create_pr_in_draft_status",
"budget_settings",
"use_new_budget_controller"
],
"fields": [
{
@@ -542,19 +553,12 @@
"fieldname": "column_break_xrnd",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If enabled, Sales Invoice will be generated instead of POS Invoice in POS Transactions for real-time update of G/L and Stock Ledger.",
"fieldname": "use_sales_invoice_in_pos",
"fieldtype": "Check",
"label": "Use Sales Invoice"
},
{
"default": "Buffered Cursor",
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor"
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
@@ -565,6 +569,81 @@
"fieldname": "legacy_section",
"fieldtype": "Section Break",
"label": "Legacy Fields"
},
{
"default": "0",
"fieldname": "maintain_same_internal_transaction_rate",
"fieldtype": "Check",
"label": "Maintain Same Rate Throughout Internal Transaction"
},
{
"default": "Stop",
"depends_on": "maintain_same_internal_transaction_rate",
"fieldname": "maintain_same_rate_action",
"fieldtype": "Select",
"label": "Action if Same Rate is Not Maintained Throughout Internal Transaction",
"mandatory_depends_on": "maintain_same_internal_transaction_rate",
"options": "Stop\nWarn"
},
{
"depends_on": "eval: doc.maintain_same_internal_transaction_rate && doc.maintain_same_rate_action == 'Stop'",
"fieldname": "role_to_override_stop_action",
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
"options": "Role"
},
{
"fieldname": "budget_settings",
"fieldtype": "Tab Break",
"label": "Budget"
},
{
"default": "1",
"fieldname": "use_new_budget_controller",
"fieldtype": "Check",
"label": "Use New Budget Controller"
},
{
"default": "1",
"description": "If enabled, user will be alerted before resetting posting date to current date in relevant transactions",
"fieldname": "confirm_before_resetting_posting_date",
"fieldtype": "Check",
"label": "Confirm before resetting posting date"
},
{
"fieldname": "item_price_settings_section",
"fieldtype": "Section Break",
"label": "Item Price Settings"
},
{
"fieldname": "column_break_feyo",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "System will do an implicit conversion using the pegged currency. <br>\nEx: Instead of AED -&gt; INR, system will do AED -&gt; USD -&gt; INR using the pegged exchange rate of AED against USD.",
"documentation_url": "/app/pegged-currencies/Pegged Currencies",
"fieldname": "allow_pegged_currencies_exchange_rates",
"fieldtype": "Check",
"label": "Allow Implicit Pegged Currency Conversion"
},
{
"default": "0",
"description": "If no taxes are set, and Taxes and Charges Template is selected, the system will automatically apply the taxes from the chosen template.",
"fieldname": "add_taxes_from_taxes_and_charges_template",
"fieldtype": "Check",
"label": "Automatically Add Taxes from Taxes and Charges Template"
},
{
"fieldname": "column_break_ntmi",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"",
"description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report",
"fieldname": "drop_ar_procedures",
"fieldtype": "Button",
"label": "Drop Procedures"
}
],
"grid_page_length": 50,
@@ -573,7 +652,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-05-05 12:29:38.302027",
"modified": "2025-06-23 15:55:33.346398",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -25,7 +25,9 @@ class AccountsSettings(Document):
acc_frozen_upto: DF.Date | None
add_taxes_from_item_tax_template: DF.Check
add_taxes_from_taxes_and_charges_template: DF.Check
allow_multi_currency_invoices_against_single_party_account: DF.Check
allow_pegged_currencies_exchange_rates: DF.Check
allow_stale: DF.Check
auto_reconcile_payments: DF.Check
auto_reconciliation_job_trigger: DF.Int
@@ -37,6 +39,7 @@ class AccountsSettings(Document):
book_tax_discount_loss: DF.Check
calculate_depr_using_total_days: DF.Check
check_supplier_invoice_uniqueness: DF.Check
confirm_before_resetting_posting_date: DF.Check
create_pr_in_draft_status: DF.Check
credit_controller: DF.Link | None
delete_linked_ledger_entries: DF.Check
@@ -50,14 +53,17 @@ class AccountsSettings(Document):
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
ignore_is_opening_check_for_reporting: DF.Check
maintain_same_internal_transaction_rate: DF.Check
maintain_same_rate_action: DF.Literal["Stop", "Warn"]
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
post_change_gl_entries: DF.Check
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
role_allowed_to_over_bill: DF.Link | None
role_to_override_stop_action: DF.Link | None
round_row_wise_tax: DF.Check
show_balance_in_coa: DF.Check
show_inclusive_tax_in_print: DF.Check
@@ -67,10 +73,11 @@ class AccountsSettings(Document):
submit_journal_entries: DF.Check
unlink_advance_payment_on_cancelation_of_order: DF.Check
unlink_payment_on_cancellation_of_invoice: DF.Check
use_sales_invoice_in_pos: DF.Check
use_new_budget_controller: DF.Check
# end: auto-generated types
def validate(self):
self.validate_auto_tax_settings()
old_doc = self.get_doc_before_save()
clear_cache = False
@@ -94,9 +101,6 @@ class AccountsSettings(Document):
if old_doc.acc_frozen_upto != self.acc_frozen_upto:
self.validate_pending_reposts()
if old_doc.use_sales_invoice_in_pos != self.use_sales_invoice_in_pos:
self.validate_invoice_mode_switch_in_pos()
if clear_cache:
frappe.clear_cache()
@@ -141,14 +145,20 @@ class AccountsSettings(Document):
if cint(self.reconciliation_queue_size) < 5 or cint(self.reconciliation_queue_size) > 100:
frappe.throw(_("Queue Size should be between 5 and 100"))
def validate_invoice_mode_switch_in_pos(self):
pos_opening_entries_count = frappe.db.count(
"POS Opening Entry", filters={"docstatus": 1, "status": "Open"}
)
if pos_opening_entries_count:
def validate_auto_tax_settings(self):
if self.add_taxes_from_item_tax_template and self.add_taxes_from_taxes_and_charges_template:
frappe.throw(
_("{0} can be enabled/disabled after all the POS Opening Entries are closed.").format(
frappe.bold(_("Use Sales Invoice"))
_("You cannot enable both the settings '{0}' and '{1}'.").format(
frappe.bold(_(self.meta.get_label("add_taxes_from_item_tax_template"))),
frappe.bold(_(self.meta.get_label("add_taxes_from_taxes_and_charges_template"))),
),
title=_("Switch Invoice Mode Error"),
title=_("Auto Tax Settings Error"),
)
@frappe.whitelist()
def drop_ar_sql_procedures(self):
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
frappe.db.sql(f"drop function if exists {InitSQLProceduresForAR.genkey_function_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")

View File

@@ -2,7 +2,7 @@
# See license.txt
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import nowdate, today
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry

View File

@@ -89,46 +89,64 @@ class BankClearance(Document):
@frappe.whitelist()
def update_clearance_date(self):
clearance_date_updated = False
invalid_document = []
invalid_cheque_date = []
entries_to_update = []
def validate_entry(d):
is_valid = True
if not d.payment_document:
invalid_document.append(str(d.idx))
is_valid = False
if d.clearance_date and d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
invalid_cheque_date.append(str(d.idx))
is_valid = False
return is_valid
for d in self.get("payment_entries"):
if d.clearance_date:
if not d.payment_document:
frappe.throw(_("Row #{0}: Payment document is required to complete the transaction"))
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
frappe.throw(
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format(
d.idx,
get_link_to_form(d.payment_document, d.payment_entry),
d.clearance_date,
d.cheque_date,
)
)
if d.clearance_date or self.include_reconciled_entries:
if validate_entry(d) and (d.clearance_date or self.include_reconciled_entries):
if not d.clearance_date:
d.clearance_date = None
if d.payment_document == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
"clearance_date",
d.clearance_date,
)
entries_to_update.append(d)
else:
# using db_set to trigger notification
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
payment_entry.db_set("clearance_date", d.clearance_date)
if invalid_document or invalid_cheque_date:
msg = _("<p>Please correct the following row(s):</p><ul>")
if invalid_document:
msg += _("<li>Payment document required for row(s): {0}</li>").format(
", ".join(invalid_document)
)
clearance_date_updated = True
if invalid_cheque_date:
msg += _("<li>Clearance date must be after cheque date for row(s): {0}</li>").format(
", ".join(invalid_cheque_date)
)
if clearance_date_updated:
self.get_payment_entries()
msgprint(_("Clearance Date updated"))
else:
msg += "</ul>"
frappe.throw(_(msg))
return
if not entries_to_update:
msgprint(_("Clearance Date not mentioned"))
return
for d in entries_to_update:
if d.payment_document == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
"clearance_date",
d.clearance_date,
)
else:
# using db_set to trigger notification
payment_entry = frappe.get_lazy_doc(d.payment_document, d.payment_entry)
payment_entry.db_set("clearance_date", d.clearance_date)
self.get_payment_entries()
msgprint(_("Clearance Date updated"))
def get_payment_entries_for_bank_clearance(

View File

@@ -146,7 +146,7 @@ def make_payment_entry():
supplier = create_supplier(supplier_name="_Test Supplier")
pi = make_purchase_invoice(
supplier=supplier,
supplier=supplier.name,
supplier_warehouse="_Test Warehouse - _TC",
expense_account="Cost of Goods Sold - _TC",
uom="Nos",

View File

@@ -40,7 +40,7 @@ class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase):
{
"doctype": "Bank Account",
"account_name": "HDFC _current_",
"bank": bank,
"bank": bank.name,
"is_company_account": True,
"account": self.bank, # account from Chart of Accounts
}

View File

@@ -70,7 +70,7 @@ frappe.ui.form.on("Bank Statement Import", {
frm.get_field("import_file").df.options = {
restrictions: {
allowed_file_types: [".csv", ".xls", ".xlsx"],
allowed_file_types: [".csv", ".xls", ".xlsx", ".TXT", ".txt"],
},
};
@@ -81,6 +81,7 @@ frappe.ui.form.on("Bank Statement Import", {
refresh(frm) {
frm.page.hide_icon_group();
frm.trigger("toggle_mt940_note");
frm.trigger("update_indicators");
frm.trigger("import_file");
frm.trigger("show_import_log");
@@ -192,6 +193,24 @@ frappe.ui.form.on("Bank Statement Import", {
});
},
import_mt940_fromat(frm) {
frm.trigger("toggle_mt940_note");
frm.save();
},
toggle_mt940_note(frm) {
if (!frm.doc.import_mt940_fromat) {
frm.set_df_property("custom_delimiters", "hidden", 0);
frm.set_df_property("google_sheets_url", "hidden", 0);
frm.set_df_property("html_5", "hidden", 0);
} else {
frm.set_df_property("custom_delimiters", "hidden", 1);
frm.set_df_property("google_sheets_url", "hidden", 1);
frm.set_df_property("html_5", "hidden", 1);
}
frm.set_value("import_mt940_fromat", frm.doc.import_mt940_fromat);
},
show_report_error_button(frm) {
if (frm.doc.status === "Error") {
frappe.db
@@ -290,23 +309,45 @@ frappe.ui.form.on("Bank Statement Import", {
.html(__("Loading import file..."))
.appendTo(frm.get_field("import_preview").$wrapper);
frm.call({
method: "get_preview_from_template",
args: {
data_import: frm.doc.name,
import_file: frm.doc.import_file,
google_sheets_url: frm.doc.google_sheets_url,
frappe.run_serially([
// Convert MT940 to CSV if .txt file
() => {
if (frm.doc.import_file && frm.doc.import_file.toLowerCase().endsWith(".txt")) {
return frm
.call({
method: "convert_mt940_to_csv",
args: {
data_import: frm.doc.name,
mt940_file_path: frm.doc.import_file,
},
})
.then((r) => {
const file_url = r.message;
frm.set_value("import_file", file_url);
frm.save();
});
}
},
error_handlers: {
TimestampMismatchError() {
// ignore this error
},
() => {
frm.call({
method: "get_preview_from_template",
args: {
data_import: frm.doc.name,
import_file: frm.doc.import_file,
google_sheets_url: frm.doc.google_sheets_url,
},
error_handlers: {
TimestampMismatchError() {
// ignore this error
},
},
}).then((r) => {
let preview_data = r.message;
frm.events.show_import_preview(frm, preview_data);
frm.events.show_import_warnings(frm, preview_data);
});
},
}).then((r) => {
let preview_data = r.message;
frm.events.show_import_preview(frm, preview_data);
frm.events.show_import_warnings(frm, preview_data);
});
]);
},
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',

View File

@@ -11,6 +11,7 @@
"bank_account",
"bank",
"column_break_4",
"import_mt940_fromat",
"custom_delimiters",
"delimiter_options",
"google_sheets_url",
@@ -20,6 +21,7 @@
"download_template",
"status",
"template_options",
"use_csv_sniffer",
"import_warnings_section",
"template_warnings",
"import_warnings",
@@ -207,14 +209,28 @@
"fieldname": "delimiter_options",
"fieldtype": "Data",
"label": "Delimiter options"
},
{
"default": "0",
"fieldname": "use_csv_sniffer",
"fieldtype": "Check",
"hidden": 1,
"label": "Use CSV Sniffer"
},
{
"default": "0",
"fieldname": "import_mt940_fromat",
"fieldtype": "Check",
"label": "Import MT940 Fromat"
}
],
"hide_toolbar": 1,
"links": [],
"modified": "2024-06-25 17:32:07.658250",
"modified": "2025-06-11 02:23:22.159961",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Import",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
@@ -230,8 +246,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -3,15 +3,19 @@
import csv
import io
import json
import re
from datetime import date, datetime
import frappe
import mt940
import openpyxl
from frappe import _
from frappe.core.doctype.data_import.data_import import DataImport
from frappe.core.doctype.data_import.importer import Importer, ImportFile
from frappe.utils.background_jobs import enqueue
from frappe.utils.file_manager import get_file, save_file
from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter
@@ -35,6 +39,7 @@ class BankStatementImport(DataImport):
delimiter_options: DF.Data | None
google_sheets_url: DF.Data | None
import_file: DF.Attach | None
import_mt940_fromat: DF.Check
import_type: DF.Literal["", "Insert New Records", "Update Existing Records"]
mute_emails: DF.Check
reference_doctype: DF.Link
@@ -43,6 +48,7 @@ class BankStatementImport(DataImport):
submit_after_import: DF.Check
template_options: DF.Code | None
template_warnings: DF.Code | None
use_csv_sniffer: DF.Check
# end: auto-generated types
def __init__(self, *args, **kwargs):
@@ -65,8 +71,9 @@ class BankStatementImport(DataImport):
self.template_warnings = ""
self.validate_import_file()
self.validate_google_sheets_url()
if self.import_file and not self.import_file.lower().endswith(".txt"):
self.validate_import_file()
self.validate_google_sheets_url()
def start_import(self):
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
@@ -79,7 +86,7 @@ class BankStatementImport(DataImport):
from frappe.utils.background_jobs import is_job_enqueued
from frappe.utils.scheduler import is_scheduler_inactive
run_now = frappe.flags.in_test or frappe.conf.developer_mode
run_now = frappe.in_test or frappe.conf.developer_mode
if is_scheduler_inactive() and not run_now:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
@@ -104,6 +111,68 @@ class BankStatementImport(DataImport):
return None
@frappe.whitelist()
def convert_mt940_to_csv(data_import, mt940_file_path):
doc = frappe.get_doc("Bank Statement Import", data_import)
file_doc, content = get_file(mt940_file_path)
if not is_mt940_format(content):
frappe.throw(_("The uploaded file does not appear to be in valid MT940 format."))
if is_mt940_format(content) and not doc.import_mt940_fromat:
frappe.throw(_("MT940 file detected. Please enable 'Import MT940 Format' to proceed."))
try:
transactions = mt940.parse(content)
except Exception as e:
frappe.throw(_("Failed to parse MT940 format. Error: {0}").format(str(e)))
if not transactions:
frappe.throw(_("Parsed file is not in valid MT940 format or contains no transactions."))
# Use in-memory file buffer instead of writing to temp file
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer)
headers = ["Date", "Deposit", "Withdrawal", "Description", "Reference Number", "Bank Account", "Currency"]
writer.writerow(headers)
for txn in transactions:
txn_date = getattr(txn, "date", None)
raw_date = txn.data.get("date", "")
if txn_date:
date_str = txn_date.strftime("%Y-%m-%d")
elif isinstance(raw_date, date | datetime):
date_str = raw_date.strftime("%Y-%m-%d")
else:
date_str = str(raw_date)
raw_amount = str(txn.data.get("amount", ""))
parts = raw_amount.strip().split()
amount_value = float(parts[0]) if parts else 0.0
deposit = amount_value if amount_value > 0 else ""
withdrawal = abs(amount_value) if amount_value < 0 else ""
description = txn.data.get("extra_details") or ""
reference = txn.data.get("transaction_reference") or ""
currency = txn.data.get("currency", "")
writer.writerow([date_str, deposit, withdrawal, description, reference, doc.bank_account, currency])
# Prepare in-memory CSV for upload
csv_content = csv_buffer.getvalue().encode("utf-8")
csv_buffer.close()
filename = f"{frappe.utils.now_datetime().strftime('%Y%m%d%H%M%S')}_converted_mt940.csv"
# Save to File Manager
saved_file = save_file(filename, csv_content, doc.doctype, doc.name, is_private=True, df="import_file")
return saved_file.file_url
@frappe.whitelist()
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
@@ -128,6 +197,12 @@ def download_import_log(data_import_name):
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
def is_mt940_format(content: str) -> bool:
"""Check if the content has key MT940 tags"""
required_tags = [":20:", ":25:", ":28C:", ":61:"]
return all(tag in content for tag in required_tags)
def parse_data_from_template(raw_data):
data = []
@@ -279,7 +354,7 @@ def get_import_status(docname):
@frappe.whitelist()
def get_import_logs(docname: str):
frappe.has_permission("Bank Statement Import")
frappe.has_permission("Bank Statement Import", throw=True)
return frappe.get_all(
"Data Import Log",

View File

@@ -1,6 +1,7 @@
import frappe
from frappe.utils import flt
from rapidfuzz import fuzz, process
from rapidfuzz.utils import default_process
class AutoMatchParty:
@@ -25,7 +26,7 @@ class AutoMatchParty:
deposit=self.deposit,
).match()
fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching")
fuzzy_matching_enabled = frappe.get_single_value("Accounts Settings", "enable_fuzzy_matching")
if not result and fuzzy_matching_enabled:
result = AutoMatchbyPartyNameDescription(
bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit
@@ -132,6 +133,7 @@ class AutoMatchbyPartyNameDescription:
query=self.get(field),
choices={row.get("name"): row.get("party_name") for row in names},
scorer=fuzz.token_set_ratio,
processor=default_process,
)
party_name, skip = self.process_fuzzy_result(result)

View File

@@ -45,7 +45,6 @@
"default": "ACC-BTN-.YYYY.-",
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 1,
"label": "Series",
"no_copy": 1,
"options": "ACC-BTN-.YYYY.-",
@@ -236,9 +235,10 @@
"fieldtype": "Column Break"
}
],
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2023-11-18 18:32:47.203694",
"modified": "2025-06-18 17:24:57.044666",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
@@ -287,9 +287,10 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "date",
"sort_order": "DESC",
"states": [],
"title_field": "bank_account",
"track_changes": 1
}
}

View File

@@ -121,7 +121,7 @@ class BankTransaction(Document):
self.allocate_payment_entries()
self.set_status()
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
if frappe.get_single_value("Accounts Settings", "enable_party_matching"):
self.auto_set_party()
def before_update_after_submit(self):

View File

@@ -2,21 +2,12 @@
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import nowdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
class UnitTestBankTransaction(UnitTestCase):
"""
Unit tests for BankTransaction.
Use this class for testing individual functions and methods.
"""
pass
class TestAutoMatchParty(IntegrationTestCase):
@classmethod
def setUpClass(cls):

View File

@@ -6,7 +6,7 @@ import json
import frappe
from frappe import utils
from frappe.model.docstatus import DocStatus
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
get_linked_payments,
@@ -24,15 +24,6 @@ from erpnext.tests.utils import if_lending_app_installed
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Cost Center"]
class UnitTestBankTransaction(UnitTestCase):
"""
Unit tests for BankTransaction.
Use this class for testing individual functions and methods.
"""
pass
class TestBankTransaction(IntegrationTestCase):
def setUp(self):
make_pos_profile()

View File

@@ -2,16 +2,7 @@
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
class UnitTestBisectAccountingStatements(UnitTestCase):
"""
Unit tests for BisectAccountingStatements.
Use this class for testing individual functions and methods.
"""
pass
from frappe.tests import IntegrationTestCase
class TestBisectAccountingStatements(IntegrationTestCase):

View File

@@ -2,16 +2,7 @@
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
class UnitTestBisectNodes(UnitTestCase):
"""
Unit tests for BisectNodes.
Use this class for testing individual functions and methods.
"""
pass
from frappe.tests import IntegrationTestCase
class TestBisectNodes(IntegrationTestCase):

View File

@@ -23,6 +23,11 @@ frappe.ui.form.on("Budget", {
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
frappe.db.get_single_value("Accounts Settings", "use_new_budget_controller").then((value) => {
if (!value) {
frm.get_field("control_action_for_cumulative_expense_section").hide();
}
});
},
refresh: function (frm) {

View File

@@ -7,10 +7,10 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"budget_against",
"company",
"cost_center",
"naming_series",
"project",
"fiscal_year",
"column_break_3",
@@ -28,6 +28,10 @@
"applicable_on_booking_actual_expenses",
"action_if_annual_budget_exceeded",
"action_if_accumulated_monthly_budget_exceeded",
"control_action_for_cumulative_expense_section",
"applicable_on_cumulative_expense",
"action_if_annual_exceeded_on_cumulative_expense",
"action_if_accumulated_monthly_exceeded_on_cumulative_expense",
"section_break_21",
"accounts"
],
@@ -195,19 +199,46 @@
},
{
"fieldname": "naming_series",
"fieldtype": "Data",
"hidden": 1,
"fieldtype": "Select",
"label": "Series",
"no_copy": 1,
"options": "BUDGET-.YYYY.-",
"print_hide": 1,
"read_only": 1,
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "control_action_for_cumulative_expense_section",
"fieldtype": "Section Break",
"label": "Control Action for Cumulative Expense"
},
{
"default": "0",
"description": "(Purchase Order + Material Request + Actual Expense)",
"fieldname": "applicable_on_cumulative_expense",
"fieldtype": "Check",
"label": "Applicable on Cumulative Expense"
},
{
"depends_on": "eval:doc.applicable_on_cumulative_expense == 1",
"fieldname": "action_if_annual_exceeded_on_cumulative_expense",
"fieldtype": "Select",
"label": "Action if Anual Budget Exceeded on Cumulative Expense",
"options": "\nStop\nWarn\nIgnore"
},
{
"depends_on": "eval:doc.applicable_on_cumulative_expense == 1",
"fieldname": "action_if_accumulated_monthly_exceeded_on_cumulative_expense",
"fieldtype": "Select",
"label": "Action if Accumulative Monthly Budget Exceeded on Cumulative Expense",
"options": "\nStop\nWarn\nIgnore"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:06:42.675933",
"modified": "2025-06-16 15:57:13.114981",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",
@@ -231,8 +262,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -36,11 +36,14 @@ class Budget(Document):
action_if_accumulated_monthly_budget_exceeded: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_accumulated_monthly_budget_exceeded_on_mr: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_accumulated_monthly_budget_exceeded_on_po: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_accumulated_monthly_exceeded_on_cumulative_expense: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_budget_exceeded: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_budget_exceeded_on_mr: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_budget_exceeded_on_po: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_exceeded_on_cumulative_expense: DF.Literal["", "Stop", "Warn", "Ignore"]
amended_from: DF.Link | None
applicable_on_booking_actual_expenses: DF.Check
applicable_on_cumulative_expense: DF.Check
applicable_on_material_request: DF.Check
applicable_on_purchase_order: DF.Check
budget_against: DF.Literal["", "Cost Center", "Project"]
@@ -48,7 +51,7 @@ class Budget(Document):
cost_center: DF.Link | None
fiscal_year: DF.Link
monthly_distribution: DF.Link | None
naming_series: DF.Data | None
naming_series: DF.Literal["BUDGET-.YYYY.-"]
project: DF.Link | None
# end: auto-generated types
@@ -136,13 +139,10 @@ class Budget(Document):
):
self.applicable_on_booking_actual_expenses = 1
def before_naming(self):
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args)
if not frappe.get_all("Budget", limit=1):
if not frappe.db.count("Budget", cache=True):
return
if args.get("company") and not args.fiscal_year:
@@ -151,7 +151,7 @@ def validate_expense_against_budget(args, expense_amount=0):
"Company", args.get("company"), "exception_budget_approver_role"
)
if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec
if not frappe.db.get_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}):
return
if not args.account:
@@ -510,7 +510,7 @@ def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_ye
accumulated_percentage = 0.0
while dt <= getdate(posting_date):
if monthly_distribution:
if monthly_distribution and distribution:
accumulated_percentage += distribution.get(getdate(dt).strftime("%B"), 0)
else:
accumulated_percentage += 100.0 / 12

View File

@@ -5,7 +5,11 @@ import unittest
import frappe
from frappe.utils import now_datetime, nowdate
from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense
from erpnext.accounts.doctype.budget.budget import (
BudgetError,
get_accumulated_monthly_budget,
get_actual_expense,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -19,6 +23,9 @@ class TestBudget(ERPNextTestSuite):
cls.make_monthly_distribution()
cls.make_projects()
def setUp(self):
frappe.db.set_single_value("Accounts Settings", "use_new_budget_controller", True)
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero(nowdate(), "cost_center")
@@ -47,10 +54,13 @@ class TestBudget(ERPNextTestSuite):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
accumulated_limit + 1,
"_Test Cost Center - _TC",
posting_date=nowdate(),
)
@@ -67,10 +77,13 @@ class TestBudget(ERPNextTestSuite):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
accumulated_limit + 1,
"_Test Cost Center - _TC",
posting_date=nowdate(),
)
@@ -100,6 +113,10 @@ class TestBudget(ERPNextTestSuite):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
mr = frappe.get_doc(
{
"doctype": "Material Request",
@@ -113,7 +130,7 @@ class TestBudget(ERPNextTestSuite):
"uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC",
"schedule_date": nowdate(),
"rate": 100000,
"rate": accumulated_limit + 1,
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
}
@@ -127,6 +144,7 @@ class TestBudget(ERPNextTestSuite):
budget.load_from_db()
budget.cancel()
mr.cancel()
def test_monthly_budget_crossed_for_po(self):
budget = make_budget(
@@ -139,7 +157,12 @@ class TestBudget(ERPNextTestSuite):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
po = create_purchase_order(
transaction_date=nowdate(), qty=1, rate=accumulated_limit + 1, do_not_submit=True
)
po.set_missing_values()
@@ -157,11 +180,13 @@ class TestBudget(ERPNextTestSuite):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
accumulated_limit + 1,
"_Test Cost Center - _TC",
project=project,
posting_date=nowdate(),
@@ -276,10 +301,13 @@ class TestBudget(ERPNextTestSuite):
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
accumulated_limit + 1,
"_Test Cost Center 2 - _TC",
posting_date=nowdate(),
)
@@ -306,10 +334,13 @@ class TestBudget(ERPNextTestSuite):
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
40000,
accumulated_limit + 1,
cost_center,
posting_date=nowdate(),
)
@@ -353,6 +384,44 @@ class TestBudget(ERPNextTestSuite):
self.assertRaises(BudgetError, jv.submit)
def test_action_for_cumulative_limit(self):
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center", applicable_on_cumulative_expense=True)
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
accumulated_limit - 1,
"_Test Cost Center - _TC",
posting_date=nowdate(),
)
jv.submit()
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_exceeded_on_cumulative_expense", "Stop"
)
po = create_purchase_order(
transaction_date=nowdate(), qty=1, rate=accumulated_limit + 1, do_not_submit=True
)
po.set_missing_values()
self.assertRaises(BudgetError, po.submit)
frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_exceeded_on_cumulative_expense", "Ignore"
)
po.submit()
budget.load_from_db()
budget.cancel()
po.cancel()
jv.cancel()
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project":
@@ -427,6 +496,7 @@ def make_budget(**args):
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
monthly_distribution.fiscal_year = fiscal_year
monthly_distribution.save()
budget.fiscal_year = fiscal_year
budget.monthly_distribution = "_Test Distribution"
@@ -451,6 +521,15 @@ def make_budget(**args):
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
)
if args.applicable_on_cumulative_expense:
budget.applicable_on_cumulative_expense = 1
budget.action_if_annual_exceeded_on_cumulative_expense = (
args.action_if_annual_exceeded_on_cumulative_expense or "Warn"
)
budget.action_if_accumulated_monthly_exceeded_on_cumulative_expense = (
args.action_if_accumulated_monthly_exceeded_on_cumulative_expense or "Warn"
)
budget.insert()
budget.submit()

View File

@@ -154,3 +154,7 @@ class CostCenterAllocation(Document):
).format(d.cost_center),
InvalidChildCostCenter,
)
def clear_cache(self):
frappe.clear_cache(doctype="Cost Center")
return super().clear_cache()

View File

@@ -36,7 +36,7 @@ class CurrencyExchangeSettings(Document):
def validate(self):
self.set_parameters_and_result()
if frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard:
if frappe.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard:
return
response, value = self.validate_parameters()
self.validate_result(response, value)

View File

@@ -4,7 +4,7 @@ import json
import frappe
from frappe.model import mapper
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import add_days, nowdate, today
from erpnext import get_default_cost_center
@@ -22,15 +22,6 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
EXTRA_TEST_RECORD_DEPENDENCIES = ["Company", "Cost Center"]
class UnitTestDunning(UnitTestCase):
"""
Unit tests for Dunning.
Use this class for testing individual functions and methods.
"""
pass
class TestDunning(IntegrationTestCase):
@classmethod
def setUpClass(cls):

View File

@@ -123,7 +123,7 @@ def check_duplicate_fiscal_year(doc):
)
for fiscal_year, ysd, yed in year_start_end_dates:
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
not frappe.flags.in_test
not frappe.in_test
):
frappe.throw(
_(

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, now
from frappe.utils import create_batch, flt, fmt_money, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -451,12 +451,20 @@ def rename_gle_sle_docs():
def rename_temporarily_named_docs(doctype):
"""Rename temporarily named docs using autoname options"""
docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation", limit=50000)
for doc in docs_to_rename:
oldname = doc.name
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, modified = %s where name = %s",
(newname, now(), oldname),
auto_commit=True,
)
autoname = frappe.get_meta(doctype).autoname
for batch in create_batch(docs_to_rename, 100):
for doc in batch:
oldname = doc.name
set_name_from_naming_options(autoname, doc)
newname = doc.name
frappe.db.sql(
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
(newname, now(), oldname),
)
for hook_type in ("on_gle_rename", "on_sle_rename"):
for hook in frappe.get_hooks(hook_type):
frappe.call(hook, newname=newname, oldname=oldname)
frappe.db.commit()

View File

@@ -39,7 +39,16 @@ class ItemTaxTemplate(Document):
check_list = []
for d in self.get("taxes"):
if d.tax_type:
account_type = frappe.get_cached_value("Account", d.tax_type, "account_type")
account_type, account_company = frappe.get_cached_value(
"Account", d.tax_type, ["account_type", "company"]
)
if account_company != self.company:
frappe.throw(
_("Item Tax Row {0}: Account must belong to Company - {1}").format(
d.idx, frappe.bold(self.company)
)
)
if account_type not in [
"Tax",

View File

@@ -20,6 +20,39 @@ frappe.ui.form.on("Journal Entry", {
"Unreconcile Payment Entries",
"Bank Transaction",
];
frm.trigger("set_queries");
},
set_queries(frm) {
frm.set_query("periodic_entry_difference_account", function () {
return {
filters: {
is_group: 0,
company: frm.doc.company,
},
};
});
frm.set_query("stock_asset_account", function () {
return {
filters: {
is_group: 0,
account_type: "Stock",
company: frm.doc.company,
},
};
});
},
get_balance_for_periodic_accounting(frm) {
frm.call({
method: "get_balance_for_periodic_accounting",
doc: frm.doc,
callback: function (r) {
refresh_field("accounts");
},
});
},
refresh: function (frm) {

View File

@@ -13,15 +13,21 @@
"title",
"voucher_type",
"naming_series",
"finance_book",
"process_deferred_accounting",
"reversal_of",
"tax_withholding_category",
"column_break1",
"from_template",
"company",
"posting_date",
"finance_book",
"apply_tds",
"tax_withholding_category",
"section_break_tcvw",
"for_all_stock_asset_accounts",
"column_break_wpau",
"stock_asset_account",
"periodic_entry_difference_account",
"get_balance_for_periodic_accounting",
"2_add_edit_gl_entries",
"accounts",
"section_break99",
@@ -40,7 +46,6 @@
"reference",
"clearance_date",
"remark",
"paid_loan",
"inter_company_journal_entry_reference",
"column_break98",
"bill_no",
@@ -89,7 +94,7 @@
"label": "Entry Type",
"oldfieldname": "voucher_type",
"oldfieldtype": "Select",
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nPeriodic Accounting Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
"reqd": 1,
"search_index": 1
},
@@ -304,13 +309,6 @@
"oldfieldtype": "Small Text",
"read_only": 1
},
{
"fieldname": "paid_loan",
"fieldtype": "Data",
"hidden": 1,
"label": "Paid Loan",
"print_hide": 1
},
{
"depends_on": "eval:doc.voucher_type== \"Inter Company Journal Entry\"",
"fieldname": "inter_company_journal_entry_reference",
@@ -543,6 +541,42 @@
"label": "Is System Generated",
"no_copy": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
"fieldname": "periodic_entry_difference_account",
"fieldtype": "Link",
"label": "Periodic Entry Difference Account",
"mandatory_depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
"options": "Account"
},
{
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
"fieldname": "section_break_tcvw",
"fieldtype": "Section Break",
"label": "Periodic Accounting"
},
{
"default": "1",
"fieldname": "for_all_stock_asset_accounts",
"fieldtype": "Check",
"label": "For All Stock Asset Accounts"
},
{
"depends_on": "eval:doc.for_all_stock_asset_accounts === 0",
"fieldname": "stock_asset_account",
"fieldtype": "Link",
"label": "Stock Asset Account",
"options": "Account"
},
{
"fieldname": "column_break_wpau",
"fieldtype": "Column Break"
},
{
"fieldname": "get_balance_for_periodic_accounting",
"fieldtype": "Button",
"label": "Get Balance"
}
],
"icon": "fa fa-file-text",
@@ -557,7 +591,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-12-26 15:32:20.730666",
"modified": "2025-07-06 15:22:58.465131",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
@@ -602,10 +636,11 @@
"role": "Auditor"
}
],
"row_format": "Dynamic",
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -24,6 +24,7 @@ from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
get_advance_payment_doctypes,
get_balance_on,
get_stock_accounts,
get_stock_and_account_balance,
@@ -62,6 +63,7 @@ class JournalEntry(AccountsController):
difference: DF.Currency
due_date: DF.Date | None
finance_book: DF.Link | None
for_all_stock_asset_accounts: DF.Check
from_template: DF.Link | None
inter_company_journal_entry_reference: DF.Link | None
is_opening: DF.Literal["No", "Yes"]
@@ -70,14 +72,15 @@ class JournalEntry(AccountsController):
mode_of_payment: DF.Link | None
multi_currency: DF.Check
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
paid_loan: DF.Data | None
pay_to_recd_from: DF.Data | None
payment_order: DF.Link | None
periodic_entry_difference_account: DF.Link | None
posting_date: DF.Date
process_deferred_accounting: DF.Link | None
remark: DF.SmallText | None
reversal_of: DF.Link | None
select_print_heading: DF.Link | None
stock_asset_account: DF.Link | None
stock_entry: DF.Link | None
tax_withholding_category: DF.Link | None
title: DF.Data | None
@@ -101,6 +104,7 @@ class JournalEntry(AccountsController):
"Opening Entry",
"Depreciation Entry",
"Asset Disposal",
"Periodic Accounting Entry",
"Exchange Rate Revaluation",
"Exchange Gain Or Loss",
"Deferred Revenue",
@@ -147,8 +151,7 @@ class JournalEntry(AccountsController):
if self.docstatus == 0:
self.apply_tax_withholding()
if not self.title:
if self.is_new() or not self.title:
self.title = self.get_title()
def validate_advance_accounts(self):
@@ -198,6 +201,76 @@ class JournalEntry(AccountsController):
self.update_inter_company_jv()
self.update_invoice_discounting()
@frappe.whitelist()
def get_balance_for_periodic_accounting(self):
self.validate_company_for_periodic_accounting()
stock_accounts = self.get_stock_accounts_for_periodic_accounting()
self.set("accounts", [])
for account in stock_accounts:
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
account, self.posting_date, self.company
)
difference_value = flt(stock_bal - account_bal, self.precision("difference"))
if difference_value == 0:
frappe.msgprint(
_("No difference found for stock account {0}").format(frappe.bold(account)),
alert=True,
)
continue
self.append(
"accounts",
{
"account": account,
"debit_in_account_currency": difference_value if difference_value > 0 else 0,
"credit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
},
)
self.append(
"accounts",
{
"account": self.periodic_entry_difference_account,
"credit_in_account_currency": difference_value if difference_value > 0 else 0,
"debit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
},
)
def validate_company_for_periodic_accounting(self):
if erpnext.is_perpetual_inventory_enabled(self.company):
frappe.throw(
_(
"Periodic Accounting Entry is not allowed for company {0} with perpetual inventory enabled"
).format(self.company)
)
if not self.periodic_entry_difference_account:
frappe.throw(_("Please select Periodic Accounting Entry Difference Account"))
def get_stock_accounts_for_periodic_accounting(self):
if self.voucher_type != "Periodic Accounting Entry":
return []
if self.for_all_stock_asset_accounts:
return frappe.get_all(
"Account",
filters={
"company": self.company,
"account_type": "Stock",
"root_type": "Asset",
"is_group": 0,
},
pluck="name",
)
if not self.stock_asset_account:
frappe.throw(_("Please select Stock Asset Account"))
return [self.stock_asset_account]
def on_update_after_submit(self):
# Flag will be set on Reconciliation
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
@@ -238,9 +311,7 @@ class JournalEntry(AccountsController):
def update_advance_paid(self):
advance_paid = frappe._dict()
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
advance_payment_doctypes = get_advance_payment_doctypes()
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in advance_payment_doctypes:
@@ -280,6 +351,10 @@ class JournalEntry(AccountsController):
frappe.throw(_("Account {0} should be of type Expense").format(d.account))
def validate_stock_accounts(self):
if self.voucher_type == "Periodic Accounting Entry":
# Skip validation for periodic accounting entry
return
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
for account in stock_accounts:
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
@@ -1171,7 +1246,7 @@ class JournalEntry(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries
merge_entries = frappe.db.get_single_value("Accounts Settings", "merge_similar_account_heads")
merge_entries = frappe.get_single_value("Accounts Settings", "merge_similar_account_heads")
gl_map = self.build_gl_map()
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
@@ -1296,7 +1371,9 @@ class JournalEntry(AccountsController):
@frappe.whitelist()
def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
def get_default_bank_cash_account(
company, account_type=None, mode_of_payment=None, account=None, *, fetch_balance=True
):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
if mode_of_payment:
@@ -1330,15 +1407,14 @@ def get_default_bank_cash_account(company, account_type=None, mode_of_payment=No
account_details = frappe.get_cached_value(
"Account", account, ["account_currency", "account_type"], as_dict=1
)
return frappe._dict(
{
"account": account,
"balance": get_balance_on(account),
"account_currency": account_details.account_currency,
"account_type": account_details.account_type,
}
)
result = {
"account": account,
"account_currency": account_details.account_currency,
"account_type": account_details.account_type,
}
if fetch_balance:
result["balance"] = get_balance_on(account)
return frappe._dict(result)
else:
return frappe._dict()

View File

@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.account.test_account import get_inventory_account
@@ -10,15 +10,6 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInv
from erpnext.exceptions import InvalidAccountCurrency
class UnitTestJournalEntry(UnitTestCase):
"""
Unit tests for JournalEntry.
Use this class for testing individual functions and methods.
"""
pass
class TestJournalEntry(IntegrationTestCase):
@IntegrationTestCase.change_settings(
"Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}
@@ -588,6 +579,18 @@ class TestJournalEntry(IntegrationTestCase):
]
self.assertEqual(expected, actual)
def test_pay_to_recd_from(self):
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
jv.pay_to_recd_from = "_Test Receiver"
jv.save()
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver")
jv.pay_to_recd_from = "_Test Receiver 2"
jv.save()
jv.submit()
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
def make_journal_entry(
account1,

View File

@@ -2,16 +2,7 @@
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
class UnitTestLedgerHealthMonitor(UnitTestCase):
"""
Unit tests for LedgerHealthMonitor.
Use this class for testing individual functions and methods.
"""
pass
from frappe.tests import IntegrationTestCase
class TestLedgerHealthMonitor(IntegrationTestCase):

View File

@@ -35,7 +35,7 @@ class LedgerMerge(Document):
from frappe.utils.background_jobs import enqueue
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
if is_scheduler_inactive() and not frappe.in_test:
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
job_id = f"ledger_merge::{self.name}"
@@ -47,7 +47,7 @@ class LedgerMerge(Document):
event="ledger_merge",
job_id=job_id,
docname=self.name,
now=frappe.conf.developer_mode or frappe.flags.in_test,
now=frappe.conf.developer_mode or frappe.in_test,
)
return True

View File

@@ -229,7 +229,7 @@ class OpeningInvoiceCreationTool(Document):
else:
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
if is_scheduler_inactive() and not frappe.in_test:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
job_id = f"opening_invoice::{self.name}"
@@ -242,7 +242,7 @@ class OpeningInvoiceCreationTool(Document):
event="opening_invoice_creation",
job_id=job_id,
invoices=invoices,
now=frappe.conf.developer_mode or frappe.flags.in_test,
now=frappe.conf.developer_mode or frappe.in_test,
)

View File

@@ -2,7 +2,7 @@
# See license.txt
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
@@ -15,15 +15,6 @@ from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_crea
EXTRA_TEST_RECORD_DEPENDENCIES = ["Customer", "Supplier", "Accounting Dimension"]
class UnitTestOpeningInvoiceCreationTool(UnitTestCase):
"""
Unit tests for OpeningInvoiceCreationTool.
Use this class for testing individual functions and methods.
"""
pass
class TestOpeningInvoiceCreationTool(IntegrationTestCase):
@classmethod
def setUpClass(cls):

View File

@@ -46,8 +46,9 @@ from erpnext.accounts.party import (
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
get_balance_on,
get_advance_payment_doctypes,
get_outstanding_invoices,
get_reconciliation_effect_date,
)
from erpnext.controllers.accounts_controller import (
AccountsController,
@@ -499,7 +500,7 @@ class PaymentEntry(AccountsController):
def delink_advance_entry_references(self):
for reference in self.references:
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(reference.reference_doctype, reference.reference_name)
doc = frappe.get_lazy_doc(reference.reference_doctype, reference.reference_name)
doc.delink_advance_entries(self.name)
def set_missing_values(self):
@@ -640,7 +641,7 @@ class PaymentEntry(AccountsController):
def validate_mandatory(self):
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
if not self.get(field):
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
frappe.throw(_("{0} is mandatory").format(_(self.meta.get_label(field))))
def validate_reference_documents(self):
valid_reference_doctypes = self.get_valid_reference_doctypes()
@@ -662,7 +663,7 @@ class PaymentEntry(AccountsController):
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))
ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name)
ref_doc = frappe.get_lazy_doc(d.reference_doctype, d.reference_name)
if d.reference_doctype != "Journal Entry":
if self.party != ref_doc.get(scrub(self.party_type)):
@@ -1100,10 +1101,7 @@ class PaymentEntry(AccountsController):
def calculate_base_allocated_amount_for_reference(self, d) -> float:
base_allocated_amount = 0
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
if d.reference_doctype in advance_payment_doctypes:
if d.reference_doctype in get_advance_payment_doctypes():
# When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
# This is so there are no Exchange Gain/Loss generated for such doctypes
@@ -1385,10 +1383,7 @@ class PaymentEntry(AccountsController):
if not self.party_account:
return
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
advance_payment_doctypes = get_advance_payment_doctypes()
if self.payment_type == "Receive":
against_account = self.paid_to
else:
@@ -1571,23 +1566,7 @@ class PaymentEntry(AccountsController):
else:
# For backwards compatibility
# Supporting reposting on payment entries reconciled before select field introduction
reconciliation_takes_effect_on = frappe.get_cached_value(
"Company", self.company, "reconciliation_takes_effect_on"
)
if reconciliation_takes_effect_on == "Advance Payment Date":
posting_date = self.posting_date
elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(
invoice.reference_doctype, invoice.reference_name, date_field
)
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
elif reconciliation_takes_effect_on == "Reconciliation Date":
posting_date = nowdate()
posting_date = get_reconciliation_effect_date(invoice, self.company, self.posting_date)
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)
@@ -1781,12 +1760,10 @@ class PaymentEntry(AccountsController):
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"
)
advance_payment_doctypes = get_advance_payment_doctypes()
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
frappe.get_lazy_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
@@ -2786,7 +2763,6 @@ def get_party_details(company, party_type, party, date, cost_center=None):
party_account = get_party_account(party_type, party, company)
account_currency = get_account_currency(party_account)
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
party_name = frappe.db.get_value(party_type, party, _party_name)
@@ -2798,7 +2774,6 @@ def get_party_details(company, party_type, party, date, cost_center=None):
"party_account": party_account,
"party_name": party_name,
"party_account_currency": account_currency,
"account_balance": account_balance,
"party_bank_account": party_bank_account,
"bank_account": bank_account,
}
@@ -2816,12 +2791,9 @@ def get_account_details(account, date, cost_center=None):
if not account_list:
frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account))
account_balance = get_balance_on(account, date, cost_center=cost_center, ignore_account_permission=True)
return frappe._dict(
{
"account_currency": get_account_currency(account),
"account_balance": account_balance,
"account_type": frappe.get_cached_value("Account", account, "account_type"),
}
)
@@ -2871,7 +2843,7 @@ def get_reference_details(
):
total_amount = outstanding_amount = exchange_rate = account = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
ref_doc = frappe.get_lazy_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
# Only applies for Reverse Payment Entries
@@ -2969,7 +2941,7 @@ def get_payment_entry(
created_from_payment_request=False,
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (100.0 + over_billing_allowance):
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
@@ -3109,7 +3081,7 @@ def get_payment_entry(
if party_account and bank:
if discount_amount:
base_total_discount_loss = 0
if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
if frappe.get_single_value("Accounts Settings", "book_tax_discount_loss"):
base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)
set_pending_discount_loss(
@@ -3311,11 +3283,16 @@ def get_bank_cash_account(doc, bank_account):
"Bank",
mode_of_payment=doc.get("mode_of_payment"),
account=bank_account,
fetch_balance=False,
)
if not bank:
bank = get_default_bank_cash_account(
doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
doc.company,
"Cash",
mode_of_payment=doc.get("mode_of_payment"),
account=bank_account,
fetch_balance=False,
)
return bank
@@ -3463,7 +3440,7 @@ def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss
# If tax loss booking is enabled, pending loss will be rounding loss.
# Otherwise it will be the total discount loss.
book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
book_tax_loss = frappe.get_single_value("Accounts Settings", "book_tax_discount_loss")
account_type = "round_off_account" if book_tax_loss else "default_discount_account"
pe.append(

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import qb
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.account.test_account import create_account
@@ -28,15 +28,6 @@ from erpnext.setup.doctype.employee.test_employee import make_employee
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Currency Exchange"]
class UnitTestPaymentEntry(UnitTestCase):
"""
Unit tests for PaymentEntry.
Use this class for testing individual functions and methods.
"""
pass
class TestPaymentEntry(IntegrationTestCase):
def tearDown(self):
frappe.db.rollback()

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import qb
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -13,15 +13,6 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.stock.doctype.item.test_item import create_item
class UnitTestPaymentLedgerEntry(UnitTestCase):
"""
Unit tests for PaymentLedgerEntry.
Use this class for testing individual functions and methods.
"""
pass
class TestPaymentLedgerEntry(IntegrationTestCase):
def setUp(self):
self.ple = qb.DocType("Payment Ledger Entry")

View File

@@ -3,7 +3,7 @@
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import getdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
@@ -17,15 +17,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
class UnitTestPaymentOrder(UnitTestCase):
"""
Unit tests for PaymentOrder.
Use this class for testing individual functions and methods.
"""
pass
class TestPaymentOrder(IntegrationTestCase):
def setUp(self):
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error

View File

@@ -407,7 +407,7 @@ class PaymentReconciliation(Document):
@frappe.whitelist()
def is_auto_process_enabled(self):
return frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments")
return frappe.get_single_value("Accounts Settings", "auto_reconcile_payments")
@frappe.whitelist()
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
@@ -532,7 +532,7 @@ class PaymentReconciliation(Document):
@frappe.whitelist()
def reconcile(self):
if frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
if frappe.get_single_value("Accounts Settings", "auto_reconcile_payments"):
running_doc = is_any_doc_running(
dict(
company=self.company,
@@ -589,7 +589,7 @@ class PaymentReconciliation(Document):
def check_mandatory_to_fetch(self):
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
if not self.get(fieldname):
frappe.throw(_("Please select {0} first").format(self.meta.get_label(fieldname)))
frappe.throw(_("Please select {0} first").format(_(self.meta.get_label(fieldname))))
def validate_entries(self):
if not self.get("invoices"):
@@ -826,7 +826,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
create_gain_loss_journal(
company,
today(),
inv.difference_posting_date,
inv.party_type,
inv.party,
inv.account,

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import qb
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
from frappe.utils.data import getdate as convert_to_date
@@ -21,15 +21,6 @@ from erpnext.stock.doctype.item.test_item import create_item
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
class UnitTestPaymentReconciliation(UnitTestCase):
"""
Unit tests for PaymentReconciliation.
Use this class for testing individual functions and methods.
"""
pass
class TestPaymentReconciliation(IntegrationTestCase):
def setUp(self):
self.create_company()
@@ -1988,7 +1979,9 @@ class TestPaymentReconciliation(IntegrationTestCase):
def test_reconciliation_on_closed_period_payment(self):
# create backdated fiscal year
first_fy_start_date = frappe.db.get_value("Fiscal Year", {"disabled": 0}, "min(year_start_date)")
first_fy_start_date = frappe.db.get_value(
"Fiscal Year", {"disabled": 0}, [{"MIN": "year_start_date"}]
)
prev_fy_start_date = add_years(first_fy_start_date, -1)
prev_fy_end_date = add_days(first_fy_start_date, -1)
create_fiscal_year(

View File

@@ -16,7 +16,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
)
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.accounts.party import get_party_account, get_party_bank_account
from erpnext.accounts.utils import get_account_currency, get_currency_precision
from erpnext.accounts.utils import get_account_currency, get_advance_payment_doctypes, get_currency_precision
from erpnext.utilities import payment_app_import_guard
ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST = [
@@ -464,10 +464,7 @@ class PaymentRequest(Document):
return create_stripe_subscription(gateway_controller, data)
def update_reference_advance_payment_status(self):
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
if self.reference_doctype in advance_payment_doctypes:
if self.reference_doctype in get_advance_payment_doctypes():
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
ref_doc.set_advance_payment_status()
@@ -826,8 +823,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
if not references:
return
precision = references[0].precision("allocated_amount")
precision = frappe.get_precision("Payment Entry Reference", "allocated_amount")
referenced_payment_requests = frappe.get_all(
"Payment Request",
filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]},

View File

@@ -6,7 +6,7 @@ import unittest
from unittest.mock import patch
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
@@ -58,15 +58,6 @@ payment_method = [
]
class UnitTestPaymentRequest(UnitTestCase):
"""
Unit tests for PaymentRequest.
Use this class for testing individual functions and methods.
"""
pass
class TestPaymentRequest(IntegrationTestCase):
def setUp(self):
for payment_gateway in payment_gateways:
@@ -797,29 +788,58 @@ class TestPaymentRequest(IntegrationTestCase):
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)
pi.save()
pi.submit()
def test_partial_paid_invoice_with_submitted_payment_entry(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
pi.save()
pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "PURINV0001"
pe.reference_date = frappe.utils.nowdate()
pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500
pe.save()
pe.submit()
pe.cancel()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "PURINV0001"
pe.reference_date = frappe.utils.nowdate()
pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500
pe.save()
pe.submit()
pe.cancel()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "PURINV0002"
pe.reference_date = frappe.utils.nowdate()
pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500
pe.save()
pe.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "PURINV0002"
pe.reference_date = frappe.utils.nowdate()
pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500
pe.save()
pe.submit()
pi.load_from_db()
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
self.assertEqual(pr.grand_total, pi.outstanding_amount)
pi.load_from_db()
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
self.assertEqual(pr.grand_total, pi.outstanding_amount)
def test_payment_request_on_unreconcile(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=500)
pi.submit()
pr = make_payment_request(
dt=pi.doctype,
dn=pi.name,
mute_email=1,
submit_doc=True,
return_doc=True,
)
self.assertEqual(pr.grand_total, pi.outstanding_amount)
pe = pr.create_payment_entry()
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": pe.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
unreconcile.submit()
pi.load_from_db()
pr.load_from_db()
self.assertEqual(pr.grand_total, pi.outstanding_amount)

View File

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

View File

@@ -0,0 +1,47 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-05-30 11:47:03.670913",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"pegged_currencies_item_section",
"pegged_currency_item"
],
"fields": [
{
"fieldname": "pegged_currencies_item_section",
"fieldtype": "Section Break"
},
{
"fieldname": "pegged_currency_item",
"fieldtype": "Table",
"options": "Pegged Currency Details"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-06-02 11:46:31.936714",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pegged Currencies",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,22 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class PeggedCurrencies(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.pegged_currencies.pegged_currencies import PeggedCurrencies
pegged_currency_item: DF.Table[PeggedCurrencies]
# end: auto-generated types
pass

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
# On IntegrationTestCase, the doctype test records and all
# link-field test record dependencies are recursively loaded
# Use these module variables to add/remove to/from that list
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
class UnitTestPeggedCurrencies(UnitTestCase):
"""
Unit tests for PeggedCurrencies.
Use this class for testing individual functions and methods.
"""
pass
class IntegrationTestPeggedCurrencies(IntegrationTestCase):
"""
Integration tests for PeggedCurrencies.
Use this class for testing interactions between multiple components.
"""
pass

View File

@@ -0,0 +1,49 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-05-30 11:59:28.219277",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"source_currency",
"pegged_against",
"pegged_exchange_rate"
],
"fields": [
{
"fieldname": "source_currency",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Currency",
"options": "Currency"
},
{
"fieldname": "pegged_exchange_rate",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Exchange Rate"
},
{
"fieldname": "pegged_against",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Pegged Against",
"options": "Currency"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-06-17 14:11:16.521193",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pegged Currency Details",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

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

View File

@@ -210,8 +210,10 @@ class PeriodClosingVoucher(AccountsController):
return gl_entry
def get_gle_for_closing_account(self, dimension_balance, dimensions):
balance_in_account_currency = flt(dimension_balance.balance_in_account_currency)
balance_in_company_currency = flt(dimension_balance.balance_in_company_currency)
debit = balance_in_company_currency if balance_in_company_currency > 0 else 0
credit = abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0
gl_entry = frappe._dict(
{
"company": self.company,
@@ -220,14 +222,10 @@ class PeriodClosingVoucher(AccountsController):
"account_currency": frappe.db.get_value(
"Account", self.closing_account_head, "account_currency"
),
"debit_in_account_currency": balance_in_account_currency
if balance_in_account_currency > 0
else 0,
"debit": balance_in_company_currency if balance_in_company_currency > 0 else 0,
"credit_in_account_currency": abs(balance_in_account_currency)
if balance_in_account_currency < 0
else 0,
"credit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0,
"debit_in_account_currency": debit,
"debit": debit,
"credit_in_account_currency": credit,
"credit": credit,
"is_period_closing_voucher_entry": 1,
"voucher_type": "Period Closing Voucher",
"voucher_no": self.name,

View File

@@ -1,11 +1,11 @@
<div class="clearfix"></div>
<div class="box">
<div class="grid-body">
<div class="grid-body" style="background-color: transparent;">
<div class="rows text-center">
<!-- Sales summary section -->
<div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Sales Summary") }}</h6>
<h6 class="text-center uppercase">{{ _("Sales Summary") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
@@ -32,7 +32,7 @@
<!-- Mode of payment section -->
<div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Mode of Payments") }}</h6>
<h6 class="text-center uppercase">{{ _("Mode of Payments") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
@@ -57,7 +57,7 @@
<!-- Taxes section -->
{% if data.taxes %}
<div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Taxes") }}</h6>
<h6 class="text-center uppercase">{{ _("Taxes") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>

View File

@@ -3,7 +3,7 @@
frappe.ui.form.on("POS Closing Entry", {
onload: async function (frm) {
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log"];
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log", "Sales Invoice"];
frm.set_query("pos_profile", function (doc) {
return {
filters: { user: doc.user },
@@ -36,17 +36,6 @@ frappe.ui.form.on("POS Closing Entry", {
}
});
const is_pos_using_sales_invoice = await frappe.db.get_single_value(
"Accounts Settings",
"use_sales_invoice_in_pos"
);
if (is_pos_using_sales_invoice) {
frm.set_df_property("pos_transactions", "hidden", 1);
}
set_html_data(frm);
if (frm.doc.docstatus == 1) {
if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.nowdate());
@@ -91,8 +80,7 @@ frappe.ui.form.on("POS Closing Entry", {
frappe.run_serially([
() => frappe.dom.freeze(__("Loading Invoices! Please Wait...")),
() => frm.trigger("set_opening_amounts"),
() => frm.trigger("get_pos_invoices"),
() => frm.trigger("get_sales_invoices"),
() => frm.trigger("get_invoices"),
() => frappe.dom.unfreeze(),
]);
}
@@ -112,9 +100,9 @@ frappe.ui.form.on("POS Closing Entry", {
});
},
get_pos_invoices(frm) {
get_invoices(frm) {
return frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices",
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
@@ -122,101 +110,14 @@ frappe.ui.form.on("POS Closing Entry", {
user: frm.doc.user,
},
callback: (r) => {
let pos_docs = r.message;
set_pos_transaction_form_data(pos_docs, frm);
let inv_docs = r.message.invoices;
set_transaction_form_data(inv_docs, frm);
refresh_payments(r.message.payments, frm);
add_taxes(r.message.taxes, frm);
refresh_fields(frm);
set_html_data(frm);
},
});
},
get_sales_invoices(frm) {
return frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_sales_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user,
},
callback: (r) => {
let sales_docs = r.message;
set_sales_invoice_transaction_form_data(sales_docs, frm);
refresh_fields(frm);
set_html_data(frm);
},
});
},
before_save: async function (frm) {
frappe.dom.freeze(__("Processing Sales! Please Wait..."));
frm.set_value("grand_total", 0);
frm.set_value("net_total", 0);
frm.set_value("total_quantity", 0);
frm.set_value("taxes", []);
for (let row of frm.doc.payment_reconciliation) {
row.expected_amount = row.opening_amount;
}
const is_pos_using_sales_invoice = await frappe.db.get_single_value(
"Accounts Settings",
"use_sales_invoice_in_pos"
);
if (is_pos_using_sales_invoice) {
await Promise.all([
frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user,
},
callback: (r) => {
let pos_invoices = r.message;
for (let doc of pos_invoices) {
frm.doc.grand_total += flt(doc.grand_total);
frm.doc.net_total += flt(doc.net_total);
frm.doc.total_quantity += flt(doc.total_qty);
refresh_payments(doc, frm, false);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
}
},
}),
]);
}
await Promise.all([
frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_sales_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user,
},
callback: (r) => {
let sales_invoices = r.message;
for (let doc of sales_invoices) {
frm.doc.grand_total += flt(doc.grand_total);
frm.doc.net_total += flt(doc.net_total);
frm.doc.total_quantity += flt(doc.total_qty);
refresh_payments(doc, frm, false);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
}
},
}),
]);
frappe.dom.unfreeze();
},
});
frappe.ui.form.on("POS Closing Entry Detail", {
@@ -226,57 +127,37 @@ frappe.ui.form.on("POS Closing Entry Detail", {
},
});
function set_pos_transaction_form_data(data, frm) {
function set_transaction_form_data(data, frm) {
data.forEach((d) => {
add_to_pos_transaction(d, frm);
add_to_transaction(d, frm);
frm.doc.grand_total += flt(d.grand_total);
frm.doc.net_total += flt(d.net_total);
frm.doc.total_quantity += flt(d.total_qty);
refresh_payments(d, frm, true);
refresh_taxes(d, frm);
frm.doc.total_taxes_and_charges += flt(d.total_taxes_and_charges);
});
}
function set_sales_invoice_transaction_form_data(data, frm) {
data.forEach((d) => {
add_to_sales_invoice_transaction(d, frm);
frm.doc.grand_total += flt(d.grand_total);
frm.doc.net_total += flt(d.net_total);
frm.doc.total_quantity += flt(d.total_qty);
refresh_payments(d, frm, true);
refresh_taxes(d, frm);
});
}
function add_to_pos_transaction(d, frm) {
frm.add_child("pos_transactions", {
pos_invoice: d.name,
function add_to_transaction(d, frm) {
const field = d.doctype === "POS Invoice" ? "pos_invoices" : "sales_invoices";
frm.add_child(field, {
posting_date: d.posting_date,
grand_total: d.grand_total,
customer: d.customer,
is_return: d.is_return,
return_against: d.return_against,
...(d.doctype === "POS Invoice" && { pos_invoice: d.name }),
...(d.doctype === "Sales Invoice" && { sales_invoice: d.name }),
});
}
function add_to_sales_invoice_transaction(d, frm) {
frm.add_child("sales_invoice_transactions", {
sales_invoice: d.name,
posting_date: d.posting_date,
grand_total: d.grand_total,
customer: d.customer,
});
}
function refresh_payments(d, frm, is_new) {
d.payments.forEach((p) => {
function refresh_payments(payments, frm) {
payments.forEach((p) => {
const payment = frm.doc.payment_reconciliation.find(
(pay) => pay.mode_of_payment === p.mode_of_payment
);
if (p.account == d.account_for_change_amount) {
p.amount -= flt(d.change_amount);
}
if (payment) {
payment.expected_amount += flt(p.amount);
if (is_new) payment.closing_amount = payment.expected_amount;
payment.closing_amount = payment.expected_amount;
payment.difference = payment.closing_amount - payment.expected_amount;
} else {
frm.add_child("payment_reconciliation", {
@@ -289,49 +170,33 @@ function refresh_payments(d, frm, is_new) {
});
}
function refresh_taxes(d, frm) {
d.taxes.forEach((t) => {
const tax = frm.doc.taxes.find((tx) => tx.account_head === t.account_head && tx.rate === t.rate);
if (tax) {
tax.amount += flt(t.tax_amount);
} else {
frm.add_child("taxes", {
account_head: t.account_head,
rate: t.rate,
amount: t.tax_amount,
});
}
function add_taxes(taxes, frm) {
taxes.forEach((t) => {
frm.add_child("taxes", {
account_head: t.account_head,
amount: t.tax_amount,
});
});
}
function reset_values(frm) {
frm.set_value("pos_transactions", []);
frm.set_value("sales_invoice_transactions", []);
frm.set_value("pos_invoices", []);
frm.set_value("sales_invoices", []);
frm.set_value("payment_reconciliation", []);
frm.set_value("taxes", []);
frm.set_value("grand_total", 0);
frm.set_value("net_total", 0);
frm.set_value("total_taxes_and_charges", 0);
frm.set_value("total_quantity", 0);
}
function refresh_fields(frm) {
frm.refresh_field("pos_transactions");
frm.refresh_field("sales_invoice_transactions");
frm.refresh_field("pos_invoices");
frm.refresh_field("sales_invoices");
frm.refresh_field("payment_reconciliation");
frm.refresh_field("taxes");
frm.refresh_field("grand_total");
frm.refresh_field("net_total");
frm.refresh_field("total_taxes_and_charges");
frm.refresh_field("total_quantity");
}
function set_html_data(frm) {
if (frm.doc.docstatus === 1 && frm.doc.status == "Submitted") {
frappe.call({
method: "get_payment_reconciliation_details",
doc: frm.doc,
callback: (r) => {
frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
},
});
}
}

View File

@@ -20,18 +20,19 @@
"pos_profile",
"user",
"section_break_12",
"pos_transactions",
"sales_invoice_transactions",
"section_break_9",
"payment_reconciliation_details",
"pos_invoices",
"sales_invoices",
"taxes_and_charges_section",
"taxes",
"section_break_13",
"column_break_16",
"total_quantity",
"column_break_ywgl",
"net_total",
"total_taxes_and_charges",
"grand_total",
"section_break_11",
"payment_reconciliation",
"section_break_13",
"grand_total",
"net_total",
"total_quantity",
"column_break_16",
"taxes",
"failure_description_section",
"error_message",
"section_break_14",
@@ -73,10 +74,13 @@
"label": "User Details"
},
{
"fetch_from": "pos_opening_entry.company",
"fetch_if_empty": 1,
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
},
{
@@ -85,11 +89,13 @@
},
{
"fetch_from": "pos_opening_entry.pos_profile",
"fetch_if_empty": 1,
"fieldname": "pos_profile",
"fieldtype": "Link",
"in_list_view": 1,
"label": "POS Profile",
"options": "POS Profile",
"read_only": 1,
"reqd": 1
},
{
@@ -98,18 +104,9 @@
"fieldtype": "Link",
"label": "Cashier",
"options": "User",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus==1",
"fieldname": "payment_reconciliation_details",
"fieldtype": "HTML"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break",
@@ -122,7 +119,6 @@
"options": "POS Closing Entry Detail"
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.docstatus==0",
"fieldname": "section_break_13",
"fieldtype": "Section Break",
@@ -177,17 +173,12 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "pos_transactions",
"fieldtype": "Table",
"label": "POS Transactions",
"options": "POS Invoice Reference"
},
{
"fieldname": "pos_opening_entry",
"fieldtype": "Link",
"label": "POS Opening Entry",
"options": "POS Opening Entry",
"print_hide": 1,
"reqd": 1
},
{
@@ -230,10 +221,36 @@
"reqd": 1
},
{
"fieldname": "sales_invoice_transactions",
"fieldname": "pos_invoices",
"fieldtype": "Table",
"label": "POS Transactions",
"options": "POS Invoice Reference",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "sales_invoices",
"fieldtype": "Table",
"label": "Sales Invoice Transactions",
"options": "Sales Invoice Reference"
"options": "Sales Invoice Reference",
"print_hide": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "taxes_and_charges_section",
"fieldtype": "Section Break",
"label": "Taxes and Charges"
},
{
"fieldname": "column_break_ywgl",
"fieldtype": "Column Break"
},
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges",
"read_only": 1
}
],
"grid_page_length": 50,
@@ -244,7 +261,7 @@
"link_fieldname": "pos_closing_entry"
}
],
"modified": "2025-03-19 19:49:58.845697",
"modified": "2025-06-14 02:38:14.962291",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",

View File

@@ -4,7 +4,10 @@
import frappe
from frappe import _
from frappe.utils import flt, get_datetime
from frappe.query_builder import DocType
from frappe.query_builder import functions as fn
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
@@ -41,35 +44,45 @@ class POSClosingEntry(StatusUpdater):
payment_reconciliation: DF.Table[POSClosingEntryDetail]
period_end_date: DF.Datetime
period_start_date: DF.Datetime
pos_invoices: DF.Table[POSInvoiceReference]
pos_opening_entry: DF.Link
pos_profile: DF.Link
pos_transactions: DF.Table[POSInvoiceReference]
posting_date: DF.Date
posting_time: DF.Time
sales_invoice_transactions: DF.Table[SalesInvoiceReference]
sales_invoices: DF.Table[SalesInvoiceReference]
status: DF.Literal["Draft", "Submitted", "Queued", "Failed", "Cancelled"]
taxes: DF.Table[POSClosingEntryTaxes]
total_quantity: DF.Float
total_taxes_and_charges: DF.Currency
user: DF.Link
# end: auto-generated types
def validate(self):
self.posting_date = self.posting_date or frappe.utils.nowdate()
self.posting_time = self.posting_time or frappe.utils.nowtime()
self.set_posting_date_and_time()
self.fetch_invoice_type()
self.validate_pos_opening_entry()
self.validate_invoice_mode()
def set_posting_date_and_time(self):
if self.posting_date:
self.posting_date = frappe.utils.nowdate()
if self.posting_time:
self.posting_time = frappe.utils.nowtime()
def fetch_invoice_type(self):
self.invoice_type = frappe.db.get_single_value("POS Settings", "invoice_type")
def validate_pos_opening_entry(self):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
self.is_pos_using_sales_invoice = frappe.db.get_single_value(
"Accounts Settings", "use_sales_invoice_in_pos"
)
if self.is_pos_using_sales_invoice == 0:
def validate_invoice_mode(self):
if self.invoice_type == "POS Invoice":
self.validate_duplicate_pos_invoices()
self.validate_pos_invoices()
if self.is_pos_using_sales_invoice == 1:
if len(self.pos_transactions) != 0:
if self.invoice_type == "Sales Invoice":
if len(self.pos_invoices) != 0:
frappe.throw(_("POS Invoices can't be added when Sales Invoice is enabled"))
self.validate_duplicate_sales_invoices()
@@ -77,7 +90,7 @@ class POSClosingEntry(StatusUpdater):
def validate_duplicate_pos_invoices(self):
pos_occurences = {}
for idx, inv in enumerate(self.pos_transactions, 1):
for idx, inv in enumerate(self.pos_invoices, 1):
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
error_list = []
@@ -92,7 +105,7 @@ class POSClosingEntry(StatusUpdater):
def validate_pos_invoices(self):
invalid_rows = []
for d in self.pos_transactions:
for d in self.pos_invoices:
invalid_row = {"idx": d.idx}
pos_invoice = frappe.db.get_values(
"POS Invoice",
@@ -130,7 +143,7 @@ class POSClosingEntry(StatusUpdater):
def validate_duplicate_sales_invoices(self):
sales_invoice_occurrences = {}
for idx, inv in enumerate(self.sales_invoice_transactions, 1):
for idx, inv in enumerate(self.sales_invoices, 1):
sales_invoice_occurrences.setdefault(inv.sales_invoice, []).append(idx)
error_list = []
@@ -145,7 +158,7 @@ class POSClosingEntry(StatusUpdater):
def validate_sales_invoices(self):
invalid_rows = []
for d in self.sales_invoice_transactions:
for d in self.sales_invoices:
invalid_row = {"idx": d.idx}
sales_invoice = frappe.db.get_values(
"Sales Invoice",
@@ -193,24 +206,19 @@ class POSClosingEntry(StatusUpdater):
frappe.throw(error_list, title=_("Invalid Sales Invoices"), as_list=True)
@frappe.whitelist()
def get_payment_reconciliation_details(self):
currency = frappe.get_cached_value("Company", self.company, "default_currency")
return frappe.render_template(
"erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
{"data": self, "currency": currency},
)
def on_submit(self):
consolidate_pos_invoices(closing_entry=self)
frappe.publish_realtime(
f"poe_{self.pos_opening_entry}_closed",
self,
f"poe_{self.pos_opening_entry}",
message={"operation": "Closed", "doc": self},
docname=f"POS Opening Entry/{self.pos_opening_entry}",
)
self.update_sales_invoices_closing_entry()
def before_cancel(self):
self.check_pce_is_cancellable()
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)
@@ -227,11 +235,20 @@ class POSClosingEntry(StatusUpdater):
opening_entry.save()
def update_sales_invoices_closing_entry(self, cancel=False):
for d in self.sales_invoice_transactions:
for d in self.sales_invoices:
frappe.db.set_value(
"Sales Invoice", d.sales_invoice, "pos_closing_entry", self.name if not cancel else None
)
def check_pce_is_cancellable(self):
if frappe.db.exists("POS Opening Entry", {"pos_profile": self.pos_profile, "status": "Open"}):
frappe.throw(
title=_("Cannot cancel POS Closing Entry"),
msg=_(
"POS Profile - {0} is currently open. Please close the POS or cancel the existing POS Opening Entry before cancelling this POS Closing Entry."
).format(frappe.bold(self.pos_profile)),
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -241,50 +258,79 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
def get_pos_invoices(start, end, pos_profile, user):
data = frappe.db.sql(
"""
select
name, timestamp(posting_date, posting_time) as "timestamp"
from
`tabPOS Invoice`
where
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
""",
(user, pos_profile),
as_dict=1,
)
def get_invoices(start, end, pos_profile, user):
invoice_doctype = frappe.db.get_single_value("POS Settings", "invoice_type")
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
# need to get taxes and payments so can't avoid get_doc
data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data]
sales_inv_query = build_invoice_query("Sales Invoice", user, pos_profile, start, end)
query = sales_inv_query
if invoice_doctype == "POS Invoice":
pos_inv_query = build_invoice_query("POS Invoice", user, pos_profile, start, end)
query = query + pos_inv_query
query = query.orderby(query.timestamp)
invoices = query.run(as_dict=1)
data = {"invoices": invoices, "payments": get_payments(invoices), "taxes": get_taxes(invoices)}
return data
@frappe.whitelist()
def get_sales_invoices(start, end, pos_profile, user):
data = frappe.db.sql(
"""
select
name, timestamp(posting_date, posting_time) as "timestamp"
from
`tabSales Invoice`
where
owner = %s
and docstatus = 1
and is_pos = 1
and pos_profile = %s
and is_created_using_pos = 1
and ifnull(pos_closing_entry,'') = ''
""",
(user, pos_profile),
as_dict=1,
)
def get_payments(invoices):
if not len(invoices):
return []
data = [d for d in data if get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end)]
# need to get taxes and payments so can't avoid get_doc
data = [frappe.get_doc("Sales Invoice", d.name).as_dict() for d in data]
invoices_name = [d.name for d in invoices]
SalesInvoicePayment = DocType("Sales Invoice Payment")
query = (
frappe.qb.from_(SalesInvoicePayment)
.where(
(SalesInvoicePayment.parenttype.isin(["Sales Invoice", "POS Invoice"]))
& (SalesInvoicePayment.parent.isin(invoices_name))
)
.groupby(SalesInvoicePayment.mode_of_payment)
.select(
SalesInvoicePayment.mode_of_payment,
SalesInvoicePayment.account,
fn.Sum(SalesInvoicePayment.amount).as_("amount"),
)
)
data = query.run(as_dict=1)
change_amount_by_account = {}
for d in invoices:
change_amount_by_account.setdefault(d.account_for_change_amount, 0)
change_amount_by_account[d.account_for_change_amount] += flt(d.change_amount)
for d in data:
if change_amount_by_account.get(d.account):
d.amount -= flt(change_amount_by_account.get(d.account))
return data
def get_taxes(invoices):
if not len(invoices):
return []
invoices_name = [d.name for d in invoices]
SalesInvoiceTaxesCharges = DocType("Sales Taxes and Charges")
query = (
frappe.qb.from_(SalesInvoiceTaxesCharges)
.where(
(SalesInvoiceTaxesCharges.parenttype.isin(["Sales Invoice", "POS Invoice"]))
& (SalesInvoiceTaxesCharges.parent.isin(invoices_name))
)
.groupby(SalesInvoiceTaxesCharges.account_head)
.select(
SalesInvoiceTaxesCharges.account_head,
fn.Sum(SalesInvoiceTaxesCharges.tax_amount_after_discount_amount).as_("tax_amount"),
)
)
data = query.run(as_dict=1)
return data
@@ -300,98 +346,98 @@ def make_closing_entry_from_opening(opening_entry):
closing_entry.grand_total = 0
closing_entry.net_total = 0
closing_entry.total_quantity = 0
closing_entry.total_taxes_and_charges = 0
is_pos_using_sales_invoice = frappe.db.get_single_value("Accounts Settings", "use_sales_invoice_in_pos")
pos_invoices = (
get_pos_invoices(
closing_entry.period_start_date,
closing_entry.period_end_date,
closing_entry.pos_profile,
closing_entry.user,
)
if is_pos_using_sales_invoice == 0
else []
)
sales_invoices = get_sales_invoices(
data = get_invoices(
closing_entry.period_start_date,
closing_entry.period_end_date,
closing_entry.pos_profile,
closing_entry.user,
)
pos_transactions = []
sales_invoice_transactions = []
taxes = []
payments = []
for detail in opening_entry.balance_details:
payments.append(
frappe._dict(
{
"mode_of_payment": detail.mode_of_payment,
"opening_amount": detail.opening_amount,
"expected_amount": detail.opening_amount,
}
)
pos_invoices = []
sales_invoices = []
taxes = [
frappe._dict({"account_head": tx.account_head, "amount": tx.tax_amount}) for tx in data.get("taxes")
]
payments = [
frappe._dict(
{
"mode_of_payment": p.mode_of_payment,
"opening_amount": 0,
"expected_amount": p.amount,
}
)
for p in data.get("payments")
]
for d in pos_invoices:
pos_transactions.append(
frappe._dict(
{
"pos_invoice": d.name,
"posting_date": d.posting_date,
"grand_total": d.grand_total,
"customer": d.customer,
}
)
for d in data.get("invoices"):
invoice = "pos_invoice" if d.doctype == "POS Invoice" else "sales_invoice"
invoice_data = frappe._dict(
{
invoice: d.name,
"posting_date": d.posting_date,
"grand_total": d.grand_total,
"customer": d.customer,
"is_return": d.is_return,
"return_against": d.return_against,
}
)
if d.doctype == "POS Invoice":
pos_invoices.append(invoice_data)
else:
sales_invoices.append(invoice_data)
for d in sales_invoices:
sales_invoice_transactions.append(
frappe._dict(
{
"sales_invoice": d.name,
"posting_date": d.posting_date,
"grand_total": d.grand_total,
"customer": d.customer,
}
)
)
for d in [*pos_invoices, *sales_invoices]:
closing_entry.grand_total += flt(d.grand_total)
closing_entry.net_total += flt(d.net_total)
closing_entry.total_quantity += flt(d.total_qty)
closing_entry.total_taxes_and_charges += flt(d.total_taxes_and_charges)
for t in d.taxes:
existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate]
if existing_tax:
existing_tax[0].amount += flt(t.tax_amount)
else:
taxes.append(
frappe._dict({"account_head": t.account_head, "rate": t.rate, "amount": t.tax_amount})
)
for p in d.payments:
existing_pay = [pay for pay in payments if pay.mode_of_payment == p.mode_of_payment]
if existing_pay:
existing_pay[0].expected_amount += flt(p.amount)
else:
payments.append(
frappe._dict(
{
"mode_of_payment": p.mode_of_payment,
"opening_amount": 0,
"expected_amount": p.amount,
}
)
)
closing_entry.set("pos_transactions", pos_transactions)
closing_entry.set("sales_invoice_transactions", sales_invoice_transactions)
closing_entry.set("pos_invoices", pos_invoices)
closing_entry.set("sales_invoices", sales_invoices)
closing_entry.set("payment_reconciliation", payments)
closing_entry.set("taxes", taxes)
return closing_entry
def build_invoice_query(invoice_doctype, user, pos_profile, start, end):
InvoiceDocType = DocType(invoice_doctype)
query = (
frappe.qb.from_(InvoiceDocType)
.select(
InvoiceDocType.name,
InvoiceDocType.customer,
InvoiceDocType.posting_date,
InvoiceDocType.grand_total,
InvoiceDocType.net_total,
InvoiceDocType.total_qty,
InvoiceDocType.total_taxes_and_charges,
InvoiceDocType.change_amount,
InvoiceDocType.account_for_change_amount,
InvoiceDocType.is_return,
InvoiceDocType.return_against,
fn.Timestamp(InvoiceDocType.posting_date, InvoiceDocType.posting_time).as_("timestamp"),
ConstantColumn(invoice_doctype).as_("doctype"),
)
.where(
(InvoiceDocType.owner == user)
& (InvoiceDocType.docstatus == 1)
& (InvoiceDocType.is_pos == 1)
& (InvoiceDocType.pos_profile == pos_profile)
& (
(fn.Timestamp(InvoiceDocType.posting_date, InvoiceDocType.posting_time) >= start)
& (fn.Timestamp(InvoiceDocType.posting_date, InvoiceDocType.posting_time) <= end)
)
)
)
if invoice_doctype == "POS Invoice":
query = query.where(fn.IfNull(InvoiceDocType.consolidated_invoice, "").eq(""))
else:
query = query.where(
(InvoiceDocType.is_created_using_pos == 1)
& fn.IfNull(InvoiceDocType.pos_closing_entry, "").eq("")
)
return query

View File

@@ -12,10 +12,10 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
@@ -25,8 +25,18 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestPOSClosingEntry(IntegrationTestCase):
@classmethod
def setUpClass(cls):
frappe.db.sql("delete from `tabPOS Opening Entry`")
cls.enterClassContext(cls.change_settings("POS Settings", {"invoice_type": "POS Invoice"}))
@classmethod
def tearDownClass(cls):
frappe.db.sql("delete from `tabPOS Opening Entry`")
def setUp(self):
# Make stock available for POS Sales
frappe.db.sql("delete from `tabPOS Opening Entry`")
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
def tearDown(self):
@@ -82,6 +92,8 @@ class TestPOSClosingEntry(IntegrationTestCase):
"""
Test if quantity is calculated correctly for an item in POS Closing Entry
"""
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
@@ -200,9 +212,6 @@ class TestPOSClosingEntry(IntegrationTestCase):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
init_user_and_profile,
)
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
)
from erpnext.stock.doctype.batch.batch import get_batch_qty
frappe.db.sql("delete from `tabPOS Invoice`")
@@ -293,45 +302,171 @@ class TestPOSClosingEntry(IntegrationTestCase):
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
self.assertEqual(batch_qty_with_pos, 10.0)
@IntegrationTestCase.change_settings("POS Settings", {"invoice_type": "Sales Invoice"})
def test_closing_entries_with_sales_invoice(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_si = create_sales_invoice(
qty=10, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
)
pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si.save()
pos_si.submit()
pos_si2 = create_sales_invoice(
qty=5, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=11
)
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si2.save()
pos_si2.submit()
pcv_doc = make_closing_entry_from_opening(opening_entry)
payment = pcv_doc.payment_reconciliation[0]
self.assertEqual(payment.mode_of_payment, "Cash")
for d in pcv_doc.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 1500
pcv_doc.submit()
self.assertEqual(pcv_doc.total_quantity, 15)
self.assertEqual(pcv_doc.net_total, 1500)
pos_si2.reload()
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc.name)
def test_sales_invoice_in_pos_invoice_mode(self):
"""
Test Sales Invoice and Return Sales Invoice creation during POS Invoice mode.
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile()
# Deleting all opening entry
frappe.db.sql("delete from `tabPOS Opening Entry`")
with self.change_settings("Accounts Settings", {"use_sales_invoice_in_pos": 1}):
opening_entry = create_opening_entry(pos_profile, test_user.name)
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
pos_si = create_sales_invoice(qty=10, do_not_save=1)
pos_si.is_pos = 1
pos_si.pos_profile = pos_profile.name
pos_si.is_created_using_pos = 1
pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si.save()
pos_si.submit()
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
pos_si2 = create_sales_invoice(qty=5, do_not_save=1)
pos_si2.is_pos = 1
pos_si2.pos_profile = pos_profile.name
pos_si2.is_created_using_pos = 1
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si2.save()
pos_si2.submit()
pos_inv = create_pos_invoice(rate=100, do_not_save=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_inv.save)
pcv_doc = make_closing_entry_from_opening(opening_entry)
payment = pcv_doc.payment_reconciliation[0]
self.assertEqual(payment.mode_of_payment, "Cash")
for d in pcv_doc.payment_reconciliation:
pcv_doc1 = make_closing_entry_from_opening(opening_entry1)
for d in pcv_doc1.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 1500
d.closing_amount = 300
pcv_doc.submit()
pcv_doc1.submit()
self.assertTrue(pcv_doc1.name)
self.assertEqual(pcv_doc.total_quantity, 15)
self.assertEqual(pcv_doc.net_total, 1500)
pos_si1.reload()
pos_si2.reload()
self.assertEqual(pos_si1.pos_closing_entry, pcv_doc1.name)
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc1.name)
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
pos_si3 = create_sales_invoice(
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
)
pos_si3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_si3.save)
# Trying to create Return Sales Invoice.
pos_rsi1 = make_sales_return(pos_si1.name)
pos_rsi1.save()
pos_rsi1.submit()
self.assertEqual(pos_rsi1.paid_amount, -100)
pcv_doc2 = make_closing_entry_from_opening(opening_entry2)
pcv_doc2.submit()
self.assertTrue(pcv_doc2.name)
pos_rsi1.reload()
self.assertEqual(pos_rsi1.pos_closing_entry, pcv_doc2.name)
self.assertIn(pos_inv1.name, [d.pos_invoice for d in pcv_doc2.pos_invoices])
self.assertNotIn(pos_inv2.name, [d.sales_invoice for d in pcv_doc2.sales_invoices])
self.assertIn(pos_rsi1.name, [d.sales_invoice for d in pcv_doc2.sales_invoices])
self.assertEqual(pcv_doc2.grand_total, 200)
def test_pos_invoice_in_sales_invoice_mode(self):
"""
Test POS Invoice and Return POS Invoice creation during Sales Invoice mode.
"""
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile()
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
pos_sinv = create_sales_invoice(
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
)
pos_sinv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_sinv.save)
pcv_doc1 = make_closing_entry_from_opening(opening_entry1)
for d in pcv_doc1.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 300
pcv_doc1.submit()
self.assertTrue(pcv_doc1.name)
self.assertIn(pos_inv1.name, [d.pos_invoice for d in pcv_doc1.pos_invoices])
self.assertEqual(pcv_doc1.grand_total, 300)
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
pos_inv3 = create_pos_invoice(rate=100, do_not_save=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_inv3.save)
# Creating Return POS Invoice
pos_rinv2 = make_sales_return(pos_inv2.name)
pos_rinv2.save()
pos_rinv2.submit()
pos_rinv2.reload()
self.assertIsNotNone(pos_rinv2.consolidated_invoice)
# Getting Sales Invoice created during POS Invoice submission.
pos_rinv2_si = frappe.get_doc("Sales Invoice", pos_rinv2.consolidated_invoice)
self.assertEqual(pos_rinv2_si.is_return, 1)
self.assertEqual(pos_rinv2_si.paid_amount, -200)
pcv_doc2 = make_closing_entry_from_opening(opening_entry2)
for d in pcv_doc1.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 100
pcv_doc2.submit()
self.assertTrue(pcv_doc2.name)
pos_si1.reload()
pos_si2.reload()
pos_rinv2_si.reload()
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc2.name)
self.assertEqual(pos_rinv2_si.pos_closing_entry, pcv_doc2.name)
def init_user_and_profile(**args):
@@ -367,3 +502,31 @@ def get_test_item_qty(pos_profile):
"actual_qty"
)
return test_item_qty
def create_multiple_sales_invoices(pos_profile):
pos_si1 = create_sales_invoice(qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1)
pos_si1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_si1.save()
pos_si1.submit()
pos_si2 = create_sales_invoice(qty=2, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1)
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 200})
pos_si2.save()
pos_si2.submit()
return pos_si1, pos_si2
def create_multiple_pos_invoices(pos_profile):
pos_inv1 = create_pos_invoice(pos_profile=pos_profile.name, rate=100, do_not_save=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv1.save()
pos_inv1.submit()
pos_inv2 = create_pos_invoice(pos_profile=pos_profile.name, qty=2, do_not_save=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 200})
pos_inv2.save()
pos_inv2.submit()
return pos_inv1, pos_inv2

View File

@@ -6,17 +6,9 @@
"engine": "InnoDB",
"field_order": [
"account_head",
"rate",
"amount"
],
"fields": [
{
"fieldname": "rate",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Tax Rate",
"read_only": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
@@ -35,15 +27,16 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:14.420657",
"modified": "2025-06-06 11:54:02.414461",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry Taxes",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -19,7 +19,6 @@ class POSClosingEntryTaxes(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
rate: DF.Percent
# end: auto-generated types
pass

View File

@@ -66,6 +66,13 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
if (doc.docstatus == 1 && !doc.is_return) {
this.frm.add_custom_button(__("Return"), this.make_sales_return.bind(this), __("Create"));
if (["Partly Paid", "Overdue", "Unpaid"].includes(doc.status)) {
this.frm.add_custom_button(
__("Payment"),
this.collect_outstanding_payment.bind(this),
__("Create")
);
}
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
@@ -210,6 +217,138 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
frm: this.frm,
});
}
async collect_outstanding_payment() {
const total_amount = flt(this.frm.doc.rounded_total) | flt(this.frm.doc.grand_total);
const paid_amount = flt(this.frm.doc.paid_amount);
const outstanding_amount = flt(this.frm.doc.outstanding_amount);
const me = this;
const table_fields = [
{
fieldname: "mode_of_payment",
fieldtype: "Link",
in_list_view: 1,
label: __("Mode of Payment"),
options: "Mode of Payment",
reqd: 1,
},
{
fieldname: "amount",
fieldtype: "Currency",
in_list_view: 1,
label: __("Amount"),
options: this.frm.doc.currency,
reqd: 1,
onchange: function () {
dialog.fields_dict.payments.df.data.some((d) => {
if (d.idx == this.doc.idx) {
d.amount = this.value === null ? 0 : this.value;
dialog.fields_dict.payments.grid.refresh();
return true;
}
});
let amount = 0;
for (let d of dialog.fields_dict.payments.df.data) {
amount += d.amount;
}
let change_amount = total_amount - (paid_amount + amount);
dialog.fields_dict.outstanding_amount.set_value(
outstanding_amount - amount < 0 ? 0 : outstanding_amount - amount
);
dialog.fields_dict.paid_amount.set_value(paid_amount + amount);
dialog.fields_dict.change_amount.set_value(change_amount < 0 ? change_amount * -1 : 0);
},
},
];
const payment_method_data = await this.fetch_pos_payment_methods();
const dialog = new frappe.ui.Dialog({
title: __("Collect Outstanding Amount"),
fields: [
{
fieldname: "payments",
fieldtype: "Table",
label: __("Payments"),
cannot_add_rows: false,
in_place_edit: true,
reqd: 1,
data: payment_method_data,
fields: table_fields,
},
{
fieldname: "section_break_1",
fieldtype: "Section Break",
},
{
fieldname: "outstanding_amount",
fieldtype: "Currency",
label: __("Outstanding Amount"),
read_only: 1,
default: outstanding_amount,
},
{
fieldname: "column_break_1",
fieldtype: "Column Break",
},
{
fieldname: "paid_amount",
fieldtype: "Currency",
label: __("Paid Amount"),
read_only: 1,
default: paid_amount,
},
{
fieldname: "change_amount",
fieldtype: "Currency",
label: __("Change Amount"),
read_only: 1,
default: 0,
},
],
primary_action_label: __("Submit"),
primary_action(values) {
dialog.hide();
me.frm.call({
doc: me.frm.doc,
method: "update_payments",
args: {
payments: values.payments.filter((d) => d.amount != 0),
},
freeze: true,
callback: function (r) {
if (!r.exc) {
frappe.show_alert({
message: __("Payments updated."),
indicator: "green",
});
me.frm.reload_doc();
} else {
frappe.show_alert({
message: __("Payments could not be updated."),
indicator: "red",
});
}
},
});
},
});
dialog.show();
}
async fetch_pos_payment_methods() {
const pos_profile = this.frm.doc.pos_profile;
if (!pos_profile) return;
const pos_profile_doc = await frappe.db.get_doc("POS Profile", pos_profile);
const data = [];
pos_profile_doc.payments.forEach((pay) => {
const { mode_of_payment } = pay;
data.push({ mode_of_payment, amount: 0 });
});
return data;
}
};
extend_cscript(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm }));

View File

@@ -30,24 +30,6 @@
"project",
"dimension_col_break",
"cost_center",
"customer_po_details",
"po_no",
"column_break_23",
"po_date",
"address_and_contact",
"customer_address",
"address_display",
"contact_person",
"contact_display",
"contact_mobile",
"contact_email",
"territory",
"col_break4",
"shipping_address_name",
"shipping_address",
"company_address",
"company_address_display",
"company_contact_person",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -91,14 +73,6 @@
"base_total_taxes_and_charges",
"column_break_47",
"total_taxes_and_charges",
"loyalty_points_redemption",
"loyalty_points",
"loyalty_amount",
"redeem_loyalty_points",
"column_break_77",
"loyalty_program",
"loyalty_redemption_account",
"loyalty_redemption_cost_center",
"section_break_49",
"coupon_code",
"apply_discount_on",
@@ -118,13 +92,7 @@
"in_words",
"total_advance",
"outstanding_amount",
"advances_section",
"allocate_advances_automatically",
"get_advances",
"advances",
"payment_schedule_section",
"payment_terms_template",
"payment_schedule",
"payments_tab",
"payments_section",
"cash_bank_account",
"payments",
@@ -137,6 +105,10 @@
"column_break_90",
"change_amount",
"account_for_change_amount",
"advances_section",
"allocate_advances_automatically",
"get_advances",
"advances",
"column_break4",
"write_off_amount",
"base_write_off_amount",
@@ -144,9 +116,41 @@
"column_break_74",
"write_off_account",
"write_off_cost_center",
"loyalty_points_redemption",
"loyalty_points",
"loyalty_amount",
"redeem_loyalty_points",
"column_break_77",
"loyalty_program",
"loyalty_redemption_account",
"loyalty_redemption_cost_center",
"contact_and_address_tab",
"address_and_contact",
"customer_address",
"address_display",
"contact_person",
"contact_display",
"contact_mobile",
"contact_email",
"territory",
"col_break4",
"shipping_address_name",
"shipping_address",
"company_address",
"company_address_display",
"company_contact_person",
"terms_tab",
"payment_schedule_section",
"payment_terms_template",
"payment_schedule",
"terms_section_break",
"tc_name",
"terms",
"more_info_tab",
"customer_po_details",
"po_no",
"column_break_23",
"po_date",
"edit_printing_settings",
"letter_head",
"group_same_items",
@@ -398,7 +402,6 @@
"label": "Customer's Purchase Order Date"
},
{
"collapsible": 1,
"fieldname": "address_and_contact",
"fieldtype": "Section Break",
"label": "Address and Contact"
@@ -1050,7 +1053,6 @@
"print_hide": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
@@ -1130,8 +1132,10 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "section_break_88",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Changes"
},
{
"depends_on": "is_pos",
@@ -1218,7 +1222,6 @@
"print_hide": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "terms",
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
@@ -1330,7 +1333,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nConsolidated\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
"options": "\nDraft\nReturn\nCredit Note Issued\nConsolidated\nSubmitted\nPaid\nPartly Paid\nUnpaid\nPartly Paid and Discounted\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
"print_hide": 1,
"read_only": 1
},
@@ -1416,6 +1419,8 @@
"width": "50%"
},
{
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"label": "Commission Rate (%)",
@@ -1568,12 +1573,32 @@
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
},
{
"fieldname": "payments_tab",
"fieldtype": "Tab Break",
"label": "Payments"
},
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Address & Contact"
},
{
"fieldname": "terms_tab",
"fieldtype": "Tab Break",
"label": "Terms"
},
{
"fieldname": "more_info_tab",
"fieldtype": "Tab Break",
"label": "More Info"
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2025-01-06 15:03:19.957277",
"modified": "2025-07-18 16:50:30.516162",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
@@ -1618,6 +1643,7 @@
"role": "All"
}
],
"row_format": "Dynamic",
"search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "creation",

View File

@@ -149,7 +149,9 @@ class POSInvoice(SalesInvoice):
"Consolidated",
"Submitted",
"Paid",
"Partly Paid",
"Unpaid",
"Partly Paid and Discounted",
"Unpaid and Discounted",
"Overdue and Discounted",
"Overdue",
@@ -220,6 +222,9 @@ class POSInvoice(SalesInvoice):
validate_coupon_code(self.coupon_code)
def before_submit(self):
self.set_outstanding_amount()
def on_submit(self):
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
if not self.is_return and self.loyalty_program:
@@ -243,7 +248,7 @@ class POSInvoice(SalesInvoice):
update_coupon_code_count(self.coupon_code, "used")
self.clear_unallocated_mode_of_payments()
if self.is_return and self.is_pos_using_sales_invoice:
if self.is_return and self.invoice_type_in_pos == "Sales Invoice":
self.create_and_add_consolidated_sales_invoice()
def before_cancel(self):
@@ -276,6 +281,8 @@ class POSInvoice(SalesInvoice):
against_psi_doc.delete_loyalty_point_entry()
against_psi_doc.make_loyalty_point_entry()
self.db_set("status", "Cancelled")
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
@@ -371,18 +378,6 @@ 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
@@ -422,10 +417,8 @@ class POSInvoice(SalesInvoice):
)
def validate_is_pos_using_sales_invoice(self):
self.is_pos_using_sales_invoice = frappe.db.get_single_value(
"Accounts Settings", "use_sales_invoice_in_pos"
)
if self.is_pos_using_sales_invoice and not self.is_return:
self.invoice_type_in_pos = frappe.db.get_single_value("POS Settings", "invoice_type")
if self.invoice_type_in_pos == "Sales Invoice" and not self.is_return:
frappe.throw(_("Sales Invoice mode is activated in POS. Please create Sales Invoice instead."))
def validate_serialised_or_batched_item(self):
@@ -537,6 +530,10 @@ class POSInvoice(SalesInvoice):
)
)
def set_outstanding_amount(self):
total = flt(self.rounded_total) or flt(self.grand_total)
self.outstanding_amount = total - flt(self.paid_amount) if total > flt(self.paid_amount) else 0
def validate_loyalty_transaction(self):
if self.redeem_loyalty_points and (
not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center
@@ -558,6 +555,8 @@ class POSInvoice(SalesInvoice):
self.status = "Draft"
return
total = flt(self.rounded_total) or flt(self.grand_total)
if not status:
if self.docstatus == 2:
status = "Cancelled"
@@ -573,6 +572,14 @@ class POSInvoice(SalesInvoice):
self.status = "Overdue and Discounted"
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()):
self.status = "Overdue"
elif (
0 < flt(self.outstanding_amount) < total
and self.is_discounted
and self.get_discounting_status() == "Disbursed"
):
self.status = "Partly Paid and Discounted"
elif 0 < flt(self.outstanding_amount) < total:
self.status = "Partly Paid"
elif (
flt(self.outstanding_amount) > 0
and getdate(self.due_date) >= getdate(nowdate())
@@ -793,6 +800,48 @@ class POSInvoice(SalesInvoice):
if pr:
return frappe.get_doc("Payment Request", pr)
@frappe.whitelist()
def update_payments(self, payments):
if self.status == "Consolidated":
frappe.throw(_("Create Payment Entry for Consolidated POS Invoices."))
paid_amount = flt(self.paid_amount)
total = flt(self.rounded_total) or flt(self.grand_total)
if paid_amount >= total:
frappe.throw(title=_("Invoice Paid"), msg=_("This invoice has already been paid."))
idx = self.payments[-1].idx if self.payments else -1
for d in payments:
idx += 1
payment = create_payments_on_invoice(self, idx, frappe._dict(d))
paid_amount += flt(payment.amount)
payment.submit()
paid_amount = flt(flt(paid_amount), self.precision("paid_amount"))
base_paid_amount = flt(flt(paid_amount * self.conversion_rate), self.precision("base_paid_amount"))
outstanding_amount = (
flt(flt(total - paid_amount), self.precision("outstanding_amount")) if total > paid_amount else 0
)
change_amount = (
flt(flt(paid_amount - total), self.precision("change_amount")) if paid_amount > total else 0
)
pi = frappe.qb.DocType("POS Invoice")
query = (
frappe.qb.update(pi)
.set(pi.paid_amount, paid_amount)
.set(pi.base_paid_amount, base_paid_amount)
.set(pi.outstanding_amount, outstanding_amount)
.set(pi.change_amount, change_amount)
.where(pi.name == self.name)
)
query.run()
self.reload()
self.set_status(update=True)
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):
@@ -944,3 +993,19 @@ def get_item_group(pos_profile):
item_groups.extend(get_descendants_of("Item Group", row.item_group))
return list(set(item_groups))
def create_payments_on_invoice(doc, idx, payment_details):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
payment = frappe.new_doc("Sales Invoice Payment")
payment.idx = idx
payment.mode_of_payment = payment_details.mode_of_payment
payment.amount = payment_details.amount
payment.base_amount = payment.amount * doc.conversion_rate
payment.parent = doc.name
payment.parentfield = "payments"
payment.parenttype = doc.doctype
payment.account = get_bank_cash_account(payment.mode_of_payment, doc.company).get("account")
return payment

View File

@@ -18,11 +18,13 @@ frappe.listview_settings["POS Invoice"] = {
Draft: "red",
Unpaid: "orange",
Paid: "green",
"Partly Paid": "yellow",
Submitted: "blue",
Consolidated: "green",
Return: "darkgrey",
"Unpaid and Discounted": "orange",
"Overdue and Discounted": "red",
"Partly Paid and Discounted": "yellow",
Overdue: "red",
};
return [__(doc.status), status_color[doc.status], "status,=," + doc.status];

View File

@@ -29,6 +29,7 @@ class TestPOSInvoice(IntegrationTestCase):
def setUpClass(cls):
super().setUpClass()
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice"))
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`")
@@ -36,10 +37,16 @@ class TestPOSInvoice(IntegrationTestCase):
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)
cls.opening_entry = create_opening_entry(cls.pos_profile, cls.test_user.name)
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
@classmethod
def tearDownClass(cls):
frappe.db.sql("delete from `tabPOS Invoice`")
opening_entry_doc = frappe.get_doc("POS Opening Entry", cls.opening_entry.name)
opening_entry_doc.cancel()
def tearDown(self):
if frappe.session.user != "Administrator":
frappe.set_user("Administrator")
@@ -394,6 +401,50 @@ class TestPOSInvoice(IntegrationTestCase):
pos_inv.insert()
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
def test_partly_paid_invoices(self):
set_allow_partial_payment(self.pos_profile, 1)
pos_inv = create_pos_invoice(pos_profile=self.pos_profile.name, rate=100, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "amount": 90},
)
pos_inv.save()
pos_inv.submit()
self.assertEqual(pos_inv.paid_amount, 90)
self.assertEqual(pos_inv.status, "Partly Paid")
pos_inv.update_payments(payments=[{"mode_of_payment": "Cash", "amount": 10}])
self.assertEqual(pos_inv.paid_amount, 100)
self.assertEqual(pos_inv.status, "Paid")
set_allow_partial_payment(self.pos_profile, 0)
def test_multi_payment_for_partly_paid_invoices(self):
set_allow_partial_payment(self.pos_profile, 1)
pos_inv = create_pos_invoice(pos_profile=self.pos_profile.name, rate=100, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "amount": 90},
)
pos_inv.save()
pos_inv.submit()
self.assertEqual(pos_inv.paid_amount, 90)
self.assertEqual(pos_inv.status, "Partly Paid")
pos_inv.update_payments(payments=[{"mode_of_payment": "Cash", "amount": 5}])
self.assertEqual(pos_inv.paid_amount, 95)
self.assertEqual(pos_inv.status, "Partly Paid")
pos_inv.update_payments(payments=[{"mode_of_payment": "Cash", "amount": 5}])
self.assertEqual(pos_inv.paid_amount, 100)
self.assertEqual(pos_inv.status, "Paid")
set_allow_partial_payment(self.pos_profile, 0)
def test_serialized_item_transaction(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
@@ -1082,8 +1133,7 @@ def create_pos_invoice(**args):
return pos_inv
def make_batch_item(item_name):
from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists(item_name):
return make_item(item_name, dict(has_batch_no=1, create_new_batch=1, is_stock_item=1))
def set_allow_partial_payment(pos_profile, value):
pos_profile.reload()
pos_profile.allow_partial_payment = value
pos_profile.save()

View File

@@ -5,6 +5,7 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"posting_date",
"posting_time",
"merge_invoices_based_on",
@@ -113,12 +114,22 @@
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Company",
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:15.620564",
"modified": "2025-07-02 17:08:04.747202",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",
@@ -179,8 +190,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -29,11 +29,10 @@ class POSInvoiceMergeLog(Document):
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import (
POSInvoiceReference,
)
from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import POSInvoiceReference
amended_from: DF.Link | None
company: DF.Link
consolidated_credit_note: DF.Link | None
consolidated_invoice: DF.Link | None
customer: DF.Link
@@ -259,6 +258,7 @@ class POSInvoiceMergeLog(Document):
if not found:
tax.charge_type = "Actual"
tax.idx = idx
tax.row_id = None
idx += 1
tax.included_in_print_rate = 0
tax.tax_amount = tax.tax_amount_after_discount_amount
@@ -338,6 +338,11 @@ class POSInvoiceMergeLog(Document):
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ""
# Unset Commission Section
invoice.set("sales_partner", None)
invoice.set("commission_rate", 0)
invoice.set("total_commission", 0)
return invoice
def get_new_sales_invoice(self):
@@ -491,8 +496,8 @@ def split_invoices_by_accounting_dimension(pos_invoices):
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_transactions"))
if frappe.flags.in_test and not invoices:
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_invoices"))
if frappe.in_test and not invoices:
invoices = get_all_unconsolidated_invoices()
invoice_by_customer = get_invoice_customer_map(invoices)
@@ -509,7 +514,7 @@ def unconsolidate_pos_invoices(closing_entry):
"POS Invoice Merge Log", filters={"pos_closing_entry": closing_entry.name}, pluck="name"
)
if len(closing_entry.pos_transactions) >= 10:
if len(closing_entry.pos_invoices) >= 10:
closing_entry.set_status(update=True, status="Queued")
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
else:
@@ -583,6 +588,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
merge_log.posting_time = (
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
)
merge_log.company = closing_entry.get("company") if closing_entry else None
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
merge_log.set("pos_invoices", _invoices)
@@ -654,7 +660,7 @@ def enqueue_job(job, **kwargs):
timeout=10000,
event="processing_merge_logs",
job_id=job_id,
now=frappe.conf.developer_mode or frappe.flags.in_test,
now=frappe.conf.developer_mode or frappe.in_test,
)
if job == create_merge_logs:
@@ -666,7 +672,7 @@ def enqueue_job(job, **kwargs):
def check_scheduler_status():
if is_scheduler_inactive() and not frappe.flags.in_test:
if is_scheduler_inactive() and not frappe.in_test:
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))

View File

@@ -3,268 +3,332 @@
import json
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
)
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
get_serial_nos_from_bundle,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class UnitTestPosInvoiceMergeLog(UnitTestCase):
"""
Unit tests for PosInvoiceMergeLog.
Use this class for testing individual functions and methods.
"""
pass
class TestPOSInvoiceMergeLog(IntegrationTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
frappe.db.sql("delete from `tabPOS Opening Entry`")
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice"))
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
def setUp(self):
frappe.db.sql("delete from `tabPOS Invoice`")
def tearDown(self):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidated_invoice_creation(self):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
try:
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.save()
pos_inv2.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.save()
pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv3.save()
pos_inv3.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv3.save()
pos_inv3.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
def test_consolidated_credit_note_creation(self):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
try:
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.save()
pos_inv2.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.save()
pos_inv2.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv3.save()
pos_inv3.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv3.save()
pos_inv3.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
pos_inv_cn.set("payments", [])
pos_inv_cn.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100})
pos_inv_cn.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
)
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
pos_inv_cn.set("payments", [])
pos_inv_cn.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100}
)
pos_inv_cn.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
)
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
pos_inv_cn.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice)
self.assertEqual(consolidated_credit_note.is_return, 1)
self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash")
self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
pos_inv_cn.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice)
self.assertEqual(consolidated_credit_note.is_return, 1)
self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash")
self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
def test_consolidated_invoice_item_taxes(self):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
try:
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv.append(
"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",
"rate": 9,
},
)
inv.insert()
inv.payments[0].amount = inv.grand_total
inv.save()
inv.submit()
inv.append(
"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",
"rate": 9,
},
)
inv.insert()
inv.payments[0].amount = inv.grand_total
inv.save()
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv2.get("items")[0].item_code = "_Test Item 2"
inv2.append(
"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",
"rate": 5,
},
)
inv2.insert()
inv2.payments[0].amount = inv.grand_total
inv2.save()
inv2.submit()
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv2.get("items")[0].item_code = "_Test Item 2"
inv2.append(
"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",
"rate": 5,
},
)
inv2.insert()
inv2.payments[0].amount = inv.grand_total
inv2.save()
inv2.submit()
consolidate_pos_invoices()
inv.load_from_db()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
expected_item_wise_tax_detail = {
"_Test Item": {
"tax_rate": 9,
"tax_amount": 9,
"net_amount": 100,
},
"_Test Item 2": {
"tax_rate": 5,
"tax_amount": 5,
"net_amount": 100,
},
}
self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
expected_item_wise_tax_detail = {
"_Test Item": {
"tax_rate": 9,
"tax_amount": 9,
"net_amount": 100,
},
"_Test Item 2": {
"tax_rate": 5,
"tax_amount": 5,
"net_amount": 100,
},
}
self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail)
def test_consolidation_round_off_error_1(self):
"""
Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
"""
frappe.db.sql("delete from `tabPOS Invoice`")
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
init_user_and_profile()
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
inv.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv.insert()
inv.submit()
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
inv.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
inv2.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv2.insert()
inv2.submit()
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
inv2.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv2.insert()
inv2.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
self.assertEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
self.assertEqual(consolidated_invoice.status, "Paid")
def test_consolidation_round_off_error_2(self):
"""
Test the same case as above but with an Unpaid POS Invoice
"""
frappe.db.sql("delete from `tabPOS Invoice`")
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
init_user_and_profile()
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
inv.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv.insert()
inv.submit()
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
inv2.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv2.insert()
inv2.submit()
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
inv3.insert()
inv3.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
self.assertEqual(consolidated_invoice.status, "Paid")
@IntegrationTestCase.change_settings(
"System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
)
def test_consolidation_round_off_error_3(self):
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
item_rates = [69, 59, 29]
for _i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
inv.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": -1,
"rate": rate,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
inv.append(
"taxes",
{
@@ -273,146 +337,56 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 7.5,
"rate": 15,
"included_in_print_rate": 1,
},
)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv.insert()
inv.payments = []
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
inv.paid_amount = -157
inv.save()
inv.submit()
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
inv2.append(
"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",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv2.insert()
inv2.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
inv3.insert()
inv3.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
self.assertEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
@IntegrationTestCase.change_settings(
"System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
)
def test_consolidation_round_off_error_3(self):
frappe.db.sql("delete from `tabPOS Invoice`")
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile()
item_rates = [69, 59, 29]
for _i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
inv.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": -1,
"rate": rate,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
inv.append(
"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",
"rate": 15,
"included_in_print_rate": 1,
},
)
inv.payments = []
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
inv.paid_amount = -157
inv.save()
inv.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.status, "Return")
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.status, "Return")
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
def test_consolidation_rounding_adjustment(self):
"""
Test if the rounding adjustment is calculated correctly
"""
frappe.db.sql("delete from `tabPOS Invoice`")
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
init_user_and_profile()
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70})
inv.insert()
inv.submit()
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60})
inv2.insert()
inv2.submit()
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60})
inv2.insert()
inv2.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
def test_serial_no_case_1(self):
"""
@@ -427,51 +401,46 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
frappe.db.sql("delete from `tabPOS Invoice`")
se = make_serialized_item(self)
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
try:
se = make_serialized_item(self)
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
init_user_and_profile()
pos_inv = create_pos_invoice(
item_code="_Test Serialized Item With Series",
serial_no=[serial_no],
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice(
item_code="_Test Serialized Item With Series",
serial_no=[serial_no],
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv.save()
pos_inv.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
pos_inv_cn.paid_amount = -100
pos_inv_cn.submit()
pos_inv_cn = make_sales_return(pos_inv.name)
pos_inv_cn.paid_amount = -100
pos_inv_cn.submit()
pos_inv2 = create_pos_invoice(
item_code="_Test Serialized Item With Series",
serial_no=[serial_no],
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv2.save()
pos_inv2.submit()
pos_inv2 = create_pos_invoice(
item_code="_Test Serialized Item With Series",
serial_no=[serial_no],
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv2.save()
pos_inv2.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
consolidate_pos_invoices()
pos_inv.load_from_db()
pos_inv2.load_from_db()
pos_inv.load_from_db()
pos_inv2.load_from_db()
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
def test_separate_consolidated_invoice_for_different_accounting_dimensions(self):
"""
@@ -482,48 +451,66 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
"""
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
frappe.db.sql("delete from `tabPOS Invoice`")
create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0)
create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0)
try:
test_user, pos_profile = init_user_and_profile()
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.cost_center = "_Test POS Cost Center 1 - _TC"
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.cost_center = "_Test POS Cost Center 1 - _TC"
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv2.save()
pos_inv2.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv2.save()
pos_inv2.submit()
pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv3.save()
pos_inv3.submit()
pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv3.save()
pos_inv3.submit()
consolidate_pos_invoices()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv2.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice))
pos_inv2.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice))
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)
self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_company_in_pos_invoice_merge_log(self):
"""
Test if the company is fetched from POS Closing Entry
"""
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
self.assertTrue(frappe.db.exists("POS Invoice Merge Log", {"pos_closing_entry": closing_entry.name}))
pos_merge_log_company = frappe.db.get_value(
"POS Invoice Merge Log", {"pos_closing_entry": closing_entry.name}, "company"
)
self.assertEqual(pos_merge_log_company, closing_entry.company)

View File

@@ -37,6 +37,8 @@ class POSOpeningEntry(StatusUpdater):
def validate(self):
self.validate_pos_profile_and_cashier()
self.check_open_pos_exists()
self.check_user_already_assigned()
self.validate_payment_method_account()
self.set_status()
@@ -49,6 +51,22 @@ class POSOpeningEntry(StatusUpdater):
if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
def check_open_pos_exists(self):
if frappe.db.exists("POS Opening Entry", {"pos_profile": self.pos_profile, "status": "Open"}):
frappe.throw(
title=_("POS Opening Entry Exists"),
msg=_(
"{0} is open. Close the POS or cancel the existing POS Opening Entry to create a new POS Opening Entry."
).format(frappe.bold(self.pos_profile)),
)
def check_user_already_assigned(self):
if frappe.db.exists("POS Opening Entry", {"user": self.user, "status": "Open"}):
frappe.throw(
title=_("Cannot Assign Cashier"),
msg=_("Cashier is currently assigned to another POS."),
)
def validate_payment_method_account(self):
invalid_modes = []
for d in self.balance_details:
@@ -71,5 +89,25 @@ class POSOpeningEntry(StatusUpdater):
def on_submit(self):
self.set_status(update=True)
def before_cancel(self):
self.check_poe_is_cancellable()
def on_cancel(self):
self.set_status(update=True)
frappe.publish_realtime(
f"poe_{self.name}",
message={"operation": "Cancelled"},
docname=f"POS Opening Entry/{self.name}",
)
def check_poe_is_cancellable(self):
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import get_invoices
invoices = get_invoices(
self.period_start_date, frappe.utils.get_datetime(), self.pos_profile, self.user
)
if invoices.get("invoices"):
frappe.throw(
title=_("POS Opening Entry Cancellation Error"),
msg=_("POS Opening Entry cannot be cancelled as unconsolidated Invoices exists."),
)

View File

@@ -3,14 +3,107 @@
import unittest
import frappe
from frappe.core.doctype.user_permission.test_user_permission import create_user
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestPOSOpeningEntry(IntegrationTestCase):
pass
@classmethod
def setUpClass(cls):
frappe.db.sql("delete from `tabPOS Opening Entry`")
cls.enterClassContext(cls.change_settings("POS Settings", {"invoice_type": "POS Invoice"}))
@classmethod
def tearDownClass(cls):
frappe.db.sql("delete from `tabPOS Opening Entry`")
def setUp(self):
# Make stock available for POS Sales
frappe.db.sql("delete from `tabPOS Opening Entry`")
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
self.init_user_and_profile = init_user_and_profile
def tearDown(self):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
def test_pos_opening_entry(self):
test_user, pos_profile = self.init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
self.assertEqual(opening_entry.status, "Open")
self.assertNotEqual(opening_entry.docstatus, 0)
def test_multiple_pos_opening_entries_for_same_pos_profile(self):
test_user, pos_profile = self.init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
self.assertEqual(opening_entry.status, "Open")
with self.assertRaises(frappe.ValidationError):
create_opening_entry(pos_profile, test_user.name)
def test_multiple_pos_opening_entry_for_multiple_pos_profiles(self):
test_user, pos_profile = self.init_user_and_profile()
opening_entry_1 = create_opening_entry(pos_profile, test_user.name)
self.assertEqual(opening_entry_1.status, "Open")
self.assertEqual(opening_entry_1.user, test_user.name)
cashier_user = create_user("test_cashier@example.com", "Accounts Manager", "Sales Manager")
frappe.set_user(cashier_user.name)
pos_profile2 = make_pos_profile(name="_Test POS Profile 2")
opening_entry_2 = create_opening_entry(pos_profile2, cashier_user.name)
self.assertEqual(opening_entry_2.status, "Open")
self.assertEqual(opening_entry_2.user, cashier_user.name)
def test_multiple_pos_opening_entry_for_same_pos_profile_by_multiple_user(self):
test_user, pos_profile = self.init_user_and_profile()
cashier_user = create_user("test_cashier@example.com", "Accounts Manager", "Sales Manager")
opening_entry = create_opening_entry(pos_profile, test_user.name)
self.assertEqual(opening_entry.status, "Open")
with self.assertRaises(frappe.ValidationError):
create_opening_entry(pos_profile, cashier_user.name)
def test_user_assignment_to_multiple_pos_profile(self):
test_user, pos_profile = self.init_user_and_profile()
opening_entry_1 = create_opening_entry(pos_profile, test_user.name)
self.assertEqual(opening_entry_1.user, test_user.name)
pos_profile2 = make_pos_profile(name="_Test POS Profile 2")
with self.assertRaises(frappe.ValidationError):
create_opening_entry(pos_profile2, test_user.name)
def test_cancel_pos_opening_entry_without_invoices(self):
test_user, pos_profile = self.init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name, get_obj=True)
opening_entry.cancel()
self.assertEqual(opening_entry.status, "Cancelled")
self.assertNotEqual(opening_entry.docstatus, 1)
def test_cancel_pos_opening_entry_with_invoice(self):
test_user, pos_profile = self.init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name, get_obj=True)
pos_inv1 = create_pos_invoice(pos_profile=pos_profile.name, rate=100, do_not_save=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv1.save()
pos_inv1.submit()
self.assertRaises(frappe.ValidationError, opening_entry.cancel)
def create_opening_entry(pos_profile, user):
def create_opening_entry(pos_profile, user, get_obj=False):
entry = frappe.new_doc("POS Opening Entry")
entry.pos_profile = pos_profile.name
entry.user = user
@@ -24,4 +117,7 @@ def create_opening_entry(pos_profile, user):
entry.set("balance_details", balance_details)
entry.submit()
if get_obj:
return entry
return entry.as_dict()

View File

@@ -26,12 +26,14 @@
"auto_add_item_to_cart",
"validate_stock_on_save",
"print_receipt_on_order_complete",
"action_on_new_invoice",
"column_break_16",
"update_stock",
"ignore_pricing_rule",
"allow_rate_change",
"allow_discount_change",
"set_grand_total_to_default_mop",
"allow_partial_payment",
"section_break_23",
"item_groups",
"column_break_25",
@@ -415,6 +417,19 @@
"fieldname": "set_grand_total_to_default_mop",
"fieldtype": "Check",
"label": "Set Grand Total to Default Payment Method"
},
{
"default": "Always Ask",
"fieldname": "action_on_new_invoice",
"fieldtype": "Select",
"label": "Action on New Invoice",
"options": "Always Ask\nSave Changes and Load New Invoice\nDiscard Changes and Load New Invoice"
},
{
"default": "0",
"fieldname": "allow_partial_payment",
"fieldtype": "Check",
"label": "Allow Partial Payment"
}
],
"grid_page_length": 50,
@@ -443,7 +458,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2025-05-09 11:23:28.632136",
"modified": "2025-06-24 11:19:19.834905",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@@ -28,7 +28,11 @@ class POSProfile(Document):
from erpnext.accounts.doctype.pos_profile_user.pos_profile_user import POSProfileUser
account_for_change_amount: DF.Link | None
action_on_new_invoice: DF.Literal[
"Always Ask", "Save Changes and Load New Invoice", "Discard Changes and Load New Invoice"
]
allow_discount_change: DF.Check
allow_partial_payment: DF.Check
allow_rate_change: DF.Check
applicable_for_users: DF.Table[POSProfileUser]
apply_discount_on: DF.Literal["Grand Total", "Net Total"]

View File

@@ -5,6 +5,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"invoice_type",
"section_break_gyos",
"invoice_fields",
"pos_search_fields"
],
@@ -12,7 +14,7 @@
{
"fieldname": "invoice_fields",
"fieldtype": "Table",
"label": "POS Field",
"label": "POS Additional Fields",
"options": "POS Field"
},
{
@@ -20,11 +22,23 @@
"fieldtype": "Table",
"label": "POS Search Fields",
"options": "POS Search Fields"
},
{
"default": "Sales Invoice",
"description": "The system will create a Sales Invoice or a POS Invoice from the POS interface based on this setting. For high-volume transactions, it is recommended to use POS Invoice.",
"fieldname": "invoice_type",
"fieldtype": "Select",
"label": "Invoice Type Created via POS Screen",
"options": "Sales Invoice\nPOS Invoice"
},
{
"fieldname": "section_break_gyos",
"fieldtype": "Section Break"
}
],
"issingle": 1,
"links": [],
"modified": "2024-03-27 13:10:17.083132",
"modified": "2025-06-06 11:36:44.885353",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Settings",
@@ -56,8 +70,9 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -21,10 +21,16 @@ class POSSettings(Document):
from erpnext.accounts.doctype.pos_search_fields.pos_search_fields import POSSearchFields
invoice_fields: DF.Table[POSField]
invoice_type: DF.Literal["Sales Invoice", "POS Invoice"]
pos_search_fields: DF.Table[POSSearchFields]
# end: auto-generated types
def validate(self):
old_doc = self.get_doc_before_save()
if old_doc.invoice_type != self.invoice_type:
self.validate_invoice_type()
self.validate_invoice_fields()
def validate_invoice_fields(self):
@@ -36,3 +42,15 @@ class POSSettings(Document):
frappe.throw(
title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field)
)
def validate_invoice_type(self):
pos_opening_entries_count = frappe.db.count(
"POS Opening Entry", filters={"docstatus": 1, "status": "Open"}
)
if pos_opening_entries_count:
frappe.throw(
_("{0} cannot be changed with opened Opening Entries.").format(
frappe.bold(_("Invoice Type"))
),
title=_("Invoice Document Type Selection Error"),
)

View File

@@ -169,7 +169,7 @@ class PricingRule(Document):
tocheck = frappe.scrub(self.get("applicable_for", ""))
if tocheck and not self.get(tocheck):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
throw(_("{0} is required").format(_(self.meta.get_label(tocheck))), frappe.MandatoryError)
if self.apply_rule_on_other:
o_field = "other_" + frappe.scrub(self.apply_rule_on_other)

View File

@@ -5,7 +5,7 @@
import unittest
import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -15,15 +15,6 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.get_item_details import get_item_details
class UnitTestPricingRule(UnitTestCase):
"""
Unit tests for PricingRule.
Use this class for testing individual functions and methods.
"""
pass
class TestPricingRule(IntegrationTestCase):
def setUp(self):
delete_existing_pricing_rules()
@@ -215,6 +206,56 @@ class TestPricingRule(IntegrationTestCase):
details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 10)
def test_unset_group_condition(self):
"""
If args are not set for group condition, then pricing rule should not be applied.
"""
from erpnext.stock.get_item_details import get_item_details
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"items": [{"item_code": "_Test Item"}],
"currency": "USD",
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
"discount_percentage": 10,
"applicable_for": "Territory",
"territory": "All Territories",
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
args = frappe._dict(
{
"item_code": "_Test Item",
"company": "_Test Company",
"price_list": "_Test Price List",
"currency": "_Test Currency",
"doctype": "Sales Order",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
"plc_conversion_rate": 1,
"order_type": "Sales",
"customer": "_Test Customer",
"name": None,
}
)
# without territory in customer
customer = frappe.get_doc("Customer", "_Test Customer")
territory = customer.territory
customer.territory = None
customer.save()
details = get_item_details(args)
self.assertEqual(details.get("discount_percentage"), 0)
customer.territory = territory
customer.save()
def test_pricing_rule_for_variants(self):
from erpnext.stock.get_item_details import get_item_details

View File

@@ -28,7 +28,7 @@ def get_pricing_rules(args, doc=None):
pricing_rules = []
values = {}
if not frappe.db.exists("Pricing Rule", {"disable": 0, args.transaction_type: 1}):
if not frappe.db.count("Pricing Rule", cache=True):
return
for apply_on in ["Item Code", "Item Group", "Brand"]:
@@ -115,8 +115,8 @@ def _get_pricing_rules(apply_on, args, values):
if apply_on_field == "item_code":
if args.get("uom", None):
item_conditions += (
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=args.get("uom")
" and ({child_doc}.uom={item_uom} or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=frappe.db.escape(args.get("uom"))
)
)
if "variant_of" not in args:
@@ -128,8 +128,8 @@ def _get_pricing_rules(apply_on, args, values):
elif apply_on_field == "item_group":
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
if args.get("uom", None):
item_conditions += " and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=args.get("uom")
item_conditions += " and ({child_doc}.uom={item_uom} or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=frappe.db.escape(args.get("uom"))
)
conditions += get_other_conditions(conditions, values, args)
@@ -223,6 +223,10 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
)
frappe.flags.tree_conditions[key] = condition
elif allow_blank:
condition = f"ifnull({table}.{field}, '') = ''"
return condition

View File

@@ -40,6 +40,13 @@ class TestProcessDeferredAccounting(IntegrationTestCase):
si.save()
si.submit()
original_gle = [
["Debtors - _TC", 3000.0, 0, "2023-07-01"],
[deferred_account, 0.0, 3000, "2023-07-01"],
]
check_gl_entries(self, si.name, original_gle, "2023-07-01")
process_deferred_accounting = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
@@ -63,6 +70,12 @@ class TestProcessDeferredAccounting(IntegrationTestCase):
]
check_gl_entries(self, si.name, expected_gle, "2023-07-01")
# cancel the process deferred accounting document
process_deferred_accounting.cancel()
# check if gl entries are cancelled
check_gl_entries(self, si.name, original_gle, "2023-07-01")
change_acc_settings()
def test_pda_submission_and_cancellation(self):

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