Compare commits

..

837 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
rohitwaghchaure
4241bfd4bc fix: allow FG as RM by default (#47543) 2025-05-14 16:20:28 +05:30
rohitwaghchaure
a18e1cffa7 fix: validation message format (#47542) 2025-05-14 16:20:02 +05:30
Diptanil Saha
5c28e01590 fix: pos item group filter fetching wrong items (#47545) 2025-05-14 15:51:02 +05:30
Kitti U. @ Ecosoft
b541b536c3 fix: POS Invoice can't use Loyalty Points when Global Rounded Total is Disabled (#47491) 2025-05-14 10:54:59 +05:30
barredterra
61e96f3fb5 fix: find table row 2025-05-13 18:13:26 +02:00
rohitwaghchaure
dfc4aa9a57 fix: stock reservation validation in the stock entry (#47524) 2025-05-13 21:13:42 +05:30
Mihir Kandoi
f1159b6ea6 feat: add routing/sequencing to work order operations (#46975)
* feat: add routing/sequencing to work order operations

* fix: add validation and remove reorderin for non sequence id operations

* chore: readability

* fix: logical error

* fix: logical error

* chore: added row number in error message
2025-05-13 17:14:57 +05:30
Khushi Rawat
0b1a316ad9 feat: non depreciable asset category
feat: non depreciable asset category
2025-05-13 17:03:13 +05:30
Khushi Rawat
d715db1226 fix: only depreciable category assets are allowed for depreciation 2025-05-13 16:44:13 +05:30
Abdeali Chharchhoda
80320ffa25 refactor: simplify source_name assignment in map_current_doc function 2025-05-13 16:43:13 +05:30
Khushi Rawat
32cb7d6388 fix: do not mandate depreciation accounts for non depreciable asset category 2025-05-13 16:43:03 +05:30
Khushi Rawat
fbbfd6531b feat: add non depreciable category checkbox in asset category 2025-05-13 16:40:59 +05:30
ruthra kumar
f67559bb1e Merge pull request #47525 from ruthra-kumar/drop_patch_for_advance_reconciliation
refactor: remove redundant patch
2025-05-13 15:08:23 +05:30
Khushi Rawat
2e992f937a refactor: Asset module code for better readability
refactor: Asset module code for better readability
2025-05-13 14:55:54 +05:30
ruthra kumar
c1bfa9f056 Merge pull request #47520 from ljain112/fic-pcv-acb
fix: ignore "Account Closing Balance" doctype on Period Closing Voucher cancellation
2025-05-13 14:50:22 +05:30
ruthra kumar
6c65a15758 refactor: remove redundant patch 2025-05-13 14:46:35 +05:30
Khushi Rawat
5e5795cba2 fix: patch conflict 2025-05-13 14:34:26 +05:30
ruthra kumar
c924f93614 Merge pull request #47521 from ljain112/fix-pe-cond
fix: condition for advance_account assignment
2025-05-13 14:12:27 +05:30
ruthra kumar
1139eaf751 Merge pull request #47468 from aerele/fix/reconciliation-takes-effect-on
fix(payment-reconciliation): use reconciliation_takes_effect_on from company
2025-05-13 14:10:00 +05:30
ljain112
ded46ce3d8 fix: condition for advance_account assignment 2025-05-13 13:39:01 +05:30
ljain112
d6602d63fc fix: ignore "Account Closing Balance" doctype on Period Closing Voucher cancellation 2025-05-13 13:12:39 +05:30
ruthra kumar
e7e3244e72 Merge pull request #47513 from frappe/l10n_develop
fix: sync translations from crowdin
2025-05-13 11:26:52 +05:30
Abdeali Chharchhodawala
e4e0bb68ec Merge pull request #47367 from Abdeali099/gl-report-field-float-to-currency
fix: Use `Currency` instead of `Float` in GL report to show details
2025-05-13 11:26:21 +05:30
Frappe PR Bot
898b5595a1 fix: Persian translations 2025-05-12 23:29:19 +05:30
rohitwaghchaure
963d1e502e fix: not able to reserve extra items against the work order (#47511) 2025-05-12 23:21:57 +05:30
Ankush Menat
a50251401f perf: move all hourly/daily jobs to maintenance queue (#47504)
None of them need to strictly happen at 00:00 or *:00, so moving them all to maintenance queue which executes with same frequency but spaced out.
2025-05-12 17:58:56 +05:30
ruthra kumar
7269dbc754 Merge pull request #47243 from ljain112/fix-p&l
fix: accumulate values for all the fiscal years in Profit And Loss Statement
2025-05-12 16:46:30 +05:30
ruthra kumar
54e4e7918e refactor(test): don't default to accumulate 2025-05-12 16:30:24 +05:30
Diptanil Saha
62cce38a06 fix: pos for small screen and checkout page (#47092)
* feat: Prevent need for scrolling in lower screens (Point of Sale)

* fix: pos checkout page

* refactor: renaming variable in pos profile conf

* fix: change variable name according to pos conf

* fix: removing redundant api call

* refactor: adding function

* refactor: moving pos settings invoice fields to modal

* fix: label for change amount and remaining amount

* fix: always display numpad

* refactor: function rename

* fix: better information

* fix: auto scroll to highlighted cart item

* chore: added patch

---------

Co-authored-by: devdiogenes <diogenes.cruz2001@gmail.com>
2025-05-12 16:07:22 +05:30
ruthra kumar
afff6b84ce test: accumulate filter on P&L report 2025-05-12 15:48:39 +05:30
ruthra kumar
61d13ce232 fix: typo 2025-05-12 15:07:02 +05:30
ljain112
6851322361 fix: accumulate values for all the fiscal years in Profit And Loss Statement 2025-05-12 14:54:39 +05:30
ruthra kumar
5c9e591297 Merge pull request #47224 from sagarvora/perf-gl
perf: improve general ledger report for larger data
2025-05-12 14:49:04 +05:30
Sagar Vora
07c7ce2d6a Merge branch 'develop' into perf-gl 2025-05-12 14:26:43 +05:30
ruthra kumar
4db3c7fc81 Merge pull request #47041 from Sanket322/pos_payment
fix: Check `return_against` and Await API Call
2025-05-12 14:10:04 +05:30
Diptanil Saha
57f3489dfa fix: POS non-stock item mistakenly hidden as unavailable (#47493) 2025-05-12 14:08:24 +05:30
Diptanil Saha
3965e2f3de fix: fiscal year error on new setup and continuous request (#47431)
fix: fiscal year error on new setup
2025-05-12 14:06:03 +05:30
ruthra kumar
04ef4c2b16 Merge pull request #47463 from ruthra-kumar/deterministic_sales_invoice_test_suite
refactor(test): flaky test case in sales invoice
2025-05-12 14:03:04 +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
rohitwaghchaure
0bc3cfe29d feat: stock reservation for production plan (#47373)
* feat: reservation for production plan

* test: test case for serial/batch

* feat: reserve & un-reserve options in the production plan
2025-05-12 12:19:23 +05:30
rohitwaghchaure
2ad16673f0 Merge pull request #47333 from mihir-kandoi/refactor_available_serial_no_report
refactor: available serial no report
2025-05-11 14:31:23 +05:30
Mihir Kandoi
c5686af7e2 Merge pull request #47481 from mihir-kandoi/st38118
fix: timesheet portal showing total billing hours
2025-05-10 11:37:24 +05:30
rohitwaghchaure
49df9ed32d Merge pull request #47385 from mihir-kandoi/st37286-3
fix: added PR/PI overbilling validation
2025-05-09 23:43:18 +05:30
l0gesh29
6d269b4409 fix(SalesAnalytics): Ignore opening entries 2025-05-09 17:27:48 +05:30
Patrick Eißler
b6b4ac5b4a feat: add column "Item Name" to "BOM Stock Report" (#47116) 2025-05-09 11:46:05 +00:00
Mihir Kandoi
b04a07fda0 fix: timesheet portal showing total billing hours 2025-05-09 13:38:42 +05:30
rohitwaghchaure
c770113f7c Merge pull request #47472 from rohitwaghchaure/fixed-validation-msg-bom-recursion
fix: better validation message with solution for BOM recursion
2025-05-08 20:39:29 +05:30
Rohit Waghchaure
7103cdd84a fix: better validation message with solution for BOM recursion 2025-05-08 17:45:21 +05:30
Bhavan23
7bc62cedc6 chore: simplify repeated condition checks 2025-05-08 16:32:30 +05:30
Soham Kulkarni
462ad4b6b3 Merge pull request #47464 from sokumon/collapsible-filters
fix: add options in report settings to become collapsible
2025-05-08 15:40:02 +05:30
Mihir Kandoi
653e0a2e3a chore: further optimizations 2025-05-08 15:24:38 +05:30
Mihir Kandoi
74eb611563 refactor: available serial no report 2025-05-08 15:18:31 +05:30
Khushi Rawat
04e46ea787 fix: more changes in the test 2025-05-08 15:13:13 +05:30
Mihir Kandoi
27e842ba02 fix: linter error 2025-05-08 14:47:19 +05:30
Mihir Kandoi
b406ec724b test: added test 2025-05-08 14:44:01 +05:30
Yaiphalemba Mangshatabam
edee75c757 fix: typo in event.js
"Sales Partners" -> "Sales Partner"
2025-05-08 14:09:06 +05:30
ruthra kumar
a9041e0c9b Merge pull request #47443 from frappe/l10n_develop
fix: sync translations from crowdin
2025-05-08 14:00:54 +05:30
Bhavan23
19f1ffbdc2 fix(payment-reconciliation): use reconciliation_takes_effect_on from company 2025-05-08 13:59:25 +05:30
ruthra kumar
fb92183679 refactor(test): set mandatory dimension at invidual test cases 2025-05-08 13:17:21 +05:30
sokumon
24184c21f0 fix: add options in report settings to become collapsible 2025-05-08 12:27:05 +05:30
ruthra kumar
74f8d65021 refactor(test): deterministic test suite for sales invoice 2025-05-08 12:07:45 +05:30
Khushi Rawat
3465c21090 fix: failing test 2025-05-08 11:57:02 +05:30
Khushi Rawat
dc06116425 fix: pass instance explicitly in static method 2025-05-08 11:57:02 +05:30
Khushi Rawat
c9980c59e7 chore: adjust regional depreciation logic after refactor 2025-05-08 11:57:02 +05:30
Khushi Rawat
0e93c573a6 chore: uncomment previously disabled code 2025-05-08 11:57:02 +05:30
Khushi Rawat
89d10ad4e6 fix: fiscal year check in depreciation logic 2025-05-08 11:57:02 +05:30
Khushi Rawat
782d16c1a3 fix: linters related fix 2025-05-08 11:57:02 +05:30
Khushi Rawat
e2cfd01bb2 test: asset splitting test 2025-05-08 11:57:02 +05:30
Khushi Rawat
6fa316177b fix(test): updated test for journal entry 2025-05-08 11:57:01 +05:30
Khushi Rawat
99e8c984ad fix(test): sales invoice test cases fixes 2025-05-08 11:57:01 +05:30
Khushi Rawat
3e32c30298 test: added tests on sale of an asset 2025-05-08 11:57:01 +05:30
Khushi Rawat
5d16936b9e fix(test): sales invoice test cases 2025-05-08 11:57:01 +05:30
Khushi Rawat
42ccce1687 test: added tests for reschedule depreciation after asset revaluation cancellation 2025-05-08 11:57:01 +05:30
Khushi Rawat
f5fa757bc6 fix(test): failing test cases 2025-05-08 11:57:01 +05:30
Khushi Rawat
cf1af451b8 fix(test): more fixes for failig test cases 2025-05-08 11:57:01 +05:30
Khushi Rawat
881e5e3417 fix(test): asset value adjustment test cases 2025-05-08 11:57:01 +05:30
Khushi Rawat
4b0279329f test: added tests for rescheduled depreciation after asset repair cancellation 2025-05-08 11:57:01 +05:30
Khushi Rawat
0e7ae25f65 fix(test): difference amount after asset revaluation 2025-05-08 11:57:01 +05:30
Khushi Rawat
b3ffdb6517 fix(test): increase in asset life after asset repair 2025-05-08 11:57:01 +05:30
Khushi Rawat
6ac68ed1e7 fix(test): failing test and bug fixes for basic depreciation process 2025-05-08 11:57:01 +05:30
Khushi Rawat
229d3634b1 fix(test): calculate total pending days of depreciation correctly 2025-05-08 11:57:01 +05:30
Khushi Rawat
2391c859b2 fix: reset consumed asset's depreciation on capitalization cancel 2025-05-08 11:57:01 +05:30
Khushi Rawat
a121c30b56 fix: update values of target asset on cancel of capitalization 2025-05-08 11:57:01 +05:30
Khushi Rawat
3855536ef1 fix: update status for composite asset 2025-05-08 11:57:01 +05:30
Khushi Rawat
8f1044a065 refactor: cleanup Assset Splitting code 2025-05-08 11:57:01 +05:30
Khushi Rawat
c7cc7d61d1 fix(test): value after depreciation for asset doc 2025-05-08 11:57:01 +05:30
Khushi Rawat
223e8e1bdb fix(test): set root type to Expense 2025-05-08 11:57:01 +05:30
Khushi Rawat
48311ee5c5 fix: set depreciation rate before return 2025-05-08 11:57:01 +05:30
Khushi Rawat
fe52e802ce refactor: depreciation booking code 2025-05-08 11:57:01 +05:30
Khushi Rawat
813164c25b fix: remove duplicate patch 2025-05-08 11:57:01 +05:30
Khushi Rawat
5f21d7ea1d chore: rebase from develop 2025-05-08 11:56:57 +05:30
Nabin Hait
44e45b55d4 refactor: asset value adjustment 2025-05-08 11:56:31 +05:30
Nabin Hait
c567a08470 refactor: Straight line depreciation after asset repair 2025-05-08 11:56:31 +05:30
Khushi Rawat
0e4706b074 fix: conflict in asset repair 2025-05-08 11:56:31 +05:30
Nabin Hait
0eb83a4474 chore: reorganised asset repair fields 2025-05-08 11:56:31 +05:30
Nabin Hait
f99d02b71e fix: remaining days for last row 2025-05-08 11:56:31 +05:30
Nabin Hait
2a89bac11d fix: missing args 2025-05-08 11:56:31 +05:30
Nabin Hait
9f3a1faa5e fix: add asset activity after insert 2025-05-08 11:56:31 +05:30
Nabin Hait
b433b125ff fix: removed commented code 2025-05-08 11:56:31 +05:30
Nabin Hait
c9487e0427 fix: Removed conflict 2025-05-08 11:56:31 +05:30
Frappe PR Bot
96071ae891 fix: Persian translations 2025-05-07 22:59:04 +05:30
rohitwaghchaure
c1edbc7e4b Merge pull request #47457 from rohitwaghchaure/fixed-stock-reco
fix: error while making SABB for backdated stock reco
2025-05-07 21:18:15 +05:30
Rohit Waghchaure
ad25636afb fix: error while making SABB for backdated stock reco 2025-05-07 20:56:05 +05:30
ruthra kumar
82ccdcfddd Merge pull request #47309 from ruthra-kumar/fix_broken_local_test_suite
chore: first step in the journey to reliable test suite
2025-05-07 18:45:43 +05:30
ruthra kumar
afc46a77f6 refactor(test): Timesheet will use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
bc7cc6eac0 refactor(test): Project will use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
835d08ab00 refactor(test): Project will use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
fbdbcdd8c0 refactor(test): Activity Cost to use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
1271ba3672 refactor(test): Opportunity will use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
b2fd354d41 refactor(test): Lead with use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
a55a86f300 refactor(test): Queries test suite to use ERPNextTestSuite 2025-05-07 18:31:39 +05:30
ruthra kumar
26915c622f refactor(test): move SI test data creation to erpnext test suite 2025-05-07 18:31:39 +05:30
ruthra kumar
543ba51009 refactor(test): create ERPNext specific test suite
and move data initialization there
2025-05-07 18:31:39 +05:30
ruthra kumar
4a4e314899 chore: handle duplicate data on sales invoice setup 2025-05-07 18:31:39 +05:30
ruthra kumar
8a3a9b985b refactor(test): deterministic test suite for 'Maintenance Schedule' 2025-05-07 18:31:39 +05:30
ruthra kumar
50551c1069 refactor(test): deterministic test suite for Task 2025-05-07 18:31:39 +05:30
ruthra kumar
80f7c507ba refactor(test): make 'Timesheet' test suite deterministic 2025-05-07 18:31:39 +05:30
ruthra kumar
66381f52ed chore: drop empty test suite for 'Monthly Distribution'
data from test records are moved to 'Budget' test suite
2025-05-07 18:31:39 +05:30
ruthra kumar
78b026a4ca refactor(test): deterministic 'Budget' test suite 2025-05-07 18:31:39 +05:30
ruthra kumar
9526f9d835 refactor(test): deterministic 'Queries' test suite 2025-05-07 18:31:39 +05:30
ruthra kumar
eda7595179 refactor(test): make tests for queries and Project deterministic 2025-05-07 18:31:39 +05:30
ruthra kumar
3ecc39a51f refactor(test): make 'Activity Cost' idempotent 2025-05-07 18:31:39 +05:30
ruthra kumar
b77e608264 refactor(test): idempotent Opportunity test suite 2025-05-07 18:31:39 +05:30
ruthra kumar
f7f3269c68 refactor(test): "Lead" test suite working locally 2025-05-07 18:31:39 +05:30
ruthra kumar
8d089218b5 chore: drop test suite for 'Sales Person' - there are no tests
it is only used to trigger dependent data creation
2025-05-07 18:31:39 +05:30
ruthra kumar
fb4ca8c386 chore: remove sales team from SI test records 2025-05-07 18:31:39 +05:30
ruthra kumar
c585bdb316 chore: setup test data in class initialization 2025-05-07 18:31:39 +05:30
ruthra kumar
520a54db0c chore: drop test records for sales person 2025-05-07 18:31:39 +05:30
ruthra kumar
9b2c89f704 chore: drop redundant test_records.json 2025-05-07 18:31:39 +05:30
Mihir Kandoi
f4ffc57b51 fix: added PR/PI overbilling validation 2025-05-07 17:51:36 +05:30
rohitwaghchaure
46eb7a7aa1 Merge pull request #47452 from rohitwaghchaure/fixed-support-37892
fix: warning message for COGS account in the stock entry
2025-05-07 16:18:50 +05:30
Rohit Waghchaure
bba6b0ff45 fix: warning message for COGS account in the stock entry 2025-05-07 16:03:59 +05:30
ruthra kumar
786f28e642 Merge pull request #47447 from ruthra-kumar/dont_update_opportunity_onload
fix: dont auto-fetch latest exchange rate
2025-05-07 11:16:43 +05:30
ruthra kumar
4ccd0a7407 fix: dont auto-fetch latest exchange rate
- also use correct currency field for comparison
2025-05-07 11:10:23 +05:30
Frappe PR Bot
208ac19650 fix: Persian translations 2025-05-06 22:36:31 +05:30
Frappe PR Bot
0bd6ffaee6 fix: Turkish translations 2025-05-06 22:36:20 +05:30
Frappe PR Bot
68e4be2887 fix: Swedish translations 2025-05-06 22:36:16 +05:30
Marica
e17f54ade0 Merge pull request #47432 from marination/iss-47424
fix: Relabel unit price settings for more clarity
2025-05-06 20:03:49 +05:30
marination
fbef81bcf6 chore: Relabel according to review changes 2025-05-06 15:28:56 +02:00
Marica
fcf874906f Merge pull request #47438 from marination/iss-47422
fix(portal): User cannot create 0 qty SQ from RFQ
2025-05-06 18:47:03 +05:30
rohitwaghchaure
bd1fb7357c Merge pull request #47435 from rohitwaghchaure/fixed-support-37539
fix: stock reco recalculate qty not works for opening stock reco
2025-05-06 18:26:53 +05:30
marination
879b966bd4 fix(portal): User cannot create 0 qty SQ from RFQ
- The portal uses `create_supplier_quotation` for SQ creation which excludes 0 qty items
2025-05-06 14:55:03 +02:00
Rohit Waghchaure
97095c7d24 fix: stock reco recalculate qty not works for opening stock reco 2025-05-06 17:58:06 +05:30
rohitwaghchaure
25264df18f Merge pull request #47397 from rohitwaghchaure/fixed-support-37756
fix: precision issue
2025-05-06 17:28:32 +05:30
Sagar Vora
7616b2b8e4 Merge pull request #47417 from sagarvora/update-pl-based-on 2025-05-06 16:45:26 +05:30
marination
e2fc10d558 fix: Relabel unit price settings for more clarity 2025-05-06 12:33:03 +02:00
Khushi Rawat
51ea33e743 fix: do not mandate depreciation account for assets without depreciation (#47427) 2025-05-06 14:53:13 +05:30
ruthra kumar
dc9dc2ca42 Merge pull request #47337 from Abdeali099/fix-error-on-paid-amount-change
fix: do not allocate amount when ref's doctype or name are not set
2025-05-06 14:32:27 +05:30
Sagar Vora
3ebde4526a feat!: configure which rate is used to auto-update price list 2025-05-06 13:01:13 +05:30
ruthra kumar
d065904431 Merge pull request #47408 from Abdeali099/proper-due-date-exceeds-msg
fix: show party type in due date exceeding message
2025-05-06 11:57:46 +05:30
ruthra kumar
682e6ceb9d Merge pull request #47411 from frappe/l10n_develop
fix: sync translations from crowdin
2025-05-06 11:29:52 +05:30
Vimal
0b4add2f2b fix: change shipping address fetching condition 2025-05-06 10:57:49 +05:30
Frappe PR Bot
9e07a48201 fix: Turkish translations 2025-05-05 22:32:40 +05:30
Marica
80bd5ced46 Merge pull request #46214 from marination/unit-price-contract-2
feat: Unit Price Items
2025-05-05 22:26:39 +05:30
Frappe PR Bot
0bbd94d549 fix: sync translations from crowdin (#47398) 2025-05-05 15:59:40 +02:00
Rohit Waghchaure
69bee93bfd fix: precision issue 2025-05-05 18:42:25 +05:30
Abdeali Chharchhoda
b6d9134014 fix: show party type in due date exceeding message 2025-05-05 18:25:56 +05:30
Diptanil Saha
45393d51a2 fix: 'time to resolve: failed' on issue (#47406)
* fix: 'time to resolve: failed' on issue

* fix: sla_resolution_date
2025-05-05 17:43:29 +05:30
Diptanil Saha
d4ffa54136 fix: backward compatibility for renamed group_by filter on reports (#47362)
* fix: backward compatibility for renamed group_by filter in general ledger report

* fix: backward compatibility for renamed group_by filter in supplier quotation comparison report
2025-05-05 17:21:41 +05:30
Diptanil Saha
3de249dcba fix: rename unchanged group_by filter related to general ledger report (#47366) 2025-05-05 17:20:23 +05:30
ruthra kumar
c31845c55a Merge pull request #47278 from barredterra/customer_supplier_ids
feat: store Customer Number at Supplier / Supplier Number at Customer
2025-05-05 16:22:54 +05:30
half-ware
e68883f0f9 feat: Algeria chart of accounts (#47038)
* Algeria chart of accounts

Algeria chart of accounts

* Update Algeria Chart Of Account

* Algeria chart of account

* Algeria Chart of Account

Algeria Chart of Account
2025-05-05 16:18:18 +05:30
Diptanil Saha
8067799692 fix: update accounts on change of mode of payment in sales invoice payment (#47381)
* fix: update accounts on change of mode of payment in sales invoice payment

* test: fixed tests
2025-05-05 15:58:53 +05:30
Marica
60201a9683 Merge branch 'develop' into unit-price-contract-2 2025-05-05 15:52:39 +05:30
ruthra kumar
5990752416 Merge pull request #47145 from ruthra-kumar/refactor_ar_ap_for_memory_usage
refactor: make AR / AP report more memory efficient
2025-05-05 15:43:09 +05:30
ruthra kumar
ca1e81e1b5 refactor: set default for fetch methods 2025-05-05 14:19:45 +05:30
ruthra kumar
b5bb6f3508 refactor: use fetch method based on configuration 2025-05-05 14:19:41 +05:30
rohitwaghchaure
f370b36b62 Merge pull request #47395 from rohitwaghchaure/fixed-serial-no-reservation-validation
fix: reserved serial nos validation
2025-05-05 14:10:13 +05:30
Rohit Waghchaure
a20951e1cd fix: reserved serial nos validation 2025-05-05 13:53:06 +05:30
ruthra kumar
66fd639b52 refactor: configurable fetch method for AR / AP report 2025-05-05 12:29:58 +05:30
ruthra kumar
08903459c2 refactor: use unbuffered cursor for fetching 2025-05-05 12:20:05 +05:30
ruthra kumar
bd4d1cd0d3 Merge pull request #47349 from frappe/l10n_develop
fix: sync translations from crowdin
2025-05-05 11:36:56 +05:30
Frappe PR Bot
1c322965b4 chore: update POT file (#47393) 2025-05-04 12:30:29 +02:00
rohitwaghchaure
fa1b706147 Merge pull request #47382 from rohitwaghchaure/fixed-serial-batch-reservation-wo
fix: serial batch from work order for fg
2025-05-03 13:21:42 +05:30
rohitwaghchaure
16306d1352 Merge pull request #47377 from rohitwaghchaure/fixed-support-37608
fix: not able to submit the stock entry
2025-05-03 13:21:30 +05:30
rohitwaghchaure
8c3af7738c Merge pull request #47376 from rohitwaghchaure/fixed-support-37187
fix: validation for difference account
2025-05-03 13:21:16 +05:30
Rohit Waghchaure
fb819c558e fix: validation for difference account 2025-05-03 11:36:42 +05:30
Rohit Waghchaure
5225d4c318 fix: serial batch from work order for fg 2025-05-03 11:35:28 +05:30
Rohit Waghchaure
1d862c111f fix: not able to submit the stock entry 2025-05-03 11:34:48 +05:30
ruthra kumar
e9663a55a3 Merge pull request #47380 from ruthra-kumar/broken_uae_vat_test_case
fix: broken CI - uae vat 201 tests failing
2025-05-03 11:26:25 +05:30
ruthra kumar
37d74e387d fix: broken test suite due to incorrect OR filter 2025-05-03 11:13:32 +05:30
rohitwaghchaure
97db9da10e fix: completed transactions showing in the list (#47374) 2025-05-02 15:23:15 +05:30
Diptanil Saha
13a84e7f82 fix: renaming group by fieldname and value in reports (#47352)
* fix: renaming in general ledger report

* fix: renaming in supplier quotation comparison report

* fix: renaming group by to categorize by in process statement of accounts

* fix: added patch

* fix: patch update to all documents

* chore: added patches to patch.txt

* chore: removing patch from v14
2025-04-30 11:15:53 +00:00
Mihir Kandoi
4fc7a8b71d refactor: portal query in timesheet.py (#47336)
* refactor: portal query in timesheet.py

* fix: use criterion.any to fix query
2025-04-30 16:13:55 +05:30
Nihantra C. Patel
e02de12ecc Merge pull request #47351 from Nihantra-Patel/fix_ledger_summary
fix: party name in Ledger Summary
2025-04-30 13:42:05 +05:30
Nihantra Patel
70bc86a4c6 fix: party name in Ledger Summary 2025-04-30 13:12:02 +05:30
Frappe PR Bot
553dd38c8d fix: Persian translations 2025-04-30 12:42:34 +05:30
Frappe PR Bot
88ebdffc9a fix: Swedish translations 2025-04-30 12:42:30 +05:30
rohitwaghchaure
ffdc4347e8 fix: warning message before changing the valuation method (#47340) 2025-04-29 22:59:22 +05:30
Abdeali Chharchhoda
b9a02b466b fix: do not allocate amount when ref's doctype or name are not set 2025-04-29 18:44:39 +05:30
Mihir Kandoi
795c2d104d Merge pull request #47334 from mihir-kandoi/fix-rfq-v16-error-2
fix: rfq error on item_code
2025-04-29 18:36:26 +05:30
Mihir Kandoi
48e6fee6a1 fix: rfq error on item_code 2025-04-29 18:19:45 +05:30
Khushi Rawat
05afad78fc fix: allow selling asset at zero rate (#47326) 2025-04-29 16:53:57 +05:30
Mihir Kandoi
b89bbe36d7 feat: add stock uom read only field to stock reconciliation item doctype (#47299) 2025-04-29 16:25:45 +05:30
Mihir Kandoi
fc02a6510e fix: require email OR phone in shipment doctype not both (#47300) 2025-04-29 16:24:51 +05:30
Mihir Kandoi
7499c25a3c fix: validate if from and to time are present on submission of job card (#47325) 2025-04-29 16:21:41 +05:30
ruthra kumar
14ac65b64d Merge pull request #47320 from frappe/l10n_develop
fix: sync translations from crowdin
2025-04-29 12:17:54 +05:30
ruthra kumar
cf29c3758a Merge pull request #47241 from ljain112/fix-ic-jv
fix: compare total debit/credit with precision for Inter Company Journal Entry
2025-04-29 12:16:57 +05:30
Diptanil Saha
7423e4187f fix: remove invalid email account creation (#47318) 2025-04-29 12:13:12 +05:30
ruthra kumar
02a25bcfcd Merge pull request #47231 from aerele/multi-currency-advance
fix(payment request): get advance amount based on transaction currency
2025-04-29 12:06:41 +05:30
Frappe PR Bot
d93b0af07c fix: Persian translations 2025-04-29 12:01:05 +05:30
Frappe PR Bot
3bd149ec3a fix: Portuguese, Brazilian translations 2025-04-29 12:01:01 +05:30
Mihir Kandoi
47927b38a9 feat: change sabb qty automatically incase of internal transfer PR if sabb only has 1 batch (#47256)
* feat: change sabb qty automatically incase of internal transfer PR if sabb only has 1 batch

* fix: prevent creation of SABB on every save

* perf: optimize code

* fix: remove unnecessary conditon

* refactor: change if to elif

* fix: remove dn_item_qty and set to item.qty

* test: added test
2025-04-29 11:45:46 +05:30
Mihir Kandoi
26564819fe Merge pull request #47302 from mihir-kandoi/46928
fix: commas in rfq portal js
2025-04-29 11:37:02 +05:30
Mihir Kandoi
ce0693be95 Merge pull request #47301 from mihir-kandoi/fix-rfq-item_name-fetch-from
fix: add fetch from to item name in rfq item doctype
2025-04-29 11:36:33 +05:30
Mihir Kandoi
710a77a327 Merge pull request #47296 from mihir-kandoi/st35392-2
fix: fix sub assembly qty calculation in production plan when bom level >= 1
2025-04-29 11:36:10 +05:30
Mihir Kandoi
e98dcfc668 Merge pull request #47257 from mihir-kandoi/st37118
fix: add transaction_date in field_no_map when creating PO from SQ
2025-04-29 11:35:39 +05:30
Mihir Kandoi
583f251edf Merge pull request #47234 from mihir-kandoi/st37040
fix: price currency in supplier quotation comparison
2025-04-29 11:35:07 +05:30
Khushi Rawat
9dee4ac891 fix: prevent cancellation of last asset movement (#47291)
* fix: prevent cancellation of last asset movement

* test: movement cancellation

* fix: allow cancellation of asset movement when cancelling asset
2025-04-29 11:34:35 +05:30
ruthra kumar
6a59744d8e Merge pull request #47111 from sokumon/supplier-address
fix: convert to text before setting value
2025-04-29 11:24:29 +05:30
Mihir Kandoi
bd727e069b fix: commas in rfq portal js 2025-04-28 23:51:51 +05:30
Mihir Kandoi
ab7973231a fix: add fetch from to item name in rfq item doctype 2025-04-28 23:31:57 +05:30
Mihir Kandoi
ee10afc074 fix: logical error 2025-04-28 21:29:47 +05:30
Mihir Kandoi
bfc4ce1d5d fix: fix sub assembly qty calculation in production plan when bom level >= 1 2025-04-28 21:09:12 +05:30
RitvikSardana
07c263d453 Merge pull request #47262 from RitvikSardana/ignore-flag-removal
fix: dont pass ignore permissions param to whitelist methods
2025-04-28 19:50:04 +05:30
rohitwaghchaure
0701a8cf5a fix: QI reference not set if 'Action If Quality Inspection Is Not Sub… (#47294)
fix: qi reference not set if 'Action If Quality Inspection Is Not Submitted' is blank
2025-04-28 19:38:37 +05:30
rohitwaghchaure
3d36d0b1df fix: validation if no stock ledger entries against stock reco (#47292) 2025-04-28 18:16:52 +05:30
Mihir Kandoi
9e640341fd fix: remove unused import 2025-04-28 17:49:27 +05:30
Mihir Kandoi
acd1529780 fix: test case 2025-04-28 17:43:19 +05:30
Marica
72113dc485 Merge branch 'develop' into unit-price-contract-2 2025-04-28 16:15:41 +05:30
Frappe PR Bot
9a7bcfe395 fix: sync translations from crowdin (#47230) 2025-04-28 12:14:45 +02:00
rohitwaghchaure
70787eb5e0 Merge pull request #47285 from rohitwaghchaure/fixed-support-34140-1
fix: allow to make quality inspection after Purchase / Delivery
2025-04-28 14:28:56 +05:30
Rohit Waghchaure
fad1a32e63 fix: allow to make quality inspection after Purchase / Delivery 2025-04-28 13:28:35 +05:30
l0gesh29
eae08bc619 fix: update quantity validation using asset quantity field instead of… (#46731)
* fix: update quantity validation using asset quantity field instead of total records

* fix: update throw message
2025-04-28 12:25:24 +05:30
ruthra kumar
cf3237a252 Merge pull request #46993 from karm1000/dispatch-field
feat: add dispatch address fields to purchase doctypes
2025-04-28 10:56:20 +05:30
ruthra kumar
c060cdc06c Merge pull request #46979 from Sanket322/refactor_report
fix: Proper Postal Code Order and Add Party Name Properly
2025-04-28 10:18:58 +05:30
ruthra kumar
dd4739c1aa Merge pull request #46774 from cogk/feat-past-events-and-todos-in-crm-activities
feat: Show past events and todos in crm_activities
2025-04-28 09:36:57 +05:30
barredterra
edcc1d39ae feat(Supplier): add table Customer Numbers 2025-04-27 18:09:13 +02:00
barredterra
be72e64633 feat(Customer): add table Supplier Numbers 2025-04-27 18:04:53 +02:00
rohitwaghchaure
a4e20c59d6 Merge pull request #47259 from rohitwaghchaure/fixed-support-36911
fix: enable use serial / batch fields on batch selection
2025-04-27 18:35:17 +05:30
Frappe PR Bot
1ed9872db1 chore: update POT file (#47275) 2025-04-27 14:53:35 +02:00
rohitwaghchaure
df08c92b4f Merge pull request #47268 from rohitwaghchaure/fixed-valuation-method-change
fix: allow to change valuation method from FIFO to Moving Average
2025-04-27 17:45:37 +05:30
Rohit Waghchaure
b454ed4b8f fix: allow to change valuation method from FIFO to Moving Average 2025-04-26 16:31:37 +05:30
Corentin Forler
aa18753f57 fix(PE): Set account types in get_payment_entry (#47246) 2025-04-25 15:30:53 +02:00
Diptanil Saha
c85edc3346 fix: consolidating pos invoices on the basis of accounting dimensions (#46961)
* fix: consolidating pos invoices on the basis of accounting dimensions

* fix: project field
2025-04-25 17:38:23 +05:30
Diptanil Saha
1ad61fb572 feat: sales invoice integration with pos (#46485)
* feat: pos configuration to activate real time update of gl and stock ledger

* feat: sales invoice on pos order list

* fix: syntax

* feat: past order list with sales invoice

* feat: customer recent transaction with sales invoice

* fix: real_time_update validation

added a validation to restrict switching between sales invoice and pos invoice in case there's already a pos opening entry

* fix: use sales invoice on accounts settings

moved the check to use sales invoice instead of pos invoice from pos profile to accounts settings.

* fix: using accounts settings to get frm doctype

* fix: added support for sales invoice in  process return

* feat: event listeners for sales invoice on pos

* fix: create and load return invoice

* fix: edit order

* refactor: function rename

* fix: sales invoice generation using pos

added fields to distinguish sales invoice generated using pos

* feat: credit note in pos invoice during sales invoice mode

* feat: pos closing entry support for sales invoices

* refactor: resolving linter issues

* refactor: fix linter issue

* fix: filters for sales invoice in toggle recent orders

* feat: disable partial payments on sales invoice transactions made using pos

* fix: resolve import error

* fix: recent order list and pos invoice returns during sales invoice mode

* fix: reset pos_closing_entry on return sales invoice

* fix: filtering out consolidated return sales invoice for pos invoice return

* fix: pos delete order

* fix: added missing reference to consolidated sales invoice item

* fix: added check to restrict sales invoice creation

* refactor: variable name

* fix: integrating sales_invoice in make_closing_entry_from_opening method

* test: test for sales invoice integration in pos

* fix: issue with accounting dimension on sales invoice

* refactor: moved invoice switching mode validation in backend

* test: removed test case

Removed Test Case for Full Payment of Sales Invoice created using POS as planning to add feature to accept Partial Payment from POS.

* test: fixed the failing tests

* test: remove explicit use of frappe.db.commit()

* test: fixing pos invoice test

* test: removed test
2025-04-25 17:33:39 +05:30
RitvikSardana
c28596f69d fix: dont pass ignore permissions param to whitelist methods 2025-04-25 15:31:02 +05:30
Rohit Waghchaure
a4471865a9 fix: enable use serial / batch fields on batch selection 2025-04-25 15:13:18 +05:30
Marica
3193e1c1a2 Merge pull request #47232 from marination/sn-warranty-report-missing
fix: Re-insert missing "Serial No Warranty Expiry" Report
2025-04-25 14:57:35 +05:30
Mihir Kandoi
3790c6c551 fix: add transaction_date in field_no_map when creating PO from SQ 2025-04-25 14:06:31 +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
Diptanil Saha
483c4a3271 fix: prohibit consolidated sales invoice return (#47251) 2025-04-25 11:09:22 +05:30
Diptanil Saha
c8ee5d9a4e fix: cancel pos closing entry failure for return pos invoices (#47248) 2025-04-24 22:29:21 +05:30
ljain112
5fe247557e fix: compare total debit/credit with precision for Inter Company Journal Entry 2025-04-24 18:54:03 +05:30
Khushi Rawat
ed8a8532e1 fix: update additional cost and total asset cost after asset repair (#47233)
* fix: add consumed stock's cost to the asset value after repair

* fix: do not copy additional cost and total asset cost
2025-04-24 16:14:42 +05:30
Mihir Kandoi
88926eb2a7 fix: price currency in supplier quotation comparison 2025-04-24 16:10:12 +05:30
marination
deefac0abf fix: Re-insert missing "Serial No Warranty Expiry" Report 2025-04-24 09:50:43 +02:00
venkat102
b570d97b4d fix(payment request): get advance amount based on transaction currency 2025-04-24 11:33:18 +05:30
Khushi Rawat
0d53e6ed7c fix: make asset quantity and amount editable (#47226) 2025-04-24 01:54:00 +05:30
Raffael Meyer
cb111c43d4 fix(Rename Tool): allow more than 500 rows (#47117)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-04-23 19:32:03 +02:00
Sagar Vora
7131396ac0 perf: evaluate conditions outside loop 2025-04-23 21:34:18 +05:30
Sagar Vora
af283d0d9e perf: improved gle map init 2025-04-23 21:34:18 +05:30
Sagar Vora
593ecb3d68 Merge pull request #47011 from sokumon/remove-eps
fix: remove default energy points from erpnext
2025-04-23 21:21:28 +05:30
Frappe PR Bot
0bc8d28969 fix: sync translations from crowdin (#47192) 2025-04-23 17:11:29 +02:00
ruthra kumar
709a2fa553 Merge pull request #47155 from sagarvora/fix-dr-cr-mismatch
fix: get total without rounding off tax amounts for distributing discount
2025-04-22 17:19:00 +05:30
ruthra kumar
1b2bde4c82 Merge pull request #47178 from aerele/delivery-note-per-billed
fix: keep per_billed 100 for billed delivery note after return
2025-04-22 17:10:33 +05:30
Sagar Vora
551639da7b chore: remove previously added logger calls 2025-04-22 17:04:16 +05:30
rohitwaghchaure
6857700eff Merge pull request #47183 from mihir-kandoi/st36696
fix: backslash in url
2025-04-22 16:15:42 +05:30
Mihir Kandoi
c3d172fac3 fix: remove unused import 2025-04-22 15:50:51 +05:30
Mihir Kandoi
5d07beee61 fix: change get_url_to_form to get_link_to_form 2025-04-22 15:50:02 +05:30
ruthra kumar
7e0681c4a4 Merge pull request #46717 from ljain112/fix-search
fix: respect field "ignore_user_permissions" property in employee query
2025-04-22 15:41:34 +05:30
ruthra kumar
bd4e9370de Merge pull request #47171 from ljain112/paid-amount
fix: set correct paid/receive amount if doc currency is different from party account currency
2025-04-22 15:35:48 +05:30
Mihir Kandoi
eaaf34cda6 fix: revert unintended changes 2025-04-22 15:27:09 +05:30
Mihir Kandoi
7a82b37f76 fix: use get_url_to_form instead 2025-04-22 15:22:54 +05:30
rohitwaghchaure
b9e09aa34e Merge pull request #47184 from mihir-kandoi/st36742
feat: add button to view Supplier Quotation Comparison directly from RFQ
2025-04-22 15:12:15 +05:30
ruthra kumar
5a6997455b Merge pull request #47180 from ljain112/fix-quo
fix: set default company address in selling Doctype on change of company
2025-04-22 14:26:22 +05:30
Soham Kulkarni
68ca4a77c9 Merge pull request #47175 from sokumon/purchase-receipt-quick-list
fix: add grand_total to show correct status in quick list widget
2025-04-22 14:19:41 +05:30
ruthra kumar
f01a9069b1 Merge pull request #47138 from ljain112/fix-tds-report
fix: rate based on posting date in Tax Withholding Report
2025-04-22 13:28:42 +05:30
rohitwaghchaure
3c53d628d9 Merge pull request #47191 from rohitwaghchaure/fixed-github-47076
fix: expense account in stock entry
2025-04-22 13:15:33 +05:30
ljain112
4be975f87c chore: added test case for employee query with user permissions 2025-04-22 13:00:37 +05:30
ruthra kumar
5eb83f763f Merge pull request #47124 from ljain112/fix-supp-led
fix: `TypeError` in group field filter in supplier ledger summary
2025-04-22 12:21:02 +05:30
Rohit Waghchaure
75874b4986 fix: expense account in stock entry 2025-04-22 08:07:09 +05:30
Mihir Kandoi
ef57d2b328 fix: show button only when RFQ is submitted 2025-04-21 20:25:31 +05:30
Mihir Kandoi
b4aa88b59b feat: add button to show request for comparison report directly from RFQ 2025-04-21 20:23:14 +05:30
Mihir Kandoi
ecf15130ba fix: backslash in url 2025-04-21 19:05:16 +05:30
ljain112
a31075692c fix: set default company address in Sales Doctype on change of company 2025-04-21 17:38:34 +05:30
Sugesh393
02356029a8 refactor: update base_outstanding calculation 2025-04-21 16:24:08 +05:30
Sugesh393
fe5898a151 test: add new unit test to keep per_billed 100 for billed delivery note 2025-04-21 16:23:32 +05:30
Sugesh393
8290a83591 fix: keep per_billed 100 for billed delivery note after return 2025-04-21 16:22:14 +05:30
ljain112
91d7bc55be fix: respect field "ignore_user_permissions" property in employee query 2025-04-21 14:21:56 +05:30
Smit Vora
ceaba4220b fix: map dispatch address correctly for inter company transactions 2025-04-21 13:49:14 +05:30
Smit Vora
fb3b7d8c34 refactor: set address details for transactions 2025-04-21 13:46:59 +05:30
Smit Vora
8ccd7a3e61 refactor: address field position 2025-04-21 13:44:20 +05:30
ljain112
9612521894 fix: set correct paid/receive amount if doc currency is different from party account currency 2025-04-21 12:11:13 +05:30
Sagar Vora
5741458c94 fix: get total without rounding off tax amounts for distributing discount 2025-04-19 14:14:58 +05:30
ljain112
515fe340a8 chore: added test case for date period in multiple tax withholding rules 2025-04-18 13:25:42 +05:30
Karm Soni
999ffe86a7 fix: correct query for dispatch_address; remove unnecessary code; increase reusability; 2025-04-18 13:24:47 +05:30
ljain112
a32a79e90a fix: rate based on posting date in Tax Withholding Report 2025-04-17 20:23:48 +05:30
Karm Soni
d12998e524 feat: add display dispatch address when dispatch address is selected 2025-04-17 16:32:59 +05:30
Karm Soni
c4bd3123fb fix: remove use of cur_frm 2025-04-17 15:43:07 +05:30
Karm Soni
9a859e54b6 fix: enhance dispatch address query logic and add supplier address query 2025-04-17 15:43:07 +05:30
Karm Soni
53d0b7be23 feat: add dispatch address support in party details and controllers 2025-04-17 15:43:07 +05:30
ljain112
872e94a316 fix: TypeError in group field filter in supplier ledger summary 2025-04-17 15:39:50 +05:30
sokumon
8da5752059 fix: convert to text before setting value 2025-04-16 11:03:22 +05:30
Marica
bbe641fd77 Merge branch 'develop' into unit-price-contract-2 2025-04-15 16:11:14 +05:30
sokumon
6d93c3adad fix: remove energy points patch 2025-04-13 17:12:29 +05:30
Sanket322
c48db0b7c0 fix: ensure backend response is awaited before saving 2025-04-13 13:24:33 +05:30
Sanket322
00b6b97197 fix: check return_against exists before api call 2025-04-13 13:17:15 +05:30
sokumon
e3af7f639d fix: remove default energy points from erpnext 2025-04-11 14:03:53 +05:30
Sanket322
54385dde24 fix: changed postalcode order and correct the condition for party_name 2025-04-10 01:15:27 +05:30
Karm Soni
54b5205221 feat: add dispatch address fields to purchase doctypes 2025-04-09 19:19:47 +05:30
Corentin Forler
bb864c8345 feat: Show past events and todos in crm_activities 2025-03-28 14:07:45 +01:00
marination
bf62f9ad57 fix: Headline rendered twice on first save
- `refresh` gets triggered twice and that renders the note twice
- Remove any existing note before rendering
2025-03-17 19:26:45 +01:00
marination
0447c7be0a fix: Treat rows as Unit Price rows only until the qty is 0
- The unit price check should depend on the row qty being 0
- Once the row ceases to be 0, it is treated as an ordinary row
- test: PO, SO and Quotation
2025-03-17 19:17:14 +01:00
marination
55981c8358 test: Sales Order + fix: Mapping of Items from Quotation & SO 2025-03-14 18:54:11 +01:00
marination
eea758f5b2 test: Purchase Order with Unit Price Items
- chore: Fix error message in accounts controller
2025-03-14 18:00:40 +01:00
marination
8f96c0b546 test: Zero Qty in RFQ and Supplier Quotation 2025-03-12 18:42:12 +01:00
marination
e403d3f153 feat: Unit Price Items in Buying (RFQ, SQ, PO)
- chore: Extract `set_unit_price_items_note` into a util
2025-03-03 17:53:00 +01:00
marination
71f65bab5e fix: Linters 2025-03-03 16:24:22 +01:00
marination
c1e4e7af28 feat: Unit Price Contract 2025-02-28 23:04:29 +01:00
557 changed files with 346704 additions and 256062 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

@@ -58,6 +58,10 @@ class TestAccountingDimension(IntegrationTestCase):
self.assertEqual(gle1.get("department"), "_Test Department - _TC")
def test_mandatory(self):
location = frappe.get_doc("Accounting Dimension", "Location")
location.dimension_defaults[0].mandatory_for_bs = True
location.save()
si = create_sales_invoice(do_not_save=1)
si.append(
"items",
@@ -121,7 +125,6 @@ def create_dimension():
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1,
},
)

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",
@@ -58,6 +66,7 @@
"pos_tab",
"pos_setting_section",
"post_change_gl_entries",
"column_break_xrnd",
"assets_tab",
"asset_settings_section",
"calculate_depr_using_total_days",
@@ -77,11 +86,16 @@
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"ignore_is_opening_check_for_reporting",
"column_break_lvjk",
"receivable_payable_remarks_length",
"accounts_receivable_payable_tuning_section",
"receivable_payable_fetch_method",
"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": [
{
@@ -532,14 +546,100 @@
"fieldtype": "Select",
"label": "Posting Date Inheritance for Exchange Gain / Loss",
"options": "Invoice\nPayment\nReconciliation Date"
},
{
"fieldname": "column_break_xrnd",
"fieldtype": "Column Break"
},
{
"default": "Buffered Cursor",
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
"fieldtype": "Section Break",
"label": "Accounts Receivable / Payable Tuning"
},
{
"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,
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-01-23 13:15:44.077853",
"modified": "2025-06-23 15:55:33.346398",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -564,8 +664,9 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

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,13 +53,17 @@ class AccountsSettings(Document):
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
ignore_is_opening_check_for_reporting: DF.Check
maintain_same_internal_transaction_rate: DF.Check
maintain_same_rate_action: DF.Literal["Stop", "Warn"]
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
post_change_gl_entries: DF.Check
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_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
@@ -66,9 +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_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
@@ -135,3 +144,13 @@ class AccountsSettings(Document):
if self.has_value_changed("reconciliation_queue_size"):
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_auto_tax_settings(self):
if self.add_taxes_from_item_tax_template and self.add_taxes_from_taxes_and_charges_template:
frappe.throw(
_("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=_("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

@@ -7,6 +7,9 @@ from frappe.tests import IntegrationTestCase
from frappe.utils import add_months, getdate
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -143,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",
@@ -172,11 +175,13 @@ def make_pos_sales_invoice():
customer = make_customer(customer="_Test Customer")
mode_of_payment = frappe.get_doc("Mode of Payment", "Wire Transfer")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank Clearance - _TC")
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
si.set("payments", [])
si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000}
)
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 1000})
si.insert()
si.submit()

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,12 +6,15 @@ 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,
reconcile_vouchers,
)
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
@@ -21,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()
@@ -430,15 +424,13 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
except frappe.DuplicateEntryError:
pass
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Wire Transfer"})
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
mode_of_payment.save()
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", gl_account)
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 109080})
si.insert()
si.submit()

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

@@ -3,18 +3,29 @@
import unittest
import frappe
from frappe.tests import IntegrationTestCase
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
EXTRA_TEST_RECORD_DEPENDENCIES = ["Monthly Distribution"]
from erpnext.tests.utils import ERPNextTestSuite
class TestBudget(IntegrationTestCase):
class TestBudget(ERPNextTestSuite):
@classmethod
def setUpClass(cls):
super().setUpClass()
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")
@@ -43,10 +54,13 @@ class TestBudget(IntegrationTestCase):
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(),
)
@@ -63,10 +77,13 @@ class TestBudget(IntegrationTestCase):
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(),
)
@@ -123,6 +140,7 @@ class TestBudget(IntegrationTestCase):
budget.load_from_db()
budget.cancel()
mr.cancel()
def test_monthly_budget_crossed_for_po(self):
budget = make_budget(
@@ -135,7 +153,12 @@ class TestBudget(IntegrationTestCase):
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()
@@ -153,11 +176,13 @@ class TestBudget(IntegrationTestCase):
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(),
@@ -272,10 +297,13 @@ class TestBudget(IntegrationTestCase):
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(),
)
@@ -302,10 +330,13 @@ class TestBudget(IntegrationTestCase):
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(),
)
@@ -349,6 +380,44 @@ class TestBudget(IntegrationTestCase):
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":
@@ -423,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"
@@ -447,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

@@ -86,7 +86,8 @@
"fieldname": "party_type",
"fieldtype": "Link",
"label": "Party Type",
"options": "DocType"
"options": "DocType",
"search_index": 1
},
{
"fieldname": "party",
@@ -104,7 +105,8 @@
"label": "Cost Center",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Cost Center"
"options": "Cost Center",
"search_index": 1
},
{
"fieldname": "debit",
@@ -241,7 +243,8 @@
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company"
"options": "Company",
"search_index": 1
},
{
"fieldname": "finance_book",
@@ -356,7 +359,7 @@
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2025-04-21 22:37:16.349564",
"modified": "2025-03-21 15:29:11.221890",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
@@ -387,9 +390,8 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "voucher_no,account,posting_date,against_voucher",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -7,7 +7,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.model.naming import set_name_from_naming_options
from frappe.utils import flt, fmt_money, now
from frappe.utils import create_batch, flt, fmt_money, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -438,14 +438,9 @@ def update_against_account(voucher_type, voucher_no):
def on_doctype_update():
add_company_indexes()
def add_company_indexes():
"""Only add company indexes if more than one company exists."""
if frappe.db.count("Company", {"name": ("not like", "%(Demo)%")}) > 1:
frappe.db.add_index("GL Entry", ["posting_date", "company"])
frappe.db.add_index("GL Entry", ["company"])
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
frappe.db.add_index("GL Entry", ["posting_date", "company"])
frappe.db.add_index("GL Entry", ["party_type", "party"])
def rename_gle_sle_docs():
@@ -456,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

@@ -197,7 +197,7 @@ frappe.ui.form.on("Invoice Discounting", {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
group_by: "Group by Voucher (Consolidated)",
categorize_by: "Categorize by Voucher (Consolidated)",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

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) {
@@ -35,7 +68,7 @@ frappe.ui.form.on("Journal Entry", {
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
finance_book: frm.doc.finance_book,
group_by: "",
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

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\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-07-18 15:32:29.413598",
"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
@@ -100,6 +103,8 @@ class JournalEntry(AccountsController):
"Write Off Entry",
"Opening Entry",
"Depreciation Entry",
"Asset Disposal",
"Periodic Accounting Entry",
"Exchange Rate Revaluation",
"Exchange Gain Or Loss",
"Deferred Revenue",
@@ -140,15 +145,14 @@ class JournalEntry(AccountsController):
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
self.validate_depr_account_and_depr_entry_voucher_type()
self.validate_company_in_accounting_dimension()
self.validate_advance_accounts()
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])
@@ -196,7 +200,76 @@ class JournalEntry(AccountsController):
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
self.update_booked_depreciation()
@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
@@ -232,7 +305,6 @@ class JournalEntry(AccountsController):
self.unlink_inter_company_jv()
self.unlink_asset_adjustment_entry()
self.update_invoice_discounting()
self.update_booked_depreciation(1)
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
@@ -253,21 +325,38 @@ class JournalEntry(AccountsController):
def validate_inter_company_accounts(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
doc = frappe.db.get_value(
"Journal Entry",
self.inter_company_journal_entry_reference,
["company", "total_debit", "total_credit"],
as_dict=True,
)
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
if account_currency == previous_account_currency:
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
credit_precision = self.precision("total_credit")
debit_precision = self.precision("total_debit")
if (flt(self.total_credit, credit_precision) != flt(doc.total_debit, debit_precision)) or (
flt(self.total_debit, debit_precision) != flt(doc.total_credit, credit_precision)
):
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
def validate_depr_entry_voucher_type(self):
if (
any(d.account_type == "Depreciation" for d in self.get("accounts"))
and self.voucher_type != "Depreciation Entry"
):
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
def validate_depr_account_and_depr_entry_voucher_type(self):
for d in self.get("accounts"):
if d.account_type == "Depreciation":
if self.voucher_type != "Depreciation Entry":
frappe.throw(
_("Journal Entry type should be set as Depreciation Entry for asset depreciation")
)
if frappe.get_cached_value("Account", d.account, "root_type") != "Expense":
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(
@@ -370,7 +459,11 @@ class JournalEntry(AccountsController):
self.remove(d)
def update_asset_value(self):
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
self.update_asset_on_depreciation()
self.update_asset_on_disposal()
def update_asset_on_depreciation(self):
if self.voucher_type != "Depreciation Entry":
return
for d in self.get("accounts"):
@@ -380,22 +473,59 @@ class JournalEntry(AccountsController):
and d.account_type == "Depreciation"
and d.debit
):
asset = frappe.get_doc("Asset", d.reference_name)
asset = frappe.get_cached_doc("Asset", d.reference_name)
if asset.calculate_depreciation:
fb_idx = 1
if self.finance_book:
for fb_row in asset.get("finance_books"):
if fb_row.finance_book == self.finance_book:
fb_idx = fb_row.idx
break
fb_row = asset.get("finance_books")[fb_idx - 1]
fb_row.value_after_depreciation -= d.debit
fb_row.db_update()
else:
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
self.update_journal_entry_link_on_depr_schedule(asset, d)
self.update_value_after_depreciation(asset, d.debit)
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
asset.set_status()
asset.set_total_booked_depreciations()
def update_value_after_depreciation(self, asset, depr_amount):
fb_idx = 1
if self.finance_book:
for fb_row in asset.get("finance_books"):
if fb_row.finance_book == self.finance_book:
fb_idx = fb_row.idx
break
fb_row = asset.get("finance_books")[fb_idx - 1]
fb_row.value_after_depreciation -= depr_amount
frappe.db.set_value(
"Asset Finance Book", fb_row.name, "value_after_depreciation", fb_row.value_after_depreciation
)
def update_journal_entry_link_on_depr_schedule(self, asset, je_row):
depr_schedule = get_depr_schedule(asset.name, "Active", self.finance_book)
for d in depr_schedule or []:
if (
d.schedule_date == self.posting_date
and not d.journal_entry
and d.depreciation_amount == flt(je_row.debit)
):
frappe.db.set_value("Depreciation Schedule", d.name, "journal_entry", self.name)
def update_asset_on_disposal(self):
if self.voucher_type == "Asset Disposal":
disposed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset"
and d.reference_name
and d.reference_name not in disposed_assets
):
frappe.db.set_value(
"Asset",
d.reference_name,
{
"disposal_date": self.posting_date,
"journal_entry_for_scrap": self.name,
},
)
asset_doc = frappe.get_doc("Asset", d.reference_name)
asset_doc.set_status()
disposed_assets.append(d.reference_name)
def update_inter_company_jv(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
@@ -450,25 +580,6 @@ class JournalEntry(AccountsController):
if status:
inv_disc_doc.set_status(status=status)
def update_booked_depreciation(self, cancel=0):
for d in self.get("accounts"):
if (
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
and d.debit
):
asset = frappe.get_doc("Asset", d.reference_name)
for fb_row in asset.get("finance_books"):
if fb_row.finance_book == self.finance_book:
if cancel:
fb_row.total_number_of_booked_depreciations -= 1
else:
fb_row.total_number_of_booked_depreciations += 1
fb_row.db_update()
break
def unlink_advance_entry_reference(self):
for d in self.get("accounts"):
if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
@@ -518,9 +629,9 @@ class JournalEntry(AccountsController):
fb_row = asset.get("finance_books")[fb_idx - 1]
fb_row.value_after_depreciation += d.debit
fb_row.db_update()
else:
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
asset.set_status()
asset.set_total_booked_depreciations()
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
journal_entry_for_scrap = frappe.db.get_value(
"Asset", d.reference_name, "journal_entry_for_scrap"
@@ -1137,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"):
@@ -1263,7 +1374,7 @@ class JournalEntry(AccountsController):
@frappe.whitelist()
def get_default_bank_cash_account(
company, account_type=None, mode_of_payment=None, account=None, ignore_permissions=False
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
@@ -1298,15 +1409,14 @@ def get_default_bank_cash_account(
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, ignore_account_permission=ignore_permissions),
"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

@@ -178,8 +178,9 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor)
if loyalty_amount > ref_doc.rounded_total:
frappe.throw(_("You can't redeem Loyalty Points having more value than the Rounded Total."))
total_amount = ref_doc.grand_total if ref_doc.is_rounded_total_disabled() else ref_doc.rounded_total
if loyalty_amount > total_amount:
frappe.throw(_("You can't redeem Loyalty Points having more value than the Total Amount."))
if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount:
ref_doc.loyalty_amount = loyalty_amount

View File

@@ -2,8 +2,26 @@
# See license.txt
import unittest
import frappe
from frappe.tests import IntegrationTestCase
class TestModeofPayment(IntegrationTestCase):
pass
def set_default_account_for_mode_of_payment(mode_of_payment, company, account):
mode_of_payment.reload()
if frappe.db.exists(
"Mode of Payment Account", {"parent": mode_of_payment.mode_of_payment, "company": company}
):
frappe.db.set_value(
"Mode of Payment Account",
{"parent": mode_of_payment.mode_of_payment, "company": company},
"default_account",
account,
)
return
mode_of_payment.append("accounts", {"company": company, "default_account": account})
mode_of_payment.save()

View File

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

View File

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

@@ -410,7 +410,7 @@ frappe.ui.form.on("Payment Entry", {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
group_by: "",
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

View File

@@ -21,7 +21,6 @@
"party_name",
"book_advance_payments_in_separate_party_account",
"reconcile_on_advance_payment_date",
"advance_reconciliation_takes_effect_on",
"column_break_11",
"bank_account",
"party_bank_account",
@@ -754,18 +753,9 @@
"options": "No\nYes",
"print_hide": 1,
"search_index": 1
},
{
"default": "Oldest Of Invoice Or Advance",
"fetch_from": "company.reconciliation_takes_effect_on",
"fieldname": "advance_reconciliation_takes_effect_on",
"fieldtype": "Select",
"hidden": 1,
"label": "Advance Reconciliation Takes Effect On",
"no_copy": 1,
"options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [
@@ -777,7 +767,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2025-03-24 16:18:19.920701",
"modified": "2025-05-08 11:18:10.238085",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@@ -817,6 +807,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "DESC",

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 (
@@ -80,9 +79,6 @@ class PaymentEntry(AccountsController):
PaymentEntryReference,
)
advance_reconciliation_takes_effect_on: DF.Literal[
"Advance Payment Date", "Oldest Of Invoice Or Advance", "Reconciliation Date"
]
amended_from: DF.Link | None
apply_tax_withholding_amount: DF.Check
auto_repeat: DF.Link | None
@@ -502,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):
@@ -665,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)):
@@ -1574,9 +1570,12 @@ class PaymentEntry(AccountsController):
else:
# For backwards compatibility
# Supporting reposting on payment entries reconciled before select field introduction
if self.advance_reconciliation_takes_effect_on == "Advance Payment Date":
reconciliation_takes_effect_on = frappe.get_cached_value(
"Company", self.company, "reconciliation_takes_effect_on"
)
if reconciliation_takes_effect_on == "Advance Payment Date":
posting_date = self.posting_date
elif self.advance_reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
@@ -1586,7 +1585,7 @@ class PaymentEntry(AccountsController):
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
elif self.advance_reconciliation_takes_effect_on == "Reconciliation Date":
elif reconciliation_takes_effect_on == "Reconciliation Date":
posting_date = nowdate()
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
@@ -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()
@@ -2081,7 +2080,7 @@ class PaymentEntry(AccountsController):
# Re allocate amount to those references which have PR set (Higher priority)
for ref in self.references:
if not ref.payment_request:
if not (ref.reference_doctype and ref.reference_name and ref.payment_request):
continue
# fetch outstanding_amount of `Reference` (Payment Term) and `Payment Request` to allocate new amount
@@ -2132,7 +2131,7 @@ class PaymentEntry(AccountsController):
)
# Re allocate amount to those references which have no PR (Lower priority)
for ref in self.references:
if ref.payment_request:
if ref.payment_request or not (ref.reference_doctype and ref.reference_name):
continue
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
@@ -2448,7 +2447,7 @@ def get_outstanding_reference_documents(args, validate=False):
accounts = get_party_account(
args.get("party_type"), args.get("party"), args.get("company"), include_advance=True
)
advance_account = accounts[1] if len(accounts) >= 1 else None
advance_account = accounts[1] if len(accounts) > 1 else None
if party_account == advance_account:
party_account = accounts[0]
@@ -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
@@ -2966,11 +2960,10 @@ def get_payment_entry(
party_type=None,
payment_type=None,
reference_date=None,
ignore_permissions=False,
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)))
@@ -2988,14 +2981,14 @@ def get_payment_entry(
)
# bank or cash
bank = get_bank_cash_account(doc, bank_account, ignore_permissions=ignore_permissions)
bank = get_bank_cash_account(doc, bank_account)
# if default bank or cash account is not set in company master and party has default company bank account, fetch it
if party_type in ["Customer", "Supplier"] and not bank:
party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
if party_bank_account:
account = frappe.db.get_value("Bank Account", party_bank_account, "account")
bank = get_bank_cash_account(doc, account, ignore_permissions=ignore_permissions)
bank = get_bank_cash_account(doc, account)
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
@@ -3025,6 +3018,8 @@ def get_payment_entry(
party_account_currency if payment_type == "Receive" else bank.account_currency
)
pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
pe.paid_from_account_type = frappe.db.get_value("Account", pe.paid_from, "account_type")
pe.paid_to_account_type = frappe.db.get_value("Account", pe.paid_to, "account_type")
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
@@ -3108,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(
@@ -3304,18 +3299,22 @@ def update_accounting_dimensions(pe, doc):
pe.set(dimension, doc.get(dimension))
def get_bank_cash_account(doc, bank_account, ignore_permissions=False):
def get_bank_cash_account(doc, bank_account):
bank = get_default_bank_cash_account(
doc.company,
"Bank",
mode_of_payment=doc.get("mode_of_payment"),
account=bank_account,
ignore_permissions=ignore_permissions,
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
@@ -3388,26 +3387,25 @@ def set_paid_amount_and_received_amount(
if party_account_currency == bank.account_currency:
paid_amount = received_amount = abs(outstanding_amount)
else:
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
if payment_type == "Receive":
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
if bank and company_currency != bank.account_currency:
received_amount = paid_amount / doc.get("conversion_rate", 1)
else:
received_amount = paid_amount * doc.get("conversion_rate", 1)
# settings if it is for receive
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
if bank and company_currency != bank.account_currency:
# doc currency can be different from bank currency
posting_date = doc.get("posting_date") or doc.get("transaction_date")
conversion_rate = get_exchange_rate(
bank.account_currency, party_account_currency, posting_date
)
received_amount = paid_amount / conversion_rate
else:
if bank and company_currency != bank.account_currency:
paid_amount = received_amount / doc.get("conversion_rate", 1)
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.get("conversion_rate", 1)
received_amount = paid_amount * doc.get("conversion_rate", 1)
# if payment type is pay, then paid amount and received amount are swapped
if payment_type == "Pay":
paid_amount, received_amount = received_amount, paid_amount
return paid_amount, received_amount
@@ -3464,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

@@ -672,7 +672,12 @@ def get_amount(ref_doc, payment_account=None):
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - ref_doc.advance_paid
advance_amount = flt(ref_doc.advance_paid)
if ref_doc.party_account_currency != ref_doc.currency:
advance_amount = flt(flt(ref_doc.advance_paid) / ref_doc.conversion_rate)
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - advance_amount
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if (
dt == "Sales Invoice"

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

@@ -47,7 +47,7 @@ frappe.ui.form.on("Period Closing Voucher", {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
group_by: "",
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

View File

@@ -133,7 +133,12 @@ class PeriodClosingVoucher(AccountsController):
self.make_gl_entries()
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Payment Ledger Entry",
"Account Closing Balance",
)
self.block_if_future_closing_voucher_exists()
self.db_set("gle_processing_status", "In Progress")
self.cancel_gl_entries()

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

@@ -2,8 +2,8 @@
// For license information, please see license.txt
frappe.ui.form.on("POS Closing Entry", {
onload: function (frm) {
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log"];
onload: async function (frm) {
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,8 +36,6 @@ frappe.ui.form.on("POS Closing Entry", {
}
});
set_html_data(frm);
if (frm.doc.docstatus == 1) {
if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.nowdate());
@@ -82,7 +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_invoices"),
() => frappe.dom.unfreeze(),
]);
}
@@ -102,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),
@@ -112,51 +110,14 @@ frappe.ui.form.on("POS Closing Entry", {
user: frm.doc.user,
},
callback: (r) => {
let pos_docs = r.message;
set_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);
},
});
},
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;
}
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);
}
},
}),
]);
frappe.dom.unfreeze();
},
});
frappe.ui.form.on("POS Closing Entry Detail", {
@@ -166,37 +127,37 @@ frappe.ui.form.on("POS Closing Entry Detail", {
},
});
function set_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 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 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", {
@@ -209,47 +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("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("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,17 +20,19 @@
"pos_profile",
"user",
"section_break_12",
"pos_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",
@@ -72,10 +74,12 @@
"label": "User Details"
},
{
"fetch_if_empty": 1,
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
},
{
@@ -84,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
},
{
@@ -99,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",
@@ -121,7 +117,6 @@
"options": "POS Closing Entry Detail"
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.docstatus==0",
"fieldname": "section_break_13",
"fieldtype": "Section Break",
@@ -176,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
},
{
@@ -227,8 +217,41 @@
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
},
{
"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",
"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,
"is_submittable": 1,
"links": [
{
@@ -236,7 +259,7 @@
"link_fieldname": "pos_closing_entry"
}
],
"modified": "2024-03-27 13:10:14.073467",
"modified": "2025-06-06 12:00:31.955176",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
@@ -285,8 +308,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

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,
@@ -28,8 +31,9 @@ class POSClosingEntry(StatusUpdater):
from erpnext.accounts.doctype.pos_closing_entry_taxes.pos_closing_entry_taxes import (
POSClosingEntryTaxes,
)
from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import (
POSInvoiceReference,
from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import POSInvoiceReference
from erpnext.accounts.doctype.sales_invoice_reference.sales_invoice_reference import (
SalesInvoiceReference,
)
amended_from: DF.Link | None
@@ -40,30 +44,53 @@ 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_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.validate_duplicate_pos_invoices()
self.validate_pos_invoices()
def validate_invoice_mode(self):
if self.invoice_type == "POS Invoice":
self.validate_duplicate_pos_invoices()
self.validate_pos_invoices()
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()
self.validate_sales_invoices()
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 = []
@@ -78,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",
@@ -114,13 +141,70 @@ class POSClosingEntry(StatusUpdater):
frappe.throw(error_list, title=_("Invalid POS 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 validate_duplicate_sales_invoices(self):
sales_invoice_occurrences = {}
for idx, inv in enumerate(self.sales_invoices, 1):
sales_invoice_occurrences.setdefault(inv.sales_invoice, []).append(idx)
error_list = []
for key, value in sales_invoice_occurrences.items():
if len(value) > 1:
error_list.append(
_("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value))
)
if error_list:
frappe.throw(error_list, title=_("Duplicate Sales Invoices found"), as_list=True)
def validate_sales_invoices(self):
invalid_rows = []
for d in self.sales_invoices:
invalid_row = {"idx": d.idx}
sales_invoice = frappe.db.get_values(
"Sales Invoice",
d.sales_invoice,
[
"pos_profile",
"docstatus",
"is_pos",
"owner",
"is_created_using_pos",
"is_consolidated",
"pos_closing_entry",
],
as_dict=1,
)[0]
if sales_invoice.pos_closing_entry:
invalid_row.setdefault("msg", []).append(_("Sales Invoice is already consolidated"))
invalid_rows.append(invalid_row)
continue
if sales_invoice.is_pos == 0:
invalid_row.setdefault("msg", []).append(_("Sales Invoice does not have Payments"))
if sales_invoice.is_created_using_pos == 0:
invalid_row.setdefault("msg", []).append(_("Sales Invoice is not created using POS"))
if sales_invoice.pos_profile != self.pos_profile:
invalid_row.setdefault("msg", []).append(
_("POS Profile doesn't match {}").format(frappe.bold(self.pos_profile))
)
if sales_invoice.docstatus != 1:
invalid_row.setdefault("msg", []).append(_("Sales Invoice is not submitted"))
if sales_invoice.owner != self.user:
invalid_row.setdefault("msg", []).append(
_("Sales Invoice isn't created by user {}").format(frappe.bold(self.owner))
)
if invalid_row.get("msg"):
invalid_rows.append(invalid_row)
if not invalid_rows:
return
error_list = []
for row in invalid_rows:
for msg in row.get("msg"):
error_list.append(_("Row #{}: {}").format(row.get("idx"), msg))
frappe.throw(error_list, title=_("Invalid Sales Invoices"), as_list=True)
def on_submit(self):
consolidate_pos_invoices(closing_entry=self)
@@ -130,9 +214,13 @@ class POSClosingEntry(StatusUpdater):
docname=f"POS Opening Entry/{self.pos_opening_entry}",
)
self.update_sales_invoices_closing_entry()
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)
self.update_sales_invoices_closing_entry(cancel=True)
@frappe.whitelist()
def retry(self):
consolidate_pos_invoices(closing_entry=self)
@@ -143,6 +231,12 @@ class POSClosingEntry(StatusUpdater):
opening_entry.set_status()
opening_entry.save()
def update_sales_invoices_closing_entry(self, cancel=False):
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
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -152,23 +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
def get_payments(invoices):
if not len(invoices):
return
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
@@ -184,69 +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
invoices = get_pos_invoices(
data = get_invoices(
closing_entry.period_start_date,
closing_entry.period_end_date,
closing_entry.pos_profile,
closing_entry.user,
)
pos_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 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)
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("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)
@@ -159,6 +171,10 @@ class TestPOSClosingEntry(IntegrationTestCase):
"""
create_dimension()
location = frappe.get_doc("Accounting Dimension", "Location")
location.dimension_defaults[0].mandatory_for_bs = True
location.save()
pos_profile = make_pos_profile(do_not_insert=1, do_not_set_accounting_dimension=1)
self.assertRaises(frappe.ValidationError, pos_profile.insert)
@@ -196,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`")
@@ -289,6 +302,172 @@ 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):
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()
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
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_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)
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):
user = "test@example.com"
@@ -323,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

@@ -323,3 +323,15 @@ frappe.ui.form.on("POS Invoice", {
});
},
});
frappe.ui.form.on("Sales Invoice Payment", {
mode_of_payment: function (frm) {
frappe.call({
doc: frm.doc,
method: "set_account_for_mode_of_payment",
callback: function (r) {
refresh_field("payments");
},
});
},
});

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _, bold
from frappe.model.mapper import map_child_doc, map_doc
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
from frappe.utils.nestedset import get_descendants_of
@@ -17,13 +18,10 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
)
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.controllers.queries import item_query as _item_query
from erpnext.controllers.sales_and_purchase_return import get_sales_invoice_item_from_consolidated_invoice
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class PartialPaymentValidationError(frappe.ValidationError):
pass
class POSInvoice(SalesInvoice):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -197,6 +195,7 @@ class POSInvoice(SalesInvoice):
# run on validate method of selling controller
super(SalesInvoice, self).validate()
self.validate_pos_opening_entry()
self.validate_is_pos_using_sales_invoice()
self.validate_auto_set_posting_time()
self.validate_mode_of_payment()
self.validate_uom_is_integer("stock_uom", "stock_qty")
@@ -244,6 +243,9 @@ class POSInvoice(SalesInvoice):
update_coupon_code_count(self.coupon_code, "used")
self.clear_unallocated_mode_of_payments()
if self.is_return and self.invoice_type_in_pos == "Sales Invoice":
self.create_and_add_consolidated_sales_invoice()
def before_cancel(self):
if (
self.consolidated_invoice
@@ -274,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
@@ -287,6 +291,47 @@ class POSInvoice(SalesInvoice):
sip = frappe.qb.DocType("Sales Invoice Payment")
frappe.qb.from_(sip).delete().where(sip.parent == self.name).where(sip.amount == 0).run()
def create_and_add_consolidated_sales_invoice(self):
sales_inv = self.create_return_sales_invoice()
self.db_set("consolidated_invoice", sales_inv.name)
self.set_status(update=True)
def create_return_sales_invoice(self):
return_sales_invoice = frappe.new_doc("Sales Invoice")
return_sales_invoice.is_pos = 1
return_sales_invoice.is_return = 1
map_doc(self, return_sales_invoice, table_map={"doctype": return_sales_invoice.doctype})
return_sales_invoice.is_created_using_pos = 1
return_sales_invoice.is_consolidated = 1
return_sales_invoice.return_against = frappe.db.get_value(
"POS Invoice", self.return_against, "consolidated_invoice"
)
items, taxes, payments = [], [], []
for d in self.items:
si_item = map_child_doc(d, return_sales_invoice, {"doctype": "Sales Invoice Item"})
si_item.pos_invoice = self.name
si_item.pos_invoice_item = d.name
si_item.sales_invoice_item = get_sales_invoice_item_from_consolidated_invoice(
self.return_against, d.pos_invoice_item
)
items.append(si_item)
for d in self.get("taxes"):
tax = map_child_doc(d, return_sales_invoice, {"doctype": "Sales Taxes and Charges"})
taxes.append(tax)
for d in self.get("payments"):
payment = map_child_doc(d, return_sales_invoice, {"doctype": "Sales Invoice Payment"})
payments.append(payment)
return_sales_invoice.set("items", items)
return_sales_invoice.set("taxes", taxes)
return_sales_invoice.set("payments", payments)
return_sales_invoice.save()
return_sales_invoice.submit()
return return_sales_invoice
def delink_serial_and_batch_bundle(self):
for row in self.items:
if row.serial_and_batch_bundle:
@@ -378,6 +423,11 @@ class POSInvoice(SalesInvoice):
title=_("Item Unavailable"),
)
def validate_is_pos_using_sales_invoice(self):
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):
error_msg = []
for d in self.get("items"):
@@ -502,20 +552,6 @@ class POSInvoice(SalesInvoice):
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
validate_loyalty_points(self, self.loyalty_points)
def validate_full_payment(self):
invoice_total = flt(self.rounded_total) or flt(self.grand_total)
if self.docstatus == 1:
if self.is_return and self.paid_amount != invoice_total:
frappe.throw(
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError
)
if self.paid_amount < invoice_total:
frappe.throw(
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError
)
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get("amended_from"):

View File

@@ -7,8 +7,12 @@ import frappe
from frappe import _
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.pos_invoice.pos_invoice import PartialPaymentValidationError, make_sales_return
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.sales_invoice.sales_invoice import PartialPaymentValidationError
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -25,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`")
@@ -32,7 +37,15 @@ 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":
@@ -235,12 +248,8 @@ class TestPOSInvoice(IntegrationTestCase):
pos = create_pos_invoice(qty=10, do_not_save=True)
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 500})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 500, "default": 1})
pos.insert()
pos.submit()
@@ -279,9 +288,7 @@ class TestPOSInvoice(IntegrationTestCase):
do_not_save=1,
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Cash", "amount": 1000, "default": 1})
pos.insert()
pos.submit()
@@ -322,9 +329,7 @@ class TestPOSInvoice(IntegrationTestCase):
do_not_save=1,
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2000, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Cash", "amount": 2000, "default": 1})
pos.insert()
pos.submit()
@@ -335,9 +340,7 @@ class TestPOSInvoice(IntegrationTestCase):
# partial return 1
pos_return1.get("items")[0].qty = -1
pos_return1.set("payments", [])
pos_return1.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
)
pos_return1.append("payments", {"mode_of_payment": "Cash", "amount": -1000, "default": 1})
pos_return1.paid_amount = -1000
pos_return1.submit()
pos_return1.reload()
@@ -354,9 +357,7 @@ class TestPOSInvoice(IntegrationTestCase):
# partial return 2
pos_return2 = make_sales_return(pos.name)
pos_return2.set("payments", [])
pos_return2.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
)
pos_return2.append("payments", {"mode_of_payment": "Cash", "amount": -1000, "default": 1})
pos_return2.paid_amount = -1000
pos_return2.submit()
@@ -376,10 +377,8 @@ class TestPOSInvoice(IntegrationTestCase):
)
pos.set("payments", [])
pos.append("payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50})
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 60, "default": 1})
pos.insert()
pos.submit()
@@ -397,7 +396,7 @@ class TestPOSInvoice(IntegrationTestCase):
pos_inv = create_pos_invoice(rate=10000, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 9000},
{"mode_of_payment": "Cash", "amount": 9000},
)
pos_inv.insert()
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
@@ -429,9 +428,7 @@ class TestPOSInvoice(IntegrationTestCase):
do_not_save=1,
)
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
pos.insert()
pos.submit()
@@ -450,9 +447,7 @@ class TestPOSInvoice(IntegrationTestCase):
do_not_save=1,
)
pos2.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
)
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
@@ -502,9 +497,7 @@ class TestPOSInvoice(IntegrationTestCase):
do_not_save=1,
)
pos2.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
)
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
@@ -569,9 +562,7 @@ class TestPOSInvoice(IntegrationTestCase):
)
pos.get("items")[0].has_serial_no = 1
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Cash", "amount": 1000, "default": 1})
pos = pos.save().submit()
# make a return
@@ -617,7 +608,7 @@ class TestPOSInvoice(IntegrationTestCase):
inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
{"mode_of_payment": "Cash", "amount": 10000},
)
inv.insert()
inv.submit()
@@ -649,7 +640,7 @@ class TestPOSInvoice(IntegrationTestCase):
pos_inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
{"mode_of_payment": "Cash", "amount": 10000},
)
pos_inv.paid_amount = 10000
pos_inv.submit()
@@ -664,7 +655,7 @@ class TestPOSInvoice(IntegrationTestCase):
inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor
inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000 - inv.loyalty_amount},
{"mode_of_payment": "Cash", "amount": 10000 - inv.loyalty_amount},
)
inv.paid_amount = 10000
inv.submit()
@@ -685,12 +676,12 @@ class TestPOSInvoice(IntegrationTestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 270})
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270})
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.append("payments", {"mode_of_payment": "Cash", "amount": 3200})
pos_inv2.save()
pos_inv2.submit()
@@ -711,7 +702,7 @@ class TestPOSInvoice(IntegrationTestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
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.append("payments", {"mode_of_payment": "Cash", "amount": 300})
pos_inv.append(
"taxes",
{
@@ -728,7 +719,7 @@ class TestPOSInvoice(IntegrationTestCase):
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
pos_inv2.additional_discount_percentage = 10
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 540})
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 540})
pos_inv2.append(
"taxes",
{
@@ -766,7 +757,7 @@ class TestPOSInvoice(IntegrationTestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
pos_inv.append(
"taxes",
{
@@ -781,7 +772,7 @@ class TestPOSInvoice(IntegrationTestCase):
self.assertRaises(frappe.ValidationError, pos_inv.submit)
pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 400})
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 400})
pos_inv2.append(
"taxes",
{
@@ -826,7 +817,7 @@ class TestPOSInvoice(IntegrationTestCase):
pos_inv1 = create_pos_invoice(item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1)
pos_inv1.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 4500},
{"mode_of_payment": "Cash", "amount": 4500},
)
pos_inv1.items[0].batch_no = batch_no
pos_inv1.save()
@@ -847,7 +838,7 @@ class TestPOSInvoice(IntegrationTestCase):
)
pos_inv2.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3000},
{"mode_of_payment": "Cash", "amount": 3000},
)
pos_inv2.save()
pos_inv2.submit()
@@ -887,7 +878,7 @@ class TestPOSInvoice(IntegrationTestCase):
)
pos_inv1.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300},
{"mode_of_payment": "Cash", "amount": 300},
)
pos_inv1.save()
pos_inv1.submit()

View File

@@ -2,13 +2,13 @@
# For license information, please see license.txt
import hashlib
import json
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc
from frappe.query_builder import DocType
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
from frappe.utils.background_jobs import enqueue, is_job_enqueued
from frappe.utils.scheduler import is_scheduler_inactive
@@ -16,6 +16,7 @@ from frappe.utils.scheduler import is_scheduler_inactive
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.controllers.sales_and_purchase_return import get_sales_invoice_item_from_consolidated_invoice
from erpnext.controllers.taxes_and_totals import ItemWiseTaxDetail
@@ -238,7 +239,7 @@ class POSInvoiceMergeLog(Document):
si_item.pos_invoice = doc.name
si_item.pos_invoice_item = item.name
if doc.is_return:
si_item.sales_invoice_item = get_sales_invoice_item(
si_item.sales_invoice_item = get_sales_invoice_item_from_consolidated_invoice(
doc.return_against, item.pos_invoice_item
)
if item.serial_and_batch_bundle:
@@ -258,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
@@ -303,10 +305,17 @@ class POSInvoiceMergeLog(Document):
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
accounting_dimensions_fields = [d.fieldname for d in accounting_dimensions]
dimension_values = frappe.db.get_value(
"POS Profile", {"name": invoice.pos_profile}, accounting_dimensions_fields, as_dict=1
"POS Profile",
{"name": invoice.pos_profile},
[*accounting_dimensions_fields, "cost_center", "project"],
as_dict=1,
)
for dimension in accounting_dimensions:
dimension_value = dimension_values.get(dimension.fieldname)
dimension_value = (
data[0].get(dimension.fieldname)
if data[0].get(dimension.fieldname)
else dimension_values.get(dimension.fieldname)
)
if not dimension_value and (dimension.mandatory_for_pl or dimension.mandatory_for_bs):
frappe.throw(
@@ -318,6 +327,14 @@ class POSInvoiceMergeLog(Document):
invoice.set(dimension.fieldname, dimension_value)
invoice.set(
"cost_center",
data[0].get("cost_center") if data[0].get("cost_center") else dimension_values.get("cost_center"),
)
invoice.set(
"project", data[0].get("project") if data[0].get("project") else dimension_values.get("project")
)
if self.merge_invoices_based_on == "Customer Group":
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ""
@@ -337,7 +354,7 @@ class POSInvoiceMergeLog(Document):
for doc in invoice_docs:
doc.load_from_db()
inv = sales_invoice
if doc.is_return:
if doc.is_return and credit_notes:
for key, value in credit_notes.items():
if doc.name in value:
inv = key
@@ -446,12 +463,37 @@ def get_invoice_customer_map(pos_invoices):
pos_invoice_customer_map.setdefault(customer, [])
pos_invoice_customer_map[customer].append(invoice)
for customer, invoices in pos_invoice_customer_map.items():
pos_invoice_customer_map[customer] = split_invoices_by_accounting_dimension(invoices)
return pos_invoice_customer_map
def split_invoices_by_accounting_dimension(pos_invoices):
# pos_invoices = {
# {'dim_field1': 'dim_field1_value1', 'dim_field2': 'dim_field2_value1'}: [],
# {'dim_field1': 'dim_field1_value2', 'dim_field2': 'dim_field2_value1'}: []
# }
pos_invoice_accounting_dimensions_map = {}
for invoice in pos_invoices:
dimension_fields = [d.fieldname for d in get_checks_for_pl_and_bs_accounts()]
accounting_dimensions = frappe.db.get_value(
"POS Invoice", invoice.pos_invoice, [*dimension_fields, "cost_center", "project"], as_dict=1
)
accounting_dimensions_dic_hash = hashlib.sha256(
json.dumps(accounting_dimensions).encode()
).hexdigest()
pos_invoice_accounting_dimensions_map.setdefault(accounting_dimensions_dic_hash, [])
pos_invoice_accounting_dimensions_map[accounting_dimensions_dic_hash].append(invoice)
return pos_invoice_accounting_dimensions_map
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)
@@ -468,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:
@@ -532,20 +574,21 @@ def split_invoices(invoices):
def create_merge_logs(invoice_by_customer, closing_entry=None):
try:
for customer, invoices in invoice_by_customer.items():
for _invoices in split_invoices(invoices):
merge_log = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
)
merge_log.posting_time = (
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
)
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
merge_log.set("pos_invoices", _invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
for customer, invoices_acc_dim in invoice_by_customer.items():
for invoices in invoices_acc_dim.values():
for _invoices in split_invoices(invoices):
merge_log = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
)
merge_log.posting_time = (
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
)
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
merge_log.set("pos_invoices", _invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
if closing_entry:
closing_entry.set_status(update=True, status="Submitted")
closing_entry.db_set("error_message", "")
@@ -612,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:
@@ -624,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"))
@@ -633,26 +676,3 @@ def get_error_message(message) -> str:
return message["message"]
except Exception:
return str(message)
def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
try:
SalesInvoice = DocType("Sales Invoice")
SalesInvoiceItem = DocType("Sales Invoice Item")
query = (
frappe.qb.from_(SalesInvoice)
.from_(SalesInvoiceItem)
.select(SalesInvoiceItem.name)
.where(
(SalesInvoice.name == SalesInvoiceItem.parent)
& (SalesInvoice.is_return == 0)
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
)
)
result = query.run(as_dict=True)
return result[0].name if result else None
except Exception:
return None

View File

@@ -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,48 +401,93 @@ 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)
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
def test_separate_consolidated_invoice_for_different_accounting_dimensions(self):
"""
Creating 3 POS Invoices where first POS Invoice has different Cost Center than the other two.
Consolidate the Invoices.
Check whether the first POS Invoice is consolidated with a separate Sales Invoice than the other two.
Check whether the second and third POS Invoice are consolidated with the same Sales Invoice.
"""
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
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)
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_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()
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_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)
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)

View File

@@ -31,7 +31,8 @@
"ignore_pricing_rule",
"allow_rate_change",
"allow_discount_change",
"disable_grand_total_to_default_mop",
"set_grand_total_to_default_mop",
"action_on_new_invoice",
"section_break_23",
"item_groups",
"column_break_25",
@@ -402,12 +403,6 @@
"fieldtype": "Check",
"label": "Print Receipt on Order Complete"
},
{
"default": "0",
"fieldname": "disable_grand_total_to_default_mop",
"fieldtype": "Check",
"label": "Disable auto setting Grand Total to default Payment Mode"
},
{
"fieldname": "project",
"fieldtype": "Link",
@@ -415,8 +410,22 @@
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Project"
},
{
"default": "1",
"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,
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
@@ -442,7 +451,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2025-04-09 11:35:13.779613",
"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]
@@ -40,7 +43,6 @@ class POSProfile(Document):
currency: DF.Link
customer: DF.Link | None
customer_groups: DF.Table[POSCustomerGroup]
disable_grand_total_to_default_mop: DF.Check
disable_rounded_total: DF.Check
disabled: DF.Check
expense_account: DF.Link | None
@@ -56,6 +58,7 @@ class POSProfile(Document):
project: DF.Link | None
select_print_heading: DF.Link | None
selling_price_list: DF.Link | None
set_grand_total_to_default_mop: DF.Check
tax_category: DF.Link | None
taxes_and_charges: DF.Link | None
tc_name: DF.Link | None

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

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