Compare commits

...

532 Commits

Author SHA1 Message Date
Ankush Menat
1475849295 ci: Rebuild mysqlclient 2025-06-26 16:25:07 +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
khushi8112
c13df330f3 fix: add missing cancellation handling for salvage value update 2025-06-24 18:31:54 +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
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
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
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
421 changed files with 317429 additions and 238266 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

@@ -4,10 +4,14 @@ set -e
cd ~ || exit
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash
sudo apt update
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client libmariadb-dev
pip cache remove mysqlclient
pip install frappe-bench
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
@@ -66,7 +70,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",

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,21 @@ 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");
},
});
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",
@@ -86,7 +93,9 @@
"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,13 +551,6 @@
"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",
@@ -565,6 +567,70 @@
"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"
}
],
"grid_page_length": 50,
@@ -573,7 +639,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,6 +53,8 @@ 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
@@ -58,6 +63,7 @@ class AccountsSettings(Document):
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,12 @@ 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"),
)

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

@@ -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(),
)
@@ -127,6 +140,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 +153,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 +176,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 +297,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 +330,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 +380,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 +492,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 +517,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,15 @@ 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),
)
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",
@@ -89,7 +95,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
},
@@ -543,6 +549,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 +599,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-12-26 15:32:20.730666",
"modified": "2025-06-17 15:18:13.322681",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
@@ -602,10 +644,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

@@ -62,6 +62,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"]
@@ -73,11 +74,13 @@ class JournalEntry(AccountsController):
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",
@@ -148,8 +152,7 @@ class JournalEntry(AccountsController):
if self.docstatus == 0:
self.apply_tax_withholding()
if not self.title:
self.title = self.get_title()
self.title = self.get_title()
def validate_advance_accounts(self):
journal_accounts = set([x.account for x in self.accounts])
@@ -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.
@@ -280,6 +353,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 +1248,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 +1373,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 +1409,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}

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,7 +46,6 @@ from erpnext.accounts.party import (
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
get_balance_on,
get_outstanding_invoices,
)
from erpnext.controllers.accounts_controller import (
@@ -499,7 +498,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):
@@ -662,7 +661,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)):
@@ -1786,7 +1785,7 @@ class PaymentEntry(AccountsController):
)
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 +2785,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 +2796,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 +2813,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 +2865,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 +2963,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 +3103,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 +3305,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 +3462,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,

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

@@ -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,28 @@ 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)

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

@@ -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,12 @@
"label": "User Details"
},
{
"fetch_if_empty": 1,
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
},
{
@@ -85,11 +88,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
},
{
@@ -100,16 +105,6 @@
"options": "User",
"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 +117,6 @@
"options": "POS Closing Entry Detail"
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.docstatus==0",
"fieldname": "section_break_13",
"fieldtype": "Section Break",
@@ -177,17 +171,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 +219,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 +259,7 @@
"link_fieldname": "pos_closing_entry"
}
],
"modified": "2025-03-19 19:49:58.845697",
"modified": "2025-06-06 12:00:31.955176",
"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,14 +206,6 @@ 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(
@@ -227,7 +232,7 @@ 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
)
@@ -241,50 +246,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 +334,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

@@ -243,7 +243,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 +276,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
@@ -422,10 +424,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):

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")

View File

@@ -259,6 +259,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
@@ -491,8 +492,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 +510,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:
@@ -654,7 +655,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 +667,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,43 @@ 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)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)

View File

@@ -32,6 +32,7 @@
"allow_rate_change",
"allow_discount_change",
"set_grand_total_to_default_mop",
"action_on_new_invoice",
"section_break_23",
"item_groups",
"column_break_25",
@@ -415,6 +416,13 @@
"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"
}
],
"grid_page_length": 50,
@@ -443,7 +451,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2025-05-09 11:23:28.632136",
"modified": "2025-05-23 12:12:32.247652",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@@ -28,6 +28,9 @@ 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_rate_change: DF.Check
applicable_for_users: DF.Table[POSProfileUser]

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

@@ -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()

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)

View File

@@ -142,7 +142,7 @@ def trigger_job_for_doc(docname: str | None = None):
if not docname:
return
if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
if not frappe.get_single_value("Accounts Settings", "auto_reconcile_payments"):
frappe.throw(
_("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format(
get_link_to_form("Accounts Settings", "Accounts Settings")
@@ -190,7 +190,7 @@ def trigger_reconciliation_for_queued_docs():
Will be called from Cron Job
Fetch queued docs and start reconciliation process for each one
"""
if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
if not frappe.get_single_value("Accounts Settings", "auto_reconcile_payments"):
frappe.msgprint(
_("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format(
get_link_to_form("Accounts Settings", "Accounts Settings")
@@ -210,7 +210,7 @@ def trigger_reconciliation_for_queued_docs():
docs_to_trigger = []
unique_filters = set()
queue_size = frappe.db.get_single_value("Accounts Settings", "reconciliation_queue_size") or 5
queue_size = frappe.get_single_value("Accounts Settings", "reconciliation_queue_size") or 5
fields = ["company", "party_type", "party", "receivable_payable_account", "default_advance_account"]

View File

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

View File

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

View File

@@ -60,6 +60,20 @@ frappe.ui.form.on("Process Statement Of Accounts", {
},
};
});
frm.set_query("cost_center", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
frm.set_query("project", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
if (frm.doc.__islocal) {
frm.set_value("from_date", frappe.datetime.add_months(frappe.datetime.get_today(), -1));
frm.set_value("to_date", frappe.datetime.get_today());

View File

@@ -129,8 +129,8 @@ def get_statement_dict(doc, get_statement_dict=False):
tax_id = frappe.get_doc("Customer", entry.customer).tax_id
presentation_currency = (
get_party_account_currency("Customer", entry.customer, doc.company)
or doc.currency
doc.currency
or get_party_account_currency("Customer", entry.customer, doc.company)
or get_company_currency(doc.company)
)

View File

@@ -212,7 +212,7 @@
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
{% if(data[i]["party"]) %}
<td>{{ (data[i]["posting_date"]) }}</td>
<td>{{ frappe.format(data[i]["posting_date"], 'Date') }}</td>
<td style="text-align: right">{{ data[i]["age"] }}</td>
<td>
{% if not(filters.show_future_payments) %}

View File

@@ -102,6 +102,7 @@ def create_process_soa(**args):
company=args.company or "_Test Company",
customers=args.customers or [{"customer": "_Test Customer"}],
enable_auto_email=1 if args.enable_auto_email else 0,
currency=args.currency or "",
frequency=args.frequency or "Weekly",
report=args.report or "General Ledger",
from_date=args.from_date or getdate(today()),

View File

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

View File

@@ -437,6 +437,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
this.frm.set_value("is_paid", 0);
frappe.msgprint(__("Please specify Company to proceed"));
}
} else {
this.frm.set_value("paid_amount", 0);
}
this.calculate_outstanding_amount();
this.frm.refresh_fields();

View File

@@ -40,7 +40,6 @@ from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
get_item_account_wise_additional_cost,
update_billed_amount_based_on_po,
)
@@ -260,6 +259,7 @@ class PurchaseInvoice(BuyingController):
self.is_opening = "No"
self.validate_posting_time()
self.validate_posting_date_with_po()
super().validate()
@@ -749,7 +749,7 @@ class PurchaseInvoice(BuyingController):
self.update_status_updater_args()
self.update_prevdoc_status()
frappe.get_doc("Authorization Control").validate_approving_authority(
frappe.get_cached_doc("Authorization Control").validate_approving_authority(
self.doctype, self.company, self.base_grand_total
)
@@ -940,7 +940,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
landed_cost_entries = self.get_item_account_wise_lcv_entries()
voucher_wise_stock_value = {}
if self.update_stock:
@@ -1718,7 +1718,7 @@ class PurchaseInvoice(BuyingController):
res = frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
current_purchase_cost = res and res[0][0] or 0
# frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
project_doc = frappe.get_doc("Project", proj)
project_doc = frappe.get_lazy_doc("Project", proj)
project_doc.total_purchase_cost = current_purchase_cost + value
project_doc.calculate_gross_margin()
project_doc.db_update()
@@ -1729,7 +1729,7 @@ class PurchaseInvoice(BuyingController):
frappe.throw(_("Supplier Invoice Date cannot be greater than Posting Date"))
if self.bill_no:
if cint(frappe.db.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
if cint(frappe.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
fiscal_year = get_fiscal_year(self.posting_date, company=self.company, as_dict=True)
pi = frappe.db.sql(
@@ -1790,7 +1790,7 @@ class PurchaseInvoice(BuyingController):
for pr in set(updated_pr):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
pr_doc = frappe.get_doc("Purchase Receipt", pr)
pr_doc = frappe.get_lazy_doc("Purchase Receipt", pr)
update_billing_percentage(
pr_doc, update_modified=update_modified, adjust_incoming_rate=adjust_incoming_rate
)
@@ -2046,21 +2046,21 @@ def make_stock_entry(source_name, target_doc=None):
@frappe.whitelist()
def change_release_date(name, release_date=None):
if frappe.db.exists("Purchase Invoice", name):
pi = frappe.get_doc("Purchase Invoice", name)
pi = frappe.get_lazy_doc("Purchase Invoice", name)
pi.db_set("release_date", release_date)
@frappe.whitelist()
def unblock_invoice(name):
if frappe.db.exists("Purchase Invoice", name):
pi = frappe.get_doc("Purchase Invoice", name)
pi = frappe.get_lazy_doc("Purchase Invoice", name)
pi.unblock_invoice()
@frappe.whitelist()
def block_invoice(name, release_date, hold_comment=None):
if frappe.db.exists("Purchase Invoice", name):
pi = frappe.get_doc("Purchase Invoice", name)
pi = frappe.get_lazy_doc("Purchase Invoice", name)
pi.block_invoice(hold_comment, release_date)

View File

@@ -1660,7 +1660,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.posting_date = add_days(pr.posting_date, 1)
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
@@ -1669,30 +1669,38 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
# Check GLE for Purchase Invoice
expected_gle = [
["Cost of Goods Sold - _TC", 250, 0, add_days(pr.posting_date, -1)],
["Creditors - _TC", 0, 250, add_days(pr.posting_date, -1)],
["Cost of Goods Sold - _TC", 250, 0, add_days(pr.posting_date, 1)],
["Creditors - _TC", 0, 250, add_days(pr.posting_date, 1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 250, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 250, pr.posting_date],
["Provision Account - _TC", 0, 250, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 250, 0, pr.posting_date],
["Provision Account - _TC", 0, 250, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 250, pi.posting_date],
["Provision Account - _TC", 250, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date, voucher_type="Purchase Receipt"
)
# Cancel purchase invoice to check reverse provisional entry cancellation
pi.cancel()
expected_gle_for_purchase_receipt_post_pi_cancel = [
["Provision Account - _TC", 0, 250, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date],
["Provision Account - _TC", 0, 250, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date)
check_gl_entries(
self,
pr.name,
expected_gle_for_purchase_receipt_post_pi_cancel,
pi.posting_date,
voucher_type="Purchase Receipt",
)
toggle_provisional_accounting_setting()
@@ -1713,7 +1721,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
# Overbill PR: rate = 2000, qty = 10
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.posting_date = add_days(pr.posting_date, 1)
pi.items[0].qty = 10
pi.items[0].rate = 2000
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
@@ -1721,30 +1729,38 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
pi.submit()
expected_gle = [
["Cost of Goods Sold - _TC", 20000, 0, add_days(pr.posting_date, -1)],
["Creditors - _TC", 0, 20000, add_days(pr.posting_date, -1)],
["Cost of Goods Sold - _TC", 20000, 0, add_days(pr.posting_date, 1)],
["Creditors - _TC", 0, 20000, add_days(pr.posting_date, 1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 5000, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
["Provision Account - _TC", 0, 5000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pr.posting_date],
["Provision Account - _TC", 0, 5000, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pi.posting_date],
["Provision Account - _TC", 5000, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date, voucher_type="Purchase Receipt"
)
# Cancel purchase invoice to check reverse provisional entry cancellation
pi.cancel()
expected_gle_for_purchase_receipt_post_pi_cancel = [
["Provision Account - _TC", 0, 5000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
["Provision Account - _TC", 0, 5000, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date)
check_gl_entries(
self,
pr.name,
expected_gle_for_purchase_receipt_post_pi_cancel,
pi.posting_date,
voucher_type="Purchase Receipt",
)
toggle_provisional_accounting_setting()
@@ -1777,13 +1793,76 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 5000, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
["Provision Account - _TC", 0, 1000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 1000, 0, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pr.posting_date],
["Provision Account - _TC", 0, 5000, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 1000, pi.posting_date],
["Provision Account - _TC", 1000, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date, voucher_type="Purchase Receipt"
)
toggle_provisional_accounting_setting()
def test_provisional_accounting_entry_multi_currency(self):
setup_provisional_accounting()
pr = make_purchase_receipt(
item_code="_Test Non Stock Item",
posting_date=add_days(nowdate(), -2),
qty=1000,
rate=111.11,
currency="USD",
do_not_save=1,
supplier="_Test Supplier USD",
)
pr.conversion_rate = 0.014783000
pr.save()
pr.submit()
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, 1)
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
self.assertEqual(pr.items[0].provisional_expense_account, "Provision Account - _TC")
# Check GLE for Purchase Invoice
expected_gle = [
["_Test Payable USD - _TC", 0, 1642.54, add_days(pr.posting_date, 1)],
["Cost of Goods Sold - _TC", 1642.54, 0, add_days(pr.posting_date, 1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["_Test Account Cost for Goods Sold - _TC", 1642.54, 0, pr.posting_date],
["Provision Account - _TC", 0, 1642.54, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 1642.54, pi.posting_date],
["Provision Account - _TC", 1642.54, 0, pi.posting_date],
]
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date, voucher_type="Purchase Receipt"
)
# Cancel purchase invoice to check reverse provisional entry cancellation
pi.cancel()
expected_gle_for_purchase_receipt_post_pi_cancel = [
["_Test Account Cost for Goods Sold - _TC", 1642.54, 0, pi.posting_date],
["Provision Account - _TC", 0, 1642.54, pi.posting_date],
]
check_gl_entries(
self,
pr.name,
expected_gle_for_purchase_receipt_post_pi_cancel,
pi.posting_date,
voucher_type="Purchase Receipt",
)
toggle_provisional_accounting_setting()
@@ -2051,7 +2130,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
def test_gl_entries_for_standalone_debit_note(self):
from erpnext.stock.doctype.item.test_item import make_item
item_code = make_item(properties={"is_stock_item": 1})
item_code = make_item(properties={"is_stock_item": 1}).name
make_purchase_invoice(item_code=item_code, qty=5, rate=500, update_stock=True)
returned_inv = make_purchase_invoice(
@@ -2813,6 +2892,17 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
# Test 4 - Since this PI is overbilled by 130% and only 120% is allowed, it will fail
self.assertRaises(frappe.ValidationError, pi.submit)
def test_discount_percentage_not_set_when_amount_is_manually_set(self):
pi = make_purchase_invoice(do_not_save=True)
discount_amount = 7
pi.discount_amount = discount_amount
pi.save()
self.assertEqual(pi.additional_discount_percentage, None)
pi.set_posting_time = 1
pi.posting_date = add_days(today(), -1)
pi.save()
self.assertEqual(pi.discount_amount, discount_amount)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

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

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