Compare commits

...

562 Commits

Author SHA1 Message Date
Raffael Meyer
927cd173cf fix: typo
(cherry picked from commit 0f2f11cb33)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json
2025-03-03 00:12:51 +00:00
barredterra
216563d1e3 feat: sort timesheets by start time
(cherry picked from commit c82611aa62)

# Conflicts:
#	erpnext/projects/doctype/timesheet/timesheet.py
2025-03-03 00:12:51 +00:00
barredterra
b2bb4d9020 feat: refactor and enhance sales invoice timesheet
(cherry picked from commit 1110f88e5a)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
#	erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json
#	erpnext/projects/doctype/timesheet/timesheet.py
2025-03-03 00:12:51 +00:00
barredterra
fcdf1b4241 feat: add total_billing_hours to Sales Invoice
(cherry picked from commit b57521a337)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json
2025-03-03 00:12:50 +00:00
Diptanil Saha
62c3915ecb refactor: using function to unset grand total to default mode of payment in pos (#46228) 2025-03-03 01:56:23 +05:30
Frappe PR Bot
9078e3798d fix: sync translations from crowdin (#46222)
fix: Bosnian translations
2025-03-02 19:04:06 +01:00
Frappe PR Bot
e803ed72e6 chore: update POT file (#46225) 2025-03-02 13:50:04 +01:00
rohitwaghchaure
5bee30ff2f Merge pull request #46219 from rohitwaghchaure/fixed-support-32834
fix: stock qty not recalculate on changing of the qty
2025-03-02 12:15:32 +05:30
Rohit Waghchaure
464e3339fe fix: stock qty not recalculate on changing of the qty 2025-03-01 21:34:06 +05:30
Frappe PR Bot
912f9bfd8e fix: sync translations from crowdin (#46216)
* fix: French translations

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

* fix: Bosnian translations

* fix: Swedish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-28 10:25:48 +01:00
ruthra kumar
e965c44908 Merge pull request #46169 from frappe/mergify/ruthra-kumar/config-update
ci(Mergify): configuration update
2025-02-28 13:34:29 +05:30
ruthra kumar
0c1e8bbfbb Merge pull request #46186 from rohitwaghchaure/fixed-support-32566
fix: removed mandatory property for the cost center field
2025-02-28 13:33:00 +05:30
Sagar Vora
453bcd7513 Merge pull request #46177 from vishakhdesai/payment-entry-fix 2025-02-28 10:42:26 +05:30
Ankush Menat
ded0aab680 perf: don't track seen for POS Invoice (#46187)
This is a moving doctype. Do people even browse the list view? 

It doesn't make much sense, either. POS INvoices are rarely "reviewed" by multiple users.
2025-02-28 04:57:57 +00:00
Rohit Waghchaure
079cf772aa fix: removed mandatory property for the cost center field 2025-02-28 09:48:19 +05:30
vishakhdesai
2dbef23244 fix: payment entry exchange gain loss issue 2025-02-27 18:18:49 +05:30
ruthra kumar
967f5ac7f3 ci(Mergify): configuration update
Signed-off-by: ruthra kumar <null>
2025-02-27 06:54:24 +05:30
ruthra kumar
fcf374928f Merge pull request #46155 from frappe/l10n_develop
fix: sync translations from crowdin
2025-02-26 13:52:06 +05:30
ruthra kumar
ac640a22b7 Merge pull request #46156 from aerele/autofetch-timesheet
fix: no permission to get project settings in sales invoice
2025-02-26 13:51:01 +05:30
venkat102
221f1468cb fix: no permission to get project settings in sales invoice 2025-02-26 11:01:40 +05:30
Frappe PR Bot
da4b28bf2f fix: Bosnian translations 2025-02-26 08:32:13 +05:30
rohitwaghchaure
5a6e06a140 Merge pull request #46097 from rohitwaghchaure/fixed-valuation-for-batch-sabb
fix: valuation rate for batch
2025-02-25 23:14:00 +05:30
ruthra kumar
45d72cbefc Merge pull request #45908 from aerele/autofetch-timesheet
fix(projects settings): add checkbox to auto fetch timesheet in sales invoice
2025-02-25 22:26:16 +05:30
Smit Vora
fd3fec3912 Merge pull request #46117 from Ninad1306/add_status_in_po_analysis_report 2025-02-25 18:36:41 +05:30
Ninad1306
936d7d4342 fix(report): filter sales / purchase orders based on date filters 2025-02-25 17:13:09 +05:30
Ninad1306
2394e76e7d fix(report): allow Closed sales orders to be visible 2025-02-25 15:59:46 +05:30
Ankush Menat
f62aa8fc57 fix(patch): Ensure SLE indexes (#46131)
Because of the way this change was pushed in parts, some sites don't see
this as "update" and don't have the new indexes.
2025-02-25 09:43:53 +00:00
Frappe PR Bot
8284efdf66 fix: sync translations from crowdin (#46111)
* fix: Spanish translations

* fix: Swedish translations

* fix: Bosnian translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-25 10:21:09 +01:00
Ninad1306
3b2879d3a1 fix(report): allow Closed purchase orders to be visible 2025-02-24 12:42:05 +05:30
Rohit Waghchaure
b88305a95f fix: valuation rate for batch 2025-02-23 22:04:52 +05:30
Frappe PR Bot
987a95d2b5 fix: sync translations from crowdin (#46036)
* fix: Persian translations

* fix: Bosnian translations

* fix: Turkish translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-23 10:38:54 +01:00
Marica
c3b45c933a Merge pull request #45932 from 0xD0M1M0/fix_discount_payment_entry
fix: discount accounting
2025-02-21 20:37:06 +05:30
Ravibharathi
1ec182430d feat: add total weight in shipment (#46049)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-02-21 15:15:21 +01:00
Diptanil Saha
8e6959dfad fix: pos opening entry dialog not saving on change data (#46066) 2025-02-21 17:50:51 +05:30
0xD0M1M0
a91fe5cbb3 feat: add testcase for discount_payment_entry 2025-02-20 17:12:21 +01:00
0xD0M1M0
8c5aea15ac Merge branch 'frappe:develop' into fix_discount_payment_entry 2025-02-20 15:57:51 +01:00
rohitwaghchaure
f68730f444 Merge pull request #46037 from rohitwaghchaure/fixed-support-31955
fix: inventory dimension for maintenance visit
2025-02-20 11:41:41 +05:30
Rohit Waghchaure
cd4ba69262 fix: inventory dimension for maintence visit 2025-02-20 09:05:35 +05:30
Frappe PR Bot
78dedc0773 fix: sync translations from crowdin (#45997) 2025-02-19 14:20:18 +01:00
rohitwaghchaure
75379b79fc Merge pull request #46021 from rohitwaghchaure/fixed-support-31874
fix: incorrect stock value difference for adjustment entry
2025-02-19 16:39:45 +05:30
Khushi Rawat
83cafb892c Merge pull request #46022 from khushi8112/fetch-asset-location-from-purchase-doc
fix: reset location only if there is value in row item location field
2025-02-19 16:30:20 +05:30
Akhil Narang
a1cbcda4c5 Merge pull request #46003 from akhilnarang/fix-xss
fix(send_message): escape HTML in the text
2025-02-19 16:14:19 +05:30
Rohit Waghchaure
df83e427a3 fix: incorrect stock value difference for adjustment entry 2025-02-19 16:02:39 +05:30
Khushi Rawat
7f8d08c8eb fix: reset location only if there is value in row item location field 2025-02-19 16:02:01 +05:30
rohitwaghchaure
8e42764274 Merge pull request #45838 from CaseSolvedUK/item-fix
fix: remove public access to list items
2025-02-19 15:50:13 +05:30
mergify[bot]
65c45a3f5b feat: added ability to use custom html format for process statement of accounts (copy #45746) (#46011)
feat: added ability to use custom html format for process statement of accounts (#45746)

* feat: added ability to use custom print format for process statement of accounts documents.

* fix: handles missing hook issues

* chore: linter changes

---------

Co-authored-by: Boy4099 <mashtawayne4099@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit a0cd08e9ea)

Co-authored-by: Steve Wilson <stevew9009@gmail.com>
2025-02-19 15:44:35 +05:30
Ejaaz Khan
941085000a fix: round sum amount in JE auditing PF (#45961) 2025-02-19 15:40:55 +05:30
Venkatesh
17a2f44290 fix(pos profile): check company while validating mandatory accounting dimension (#45974) 2025-02-19 15:39:01 +05:30
Sanket Shah
24394765a6 fix: handle division by zero error (#45966)
Co-authored-by: Sanket322 <shahsanket322003.com>
2025-02-19 15:37:54 +05:30
Khushi Rawat
fa31a0282e Merge pull request #45898 from nabinhait/asset-depr-schedule-patch
fix: patch for creating asset depreciation schedule records
2025-02-19 15:37:27 +05:30
Khushi Rawat
38e1054eb7 Merge pull request #45872 from khushi8112/set-purchase-doc-row-item-to-asset
fix: enable asset value editing for duplicate item code
2025-02-19 15:30:53 +05:30
Khushi Rawat
2bb79197aa fix: reset location only if there is value in row item location field 2025-02-19 15:04:26 +05:30
Bhavansathru
73e82b7afa fix: fetch child account data for selected parent (#45904)
* fix: fetch child account data for selected parent

* fix: change reference name

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
2025-02-19 13:57:31 +05:30
Khushi Rawat
4821c44227 Merge pull request #45895 from khushi8112/fix-scrapping-of-fully-depreciated-asset
fix: do not reschedule depreciation for fully depreciated asset on scrap
2025-02-19 13:06:39 +05:30
Khushi Rawat
44c1425e73 fix: validate if no matching item found 2025-02-19 13:01:27 +05:30
rohitwaghchaure
4b87610d95 Merge pull request #45750 from mihir-kandoi/st20447
feat: added option to enforce free item qty in pricing rule
2025-02-19 12:49:01 +05:30
Akhil Narang
448a5db20f fix(send_message): escape HTML in the text
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-02-19 12:23:10 +05:30
ruthra kumar
5fed3866b6 Merge pull request #45882 from aerele/quotation-exchange-rate
fix(quotation): fetch exchange rate on currency change
2025-02-19 10:50:38 +05:30
rohitwaghchaure
f05c1d7e7b Merge pull request #45980 from mihir-kandoi/st31872
fix: check if employee is currently working on another workstation
2025-02-18 20:41:22 +05:30
rohitwaghchaure
32f616ad87 Merge pull request #45977 from rohitwaghchaure/fixed-support-31935
fix: millisecond issue for posting datetime
2025-02-18 20:08:15 +05:30
rohitwaghchaure
93a8440908 Merge pull request #45971 from mihir-kandoi/st32087
fix: set sco_qty field of PO to non negative
2025-02-18 19:58:00 +05:30
rohitwaghchaure
be813b5bba Merge pull request #45976 from rohitwaghchaure/fixed-support-32073
fix: slow query
2025-02-18 19:56:37 +05:30
Rohit Waghchaure
ac9e5c0163 fix: millisecond issue for posting datetime 2025-02-18 18:58:45 +05:30
Mihir Kandoi
4487edb255 fix: throw correct exception 2025-02-18 14:49:52 +05:30
Mihir Kandoi
8234e659c8 fix: check if employee is currently working on another workstation 2025-02-18 14:22:39 +05:30
ruthra kumar
927eae79d5 Merge pull request #45765 from aerele/dimensions-in-order-item
fix: add accounting dimensions section in sales order item
2025-02-18 14:10:49 +05:30
Rohit Waghchaure
8cfab57fc8 fix: slow query 2025-02-18 13:52:26 +05:30
rohitwaghchaure
74621eeb1b Merge pull request #45970 from rohitwaghchaure/fixed-support-32040
fix: serial no status for internal transfer delivery note
2025-02-18 13:51:00 +05:30
ruthra kumar
1eea76cbad Merge pull request #45723 from aerele/validate-payment-request-total
fix: validate payment request total of partly paid invoice
2025-02-18 13:11:08 +05:30
Rohit Waghchaure
3333331a3d fix: serial no status for internal transfer delivery note 2025-02-18 12:59:10 +05:30
Mihir Kandoi
dfc3dc4944 fix: set sco_qty field of PO to non negative 2025-02-18 12:37:07 +05:30
ruthra kumar
7c7aa831ec Merge pull request #45965 from frappe/l10n_develop
fix: sync translations from crowdin
2025-02-18 12:12:22 +05:30
Diptanil Saha
70260c8c86 fix: pos checkout button color on dark mode (#45967) 2025-02-18 11:48:04 +05:30
ruthra kumar
c46af238d4 Merge pull request #45687 from aerele/validate_equity_account
fix: add validation to allow equity account
2025-02-18 11:44:34 +05:30
Frappe PR Bot
7e61b67ba8 fix: German translations 2025-02-18 05:48:36 +05:30
Frappe PR Bot
5e8124900f fix: Bosnian translations 2025-02-18 05:48:32 +05:30
Frappe PR Bot
3788339ea0 fix: Swedish translations 2025-02-18 05:48:20 +05:30
Frappe PR Bot
fe47ca8566 fix: sync translations from crowdin (#45927)
* fix: Swedish translations

* fix: Bosnian translations

* fix: Bosnian translations

* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations
2025-02-17 20:07:11 +01:00
Mihir Kandoi
844f1636c0 fix: set default value to 0 as per new logic 2025-02-17 18:47:35 +05:30
Mihir Kandoi
f3d598881c refactor: rename field 2025-02-17 18:21:22 +05:30
rohitwaghchaure
2fcc5e3f55 Merge pull request #45946 from rohitwaghchaure/fixed-support-32008
fix: provision to enable naming series for SABB
2025-02-17 17:00:34 +05:30
Rohit Waghchaure
fe43975cdd fix: provision to enable naming series for SABB 2025-02-17 16:36:55 +05:30
rohitwaghchaure
c2b4a79b5b Merge pull request #45950 from rohitwaghchaure/fixed-bom-creator-issue
fix: remove unused fields
2025-02-17 16:11:55 +05:30
Soham Kulkarni
98cc1558d3 Small UI changes (#45938)
* fix: change erpnext logo and home workpsace icon

* fix: actually change home workspace icon

* fix: typo in hooks.py
2025-02-17 15:40:22 +05:30
Rohit Waghchaure
f206034a79 fix: remove unused fields 2025-02-17 15:40:17 +05:30
0xD0M1M0
6f1bc5225a fix: auto create asset due to message error (#45934)
* fix: auto create asset due to message error

* fix: linters
2025-02-17 15:12:26 +05:30
Diptanil Saha
5506b44b6f fix: improve pos return (#45671)
* fix: pos return validation for zero qty item

* fix: pos return invoice onload ui

* feat: added item qty returned in pos ui

* refactor: removed console log statement

* feat: check return can be made before loading it on pos

* fix: pos edit invoice onload ui

* fix: returned

* refactor: code cleanup
2025-02-17 15:00:56 +05:30
venkat102
5880f1d5c6 fix: enable fetch_timesheet_in_sales_invoice in test 2025-02-17 14:49:32 +05:30
Diptanil Saha
d94802067b fix: disable partial payment in pos (#45752)
* fix: disable partial payment in pos

* test: disable partial payment

* test: removed print statement

* test: using save method to auto calculate paid_amount

* test: paid_amount calculation using save method

* test: added save method to calculate paid_amount

* test: outstanding amount

* test: added test for partial payments in pos invoice

* fix: custom validation error for partial payment

* test: using partial payment validation

* fix: validate only on submit
2025-02-17 14:26:50 +05:30
rohitwaghchaure
6c8cb9717d Merge pull request #45941 from rohitwaghchaure/fixed-support-31066
fix: letter head for quality inspection
2025-02-17 13:57:23 +05:30
Rohit Waghchaure
cdd41373b6 fix: letter head for quality inspection 2025-02-17 13:20:52 +05:30
ruthra kumar
419b149d05 Merge pull request #45630 from ruthra-kumar/remove_acc_balance_field_from_payment_entry
refactor: remove redundant balance fields from Payment Entry
2025-02-17 10:58:02 +05:30
Frappe PR Bot
019f6422be chore: update POT file (#45935) 2025-02-16 12:15:43 +01:00
0xD0M1M0
947a4fb091 fix: only negative for "Pay" 2025-02-15 22:36:34 +01:00
0xD0M1M0
03d515208a fix: discount accounting 2025-02-15 21:23:05 +01:00
rohitwaghchaure
9e4c82e055 Merge pull request #45914 from rohitwaghchaure/fixed-reposting-issue
fix: start reposting button not working
2025-02-14 21:28:20 +05:30
Diptanil Saha
60a5f4f30d fix: pos accounting dimension fieldname error (#45899)
* fix: pos accounting dimension fieldname error

* fix: method to get enabled accounting dimensions

* fix: fetch enabled accounting dimensions

* fix: clear flags for accounting_dimensions_details on_update

* refactor: validation for doctype

* fix: using get_checks_for_pl_and_bs_accounts for accounting dimensions
2025-02-14 17:31:53 +05:30
Diptanil Saha
bee26e046e fix: prevent duplicate pos fields in pos settings (#45873)
fix: restrict duplicate pos fields in pos settings
2025-02-14 17:26:54 +05:30
Frappe PR Bot
e6b6849940 fix: sync translations from crowdin (#45876)
* fix: Swedish translations

* fix: Turkish translations

* fix: Persian translations

* fix: Bosnian translations

* fix: Spanish translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Persian translations

* fix: Bosnian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-14 12:26:34 +01:00
ruthra kumar
6e67a1ba7c Merge pull request #45912 from aerele/add-payment-gateway
fix: include missing payment_gateway parameter in Payment Request URL
2025-02-14 16:44:04 +05:30
Diptanil Saha
f0a6399056 feat: disable auto setting grand total to default mode of payment (#45591) 2025-02-14 16:43:11 +05:30
Rohit Waghchaure
c81ce29e4c fix: start reposting button not working 2025-02-14 15:33:45 +05:30
rohitwaghchaure
0a364ed1c9 Merge pull request #45903 from rohitwaghchaure/fixed-support-31510
fix: allow scrap item with zero qty
2025-02-14 15:15:55 +05:30
Navin-S-R
dbac8cfc94 fix: include missing payment_gateway parameter in Payment Request URL 2025-02-14 13:14:27 +05:30
venkat102
43b13b91be fix: check value as int 2025-02-13 23:24:05 +05:30
venkat102
914ad357fd fix(sales invoice): check fetch_timesheet_in_sales_invoice enabled before fetching the timesheet 2025-02-13 23:07:45 +05:30
venkat102
876082ea2f fix(project settings): add checkbox to auto fetch timesheet in sales invoice 2025-02-13 23:05:34 +05:30
Rohit Waghchaure
706cb64279 fix: allow scrap item with zero qty 2025-02-13 19:10:21 +05:30
Nabin Hait
7324dcb7c8 fix: patch for creating asset depreciation schedule records 2025-02-13 18:02:56 +05:30
Khushi Rawat
fd4c4f98fa fix: do not reschedule depreciation for fully depreciated asset on scrap 2025-02-13 14:21:29 +05:30
ruthra kumar
a81867bc43 Merge pull request #45804 from aerele/auto-email-multiselect
fix(report): add options to multiselectlist fields
2025-02-13 14:16:16 +05:30
ruthra kumar
6886f5ef41 Merge pull request #45874 from ruthra-kumar/incorrect_taxable_amount_on_other_charges_calculation
fix: incorrect taxable_amount on `other_charges_calculation`
2025-02-13 13:08:56 +05:30
Diptanil Saha
72f8fc8a7b style: pos checkout button color (#45890)
style: pos checkout button color
2025-02-13 00:09:21 +05:30
rohitwaghchaure
5f554e1ec3 Merge pull request #45786 from barredterra/skip-warning-for-free-items
fix: skip warning for free items
2025-02-12 14:48:25 +05:30
venkat102
2f77a8bed1 fix(quotation): fetch exchange rate on currency change 2025-02-12 12:54:30 +05:30
rohitwaghchaure
b839094551 Merge pull request #45879 from rohitwaghchaure/fixed-SABB-naming
fix: changed naming series to random for SABB
2025-02-12 10:19:17 +05:30
Rohit Waghchaure
a007dc285d fix: changed naming series to random for SABB 2025-02-12 09:55:20 +05:30
rohitwaghchaure
3e990153a4 Merge pull request #45865 from mihir-kandoi/st30672-2
fix: dont update rate of free item on save
2025-02-12 08:42:19 +05:30
rohitwaghchaure
591ad7641f Merge pull request #45875 from mihir-kandoi/serial-batch-bundle-precision-fix
fix: add precision in serial_batch_bundle.py
2025-02-12 08:35:29 +05:30
Mihir Kandoi
4bf85d1a5a fix: add precision in serial_batch_bundle.py 2025-02-11 22:51:17 +05:30
ruthra kumar
6ab2106322 fix: incorrect taxable_amount on other_charges_calculation 2025-02-11 17:52:15 +05:30
rohitwaghchaure
c522071b58 Merge pull request #45862 from rohitwaghchaure/fixed-support-31345
fix: remove serial no if qty is zero
2025-02-11 16:56:56 +05:30
rohitwaghchaure
5df8609b33 Merge pull request #45869 from rohitwaghchaure/fixed-support-31464
fix: stock reco current valuation rate
2025-02-11 16:53:04 +05:30
Mihir Kandoi
4dcac56486 fix: add is_new in if condition 2025-02-11 16:29:01 +05:30
Khushi Rawat
da1b4cb9ab fix: link correct row item of purchase doc 2025-02-11 16:17:42 +05:30
Rohit Waghchaure
8d8f3afb39 fix: stock reco current valuation rate 2025-02-11 16:14:39 +05:30
Khushi Rawat
8af9dcb33e fix: make purchase_receipt_item and purchase_invoice_item fields of data type 2025-02-11 16:05:02 +05:30
Diptanil Saha
b95b13ecd8 fix: added validation for required invoice_fields in POS (#45780)
fix: added missing validation for required invoice_fields
2025-02-11 16:04:36 +05:30
Mihir Kandoi
6591e76a63 fix: dont update rate of free item on save 2025-02-11 15:32:35 +05:30
Smit Vora
c33c6b6560 Merge pull request #45532 from ljain112/fix-lcv-gl
fix: correct amt in account currency for lcv with manually distributed charges.
2025-02-11 15:32:01 +05:30
Smit Vora
0fdaa541f1 Merge pull request #45772 from ljain112/po-portal
fix: Party name in Supplier Portal for Purchase Order
2025-02-11 15:01:27 +05:30
Smit Vora
0bae273d41 fix: do not allow "Finance Book" in Accounting Dimensions (#45696) 2025-02-11 14:55:32 +05:30
Lakshit Jain
494310293c fix(regional): removed payment schedule validation in sales invoice for italy (#45852) 2025-02-11 14:50:55 +05:30
Rohit Waghchaure
3a4ae8c463 fix: remove serial no if qty is zero 2025-02-11 13:31:24 +05:30
ruthra kumar
110412d8ad Merge pull request #45794 from ljain112/fix-gl-rev
fix: correct amount in transaction currency for reverse gl entries
2025-02-11 12:59:24 +05:30
ruthra kumar
675b3330d9 Merge pull request #45792 from ljain112/fix-gl-val
fix: do not validate party against Receivable and Payable account for cancelled gl entries
2025-02-11 12:13:40 +05:30
ruthra kumar
8296e28689 Merge pull request #45839 from frappe/l10n_develop
fix: sync translations from crowdin
2025-02-11 10:39:43 +05:30
ruthra kumar
3977a7a06e Merge pull request #45781 from Sanket322/total_row_in_non_group
fix: Added Total Row for `Gross Profit` Report in Non-Grouped Invoices
2025-02-11 10:20:08 +05:30
ruthra kumar
1c725dee5d Merge pull request #45832 from ruthra-kumar/fix_migration_issue_for_reconciliation_sync
fix: possible model sync issue
2025-02-11 09:54:48 +05:30
Frappe PR Bot
e8871257a2 fix: Bosnian translations 2025-02-11 03:39:50 +05:30
Frappe PR Bot
a11b8db389 fix: Turkish translations 2025-02-11 03:39:41 +05:30
Frappe PR Bot
3c8214827c fix: Swedish translations 2025-02-11 03:39:37 +05:30
CaseSolved
2bd596ee3d fix: remove public access to list items 2025-02-10 20:46:41 +00:00
ruthra kumar
0069581aa3 fix: possible model sync issue 2025-02-10 17:16:09 +05:30
Raffael Meyer
cbcdc5e7e5 Merge pull request #45745 from HenningWendtland/buying-project
fix: map project from rfq to supplier quotation
2025-02-10 12:05:51 +01:00
Diptanil Saha
0b9c28620f fix: pos numpad editable action buttons (#45823) 2025-02-10 16:23:54 +05:30
HenningWendtland
8fa39bec61 fix: add project field map from mr to rfq 2025-02-10 11:45:58 +01:00
rohitwaghchaure
03290ef45f Merge pull request #45774 from DaizyModi/fix-attribute-error
fix: AttributeError in `get_item_details` when selecting/scanning batch in Delivery Note
2025-02-10 15:47:19 +05:30
ruthra kumar
71a507d30b Merge pull request #45747 from Sanket322/valid_response_json
fix: Handle Empty JSON in Report Parsing
2025-02-10 11:42:01 +05:30
ruthra kumar
e6176db2c9 Merge pull request #45809 from frappe/l10n_develop
fix: sync translations from crowdin
2025-02-10 11:01:56 +05:30
ruthra kumar
bcad6cb757 Merge pull request #45762 from ruthra-kumar/sync_received_and_paid_if_unset
refactor: set received and paid amount based on each other, if unset
2025-02-10 10:36:43 +05:30
ruthra kumar
95f6b586ff Merge pull request #45793 from asmitahase/employee-image
fix: unable to remove image from employee
2025-02-10 10:32:12 +05:30
ruthra kumar
c636cc33b3 refactor: remove paid_to_account_balance 2025-02-10 10:16:19 +05:30
ruthra kumar
9b5c4a0144 refactor: remove paid_from_account_balance 2025-02-10 10:16:04 +05:30
ruthra kumar
1bb6cd33a3 refactor: remove party_balance 2025-02-10 10:15:39 +05:30
rohitwaghchaure
4178154b19 Merge pull request #45810 from rohitwaghchaure/fixed-support-31257
fix: not able to select the item in the sales invoice
2025-02-10 10:08:50 +05:30
Rohit Waghchaure
35388e7a04 fix: not able to select the item in the sales invoice 2025-02-10 09:27:36 +05:30
Frappe PR Bot
91f6c65c0a fix: Esperanto translations 2025-02-10 03:17:28 +05:30
Frappe PR Bot
7137331355 fix: German translations 2025-02-10 03:17:25 +05:30
Frappe PR Bot
5e2879e9ea fix: Bosnian translations 2025-02-10 03:17:22 +05:30
Frappe PR Bot
06a93ffc80 fix: Persian translations 2025-02-10 03:17:19 +05:30
Frappe PR Bot
862ca7ddd3 fix: Chinese Simplified translations 2025-02-10 03:17:16 +05:30
Frappe PR Bot
e4530232e4 fix: Turkish translations 2025-02-10 03:17:12 +05:30
Frappe PR Bot
ed7ad79c2c fix: Swedish translations 2025-02-10 03:17:09 +05:30
Frappe PR Bot
ec9af4ae29 fix: Russian translations 2025-02-10 03:17:06 +05:30
Frappe PR Bot
5c002df014 fix: Polish translations 2025-02-10 03:17:03 +05:30
Frappe PR Bot
814be2b80a fix: Hungarian translations 2025-02-10 03:17:00 +05:30
Frappe PR Bot
b0bb3ca798 fix: Arabic translations 2025-02-10 03:16:57 +05:30
Frappe PR Bot
7e752756cf fix: Spanish translations 2025-02-10 03:16:54 +05:30
Frappe PR Bot
613c2a0379 fix: French translations 2025-02-10 03:16:50 +05:30
Frappe PR Bot
e2b586d5a3 fix: sync translations from crowdin (#45798)
* fix: Swedish translations

* fix: Bosnian translations

* fix: Swedish translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-09 13:47:15 +01:00
Frappe PR Bot
b0d197119f chore: update POT file (#45805) 2025-02-09 11:20:14 +01:00
venkat102
8785342fce fix(report): add options to multiselectlist fields 2025-02-08 22:55:20 +05:30
ljain112
0809e00455 fix: do not validate party against Receivable and Payable account for cancelled gl entries 2025-02-07 18:30:26 +05:30
ljain112
6077c248b0 fix: correct amount in tansaction currency for reverse gl entries 2025-02-07 17:48:45 +05:30
Asmita Hase
0207d2d7b6 fix: unable to remove image from employee
fix: employee image disappears when newly created user_id is linked to employee
2025-02-07 17:27:44 +05:30
ruthra kumar
7b955b2ea6 Merge pull request #45725 from karm1000/set_max_date
fix: set max_date range for date of birth to today in employee
2025-02-07 16:15:10 +05:30
barredterra
772776ad8a fix: skip warning for free items 2025-02-07 11:43:25 +01:00
ruthra kumar
0df18daf10 Merge pull request #45644 from aerele/update-party-balance
fix: add allow_on_submit for party_balance, paid_from_account_balance and paid_to_account_balance
2025-02-07 16:10:17 +05:30
ruthra kumar
8f3cc6af16 Merge pull request #45640 from aerele/repost-accounting-ledger-payment-entry
feat: add repost accounting ledger entry for payment entry
2025-02-07 16:09:42 +05:30
harshpwctech
88e68bb803 Bug fix in email_campaign's update_status function. (#45679)
During the scheduler event of set_email_campaign_status, the function calling update_status isn't saving the modified status field.
2025-02-07 13:13:19 +05:30
ruthra kumar
b80b5574d3 Merge pull request #45639 from ruthra-kumar/incorrect_tds_on_0_rate_ldc
fix: '0' rate LDC's Invoice net totals should be ignored
2025-02-07 13:12:53 +05:30
Sanket322
2d32ddacc3 fix: add total row in non_grouped_invoices 2025-02-07 12:37:33 +05:30
ruthra kumar
0cdd346f8f test: ldc @ 0 rate 2025-02-07 12:18:01 +05:30
Frappe PR Bot
3d5b52f3bf fix: sync translations from crowdin (#45776)
* fix: Swedish translations

* fix: Bosnian translations
2025-02-07 00:17:18 +01:00
rohitwaghchaure
c83398471c Merge pull request #45767 from rohitwaghchaure/fixed-support-30947
fix: the project document timed out while opening
2025-02-06 22:07:22 +05:30
DaizyModi
820b32eb8a fix: Attibute error selling_price_list 2025-02-06 21:54:57 +05:30
ljain112
fc8663421b fix: Party name in Supplier Portal for Purchase Order 2025-02-06 18:37:22 +05:30
Rohit Waghchaure
33d03b1542 fix: the project document timed out while opening 2025-02-06 16:04:51 +05:30
rohitwaghchaure
fd8c78628f Merge pull request #45763 from rohitwaghchaure/fixed-support-30415
fix: stock reservation not working for sales invoice with update stock
2025-02-06 15:05:07 +05:30
Rohit Waghchaure
0c9d0ea1f4 fix: stock reservation not working for sales invoice with update stock 2025-02-06 14:46:19 +05:30
ruthra kumar
99e721e622 refactor: set paid amount based on received amount if unset 2025-02-06 14:15:24 +05:30
ruthra kumar
5ff540bd82 refactor: set received amount based on paid amount 2025-02-06 14:00:48 +05:30
Sugesh393
7d47869f4b fix: add accounting dimensions section in sales order item 2025-02-06 13:31:10 +05:30
Sugesh393
f8472c32d9 test: add unit test to validate payment request grand_total for partly paid invoice 2025-02-06 11:59:29 +05:30
ruthra kumar
325c4e3536 fix: '0' rate LDC's Invoice net totals should be ignored 2025-02-06 11:05:04 +05:30
Frappe PR Bot
52c687ecc9 fix: sync translations from crowdin (#45756)
* fix: Swedish translations

* fix: Turkish translations

* fix: Bosnian translations
2025-02-05 23:32:06 +01:00
Mihir Kandoi
ac3259b8f1 test: added test 2025-02-05 21:34:19 +05:30
Mihir Kandoi
366ae85d85 fix: tests 2025-02-05 19:18:01 +05:30
Mihir Kandoi
19c01b1457 feat: added option to enforce free item qty in pricing rule 2025-02-05 18:48:52 +05:30
Sanket322
133e0417b8 fix: handle response when json is None 2025-02-05 17:41:07 +05:30
HenningWendtland
d0479036bb fix: map project from rfq to supplier quotation 2025-02-05 12:11:13 +01:00
rohitwaghchaure
54a8d41f88 Merge pull request #45739 from mihir-kandoi/st30984
fix: create job card with wip warehouse set to source warehouse if material transfer to wip warehouse is skipped in work order
2025-02-05 16:39:11 +05:30
rohitwaghchaure
e72687d3eb Merge pull request #45741 from rohitwaghchaure/fixed-removed-unused-field
fix: removed unused field
2025-02-05 16:38:45 +05:30
ruthra kumar
fb7bba8cd0 Merge pull request #45604 from aerele/item_tax_template
fix: filter the item tax template using the input text
2025-02-05 16:30:07 +05:30
rohitwaghchaure
ab486d2515 Merge pull request #45734 from mihir-kandoi/st31003
fix: added correct options for incoming_rate field of delivery note item
2025-02-05 16:16:42 +05:30
Rohit Waghchaure
2d7a576da5 fix: removed unused field 2025-02-05 16:11:44 +05:30
rohitwaghchaure
6a2f2ae3fe Merge pull request #45692 from mihir-kandoi/st30672
fix: fetch rate from item price list when document is saved
2025-02-05 15:52:37 +05:30
Mihir Kandoi
723e902470 fix: create job card with wip warehouse set to source warehouse if material transfer to wip warehouse is skipped in work order 2025-02-05 15:48:08 +05:30
Khushi Rawat
f6fca3acec Merge pull request #45735 from khushi8112/asset-value-adjustment-cancellation-issue
fix: set asset value correctly after cancelling value adjustment
2025-02-05 15:25:47 +05:30
ruthra kumar
218670a720 Merge pull request #45717 from iamejaaz/30567-purchase-invoice-duplicate
fix(Purchase Invoice): default payment terms template selected while duplicating
2025-02-05 15:16:57 +05:30
Khushi Rawat
fee3846144 fix: set asset value correctly after cancelling value adjustment 2025-02-05 15:06:55 +05:30
Mihir Kandoi
417bf49a8d fix: added correct options for incoming_rate field of delivery note item 2025-02-05 14:55:51 +05:30
ruthra kumar
54279ee21d Merge pull request #45590 from aerele/tax-withholding-category
fix: remove tds account in taxes table on change of Tax Withholding C…
2025-02-05 14:08:29 +05:30
ruthra kumar
98e727a061 Merge pull request #45447 from aerele/pricing-rule-pos-qty
fix(pos): add item in the existing item row when discount is applied
2025-02-05 13:59:08 +05:30
ruthra kumar
79d4852d7d Merge pull request #45686 from aerele/process-statement-of-accounts
fix: allow multiple email ids
2025-02-05 13:48:57 +05:30
Mihir Kandoi
07adfadd58 test: added test 2025-02-05 13:12:25 +05:30
ruthra kumar
7f34b490f4 Merge pull request #45610 from ljain112/fix-pe-inv
fix: respect user set account if not advance account for getting outsanding invoices in payment entry
2025-02-05 12:09:16 +05:30
ruthra kumar
85378f9d1a chore: fix typo 2025-02-05 11:52:52 +05:30
Sugesh393
899c18df18 fix: validate payment request total of partly paid invoice 2025-02-05 11:00:55 +05:30
Frappe PR Bot
848b98e6d6 fix: sync translations from crowdin (#45720)
* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-05 00:22:13 +01:00
rohitwaghchaure
b57a2612ce Merge pull request #45678 from mihir-kandoi/45572-2
fix: copy correct uom from delivery note when creating packing list
2025-02-05 00:35:19 +05:30
rohitwaghchaure
0edba914fa Merge pull request #45710 from mihir-kandoi/30355
fix: show only items with inspection enabled on create QI dialog
2025-02-05 00:33:39 +05:30
Ejaaz Khan
bfc01441a0 refactor: remove log 2025-02-05 00:09:16 +05:30
Ejaaz Khan
fb3f08a441 fix: payment schedule table is empty while duplicating record 2025-02-05 00:06:48 +05:30
Ejaaz Khan
18127603fe fix: default payment terms template selected while duplicating 2025-02-04 23:39:29 +05:30
Karm Soni
cd87ad0613 fix: set max_date range for date of birth to today in employee 2025-02-04 19:22:51 +05:30
Mihir Kandoi
ffd10d1fe9 fix: semgrep 2025-02-04 17:50:26 +05:30
Aayush Dalal
d1c927530e fix: handling company in bank reconciliation tool (#45582)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-02-04 11:44:01 +00:00
Mihir Kandoi
c92ec312b9 fix: show only items with inspection enabled on create QI dialog 2025-02-04 16:33:27 +05:30
rohitwaghchaure
3947b5943e Merge pull request #45701 from mihir-kandoi/st30429
fix: subcontracting validation precision issue
2025-02-04 15:23:03 +05:30
rohitwaghchaure
ab2d6fdefd Merge pull request #45698 from rohitwaghchaure/feat-report-incorrect-sabb
feat: report to find incorrect SABB
2025-02-04 15:15:41 +05:30
Diptanil Saha
a20116816e fix: pos payment cash shortcut decimal (#45702) 2025-02-04 15:08:37 +05:30
Rohit Waghchaure
7e24395e00 feat: report to find incorrect SABB 2025-02-04 13:47:55 +05:30
Mihir Kandoi
8720d412bd fix: subcontracting valiation precision issue 2025-02-04 13:21:09 +05:30
Diptanil Saha
51a65899ec fix: point of sale padding (#45697) 2025-02-04 13:09:27 +05:30
ljain112
a44be73a98 fix: do not allow "Finance Book" in Accounting Dimensions 2025-02-04 12:24:15 +05:30
Shariq Ansari
2482a3a205 Merge pull request #45694 from shariquerik/cannot-create-customer 2025-02-04 12:10:19 +05:30
Shariq Ansari
716edeb465 fix: only system manager was able to create customer & prospect 2025-02-04 11:54:05 +05:30
Mihir Kandoi
fee318a275 fix: logical error failing tests 2025-02-04 11:46:11 +05:30
rethik
f82837a4a2 fix: remove party type from validate 2025-02-04 11:07:41 +05:30
Sudharsanan11
9950e4aa0c fix: check billing address 2025-02-04 10:59:10 +05:30
Mihir Kandoi
1e4b9fbdf0 fix: fetch rate from item price list when document is saved 2025-02-04 10:54:57 +05:30
Frappe PR Bot
4c39dc5493 fix: sync translations from crowdin (#45691) 2025-02-04 01:35:52 +01:00
rohitwaghchaure
2394dd71e6 Merge pull request #45680 from rohitwaghchaure/fixed-sabb-query
fix: slow SABB query
2025-02-03 20:56:38 +05:30
rethik
9422ce5aee test: add unit test to validate account type and party type 2025-02-03 19:29:59 +05:30
rethik
2c8e3f3409 fix: add validate to allow equity account and party_type shareholder 2025-02-03 19:22:37 +05:30
Sudharsanan11
423decb93c fix: allow multiple email ids 2025-02-03 18:27:32 +05:30
ruthra kumar
daf9c9d34a Merge pull request #45247 from UmakanthKaspa/feature/update-item-name
feat: show item name alongside item code in the update items dialog
2025-02-03 16:53:21 +05:30
ruthra kumar
d762180231 refactor: reuse values from get_item_details 2025-02-03 16:45:12 +05:30
Rohit Waghchaure
81978a0bd8 fix: slow SABB query 2025-02-03 15:34:29 +05:30
UmakanthKaspa
7b6e8b9c29 feat: show item name alongside item code in the update items dialog 2025-02-03 15:31:16 +05:30
Mihir Kandoi
3cdaa80526 fix: copy correct uom from devliery note when creating packing list 2025-02-03 14:49:50 +05:30
ruthra kumar
adfbfe5b32 Merge pull request #45660 from frappe/pot_develop_2025-02-02
chore: update POT file
2025-02-03 14:31:41 +05:30
Ankush Menat
827afbfa2e fix: track employee changes (#45674)
closes https://github.com/frappe/erpnext/issues/45571
2025-02-03 08:51:50 +00:00
ruthra kumar
1fb3a4333d Merge pull request #45631 from deepeshgarg007/current_subscription_invoice
fix: Do not check for cancelled invoices
2025-02-03 14:11:13 +05:30
ruthra kumar
4b434d01f3 Merge pull request #45659 from frappe/mergify/bp/develop/pr-45617
fix: ignore expired batch for pick list (backport #45617)
2025-02-03 14:07:45 +05:30
ruthra kumar
cd0faf12a4 Merge pull request #45661 from frappe/l10n_develop
fix: sync translations from crowdin
2025-02-03 14:06:20 +05:30
Shariq Ansari
1362e2b6bf Merge pull request #45637 from shariquerik/notes-tab 2025-02-03 13:22:36 +05:30
Shariq Ansari
018df3135a fix: renamed Commments Tab to Notes tab in Lead doctype 2025-02-03 12:12:36 +05:30
rohitwaghchaure
0a25fe981b Merge pull request #45619 from mihir-kandoi/45442
feat: set bank account of company to default company bank account fro…
2025-02-03 12:00:35 +05:30
rohitwaghchaure
60da21be55 Merge pull request #45621 from mihir-kandoi/delivered-button
fix: delivered button of purchase order
2025-02-03 11:51:37 +05:30
rohitwaghchaure
3cdc636111 Merge pull request #45648 from mihir-kandoi/st30593
fix: consider process_loss_qty in work order
2025-02-03 11:50:31 +05:30
Frappe PR Bot
bd2c3711c0 fix: Bosnian translations 2025-02-03 03:17:38 +05:30
Frappe PR Bot
589325e14b fix: Persian translations 2025-02-03 03:17:35 +05:30
Frappe PR Bot
cd20d7aa8e fix: Chinese Simplified translations 2025-02-03 03:17:31 +05:30
frappe-pr-bot
99c3fb6ba5 chore: update POT file 2025-02-02 09:35:01 +00:00
Dany Robert
2a258c1629 fix: ignore expired batch for pick list
(cherry picked from commit 786db3d0fa)
2025-02-02 08:04:04 +00:00
Frappe PR Bot
4b345cf495 fix: sync translations from crowdin (#45656)
* fix: Swedish translations

* fix: Persian translations

* fix: Bosnian translations
2025-02-01 23:48:25 +01:00
Frappe PR Bot
33ce49730a fix: sync translations from crowdin (#45650) 2025-02-01 20:01:17 +01:00
Mihir Kandoi
95fda47b6c fix: consider process_loss_qty in work order 2025-02-01 00:35:12 +05:30
rohitwaghchaure
75567dc9ff Merge pull request #45646 from rohitwaghchaure/fixed-support-30611
fix: not able to make manufacturing entry for alternate items
2025-01-31 23:47:43 +05:30
Rohit Waghchaure
1607aa1a44 fix: not able to make manufacturing entry for alternate items 2025-01-31 23:28:54 +05:30
rohitwaghchaure
1a83316112 Merge pull request #45642 from rohitwaghchaure/fixed-support-30559
fix: actual qty showing blank for sub-assembly items
2025-01-31 22:38:04 +05:30
Sugesh393
707c01487e fix: add allow_on_submit for party_balance, paid_from_account_balance and paid_to_account_balance 2025-01-31 17:49:28 +05:30
Rohit Waghchaure
5be2e71a35 fix: actual qty showing blank for sub-assembly items 2025-01-31 16:49:55 +05:30
l0gesh29
5676d60ed3 feat: add repost accounting ledger entry for payment entry 2025-01-31 16:00:00 +05:30
rohitwaghchaure
3e35c48cf7 Merge pull request #45626 from rohitwaghchaure/fixed-support-29212-1
fix: validation to prevent submission, if the SABB is not linked to a stock transaction
2025-01-31 14:43:12 +05:30
Rohit Waghchaure
f976115a2b fix: validation to prevent submission if the SABB is not linked to a stock transaction 2025-01-31 14:04:27 +05:30
ruthra kumar
e2db3b9cfc Merge pull request #45615 from ljain112/fix-pe-erroe
fix: correct error message in payment entry
2025-01-31 12:56:14 +05:30
Deepesh Garg
2c94867b0e fix: Do not check for cancelled invoices 2025-01-31 12:17:53 +05:30
rohitwaghchaure
a913c9b202 Merge pull request #45629 from mihir-kandoi/st30495
fix: attribute 'msgbox' not found in sales invoice.js
2025-01-31 12:03:47 +05:30
Diptanil Saha
fe51535392 fix: pos print receipt on submit (#45632) 2025-01-31 12:00:00 +05:30
Deepesh Garg
701fc02050 fix: Do not check for cancelled invoices 2025-01-31 11:44:51 +05:30
Mihir Kandoi
5643385c22 fix: attribute 'msgbox' not found in sales invoice.js 2025-01-31 11:26:01 +05:30
Diptanil Saha
5a1851dfe3 fix: loading print receipt only at order complete (#45627) 2025-01-31 11:17:48 +05:30
ruthra kumar
437444fe04 Merge pull request #45623 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-31 10:54:40 +05:30
Frappe PR Bot
00f9a36980 fix: Swedish translations 2025-01-31 03:11:13 +05:30
Mihir Kandoi
41649cf52d fix: bind this to function 2025-01-30 22:53:00 +05:30
ljain112
592704cfd0 fix: correct error message in payment entry 2025-01-30 17:27:44 +05:30
rohitwaghchaure
b3ffb82586 Merge pull request #45612 from rohitwaghchaure/fixed-posting-date
fix: posting_date to posting_datetime in stock related queries
2025-01-30 17:05:26 +05:30
Rohit Waghchaure
e61ab48145 fix: posting_date to posting_datetime in stock related queries 2025-01-30 16:04:06 +05:30
rohitwaghchaure
b8ee15269c Merge pull request #45609 from rohitwaghchaure/fixed-support-30336
fix: reposting issue with s3 backup
2025-01-30 15:58:14 +05:30
Mihir Kandoi
ce7702cc19 feat: set bank account of company to default company bank account from masters 2025-01-30 12:50:01 +05:30
ljain112
9faf78d3e5 fix: respect user set account if not advance account for getting outstanding invoices in payment entry 2025-01-30 12:32:14 +05:30
Rohit Waghchaure
6b454ca9a7 fix: reposting issue with s3 backup 2025-01-30 11:59:40 +05:30
Frappe PR Bot
9a34c4c1ec fix: sync translations from crowdin (#45608)
* fix: Turkish translations

* fix: Persian translations
2025-01-30 11:20:01 +05:30
Bhavan23
4dd37ba033 fix: filter the item tax template using the input text 2025-01-29 22:19:30 +05:30
rohitwaghchaure
968e235a3f Merge pull request #45486 from rohitwaghchaure/fixed-SABB-performance-issue
perf: stock entry with batch
2025-01-29 20:49:09 +05:30
rohitwaghchaure
90eefac6f6 Merge pull request #45600 from rohitwaghchaure/fixed-validation-msg
fix: validation message
2025-01-29 20:41:39 +05:30
Rohit Waghchaure
4c8dff942d fix: validation message 2025-01-29 20:36:58 +05:30
rohitwaghchaure
378afd2f65 Merge pull request #45596 from frappe/mergify/bp/develop/pr-45595
fix: do not allow to manually submit the SABB (backport #45595)
2025-01-29 16:44:12 +05:30
Sanket Shah
aaf720ab61 fix: Gross Profit Report with Correct Totals and Gross Margin (#45548)
Co-authored-by: Sanket322 <shahsanket322003.com>
2025-01-29 16:42:51 +05:30
Rohit Waghchaure
d042c841e4 fix: do not allow to manually submit the SABB
(cherry picked from commit 2b16eb5381)
2025-01-29 11:08:59 +00:00
ruthra kumar
409e512d47 Merge pull request #45569 from ljain112/fix-vch-outstanding
fix: update voucher outstanding from payment ledger
2025-01-29 16:11:02 +05:30
Safvan Huzain
3f2e93dcb6 fix(query): remove duplicate docstatus condition (#45586)
fix: remove duplicate docstatus condition in query
2025-01-29 10:27:01 +00:00
l0gesh29
79b5a3e1dd fix: remove tds account in taxes table on change of Tax Withholding Category 2025-01-29 15:37:38 +05:30
ruthra kumar
72b940e3d3 Merge pull request #45441 from ljain112/fix-tax-withhel-details
fix: show payment entries in tax withheld vouchers
2025-01-29 15:35:50 +05:30
ruthra kumar
9ae8e94e6c Merge pull request #45585 from ruthra-kumar/auto_add_taxes
refactor: auto add taxes from template
2025-01-29 15:28:12 +05:30
ruthra kumar
d1086722bf refactor: auto add taxes from template 2025-01-29 14:38:50 +05:30
Sugesh G
d748b491ee fix: use currency from opportunity while creating quotation (#45540) 2025-01-29 12:35:45 +05:30
rohitwaghchaure
5e8e7dd3d8 Merge pull request #45536 from rtdany10/precision-return
fix: return qty error due to precision
2025-01-29 12:19:54 +05:30
Ejaaz Khan
5a023dc8d4 fix: add multiple item issue in stock entry (#45544) 2025-01-29 12:19:01 +05:30
Lakshit Jain
9f20854bd9 fix: get stock balance filtered by company for validating stock value in jv (#45549)
* fix: get stock balance filtered by company for validating stock value in jv

* test: error is raised  on validate
2025-01-29 12:16:11 +05:30
ruthra kumar
a64a4f9b20 Merge pull request #45575 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-29 11:25:43 +05:30
Frappe PR Bot
ac32c554af fix: Turkish translations 2025-01-29 03:14:46 +05:30
Frappe PR Bot
ceb4f249cb fix: Swedish translations 2025-01-29 03:14:43 +05:30
rohitwaghchaure
2609f9809d Merge pull request #43270 from rohitwaghchaure/feat-stock-reservation-for-wo
feat: stock reservation for Work Order
2025-01-28 18:06:57 +05:30
ljain112
dd77070351 fix: update voucher outstanding from payment ledger 2025-01-28 17:54:16 +05:30
Rohit Waghchaure
4d050441b3 feat: stock reservation for Work Order 2025-01-28 16:42:57 +05:30
Akhil Narang
8e77b26641 Merge pull request #45554 from akhilnarang/fixup-44604
fix(book_appointment): stop trying to use pytz api with zoneinfo
2025-01-28 16:25:27 +05:30
Akhil Narang
b08da0f6bd fix(book_appointment): stop trying to use pytz api with zoneinfo
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-01-28 16:09:26 +05:30
ruthra kumar
b1eb604363 Merge pull request #45560 from ruthra-kumar/rename_swiss_coa
chore: rename file to standard name format
2025-01-28 16:03:19 +05:30
ruthra kumar
7bc075376b chore: rename file to standard name format 2025-01-28 14:34:49 +05:30
ruthra kumar
7e7209e448 Merge pull request #45452 from aerele/payment-entry-transaction-currency
fix(payment entry): get amount in transaction currency
2025-01-28 14:33:30 +05:30
ruthra kumar
9964ddc0e9 Merge pull request #45551 from eagleautomate/swiss_coa
feat: Add chart of accounts for Switzerland
2025-01-28 11:33:57 +05:30
Sanket Shah
9933d3c8ff fix: update fields on change of item code In Update Items of Sales Order (#45125)
* fix: update fields on change of item code

* fix: minor update

* fix: set the new values always

* Revert "fix: set the new values always"

This reverts commit 44daa0a641.

---------

Co-authored-by: Sanket322 <shahsanket322003.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-01-28 11:27:28 +05:30
ruthra kumar
813cfdfff6 Merge pull request #45552 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-28 11:03:49 +05:30
Rohit Waghchaure
0b1b964b77 perf: stock entry with batch 2025-01-28 09:49:37 +05:30
Frappe PR Bot
d1fbeb11cb fix: Turkish translations 2025-01-28 03:03:54 +05:30
Frappe PR Bot
dc7e7118af fix: Swedish translations 2025-01-28 03:03:50 +05:30
Frappe PR Bot
26afba142e fix: French translations 2025-01-28 03:03:33 +05:30
eagleautomate
2c644ec2ef feat: Add chart of accounts for Switzerland
240812 Schulkontenrahmen VEB - DE
2025-01-27 19:29:36 +05:30
ruthra kumar
47c2c5377c Merge pull request #45345 from Sanket322/set_discount_properly
fix:  maintain existing discounts in get_pricing_rule_for_item
2025-01-27 16:01:46 +05:30
Dany Robert
3078578692 fix: return qty error due to precision 2025-01-27 09:54:06 +00:00
ruthra kumar
49570a5544 Merge pull request #45302 from aerele/validate-dimensions-company
feat: add company level validation for accounting dimension
2025-01-27 14:53:49 +05:30
meera-greycube
e88f96b92d feat : company field required for multi company setup (#45415)
* Update vehicle.json

feat : company field required for multi company setup

* Update vehicle.json

allow_rename fixed in vehicle.json

* fix: fix permission issue
2025-01-27 14:28:11 +05:30
ruthra kumar
e55d4030ec Merge pull request #45284 from aerele/pos-return-currency
fix: set party_account_currency for pos_invoice returns
2025-01-27 14:27:41 +05:30
ljain112
db38e7bf5a fix: correct amt in account currency for lcv with manually distributed charges. 2025-01-27 14:22:36 +05:30
Raffael Meyer
11f65f20a0 feat(Sales Invoice): allow linking to project without adding timesheets (#44295)
* feat(Sales Invoice): allow linking to project without adding timesheets

* test: add timesheet data
2025-01-27 13:20:57 +05:30
Diptanil Saha
2ac8c92e7f fix: currency decimal on POS Past Order List (#45524)
* fix: currency decimal on POS

* fix: removed precision
2025-01-27 13:02:24 +05:30
ruthra kumar
fb285749dd Merge pull request #45300 from HarryPaulo/fix-prevent-commit
fix: prevent unnecessary db.commit for contact insert
2025-01-27 12:18:07 +05:30
Frappe PR Bot
887645e55f fix: sync translations from crowdin (#45522)
* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations
2025-01-26 23:19:32 +01:00
venkat102
af97f42429 fix(payment entry): get amount in transaction currency 2025-01-26 19:52:06 +05:30
Frappe PR Bot
eabdd5992b chore: update POT file (#45451) 2025-01-26 13:54:13 +01:00
Frappe PR Bot
29341245b8 fix: sync translations from crowdin (#45444)
* fix: Swedish translations

* fix: Turkish translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Persian translations
2025-01-26 12:15:18 +05:30
rohitwaghchaure
b5afd901f3 Merge pull request #45443 from rohitwaghchaure/fixed-support-29487
fix: allow to fix negative stock for batch using stock reco
2025-01-25 15:03:22 +05:30
Rohit Waghchaure
2e8cde3378 fix: allow to fix negative stock for batch using stock reco 2025-01-25 14:44:25 +05:30
ljain112
d97e78e5d3 fix: variable names 2025-01-25 13:16:06 +05:30
ljain112
55733d4f18 fix: show payment entries in Tax Withheld Vouchers 2025-01-25 13:04:12 +05:30
venkat102
bee2c04d0b fix(pos): add item in the existing item row when discount is applied 2025-01-25 12:59:04 +05:30
rohitwaghchaure
53704b98b5 fix: do not check budget during reposting (#45432) 2025-01-24 12:00:45 +00:00
rohitwaghchaure
bad1ac9fbc Merge pull request #45423 from rohitwaghchaure/fixed-support-29882
fix: precision issue in stock entry
2025-01-24 17:07:16 +05:30
Raffael Meyer
4008ca5ddd feat(UX): scroll to required field (#44367) 2025-01-24 16:36:46 +05:30
Rohit Waghchaure
9f3b8520fe fix: precision issue in stock entry 2025-01-24 16:07:25 +05:30
Sanket Shah
d862e9b771 fix: validate items against selling settings (#45288)
fix: validate_for_duplicate_items

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-01-24 15:10:43 +05:30
Venkatesh
97acbb3134 fix: disable load_after_mapping when purchase order created from sales order (#45405) 2025-01-24 15:02:56 +05:30
Raffael Meyer
a9bc395e98 fix: secure bulk transaction (#45386) 2025-01-24 14:44:26 +05:30
Tufan Kaynak
42edb9f5b1 fix(material request): mapping Sales Order Item Delivery Date to Mate… (#45227)
* fix(material request): mapping Sales Order Item Delivery Date to Material Request Item Required By

as mentioned in https://discuss.frappe.io/t/item-delivery-date-on-sales-order-is-not-transferred-to-material-request-item-required-by-date/140479 
fixing
When you create a Material Request directly on the Sales Order via → Create → Material Request, Delivery Date on Sales Order Item is not transferred to Material Request Item Required By date.

* fix(linters): meaningless linters formatting message applied

In order to pass the linters test which I find meaningless as it asks for the comma after the last item in a dictionary data type

* fix(linters): formatting code for linters pass

Linters formatting applied
2025-01-24 14:20:20 +05:30
Martin Luessi
e82911041d fix: remove unnecessary auth from plaid connector (#44305) 2025-01-24 13:11:32 +05:30
rohitwaghchaure
3d7f1026ca Merge pull request #45335 from rohitwaghchaure/fixed-valuation-for-batch
fix: valuation for batch
2025-01-24 12:50:55 +05:30
Diptanil Saha
54d234e05d fix: resolved pos return setting to default mode of payment instead of user selection (#45377)
* fix: resolved pos return setting to default mode of payment instead of user selection

* refactor: removed console log statement

* refactor: moved get_payment_data to sales_and_purchase_return.py
2025-01-24 12:41:01 +05:30
Diptanil Saha
78c7c1c631 feat: full screen on pos (#45404)
* feat: full screen on pos

* refactor: variables for label

* fix: refactor and handled button label change

* refactor: rename enable fullscreen label
2025-01-24 07:07:36 +00:00
Rohit Waghchaure
8028dd2683 fix: version 2025-01-24 12:32:16 +05:30
mahsem
cd3f03696e fix: use frappe.datetime.str_to_user (#45216)
* fix: default_datetime_format

* fix: add_format_datetime

* fix: update to str_to_user  in point_of_sale/pos_controller.js

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* fix: convert_to_str_to_user

* fix: linters

* fix: whitespace

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-01-24 12:16:47 +05:30
Sugesh G
6ec18fb40d fix: set expense_account and cost_center based on company in stock entry (#45159)
* fix: set expense_account and cost_center based on company in stock entry

* fix: remove is_perpetual_inventory_enabled validation for cost_center
2025-01-24 12:13:44 +05:30
ruthra kumar
5bf90dccbb Merge pull request #45412 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-24 11:05:12 +05:30
Frappe PR Bot
9888c62e13 fix: Swedish translations 2025-01-24 02:26:17 +05:30
Rohit Waghchaure
5088d8576f fix: valuation for batch 2025-01-23 20:38:14 +05:30
Raffael Meyer
d92f9330fa fix: get default stock uom (#45384)
Co-authored-by: Sagar Vora <sagar@resilient.tech>
2025-01-23 14:32:39 +01:00
mergify[bot]
d1329c2910 fix: add condition to check if item is delivered by supplier in make_purchase_order_for_default_supplier() (backport #45370) (#45409)
fix: add condition to check if item is delivered by supplier in make_purchase_order_for_default_supplier() (#45370)

(cherry picked from commit 69464ab7ff)

Co-authored-by: Shanuka Hewage <89955436+Shanuka-98@users.noreply.github.com>
2025-01-23 18:18:58 +05:30
meike289
1758e125e0 fix: fix creating documents from sales invoice (#45346)
Co-authored-by: Meike Nedwidek <nedwidek@kk-software.de>
2025-01-23 17:54:00 +05:30
ruthra kumar
85b6fdd067 Merge pull request #45248 from niyazrazak/patch-22
chore: consider currency precision in bank clearance
2025-01-23 16:57:28 +05:30
ruthra kumar
c889bdba0f chore: import statements and linter 2025-01-23 16:39:03 +05:30
ruthra kumar
766b5fa5ed Merge pull request #44790 from ruthra-kumar/configurable_posting_date_for_exc_gain_loss
refactor: configurable posting date for Exc Gain / Loss journal
2025-01-23 16:28:21 +05:30
ruthra kumar
3923b784e3 Merge pull request #45129 from marination/ac-match-simplify
fix: Wrong `bank_ac_no` filter + simplify logic in automatch
2025-01-23 16:27:49 +05:30
ruthra kumar
7b8a099d95 Merge pull request #44950 from mahsem/postal_code_move_and_fixes
fix: postal_code_move_and_fixes
2025-01-23 16:17:15 +05:30
ruthra kumar
a71718883e refactor: support JE posting date in semi-auto reconciilation tool 2025-01-23 16:11:35 +05:30
ruthra kumar
2f3281579a test: exc gain/loss posting date based on configuration 2025-01-23 14:44:40 +05:30
ruthra kumar
b2c3da135e refactor: only apply configuration on normal payments
patch to update default value
2025-01-23 14:12:24 +05:30
ruthra kumar
95af63e305 refactor: allow reconciliation date for exchange gain / loss 2025-01-23 13:48:09 +05:30
rohitwaghchaure
f79eea2261 Merge pull request #45394 from rohitwaghchaure/fixed-support-30031
fix: JobCardTimeLog' object has no attribute 'remaining_time_in_mins'
2025-01-23 13:22:17 +05:30
ruthra kumar
5257413a93 refactor: configurable posting date for Exc Gain / Loss journal 2025-01-23 13:06:30 +05:30
Rohit Waghchaure
41dda35db7 fix: JobCardTimeLog' object has no attribute 'remaining_time_in_mins' 2025-01-23 13:03:37 +05:30
Sanket322
50223c6bec fix: remove applied pricing rule 2025-01-23 11:55:12 +05:30
Sanket Shah
2a400dd3f8 perf: optimize DB calls with frappe.get_all (#45289)
* perf: reduce multiple db queries

* fix: use frappe._dict instread of extra iteration

---------

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-01-23 11:29:41 +05:30
Frappe PR Bot
551fa500e8 fix: sync translations from crowdin (#45387)
* fix: Swedish translations

* fix: Turkish translations
2025-01-23 11:18:59 +05:30
Mihir Kandoi
fe43d20545 fix: added item_group filter in item_code field in stock balance report (#45340)
* fix: added item_group filter in item_code field in stock balance report

* feat: added filter to not show non stock items
2025-01-23 11:17:35 +05:30
rohitwaghchaure
8e18c572f4 Merge pull request #45367 from rohitwaghchaure/fixed-incorrect-calculation-of-qty
fix: batch qty calculation
2025-01-23 09:40:16 +05:30
Rohit Waghchaure
f07a71a882 fix: batch qty calculation 2025-01-22 21:06:43 +05:30
rohitwaghchaure
c5f21a5686 Merge pull request #45382 from rohitwaghchaure/fixed-support-29769
fix: precision issue causing incorrect status
2025-01-22 20:17:20 +05:30
Rohit Waghchaure
4a7586cc01 fix: precision issue causing incorrect status 2025-01-22 18:04:52 +05:30
ruthra kumar
3fbd2ca0d9 refactor: configurable posting date for Exc Gain / Loss journal 2025-01-22 17:54:00 +05:30
Diógenes Souza
b26f0b6633 fix: System was allowing to save payment schedule amount less than grand total (#45322)
* fix: System was allowing to save payment schedule amount less than grand_total

* style: After run pre-commit
2025-01-22 17:52:26 +05:30
Rethik M
05579959f2 fix: validate non-stock item for exchange loss/gain (#45306)
* fix: validate non-stock item

* test: add unit test to validate non-stock item exchange difference

* fix: use usd supplier
2025-01-22 17:44:53 +05:30
gavin
4481ca83ff fix: set preferred email in Employee via backend controller (#45320)
fix: set preferred email in Employee (backend)

Set "Preferred Email" for Employee via validate. Unset value when
prefered_contact_email is also unset.
2025-01-22 17:41:41 +05:30
Khushi Rawat
9ff3101b2d fix: added debounce to prevent multiple clicks (#45369)
* fix: added debounce to prevent multiple clicks

* fix: linters check
2025-01-22 17:26:38 +05:30
mergify[bot]
8b6a20d501 fix: precision on work order total qty (backport #45341) (#45361)
fix: precision on work order total qty (#45341)

* fix: precision on work order total qty

* chore: linters

(cherry picked from commit 53468202de)

Co-authored-by: Dany Robert <rtdany10@gmail.com>
2025-01-22 17:25:50 +05:30
Raffael Meyer
be2593bb51 refactor!: remove redundant title field from sales transactions (#45115)
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-01-22 11:35:41 +00:00
Ejaaz Khan
5207917993 feat: set default view as tree-view (#45365)
feat: set default view as treeview
2025-01-22 16:18:31 +05:30
Deepesh Garg
84d379914c Merge pull request #45371 from deepeshgarg007/closing_balance_patch_rerun
fix: Do no query GLs if no PCVs are posted
2025-01-22 14:02:23 +05:30
Deepesh Garg
f4d1a54588 fix: Do no query GLs if no PCVs are posted 2025-01-22 13:46:37 +05:30
ruthra kumar
1c6a7830c3 Merge pull request #45363 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-22 12:12:43 +05:30
Frappe PR Bot
4455312b73 fix: Turkish translations 2025-01-22 01:34:43 +05:30
Sugesh393
36bae55299 chore: update variable names for improved readability 2025-01-21 17:38:24 +05:30
Niklas Liechti
dedb96d337 refactor: return job_id to be able to react to it when job is finished. (#45111) 2025-01-21 17:38:12 +05:30
ruthra kumar
d73c17aa98 Merge pull request #45242 from aerele/pos-invoice-item
fix: include pos invoice in modifing key for returned item validation
2025-01-21 16:34:59 +05:30
ruthra kumar
4f55356c79 Merge pull request #45215 from aerele/handle_aed_exchange_rate_manually
fix: calculate AED exchange rate based on pegged value with USD
2025-01-21 13:58:21 +05:30
Deepesh Garg
993f40fa43 perf: Ignore is_opening column in GL Queries (#45327)
* perf: Ignore is_opening column in GL Queries

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes
2025-01-21 13:45:25 +05:30
ruthra kumar
2e535955b3 refactor: use dictionary for better expandability 2025-01-21 12:15:26 +05:30
ruthra kumar
dd923332cb refactor: fix type error 2025-01-21 12:15:26 +05:30
Frappe PR Bot
7b0c21e989 fix: sync translations from crowdin (#45323)
* fix: Swedish translations

* fix: Turkish translations

* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Hungarian translations

* fix: Polish translations

* fix: Russian translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Persian translations

* fix: Bosnian translations

* fix: German translations

* fix: Esperanto translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Persian translations
2025-01-20 22:57:21 +01:00
rohitwaghchaure
6c32397313 Merge pull request #45282 from mihir-kandoi/44976
feat: Add corrective job card operating cost as additional costs in s…
2025-01-20 22:48:46 +05:30
rohitwaghchaure
5638dac414 Merge pull request #45344 from iamejaaz/broken-bom-tree
fix: broken image issue in BOM Tree
2025-01-20 22:48:05 +05:30
Sanket322
e2a32b7257 fix: use user defined discount amount or default 2025-01-20 18:01:31 +05:30
Ejaaz Khan
7c6e279599 fix: broken image issue in BOM Tree 2025-01-20 17:49:18 +05:30
Diptanil Saha
68fb1b28eb fix: set invoice start date to subscription start date (#45342) 2025-01-20 17:06:02 +05:30
Mihir Kandoi
47f8a86003 fix: logical error in where condition of qb query 2025-01-20 12:41:28 +05:30
ruthra kumar
5fb158a6f7 Merge pull request #45202 from DaizyModi/fix-party-bank-acc-in-pe
fix: Correct Party Bank Account mapping in `Payment Entry` from Transactional Doctypes
2025-01-20 12:22:10 +05:30
ruthra kumar
30e87c37dc Merge pull request #45334 from mahsem/translation_fixes
fix: translation fixes
2025-01-20 12:11:32 +05:30
ruthra kumar
f9200a9575 Merge pull request #45167 from aerele/validate-sales-person
fix: validate linked sales person
2025-01-20 11:49:14 +05:30
mahsem
3697ba0772 fix: linters 2025-01-19 21:00:02 +01:00
mahsem
1d81a9f933 fix: translation fixes 2025-01-19 20:52:58 +01:00
Frappe PR Bot
da34d7923d chore: update POT file (#45331) 2025-01-19 11:47:50 +01:00
rohitwaghchaure
04364d680f Merge pull request #44783 from rohitwaghchaure/fixed-support-27273
fix: do not reset picked items
2025-01-18 14:22:54 +05:30
Rohit Waghchaure
34a80bfcd3 fix: do not reset picked items 2025-01-18 12:44:30 +05:30
rohitwaghchaure
76ba17808b Merge pull request #45298 from rohitwaghchaure/fixed-qi-company
fix: company in quality inspection
2025-01-18 12:14:54 +05:30
Rohit Waghchaure
397cd79e1e fix: company in quality inspection 2025-01-18 11:09:00 +05:30
Diptanil Saha
97e3770872 fix: fixed typo in manufacturing settings and field rename (#45238)
* chore: field rename and patch entry

* chore: patch file rename and description improvement

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-01-17 11:54:39 +00:00
ruthra kumar
8f9b5aaae7 Merge pull request #44811 from sagarvora/fix-pcv-patch-99
fix!: ensure multiple PCVs in same fiscal year are considered in patch
2025-01-17 17:00:40 +05:30
Diptanil Saha
d13c03676b chore: quickbooks migrator integration removal (#45240) 2025-01-17 16:53:36 +05:30
Diptanil Saha
98d401bee4 fix: fetching items from blanket order to sales/purchase order (#45262)
* fix: blanket order with zero item quantity

* fix: item quantity validation

* refactor: resolve linter issue
2025-01-17 16:53:06 +05:30
Nabin Hait
ec487c14d9 fix: Ambiguous column error while creating Sales Return (#45275) 2025-01-17 16:51:07 +05:30
Nabin Hait
2d45d0e5d5 fix: updated modified timestamp for stock entry type (#45280) 2025-01-17 16:50:43 +05:30
Lakshit Jain
ada272a29b fix: round off tax withholding amount (#45271) 2025-01-17 16:50:15 +05:30
Patrick Eißler
8d66142865 fix(Project): re-phrase welcome email (#45175) 2025-01-17 12:17:32 +01:00
Sagar Vora
84e0b41c4f fix: ensure multiple PCVs in same fiscal year are considered in patch 2025-01-17 16:44:06 +05:30
Sanket Shah
19c8708e5e fix: don't update party-type on change of cost center in Journal Entry (#45291)
fix: don't update party-type on change of cost center

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-01-17 16:42:04 +05:30
Ejaaz Khan
aa38895caf feat: add option to update modified on communication recieved (#45307) 2025-01-17 16:40:35 +05:30
rohitwaghchaure
9163f60191 fix: sales return for multi-uom (#45303) 2025-01-17 16:35:59 +05:30
Sugesh393
454067198e fix: set company related values 2025-01-17 11:46:17 +05:30
Sugesh393
c94091d68f test: add new unit test for company validation in accounting dimension 2025-01-17 11:45:51 +05:30
Sugesh393
60efd3e219 feat: add company level validation for accounting dimension 2025-01-17 11:41:52 +05:30
Frappe PR Bot
0e7e9b5f0a fix: sync translations from crowdin (#45299)
* fix: Turkish translations

* fix: Persian translations
2025-01-16 22:49:23 +01:00
HarryPaulo
87de5c7450 fix: prevent unnecessary db.commit for contact insert [Linters] 2025-01-16 17:51:22 -03:00
HarryPaulo
5f15b0b65b fix: prevent unnecessary db.commit 2025-01-16 17:40:54 -03:00
Mihir Kandoi
063a205e5a refactor: added condition which checks for corrective operation setting 2025-01-16 20:55:27 +05:30
rohitwaghchaure
43ce185429 Merge pull request #45283 from rohitwaghchaure/fixed-github-45219
fix: Does not allow to create Sub-Asseblies of Sub Assemblies
2025-01-16 14:57:56 +05:30
Rohit Waghchaure
318a945d66 fix: Does not allow to create Sub-Asseblies of Sub Assemblies 2025-01-16 13:51:11 +05:30
ruthra kumar
6628d290de Merge pull request #45285 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-16 11:10:32 +05:30
Mihir Kandoi
4fb48b7f22 test: Added test for new feature 2025-01-16 10:08:47 +05:30
Frappe PR Bot
ed2764ab8d fix: Persian translations 2025-01-15 23:49:21 +05:30
Frappe PR Bot
a7cd17ac8b fix: Turkish translations 2025-01-15 23:49:14 +05:30
Sugesh393
484ecf2479 test: add new unit test to check payments amount of pos_invoice returns 2025-01-15 18:27:57 +05:30
Sugesh393
2af6fca7fa fix: set party_account_currency for pos_invoice returns 2025-01-15 18:20:20 +05:30
Mihir Kandoi
2bf10f68a8 feat: Add corrective job card operating cost as additional costs in stock entry 2025-01-15 18:14:19 +05:30
rohitwaghchaure
cb91e8e69e Merge pull request #45278 from rohitwaghchaure/fixed-github-45246
fix: status of the serial no for the raw materials
2025-01-15 18:13:34 +05:30
Rohit Waghchaure
9607b16dcf fix: status of the serial no for the raw materials 2025-01-15 17:53:11 +05:30
Lakshit Jain
37a5767be5 fix: check if tds deducted based on Purchase Taxes and Charges (#45161) 2025-01-15 15:31:56 +05:30
rohitwaghchaure
195254756f Merge pull request #45270 from mihir-kandoi/st29053
fix: Skip WIP Warehouse transfer
2025-01-15 15:05:49 +05:30
Lakshit Jain
a4453fb77b fix: use currency defined in plan for subscription invoice (#45104) 2025-01-15 14:52:25 +05:30
Mihir Kandoi
6edb454eea test: Added new test to check wip skip 2025-01-15 13:49:11 +05:30
rohitwaghchaure
001d1eb3f9 Merge pull request #45259 from rohitwaghchaure/fixed-support-29045
fix: incorrect valuation for sales return with different warhouse
2025-01-14 18:33:34 +05:30
Rohit Waghchaure
3a2e816759 fix: incorrect valuation for sales return with different warhouse 2025-01-14 18:11:17 +05:30
ruthra kumar
ce9c606f71 refactor: allow users to configure interval for Semi-Auto payment reconciliation (#45211)
* refactor: configurable interval for reconciliation trigger

* refactor: set default value for interval

* refactor: configurable queue size

* refactor: use patch to setup default cron job

* refactor: use 'after_migrate' to setup cron for reconciliation

User specified interval will be used

* chore: type casting

* refactor: use scheduler_event to persist cron

* chore: rename field

* chore: use configured queue size

* chore: remove unwanted field
2025-01-14 16:59:15 +05:30
Mihir Kandoi
09d26a835f fix: tests 2025-01-14 16:27:49 +05:30
ruthra kumar
aea69af4ff Merge pull request #45182 from ruthra-kumar/configurable_reconciliation_dates
refactor: configurable reconciliation dates for Advance Payments
2025-01-14 15:18:46 +05:30
ruthra kumar
fd442a36a9 Merge pull request #45260 from ruthra-kumar/incorrect_label
fix: incorrect label in Item-wise sales register
2025-01-14 13:55:20 +05:30
ruthra kumar
9ee5fcc602 refactor: only update reconcile_effect_on advance in separate acc 2025-01-14 13:52:21 +05:30
ruthra kumar
d9013e1054 fix: incorrect label in Item-wise sales register 2025-01-14 13:34:12 +05:30
rohitwaghchaure
9bb7d4e428 Merge pull request #45241 from rohitwaghchaure/fixed-support-28979
fix: auto fetch batch and serial no for draft stock transactions
2025-01-14 12:35:17 +05:30
mahsem
a3165c5719 fix: change string to be able to translate (#45090)
* fix: change_string_to_translate

* fix: debit note translation

* chore: update 'modified' field

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-01-14 06:26:01 +00:00
ruthra kumar
601bc3026d Merge pull request #45249 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-14 11:17:27 +05:30
ruthra kumar
aeedaae761 Merge pull request #45078 from Sanket322/cancel_subscription
fix: don't create invoice if `current_invoice_start ` is in future
2025-01-14 10:12:33 +05:30
ruthra kumar
2e8723407f Merge pull request #45074 from Sanket322/set_shipping_address
fix: set billing and shipping address on change of company
2025-01-14 10:04:13 +05:30
ruthra kumar
4b483e176d Merge pull request #45001 from ljain112/fix-tds-excess
fix: deduct tds on excess amount if checked
2025-01-14 10:02:06 +05:30
ruthra kumar
f7a93640b0 Merge pull request #44798 from aerele/apply_pricing_rule
fix: update  discounting on mixed conditions
2025-01-14 09:58:03 +05:30
Frappe PR Bot
8f8cddb03c fix: Persian translations 2025-01-13 23:39:50 +05:30
Frappe PR Bot
ea4526ad30 fix: Turkish translations 2025-01-13 23:39:40 +05:30
Frappe PR Bot
6c5a9f5a74 fix: sync translations from crowdin (#45174) 2025-01-13 18:02:50 +01:00
Mihir Kandoi
bbb5f8056b fix: Skip WIP Warehouse transfer 2025-01-13 21:32:34 +05:30
NIYAZ RAZAK
83bb3926b1 chore: consider currency precision 2025-01-13 18:53:59 +03:00
ruthra kumar
9fa1865cb7 refactor: backwards compatibility 2025-01-13 17:17:18 +05:30
Rohit Waghchaure
88ab9be79c fix: auto fetch batch and serial no for draft stock transactions 2025-01-13 17:17:03 +05:30
venkat102
2936139c79 fix: include pos invoice in modifing key for returned item validation 2025-01-13 16:51:25 +05:30
ruthra kumar
a4271aa5d1 refactor: save reconcile effect on reference table 2025-01-13 16:20:58 +05:30
ruthra kumar
7e7775aa44 refactor: store reconciliation date in reference
Helps with reposting
2025-01-13 15:56:41 +05:30
Diptanil Saha
e529f82392 fix: batch number search on pos (#45209)
* fix: price of items with batch number not having seperate item price on pos search bar

* fix: introduced batch number based sorting
2025-01-13 12:06:46 +05:30
Ejaaz Khan
eea0eff001 fix: ui issues across multiple pages (#45224)
* fix: ui issues across multiple pages

* refactor: change cur_from to frm
2025-01-13 11:56:59 +05:30
Ejaaz Khan
6eb3b0b1c2 refactor: remove columns from list in child table (#45189) 2025-01-13 11:54:44 +05:30
Ejaaz Khan
53c282c86e refactor: remove party type from general ledger print format (#45196)
refactor: remove party type from print format
2025-01-13 11:52:45 +05:30
Frappe PR Bot
959434601d chore: update POT file (#45231) 2025-01-12 11:27:09 +01:00
rohitwaghchaure
c6c7d7832a Merge pull request #45207 from rohitwaghchaure/fixed-support-29140
fix: incorrect valuation rate for PI based revaluation
2025-01-11 10:07:54 +05:30
rohitwaghchaure
edb254e43e Merge pull request #45214 from rohitwaghchaure/fixed-support-29172
fix: delivery_document_no column issue
2025-01-11 10:07:26 +05:30
Kavin
455ef6f084 fix: calculate AED exchange rate based on pegged value with USD 2025-01-10 18:20:47 +05:30
Rohit Waghchaure
61efb2bb39 fix: delivery_document_no column issue 2025-01-10 18:19:48 +05:30
ruthra kumar
856ec08484 Merge pull request #45195 from sokumon/coa-ui
fix: coa actions cleanup
2025-01-10 16:08:19 +05:30
rohitwaghchaure
31005c5984 Merge pull request #45076 from frappe/valuation_in_ageing_report
feat: Added valuation of quantity for each age group in stock ageing …
2025-01-10 14:25:50 +05:30
Rohit Waghchaure
14ce2337df fix: incorrect valuation rate for PI based revaluation 2025-01-10 14:20:48 +05:30
ruthra kumar
e0517852bc test: ensure reconciliation date config takes effect 2025-01-10 12:17:13 +05:30
rohitwaghchaure
e4d3235b9c Merge pull request #45204 from frappe/mergify/bp/develop/pr-45197
fix: precision loss causing process loss variance (backport #45197)
2025-01-10 12:00:37 +05:30
rohitwaghchaure
5ccf4a1783 chore: fix conflicts 2025-01-10 11:25:53 +05:30
FATHIH MOHAMMED
1ef9f7f8fd fix: precision loss causing process loss variance
(cherry picked from commit d84601b2a3)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2025-01-10 05:49:59 +00:00
DaizyModi
376bdc75f4 fix: Correct Party Bank Account mapping in Payment Entry 2025-01-10 11:06:39 +05:30
ruthra kumar
c8e93e7a61 refactor: hide old checkbox 2025-01-09 20:41:29 +05:30
ruthra kumar
bb8d2c994c refactor: payment entry to handle posting date on configuation 2025-01-09 20:41:29 +05:30
ruthra kumar
fb6c72a247 refactor: test cases updated 2025-01-09 20:41:29 +05:30
ruthra kumar
a8a8ac71b6 refactor: patch to migrate checkbox to select 2025-01-09 20:41:27 +05:30
ruthra kumar
8b2c981fc3 refactor: introduce select fields in company and payment entry 2025-01-09 20:22:05 +05:30
sokumon
108a91d788 fix: coa actions cleanup 2025-01-09 16:49:24 +05:30
rohitwaghchaure
1f1c01d88e Merge pull request #44988 from frappe/35225
feat: Validate sub assembly and material request items in Production …
2025-01-09 16:28:29 +05:30
rohitwaghchaure
90339511aa Merge pull request #45190 from diptanilsaha/fix-typo-in-manufacturing-settings
fix: typo in manufacturing settings
2025-01-09 16:26:01 +05:30
diptanilsaha
a9b761f862 fix: typo in manufacturing settings 2025-01-09 16:05:40 +05:30
rohitwaghchaure
4ec824d71e Merge pull request #45183 from rohitwaghchaure/fixed-support-29059
fix: not able to see create Quality Inspection button
2025-01-09 14:36:52 +05:30
rohitwaghchaure
bb9dd7b8cc Merge pull request #45180 from rohitwaghchaure/fixed-support-28867
fix: do not add ordered items from Quotation to new Sales Order
2025-01-09 14:34:13 +05:30
Rohit Waghchaure
b291835ccd fix: not able to see create Quality Inspection button 2025-01-09 14:31:01 +05:30
Rohit Waghchaure
2e930eb97b fix: do not add ordered items from Quotation to new Sales Order 2025-01-09 13:52:34 +05:30
rohitwaghchaure
00dadc1a89 Merge pull request #45177 from rohitwaghchaure/fixed-support-29008
fix: timeout error for work order
2025-01-09 13:47:17 +05:30
Rohit Waghchaure
b4ceda6f2c fix: timeout error for work order 2025-01-09 13:27:43 +05:30
Khushi Rawat
7ea73d8265 chore: removal of decapitalization feature (#45162)
* chore: removal of decapitalization feature

* fix: rearrangement of asset capitalization doctype fields
2025-01-08 20:44:12 +05:30
Raffael Meyer
9e760e54a5 fix(Timesheet): ignore permissions when updating Task and Project (#45168) 2025-01-08 13:36:31 +01:00
Sudharsanan11
e614f07795 fix: validate linked sales person 2025-01-08 13:51:33 +05:30
Ejaaz Khan
81d8f257aa refactor: validate due date code and message according to doctype (#45126)
* refactor: change message of date comparision and refactor code

* refactor: commonify function call for sales and purchase invoice

* refactor: remove redundant mandatory error validation
2025-01-08 12:49:02 +05:30
Mihir Kandoi
dbb572eec1 test: Valuation of ageing stock 2025-01-08 11:52:24 +05:30
ruthra kumar
2b453219fc Merge pull request #45148 from frappe/l10n_develop
fix: sync translations from crowdin
2025-01-08 09:01:02 +05:30
ruthra kumar
ca44a31420 Merge pull request #45154 from ruthra-kumar/remove_possible_deadlock_in_auto_reconcile
fix: possible deadlock while using auto reconciliation
2025-01-08 09:00:42 +05:30
ruthra kumar
4620025dcd chore: remove 'Experimental' tag 2025-01-08 08:22:37 +05:30
ruthra kumar
5df9a8ab99 Merge pull request #45112 from marination/bank-reco-bank-balance
fix: Missing company filter breaks `get_account_balance` in Bank Reco
2025-01-08 08:16:21 +05:30
Frappe PR Bot
731822efac fix: Spanish translations 2025-01-07 21:48:22 +05:30
rohitwaghchaure
a24d7e8ecd Merge pull request #45144 from rohitwaghchaure/fixed-support-28796
fix: issue in returning components against the SCO
2025-01-07 18:31:18 +05:30
Rohit Waghchaure
729ce1dc50 fix: issue in returning components against the SCO 2025-01-07 18:11:56 +05:30
Diptanil Saha
31dd32dcdf fix: serial and batch no. buttons on pos (#45048) 2025-01-07 18:06:02 +05:30
Khushi Rawat
6850019649 feat: work in progress status for asset (#45066)
* feat: work in progress status for asset

* fix: test case correction

* fix(patch): added patch to update status of assets

* fix: updated tests
2025-01-07 17:38:21 +05:30
Diptanil Saha
2788739c1e feat: pos configuration for print receipt on complete order (#45024) 2025-01-07 17:35:01 +05:30
Diptanil Saha
9f77793f16 chore: removal of tally migration feature (#45100) 2025-01-07 17:28:46 +05:30
Ejaaz Khan
a0f17f8e73 refactor: change sales invoice button position (#45130) 2025-01-07 17:17:59 +05:30
Venkatesh
dc5bff9008 fix: ignore crm deal in tax_rule search filter (#45134) 2025-01-07 17:11:31 +05:30
marination
8521796811 fix: Wrong bank_ac_no filter + simplify convoluted logic 2025-01-07 13:28:55 +05:30
Sanket322
61d4593236 fix: minor update for readability 2025-01-07 12:44:29 +05:30
marination
d7bf73cffa fix: Override pre-commit behaviour due to conflicts with CI 2025-01-06 21:14:41 +05:30
marination
8de0fe78ea fix: Missing company filter breaks get_account_balance in Bank Reco 2025-01-06 20:27:06 +05:30
Mihir Kandoi
87f1f6e15c fix: Attempt to fix status updater 1 2025-01-06 19:47:44 +05:30
Mihir Kandoi
1f2d7da426 fix: Fixed final test case 2025-01-04 15:22:28 +05:30
Mihir Kandoi
f996f71d16 fix: Fixed more test cases 2025-01-04 15:17:31 +05:30
Mihir Kandoi
839b79ffd0 fix: Test case for ageing report 2025-01-03 20:00:36 +05:30
Sanket322
ce99764772 fix: pass right existing address 2025-01-03 18:04:56 +05:30
Sanket322
058fdca981 fix: don't create invoice if invoice start date is in future 2025-01-03 17:44:05 +05:30
Mihir Kandoi
2f80c4dee5 feat: Added valuation of quantity for each age group in stock ageing report 2025-01-03 16:51:44 +05:30
Sanket322
f46f1bead4 fix: set billing and shipping address on change of company 2025-01-03 16:44:24 +05:30
Mihir Kandoi
5dacfd5cda fix: Test case and refactored some code 2025-01-02 12:28:01 +05:30
Mihir Kandoi
015fd4a05b fix: Made requested changes by mentor and fixed some bugs in Production Plan Summary report 2025-01-01 16:51:48 +05:30
ljain112
a203e3ffaf fix: deduct tds on excess amount if checked 2024-12-31 13:46:50 +05:30
Mihir Kandoi
22d38c2af4 feat: Validate sub assembly and material request items in Production Plan and fix Production Plan summary reports not showing correct received quantity from subcontracted POs 2024-12-30 20:12:14 +05:30
mahsem
185bbb4c20 fix: postal_code_move_and_fixes 2024-12-28 09:25:30 +01:00
DHINESH00
547c8004eb fix: Semgrep rules 2024-12-23 11:23:23 +05:30
DHINESH00
d541259da9 fix: update discounting on mixed conditions 2024-12-19 17:15:11 +05:30
325 changed files with 49230 additions and 47188 deletions

View File

@@ -133,7 +133,7 @@ jobs:
run: cat ~/frappe-bench/bench_start.log || true
- name: Upload coverage data
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: coverage-${{ matrix.container }}
@@ -149,7 +149,7 @@ jobs:
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: Upload coverage data
uses: codecov/codecov-action@v4

View File

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

View File

@@ -3,6 +3,7 @@
"allow_copy": 1,
"allow_import": 1,
"creation": "2013-01-30 12:49:46",
"default_view": "Tree",
"description": "Heads (or groups) against which Accounting Entries are made and balances are maintained.",
"doctype": "DocType",
"document_type": "Setup",
@@ -194,7 +195,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2024-08-19 15:19:11.095045",
"modified": "2025-01-22 10:40:35.766017",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",

View File

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

View File

@@ -10,6 +10,7 @@ frappe.treeview_settings["Account"] = {
fieldtype: "Select",
options: erpnext.utils.get_tree_options("company"),
label: __("Company"),
render_on_toolbar: true,
default: erpnext.utils.get_tree_default("company"),
on_change: function () {
var me = frappe.treeview_settings["Account"].treeview;
@@ -182,7 +183,9 @@ frappe.treeview_settings["Account"] = {
function () {
frappe.set_route("Tree", "Cost Center", { company: get_company() });
},
__("View")
__("View"),
"default",
true
);
treeview.page.add_inner_button(
@@ -190,31 +193,12 @@ frappe.treeview_settings["Account"] = {
function () {
frappe.set_route("Form", "Opening Invoice Creation Tool", { company: get_company() });
},
__("View")
__("View"),
"default",
true
);
treeview.page.add_inner_button(
__("Period Closing Voucher"),
function () {
frappe.set_route("List", "Period Closing Voucher", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(
__("Journal Entry"),
function () {
frappe.new_doc("Journal Entry", { company: get_company() });
},
__("Create")
);
treeview.page.add_inner_button(
__("Company"),
function () {
frappe.new_doc("Company");
},
__("Create")
);
treeview.page.add_divider_to_button_group(__("View"));
// financial statements
for (let report of [
@@ -231,7 +215,7 @@ frappe.treeview_settings["Account"] = {
function () {
frappe.set_route("query-report", report, { company: get_company() });
},
__("Financial Statements")
__("View")
);
}
},

View File

@@ -0,0 +1,532 @@
{
"country_code": "ch",
"name": "240812 Schulkontenrahmen VEB - DE",
"tree": {
"Aktiven": {
"account_number": "1",
"is_group": 1,
"root_type": "Asset",
"Umlaufvermögen": {
"account_number": "10",
"is_group": 1,
"Flüssige Mittel": {
"account_number": "100",
"is_group": 1,
"Kasse": {
"account_number": "1000",
"account_type": "Cash"
},
"Bankguthaben": {
"account_number": "1020",
"account_type": "Bank"
}
},
"Kurzfristig gehaltene Aktiven mit Börsenkurs": {
"account_number": "106",
"is_group": 1,
"Wertschriften": {
"account_number": "1060"
},
"Wertberichtigungen Wertschriften": {
"account_number": "1069"
}
},
"Forderungen aus Lieferungen und Leistungen": {
"account_number": "110",
"is_group": 1,
"Forderungen aus Lieferungen und Leistungen (Debitoren)": {
"account_number": "1100"
},
"Delkredere": {
"account_number": "1109"
}
},
"Übrige kurzfristige Forderungen": {
"account_number": "114",
"is_group": 1,
"Vorschüsse und Darlehen": {
"account_number": "1140"
},
"Wertberichtigungen Vorschüsse und Darlehen": {
"account_number": "1149"
},
"Vorsteuer MWST Material, Waren, Dienstleistungen, Energie": {
"account_number": "1170"
},
"Vorsteuer MWST Investitionen, übriger Betriebsaufwand": {
"account_number": "1171"
},
"Verrechnungssteuer": {
"account_number": "1176"
},
"Forderungen gegenüber Sozialversicherungen und Vorsorgeeinrichtungen": {
"account_number": "1180"
},
"Quellensteuer": {
"account_number": "1189"
},
"Sonstige kurzfristige Forderungen": {
"account_number": "1190"
},
"Wertberichtigungen sonstige kurzfristige Forderungen": {
"account_number": "1199"
}
},
"Vorräte und nicht fakturierte Dienstleistungen": {
"account_number": "120",
"is_group": 1,
"Handelswaren": {
"account_number": "1200"
},
"Rohstoffe": {
"account_number": "1210"
},
"Werkstoffe": {
"account_number": "1220"
},
"Hilfs- und Verbrauchsmaterial": {
"account_number": "1230"
},
"Handelswaren in Konsignation": {
"account_number": "1250"
},
"Fertige Erzeugnisse": {
"account_number": "1260"
},
"Unfertige Erzeugnisse": {
"account_number": "1270"
},
"Nicht fakturierte Dienstleistungen": {
"account_number": "1280"
}
},
"Aktive Rechnungsabgrenzungen": {
"account_number": "130",
"is_group": 1,
"Bezahlter Aufwand des Folgejahres": {
"account_number": "1300"
},
"Noch nicht erhaltener Ertrag": {
"account_number": "1301"
}
}
},
"Anlagevermögen": {
"account_number": "14",
"is_group": 1,
"Finanzanlagen": {
"account_number": "140",
"is_group": 1,
"Wertschriften": {
"account_number": "1400"
},
"Wertberichtigungen Wertschriften": {
"account_number": "1409"
},
"Darlehen": {
"account_number": "1440"
},
"Hypotheken": {
"account_number": "1441"
},
"Wertberichtigungen langfristige Forderungen": {
"account_number": "1449"
}
},
"Beteiligungen": {
"account_number": "148",
"is_group": 1,
"Beteiligungen": {
"account_number": "1480"
},
"Wertberichtigungen Beteiligungen": {
"account_number": "1489"
}
},
"Mobile Sachanlagen": {
"account_number": "150",
"is_group": 1,
"Maschinen und Apparate": {
"account_number": "1500"
},
"Wertberichtigungen Maschinen und Apparate": {
"account_number": "1509"
},
"Mobiliar und Einrichtungen": {
"account_number": "1510"
},
"Wertberichtigungen Mobiliar und Einrichtungen": {
"account_number": "1519"
},
"Büromaschinen, Informatik, Kommunikationstechnologie": {
"account_number": "1520"
},
"Wertberichtigungen Büromaschinen, Informatik, Kommunikationstechnologie": {
"account_number": "1529"
},
"Fahrzeuge": {
"account_number": "1530"
},
"Wertberichtigungen Fahrzeuge": {
"account_number": "1539"
},
"Werkzeuge und Geräte": {
"account_number": "1540"
},
"Wertberichtigungen Werkzeuge und Geräte": {
"account_number": "1549"
}
},
"Immobile Sachanlagen": {
"account_number": "160",
"is_group": 1,
"Geschäftsliegenschaften": {
"account_number": "1600"
},
"Wertberichtigungen Geschäftsliegenschaften": {
"account_number": "1609"
}
},
"Immaterielle Werte": {
"account_number": "170",
"is_group": 1,
"Patente, Know-how, Lizenzen, Rechte, Entwicklungen": {
"account_number": "1700"
},
"Wertberichtigungen Patente, Know-how, Lizenzen, Rechte, Entwicklungen": {
"account_number": "1709"
},
"Goodwill": {
"account_number": "1770"
},
"Wertberichtigungen Goodwill": {
"account_number": "1779"
}
},
"Nicht einbezahltes Grund-, Gesellschafter- oder Stiftungskapital": {
"account_number": "180",
"is_group": 1,
"Nicht einbezahltes Aktien-, Stamm-, Anteilschein- oder Stiftungskapital": {
"account_number": "1850"
}
}
}
},
"Passiven": {
"account_number": "2",
"is_group": 1,
"root_type": "Liability",
"Kurzfristiges Fremdkapital": {
"account_number": "20",
"is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen": {
"account_number": "200",
"is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen (Kreditoren)": {
"account_number": "2000"
},
"Erhaltene Anzahlungen": {
"account_number": "2030"
}
},
"Kurzfristige verzinsliche Verbindlichkeiten": {
"account_number": "210",
"is_group": 1,
"Bankverbindlichkeiten": {
"account_number": "2100"
},
"Verbindlichkeiten aus Finanzierungsleasing": {
"account_number": "2120"
},
"Übrige verzinsliche Verbindlichkeiten": {
"account_number": "2140"
}
},
"Übrige kurzfristige Verbindlichkeiten": {
"account_number": "220",
"is_group": 1,
"Geschuldete MWST (Umsatzsteuer)": {
"account_number": "2200"
},
"Abrechnungskonto MWST": {
"account_number": "2201"
},
"Verrechnungssteuer": {
"account_number": "2206"
},
"Direkte Steuern": {
"account_number": "2208"
},
"Sonstige kurzfristige Verbindlichkeiten": {
"account_number": "2210"
},
"Beschlossene Ausschüttungen": {
"account_number": "2261"
},
"Sozialversicherungen und Vorsorgeeinrichtungen": {
"account_number": "2270"
},
"Quellensteuer": {
"account_number": "2279"
}
},
"Passive Rechnungsabgrenzungen und kurzfristige Rückstellungen": {
"account_number": "230",
"is_group": 1,
"Noch nicht bezahlter Aufwand": {
"account_number": "2300"
},
"Erhaltener Ertrag des Folgejahres": {
"account_number": "2301"
},
"Kurzfristige Rückstellungen": {
"account_number": "2330"
}
}
},
"Langfristiges Fremdkapital": {
"account_number": "24",
"is_group": 1,
"Langfristige verzinsliche Verbindlichkeiten": {
"account_number": "240",
"is_group": 1,
"Bankverbindlichkeiten": {
"account_number": "2400"
},
"Verbindlichkeiten aus Finanzierungsleasing": {
"account_number": "2420"
},
"Obligationenanleihen": {
"account_number": "2430"
},
"Darlehen": {
"account_number": "2450"
},
"Hypotheken": {
"account_number": "2451"
}
},
"Übrige langfristige Verbindlichkeiten": {
"account_number": "250",
"is_group": 1,
"Übrige langfristige Verbindlichkeiten (unverzinslich)": {
"account_number": "2500"
}
},
"Rückstellungen sowie vom Gesetz vorgesehene ähnliche Positionen": {
"account_number": "260",
"is_group": 1,
"Rückstellungen": {
"account_number": "2600"
}
}
},
"Eigenkapital (juristische Personen)": {
"account_number": "28",
"is_group": 1,
"Grund-, Gesellschafter- oder Stiftungskapital": {
"account_number": "280",
"is_group": 1,
"Aktien-, Stamm-, Anteilschein- oder Stiftungskapital": {
"account_number": "2800"
}
},
"Reserven und Jahresgewinn oder Jahresverlust": {
"account_number": "290",
"is_group": 1,
"Gesetzliche Kapitalreserve": {
"account_number": "2900"
},
"Reserve für eigene Kapitalanteile": {
"account_number": "2930"
},
"Aufwertungsreserve": {
"account_number": "2940"
},
"Gesetzliche Gewinnreserve": {
"account_number": "2950"
},
"Freiwillige Gewinnreserven": {
"account_number": "2960"
},
"Gewinnvortrag oder Verlustvortrag": {
"account_number": "2970"
},
"Jahresgewinn oder Jahresverlust": {
"account_number": "2979"
},
"Eigene Aktien, Stammanteile oder Anteilscheine (Minusposten)": {
"account_number": "2980"
}
}
}
},
"Betrieblicher Ertrag aus Lieferungen und Leistungen": {
"account_number": "3",
"is_group": 1,
"root_type": "Income",
"Produktionserlöse": {
"account_number": "3000"
},
"Handelserlöse": {
"account_number": "3200"
},
"Dienstleistungserlöse": {
"account_number": "3400"
},
"Übrige Erlöse aus Lieferungen und Leistungen": {
"account_number": "3600"
},
"Eigenleistungen": {
"account_number": "3700"
},
"Eigenverbrauch": {
"account_number": "3710"
},
"Erlösminderungen": {
"account_number": "3800"
},
"Verluste Forderungen (Debitoren), Veränderung Delkredere": {
"account_number": "3805"
},
"Bestandesänderungen unfertige Erzeugnisse": {
"account_number": "3900"
},
"Bestandesänderungen fertige Erzeugnisse": {
"account_number": "3901"
},
"Bestandesänderungen nicht fakturierte Dienstleistungen": {
"account_number": "3940"
}
},
"Aufwand für Material, Handelswaren, Dienstleistungen und Energie": {
"account_number": "4",
"is_group": 1,
"root_type": "Expense",
"Materialaufwand Produktion": {
"account_number": "4000"
},
"Handelswarenaufwand": {
"account_number": "4200"
},
"Aufwand für bezogene Dienstleistungen": {
"account_number": "4400"
},
"Energieaufwand zur Leistungserstellung": {
"account_number": "4500"
},
"Aufwandminderungen": {
"account_number": "4900"
}
},
"Personalaufwand": {
"account_number": "5",
"is_group": 1,
"root_type": "Expense",
"Lohnaufwand": {
"account_number": "5000"
},
"Sozialversicherungsaufwand": {
"account_number": "5700"
},
"Übriger Personalaufwand": {
"account_number": "5800"
},
"Leistungen Dritter": {
"account_number": "5900"
}
},
"Übriger betrieblicher Aufwand, Abschreibungen und Wertberichtigungen sowie Finanzergebnis": {
"account_number": "6",
"is_group": 1,
"root_type": "Expense",
"Raumaufwand": {
"account_number": "6000"
},
"Unterhalt, Reparaturen, Ersatz mobile Sachanlagen": {
"account_number": "6100"
},
"Leasingaufwand mobile Sachanlagen": {
"account_number": "6105"
},
"Fahrzeug- und Transportaufwand": {
"account_number": "6200"
},
"Fahrzeugleasing und -mieten": {
"account_number": "6260"
},
"Sachversicherungen, Abgaben, Gebühren, Bewilligungen": {
"account_number": "6300"
},
"Energie- und Entsorgungsaufwand": {
"account_number": "6400"
},
"Verwaltungsaufwand": {
"account_number": "6500"
},
"Informatikaufwand inkl. Leasing": {
"account_number": "6570"
},
"Werbeaufwand": {
"account_number": "6600"
},
"Sonstiger betrieblicher Aufwand": {
"account_number": "6700"
},
"Abschreibungen und Wertberichtigungen auf Positionen des Anlagevermögens": {
"account_number": "6800"
},
"Finanzaufwand": {
"account_number": "6900"
},
"Finanzertrag": {
"account_number": "6950"
}
},
"Betrieblicher Nebenerfolg": {
"account_number": "7",
"is_group": 1,
"root_type": "Income",
"Ertrag Nebenbetrieb": {
"account_number": "7000"
},
"Aufwand Nebenbetrieb": {
"account_number": "7010"
},
"Ertrag betriebliche Liegenschaft": {
"account_number": "7500"
},
"Aufwand betriebliche Liegenschaft": {
"account_number": "7510"
}
},
"Betriebsfremder, ausserordentlicher, einmaliger oder periodenfremder Aufwand und Ertrag": {
"account_number": "8",
"is_group": 1,
"root_type": "Expense",
"Betriebsfremder Aufwand": {
"account_number": "8000"
},
"Betriebsfremder Ertrag": {
"account_number": "8100"
},
"Ausserordentlicher, einmaliger oder periodenfremder Aufwand": {
"account_number": "8500"
},
"Ausserordentlicher, einmaliger oder periodenfremder Ertrag": {
"account_number": "8510"
},
"Direkte Steuern": {
"account_number": "8900"
}
},
"Abschluss": {
"account_number": "9",
"is_group": 1,
"root_type": "Equity",
"Jahresgewinn oder Jahresverlust": {
"account_number": "9200"
}
}
}
}

View File

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

View File

@@ -41,6 +41,11 @@ class AccountingDimension(Document):
self.set_fieldname_and_label()
def validate(self):
self.validate_doctype()
validate_column_name(self.fieldname)
self.validate_dimension_defaults()
def validate_doctype(self):
if self.document_type in (
*core_doctypes_list,
"Accounting Dimension",
@@ -49,6 +54,7 @@ class AccountingDimension(Document):
"Accounting Dimension Detail",
"Company",
"Account",
"Finance Book",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
@@ -61,9 +67,6 @@ class AccountingDimension(Document):
if not self.is_new():
self.validate_document_type_change()
validate_column_name(self.fieldname)
self.validate_dimension_defaults()
def validate_document_type_change(self):
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
if doctype_before_save != self.document_type:
@@ -102,6 +105,7 @@ class AccountingDimension(Document):
def on_update(self):
frappe.flags.accounting_dimensions = None
frappe.flags.accounting_dimensions_details = None
def make_dimension_in_accounting_doctypes(doc, doclist=None):
@@ -262,7 +266,7 @@ def get_checks_for_pl_and_bs_accounts():
frappe.flags.accounting_dimensions_details = frappe.db.sql(
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent""",
WHERE p.name = c.parent AND p.disabled = 0""",
as_dict=1,
)

View File

@@ -12,7 +12,7 @@ frappe.ui.form.on("Accounts Settings", {
msg += " ";
msg += __("Please enable only if the understand the effects of enabling this.");
msg += "<br>";
msg += "Do you still want to enable immutable ledger?";
msg += __("Do you still want to enable immutable ledger?");
frappe.confirm(
msg,

View File

@@ -40,9 +40,14 @@
"show_payment_schedule_in_print",
"currency_exchange_section",
"allow_stale",
"column_break_yuug",
"stale_days",
"section_break_jpd0",
"auto_reconcile_payments",
"stale_days",
"auto_reconciliation_job_trigger",
"reconciliation_queue_size",
"column_break_resa",
"exchange_gain_loss_posting_date",
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
@@ -72,6 +77,7 @@
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"ignore_is_opening_check_for_reporting",
"column_break_lvjk",
"receivable_payable_remarks_length",
"payment_request_settings",
@@ -384,7 +390,7 @@
{
"fieldname": "section_break_jpd0",
"fieldtype": "Section Break",
"label": "Payment Reconciliations"
"label": "Payment Reconciliation Settings"
},
{
"default": "0",
@@ -489,6 +495,43 @@
"fieldname": "create_pr_in_draft_status",
"fieldtype": "Check",
"label": "Create in Draft Status"
},
{
"fieldname": "column_break_yuug",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_resa",
"fieldtype": "Column Break"
},
{
"default": "15",
"description": "Interval should be between 1 to 59 MInutes",
"fieldname": "auto_reconciliation_job_trigger",
"fieldtype": "Int",
"label": "Auto Reconciliation Job Trigger"
},
{
"default": "5",
"description": "Documents Processed on each trigger. Queue Size should be between 5 and 100",
"fieldname": "reconciliation_queue_size",
"fieldtype": "Int",
"label": "Reconciliation Queue Size"
},
{
"default": "0",
"description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports",
"fieldname": "ignore_is_opening_check_for_reporting",
"fieldtype": "Check",
"label": "Ignore Is Opening check for reporting"
},
{
"default": "Payment",
"description": "Only applies for Normal Payments",
"fieldname": "exchange_gain_loss_posting_date",
"fieldtype": "Select",
"label": "Posting Date Inheritance for Exchange Gain / Loss",
"options": "Invoice\nPayment\nReconciliation Date"
}
],
"icon": "icon-cog",
@@ -496,7 +539,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-07-26 06:48:52.714630",
"modified": "2025-01-23 13:15:44.077853",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -10,6 +10,7 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
from frappe.model.document import Document
from frappe.utils import cint
from erpnext.accounts.utils import sync_auto_reconcile_config
from erpnext.stock.utils import check_pending_reposting
@@ -27,6 +28,7 @@ class AccountsSettings(Document):
allow_multi_currency_invoices_against_single_party_account: DF.Check
allow_stale: DF.Check
auto_reconcile_payments: DF.Check
auto_reconciliation_job_trigger: DF.Int
automatically_fetch_payment_terms: DF.Check
automatically_process_deferred_accounting_entry: DF.Check
book_asset_depreciation_entry_automatically: DF.Check
@@ -43,14 +45,17 @@ class AccountsSettings(Document):
enable_fuzzy_matching: DF.Check
enable_immutable_ledger: DF.Check
enable_party_matching: DF.Check
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
frozen_accounts_modifier: DF.Link | None
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
ignore_is_opening_check_for_reporting: DF.Check
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_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
role_allowed_to_over_bill: DF.Link | None
round_row_wise_tax: DF.Check
show_balance_in_coa: DF.Check
@@ -90,6 +95,8 @@ class AccountsSettings(Document):
if clear_cache:
frappe.clear_cache()
self.validate_and_sync_auto_reconcile_config()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
@@ -114,3 +121,17 @@ class AccountsSettings(Document):
def validate_pending_reposts(self):
if self.acc_frozen_upto:
check_pending_reposting(self.acc_frozen_upto)
def validate_and_sync_auto_reconcile_config(self):
if self.has_value_changed("auto_reconciliation_job_trigger"):
if (
cint(self.auto_reconciliation_job_trigger) > 0
and cint(self.auto_reconciliation_job_trigger) < 60
):
sync_auto_reconcile_config(self.auto_reconciliation_job_trigger)
else:
frappe.throw(_("Cron Interval should be between 1 and 59 Min"))
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"))

View File

@@ -6,7 +6,7 @@ import frappe
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
from frappe.utils import cint, flt, fmt_money, get_link_to_form, getdate
from pypika import Order
import erpnext
@@ -48,6 +48,7 @@ class BankClearance(Document):
entries = []
# get entries from all the apps
precision = cint(frappe.db.get_default("currency_precision")) or 2
for method_name in frappe.get_hooks("get_payment_entries_for_bank_clearance"):
entries += (
frappe.get_attr(method_name)(
@@ -77,7 +78,7 @@ class BankClearance(Document):
if not d.get("account_currency"):
d.account_currency = default_currency
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
formatted_amount = fmt_money(abs(amount), precision, d.account_currency)
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
d.posting_date = getdate(d.posting_date)

View File

@@ -19,10 +19,15 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
if (!frm.doc.company) {
frm.set_value("company", frappe.defaults.get_default("company"));
}
// Set default filter dates
let today = frappe.datetime.get_today();
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger("bank_account");
},
@@ -98,7 +103,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
make_reconciliation_tool(frm) {
frm.get_field("reconciliation_tool_cards").$wrapper.empty();
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
frm.trigger("get_cleared_balance").then(() => {
if (
frm.doc.bank_account &&
@@ -114,12 +119,13 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
bank_account: frm.doc.bank_account,
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1),
company: frm.doc.company,
},
callback: (response) => {
frm.set_value("account_opening_balance", response.message);
@@ -129,12 +135,13 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
bank_account: frm.doc.bank_account,
till_date: frm.doc.bank_statement_to_date,
company: frm.doc.company,
},
callback: (response) => {
frm.cleared_balance = response.message;

View File

@@ -79,10 +79,17 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
@frappe.whitelist()
def get_account_balance(bank_account, till_date):
def get_account_balance(bank_account, till_date, company):
# returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
filters = frappe._dict(
{
"account": account,
"report_date": till_date,
"include_pos_transactions": 1,
"company": company,
}
)
data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -94,11 +101,7 @@ def get_account_balance(bank_account, till_date):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = (
flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
)
return bank_bal
return flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
@frappe.whitelist()
@@ -799,7 +802,6 @@ def get_je_matching_query(
.where(je.clearance_date.isnull())
.where(jea.account == common_filters.bank_account)
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(je.docstatus == 1)
.where(filter_by_date)
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
)

View File

@@ -99,9 +99,9 @@ class BankStatementImport(DataImport):
template_options=self.template_options,
now=run_now,
)
return True
return job_id
return False
return None
@frappe.whitelist()
@@ -113,7 +113,8 @@ def get_preview_from_template(data_import, import_file=None, google_sheets_url=N
@frappe.whitelist()
def form_start_import(data_import):
return frappe.get_doc("Bank Statement Import", data_import).start_import()
job_id = frappe.get_doc("Bank Statement Import", data_import).start_import()
return job_id is not None
@frappe.whitelist()

View File

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

View File

@@ -3,6 +3,7 @@
"allow_copy": 1,
"allow_import": 1,
"creation": "2013-01-23 19:57:17",
"default_view": "Tree",
"description": "Track separate Income and Expense for product verticals or divisions.",
"doctype": "DocType",
"document_type": "Setup",
@@ -125,7 +126,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2024-04-24 10:55:54.083042",
"modified": "2025-01-22 10:46:42.904001",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",

View File

@@ -129,7 +129,7 @@ class GLEntry(Document):
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
if not (self.party_type and self.party):
if not self.is_cancelled and not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type == "Receivable":
frappe.throw(

View File

@@ -123,3 +123,20 @@ class TestGLEntry(IntegrationTestCase):
str(e),
"Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC",
)
def test_validate_account_party_type_shareholder(self):
jv = make_journal_entry(
"Opening Balance Equity - _TC",
"Cash - _TC",
100,
"_Test Cost Center - _TC",
save=False,
submit=False,
)
for row in jv.accounts:
row.party_type = "Shareholder"
break
jv.save().submit()
self.assertEqual(1, jv.docstatus)

View File

@@ -430,12 +430,6 @@ frappe.ui.form.on("Journal Entry Account", {
});
}
},
cost_center: function (frm, dt, dn) {
// Don't reset for Gain/Loss type journals, as it will make Debit and Credit values '0'
if (frm.doc.voucher_type != "Exchange Gain Or Loss") {
erpnext.journal_entry.set_account_details(frm, dt, dn);
}
},
account: function (frm, dt, dn) {
erpnext.journal_entry.set_account_details(frm, dt, dn);

View File

@@ -154,10 +154,9 @@ class TestJournalEntry(IntegrationTestCase):
"credit_in_account_currency": 0 if diff > 0 else abs(diff),
},
)
jv.insert()
if account_bal == stock_bal:
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
self.assertRaises(StockAccountInvalidTransaction, jv.save)
frappe.db.rollback()
else:
jv.submit()

View File

@@ -11,6 +11,7 @@ from frappe.utils.background_jobs import enqueue, is_job_enqueued
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.stock.utils import get_default_stock_uom
class OpeningInvoiceCreationTool(Document):
@@ -172,7 +173,7 @@ class OpeningInvoiceCreationTool(Document):
income_expense_account_field = (
"income_account" if row.party_type == "Customer" else "expense_account"
)
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or "Nos"
default_uom = get_default_stock_uom()
rate = flt(row.outstanding_amount) / flt(row.qty)
item_dict = frappe._dict(

View File

@@ -258,6 +258,10 @@ frappe.ui.form.on("Payment Entry", {
frappe.flags.allocate_payment_amount = true;
},
validate: async function (frm) {
await frm.events.set_exchange_gain_loss_deduction(frm);
},
validate_company: (frm) => {
if (!frm.doc.company) {
frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") });
@@ -374,7 +378,6 @@ frappe.ui.form.on("Payment Entry", {
frm.set_df_property("total_allocated_amount", "options", currency_field);
frm.set_df_property("unallocated_amount", "options", currency_field);
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
frm.set_df_property("party_balance", "options", currency_field);
frm.set_currency_labels(
["total_amount", "outstanding_amount", "allocated_amount"],
@@ -422,15 +425,7 @@ frappe.ui.form.on("Payment Entry", {
if (frm.doc.payment_type == "Internal Transfer") {
$.each(
[
"party",
"party_type",
"party_balance",
"paid_from",
"paid_to",
"references",
"total_allocated_amount",
],
["party", "party_type", "paid_from", "paid_to", "references", "total_allocated_amount"],
function (i, field) {
frm.set_value(field, null);
}
@@ -478,13 +473,10 @@ frappe.ui.form.on("Payment Entry", {
$.each(
[
"party",
"party_balance",
"paid_from",
"paid_to",
"paid_from_account_currency",
"paid_from_account_balance",
"paid_to_account_currency",
"paid_to_account_balance",
"references",
"total_allocated_amount",
],
@@ -529,17 +521,14 @@ frappe.ui.form.on("Payment Entry", {
"paid_from_account_currency",
r.message.party_account_currency
);
frm.set_value("paid_from_account_balance", r.message.account_balance);
} else if (frm.doc.payment_type == "Pay") {
frm.set_value("paid_to", r.message.party_account);
frm.set_value(
"paid_to_account_currency",
r.message.party_account_currency
);
frm.set_value("paid_to_account_balance", r.message.account_balance);
}
},
() => frm.set_value("party_balance", r.message.party_balance),
() => frm.set_value("party_name", r.message.party_name),
() => frm.clear_table("references"),
() => frm.events.hide_unhide_fields(frm),
@@ -591,7 +580,6 @@ frappe.ui.form.on("Payment Entry", {
frm,
frm.doc.paid_from,
"paid_from_account_currency",
"paid_from_account_balance",
function (frm) {
if (frm.doc.payment_type == "Pay") {
frm.events.paid_amount(frm);
@@ -607,7 +595,6 @@ frappe.ui.form.on("Payment Entry", {
frm,
frm.doc.paid_to,
"paid_to_account_currency",
"paid_to_account_balance",
function (frm) {
if (frm.doc.payment_type == "Receive") {
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
@@ -623,13 +610,7 @@ frappe.ui.form.on("Payment Entry", {
);
},
set_account_currency_and_balance: function (
frm,
account,
currency_field,
balance_field,
callback_function
) {
set_account_currency_and_balance: function (frm, account, currency_field, callback_function) {
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.posting_date && account) {
frappe.call({
@@ -644,8 +625,6 @@ frappe.ui.form.on("Payment Entry", {
frappe.run_serially([
() => frm.set_value(currency_field, r.message["account_currency"]),
() => {
frm.set_value(balance_field, r.message["account_balance"]);
if (
frm.doc.payment_type == "Receive" &&
currency_field == "paid_to_account_currency"
@@ -812,27 +791,41 @@ frappe.ui.form.on("Payment Entry", {
paid_amount: function (frm) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (!frm.doc.received_amount) {
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.paid_amount);
} else if (company_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.base_paid_amount);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
}
frm.trigger("reset_received_amount");
frm.events.hide_unhide_fields(frm);
},
received_amount: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frm.set_paid_amount_based_on_received_amount = true;
if (!frm.doc.paid_amount && frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("paid_amount", frm.doc.received_amount);
if (frm.doc.target_exchange_rate) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
}
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
}
frm.set_value(
"base_received_amount",
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
);
if (!frm.doc.paid_amount) {
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("paid_amount", frm.doc.received_amount);
if (frm.doc.target_exchange_rate) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
}
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
} else if (company_currency == frm.doc.paid_from_account_currency) {
frm.set_value("paid_amount", frm.doc.base_received_amount);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
}
}
if (frm.doc.payment_type == "Pay")
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, true);
else frm.events.set_unallocated_amount(frm);
@@ -1670,37 +1663,6 @@ frappe.ui.form.on("Payment Entry", {
return current_tax_amount;
},
cost_center: function (frm) {
if (frm.doc.posting_date && (frm.doc.paid_from || frm.doc.paid_to)) {
return frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
args: {
company: frm.doc.company,
date: frm.doc.posting_date,
paid_from: frm.doc.paid_from,
paid_to: frm.doc.paid_to,
ptype: frm.doc.party_type,
pty: frm.doc.party,
cost_center: frm.doc.cost_center,
},
callback: function (r, rt) {
if (r.message) {
frappe.run_serially([
() => {
frm.set_value(
"paid_from_account_balance",
r.message.paid_from_account_balance
);
frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
frm.set_value("party_balance", r.message.party_balance);
},
]);
}
},
});
}
},
after_save: function (frm) {
const { matched_payment_requests } = frappe.last_response;
if (!matched_payment_requests) return;
@@ -1879,8 +1841,6 @@ function prompt_for_missing_account(frm, account) {
(values) => resolve(values?.[account]),
__("Please Specify Account")
);
dialog.on_hide = () => resolve("");
});
}

View File

@@ -21,22 +21,20 @@
"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",
"contact_person",
"contact_email",
"payment_accounts_section",
"party_balance",
"paid_from",
"paid_from_account_type",
"paid_from_account_currency",
"paid_from_account_balance",
"column_break_18",
"paid_to",
"paid_to_account_type",
"paid_to_account_currency",
"paid_to_account_balance",
"payment_amounts_section",
"paid_amount",
"paid_amount_after_tax",
@@ -222,15 +220,6 @@
"fieldtype": "Section Break",
"label": "Accounts"
},
{
"depends_on": "party",
"fieldname": "party_balance",
"fieldtype": "Currency",
"label": "Party Balance",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"bold": 1,
"depends_on": "eval:(in_list([\"Internal Transfer\", \"Pay\"], doc.payment_type) || doc.party)",
@@ -252,15 +241,6 @@
"read_only": 1,
"reqd": 1
},
{
"depends_on": "paid_from",
"fieldname": "paid_from_account_balance",
"fieldtype": "Currency",
"label": "Account Balance (From)",
"options": "paid_from_account_currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
@@ -285,15 +265,6 @@
"read_only": 1,
"reqd": 1
},
{
"depends_on": "paid_to",
"fieldname": "paid_to_account_balance",
"fieldtype": "Currency",
"label": "Account Balance (To)",
"options": "paid_to_account_currency",
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:(doc.paid_to && doc.paid_from)",
"fieldname": "payment_amounts_section",
@@ -783,6 +754,16 @@
"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"
}
],
"index_web_pages_for_search": 1,
@@ -796,7 +777,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-11-07 11:19:19.320883",
"modified": "2025-01-31 11:24:58.076393",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@@ -842,4 +823,4 @@
"states": [],
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -25,6 +25,10 @@ from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -71,12 +75,16 @@ 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
bank: DF.ReadOnly | None
bank_account: DF.Link | None
bank_account_no: DF.ReadOnly | None
base_in_words: DF.SmallText | None
base_paid_amount: DF.Currency
base_paid_amount_after_tax: DF.Currency
base_received_amount: DF.Currency
@@ -92,21 +100,20 @@ class PaymentEntry(AccountsController):
custom_remarks: DF.Check
deductions: DF.Table[PaymentEntryDeduction]
difference_amount: DF.Currency
in_words: DF.SmallText | None
is_opening: DF.Literal["No", "Yes"]
letter_head: DF.Link | None
mode_of_payment: DF.Link | None
naming_series: DF.Literal["ACC-PAY-.YYYY.-"]
paid_amount: DF.Currency
paid_amount_after_tax: DF.Currency
paid_from: DF.Link
paid_from_account_balance: DF.Currency
paid_from_account_currency: DF.Link
paid_from_account_type: DF.Data | None
paid_to: DF.Link
paid_to_account_balance: DF.Currency
paid_to_account_currency: DF.Link
paid_to_account_type: DF.Data | None
party: DF.DynamicLink | None
party_balance: DF.Currency
party_bank_account: DF.Link | None
party_name: DF.Data | None
party_type: DF.Link | None
@@ -119,6 +126,7 @@ class PaymentEntry(AccountsController):
purchase_taxes_and_charges_template: DF.Link | None
received_amount: DF.Currency
received_amount_after_tax: DF.Currency
reconcile_on_advance_payment_date: DF.Check
reference_date: DF.Date | None
reference_no: DF.Data | None
references: DF.Table[PaymentEntryReference]
@@ -196,6 +204,23 @@ class PaymentEntry(AccountsController):
self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def validate_for_repost(self):
validate_docs_for_voucher_types(["Payment Entry"])
validate_docs_for_deferred_accounting([self.name], [])
def on_update_after_submit(self):
# Flag will be set on Reconciliation
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
if self.flags.get("ignore_reposting_on_reconciliation"):
return
self.needs_repost = self.check_if_fields_updated(
fields_to_check=[], child_tables={"references": [], "taxes": [], "deductions": []}
)
if self.needs_repost:
self.validate_for_repost()
self.repost_accounting_entries()
def set_liability_account(self):
# Auto setting liability account should only be done during 'draft' status
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
@@ -478,7 +503,6 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Internal Transfer":
for field in (
"party",
"party_balance",
"total_allocated_amount",
"base_total_allocated_amount",
"unallocated_amount",
@@ -506,25 +530,19 @@ class PaymentEntry(AccountsController):
)
else:
complete_contact_details(self)
if not self.party_balance:
self.party_balance = get_balance_on(
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
)
if not self.party_account:
party_account = get_party_account(self.party_type, self.party, self.company)
self.set(self.party_account_field, party_account)
self.party_account = party_account
if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance:
if self.paid_from and not self.paid_from_account_currency:
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
self.paid_from_account_currency = acc.account_currency
self.paid_from_account_balance = acc.account_balance
if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance:
if self.paid_to and not self.paid_to_account_currency:
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
self.paid_to_account_currency = acc.account_currency
self.paid_to_account_balance = acc.account_balance
self.party_account_currency = (
self.paid_from_account_currency
@@ -635,7 +653,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
_("Reference Doctype must be one of {0}").format(
comma_or(_(d) for d in valid_reference_doctypes)
comma_or([_(d) for d in valid_reference_doctypes])
)
)
@@ -1500,16 +1518,26 @@ class PaymentEntry(AccountsController):
"voucher_detail_no": invoice.name,
}
if self.reconcile_on_advance_payment_date:
posting_date = self.posting_date
if invoice.reconcile_effect_on:
posting_date = invoice.reconcile_effect_on
else:
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
if getdate(posting_date) < getdate(self.posting_date):
# For backwards compatibility
# Supporting reposting on payment entries reconciled before select field introduction
if self.advance_reconciliation_takes_effect_on == "Advance Payment Date":
posting_date = self.posting_date
elif self.advance_reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(
invoice.reference_doctype, invoice.reference_name, date_field
)
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
elif self.advance_reconciliation_takes_effect_on == "Reconciliation Date":
posting_date = nowdate()
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
args_dict["account"] = account
@@ -1665,6 +1693,14 @@ class PaymentEntry(AccountsController):
elif self.payment_type in ("Pay", "Internal Transfer"):
return self.paid_from
def get_value_in_transaction_currency(self, account_currency, gl_dict, field):
company_currency = erpnext.get_company_currency(self.company)
conversion_rate = self.target_exchange_rate
if self.paid_from_account_currency != company_currency:
conversion_rate = self.source_exchange_rate
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
advance_payment_doctypes = frappe.get_hooks(
@@ -1882,7 +1918,7 @@ class PaymentEntry(AccountsController):
paid_amount -= sum(flt(d.amount, precision) for d in self.deductions)
for ref in self.references:
reference_outstanding_amount = ref.outstanding_amount
reference_outstanding_amount = flt(ref.outstanding_amount)
abs_outstanding_amount = abs(reference_outstanding_amount)
if reference_outstanding_amount > 0:
@@ -2327,10 +2363,17 @@ def get_outstanding_reference_documents(args, validate=False):
outstanding_invoices = []
negative_outstanding_invoices = []
party_account = args.get("party_account")
# get party account if advance account is set.
if args.get("book_advance_payments_in_separate_party_account"):
party_account = get_party_account(args.get("party_type"), args.get("party"), args.get("company"))
else:
party_account = args.get("party_account")
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
if party_account == advance_account:
party_account = accounts[0]
if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
@@ -2668,9 +2711,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
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)
party_balance = get_balance_on(
party_type=party_type, party=party, company=company, cost_center=cost_center
)
if party_type in ["Customer", "Supplier"]:
party_bank_account = get_party_bank_account(party_type, party)
bank_account = get_default_company_bank_account(company, party_type, party)
@@ -2679,7 +2720,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,
"party_balance": party_balance,
"account_balance": account_balance,
"party_bank_account": party_bank_account,
"bank_account": bank_account,
@@ -2910,6 +2950,7 @@ def get_payment_entry(
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name")
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
pe.project = doc.get("project") or reduce(
@@ -2918,7 +2959,7 @@ def get_payment_entry(
if pe.party_type in ["Customer", "Supplier"]:
bank_account = get_party_bank_account(pe.party_type, pe.party)
pe.set("bank_account", bank_account)
pe.set("party_bank_account", bank_account)
pe.set_bank_account_data()
# only Purchase Invoice can be blocked individually
@@ -3396,13 +3437,14 @@ def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
"""Add loss on income discount in base currency."""
precision = doc.precision("total")
base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)
positive_negative = -1 if pe.payment_type == "Pay" else 1
pe.append(
"deductions",
{
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(base_loss_on_income, precision),
"amount": flt(base_loss_on_income, precision) * positive_negative,
},
)
@@ -3414,6 +3456,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
tax_discount_loss = {}
base_total_tax_loss = 0
precision = doc.precision("tax_amount_after_discount_amount", "taxes")
positive_negative = -1 if pe.payment_type == "Pay" else 1
# The same account head could be used more than once
for tax in doc.get("taxes", []):
@@ -3436,7 +3479,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"account": account,
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
"amount": flt(loss, precision) * positive_negative,
},
)
@@ -3504,19 +3547,6 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
return paid_amount[0][0] if paid_amount else 0
@frappe.whitelist()
def get_party_and_account_balance(
company, date, paid_from=None, paid_to=None, ptype=None, pty=None, cost_center=None
):
return frappe._dict(
{
"party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
"paid_from_account_balance": get_balance_on(paid_from, date, cost_center=cost_center),
"paid_to_account_balance": get_balance_on(paid_to, date=date, cost_center=cost_center),
}
)
@frappe.whitelist()
def make_payment_order(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc

View File

@@ -291,6 +291,48 @@ class TestPaymentEntry(IntegrationTestCase):
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_entry_against_payment_terms_with_discount_on_pi(self):
pi = make_purchase_invoice(do_not_save=1)
create_payment_terms_template_with_discount()
pi.payment_terms_template = "Test Discount Template"
frappe.db.set_value("Company", pi.company, "default_discount_account", "Write Off - _TC")
pi.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18,
},
)
pi.save()
pi.submit()
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
pe_with_tax_loss = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(pe_with_tax_loss.payment_type, "Pay")
self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 295.0)
self.assertEqual(pe_with_tax_loss.paid_amount, 265.5)
self.assertEqual(pe_with_tax_loss.difference_amount, 0)
self.assertEqual(pe_with_tax_loss.deductions[0].amount, -25.0) # Loss on Income
self.assertEqual(pe_with_tax_loss.deductions[1].amount, -4.5) # Loss on Tax
self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(pe.payment_type, "Pay")
self.assertEqual(pe.references[0].allocated_amount, 295.0)
self.assertEqual(pe.paid_amount, 265.5)
self.assertEqual(pe.deductions[0].amount, -29.5)
self.assertEqual(pe.difference_amount, 0)
def test_payment_entry_against_payment_terms_with_discount(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template_with_discount()

View File

@@ -13,6 +13,7 @@
"payment_term_outstanding",
"account_type",
"payment_type",
"reconcile_effect_on",
"column_break_4",
"total_amount",
"outstanding_amount",
@@ -144,12 +145,18 @@
"is_virtual": 1,
"label": "Payment Request Outstanding",
"read_only": 1
},
{
"fieldname": "reconcile_effect_on",
"fieldtype": "Date",
"label": "Reconcile Effect On",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-09-16 18:11:50.019343",
"modified": "2025-01-13 15:56:18.895082",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",

View File

@@ -30,6 +30,7 @@ class PaymentEntryReference(Document):
payment_term: DF.Link | None
payment_term_outstanding: DF.Float
payment_type: DF.Data | None
reconcile_effect_on: DF.Date | None
reference_doctype: DF.Link
reference_name: DF.DynamicLink
total_amount: DF.Float

View File

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

View File

@@ -6,6 +6,7 @@ import frappe
from frappe import qb
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
from frappe.utils.data import getdate as convert_to_date
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -1680,7 +1681,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
"reconcile_on_advance_payment_date": 1,
"reconciliation_takes_effect_on": "Advance Payment Date",
},
)
@@ -1729,7 +1730,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_received_account": self.advance_receivable_account,
"reconcile_on_advance_payment_date": 0,
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
amount = 200.0
@@ -1838,7 +1839,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
"reconcile_on_advance_payment_date": 0,
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
amount = 200.0
@@ -2057,6 +2058,102 @@ class TestPaymentReconciliation(IntegrationTestCase):
self.assertEqual(pr.get("invoices"), [])
self.assertEqual(pr.get("payments"), [])
def test_advance_reconciliation_effect_on_same_date(self):
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_received_account": self.advance_receivable_account,
"reconciliation_takes_effect_on": "Reconciliation Date",
},
)
inv_date = convert_to_date(add_days(nowdate(), -1))
adv_date = convert_to_date(add_days(nowdate(), -2))
si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=200)
pe = self.create_payment_entry(posting_date=adv_date, amount=80).save().submit()
pr = self.create_payment_reconciliation()
pr.from_invoice_date = add_days(nowdate(), -1)
pr.to_invoice_date = nowdate()
pr.from_payment_date = add_days(nowdate(), -2)
pr.to_payment_date = nowdate()
pr.default_advance_account = self.advance_receivable_account
# reconcile multiple payments against invoice
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
si.reload()
self.assertEqual(si.status, "Partly Paid")
# check PR tool output post reconciliation
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 120)
self.assertEqual(pr.get("payments"), [])
# Assert Ledger Entries
gl_entries = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pe.name},
fields=["account", "posting_date", "voucher_no", "against_voucher", "debit", "credit"],
order_by="account, against_voucher, debit",
)
expected_gl = [
{
"account": self.advance_receivable_account,
"posting_date": adv_date,
"voucher_no": pe.name,
"against_voucher": pe.name,
"debit": 0.0,
"credit": 80.0,
},
{
"account": self.advance_receivable_account,
"posting_date": convert_to_date(nowdate()),
"voucher_no": pe.name,
"against_voucher": pe.name,
"debit": 80.0,
"credit": 0.0,
},
{
"account": self.debit_to,
"posting_date": convert_to_date(nowdate()),
"voucher_no": pe.name,
"against_voucher": si.name,
"debit": 0.0,
"credit": 80.0,
},
{
"account": self.bank,
"posting_date": adv_date,
"voucher_no": pe.name,
"against_voucher": None,
"debit": 80.0,
"credit": 0.0,
},
]
self.assertEqual(expected_gl, gl_entries)
# cancel PE
pe.reload()
pe.cancel()
pr.get_unreconciled_entries()
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 0)
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -300,6 +300,7 @@ class PaymentRequest(Document):
"payer_name": data.customer_name,
"order_id": self.name,
"currency": self.currency,
"payment_gateway": self.payment_gateway,
}
)
@@ -780,7 +781,10 @@ def get_existing_paid_amount(doctype, name):
frappe.qb.from_(PL)
.left_join(PER)
.on(
(PER.reference_doctype == PL.against_voucher_type) & (PER.reference_name == PL.against_voucher_no)
(PL.against_voucher_type == PER.reference_doctype)
& (PL.against_voucher_no == PER.reference_name)
& (PL.voucher_type == PER.parenttype)
& (PL.voucher_no == PER.parent)
)
.select(Abs(Sum(PL.amount)).as_("total_paid_amount"))
.where(PL.against_voucher_type.eq(doctype))

View File

@@ -709,6 +709,45 @@ class TestPaymentRequest(IntegrationTestCase):
self.assertEqual(pr.grand_total, si.outstanding_amount)
def test_partial_paid_invoice_with_more_payment_entry(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=500)
pi.submit()
pi_1 = make_purchase_invoice(currency="INR", qty=1, rate=300)
pi_1.submit()
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1, submit_doc=0, return_doc=1)
pr.grand_total = 200
pr.submit()
pr.create_payment_entry()
pr_1 = make_payment_request(
dt="Purchase Invoice", dn=pi.name, mute_email=1, submit_doc=0, return_doc=1
)
pr_1.grand_total = 200
pr_1.submit()
pr_1.create_payment_entry()
pe = get_payment_entry(dt="Purchase Invoice", dn=pi.name)
pe.paid_amount = 200
pe.references[0].reference_doctype = pi.doctype
pe.references[0].reference_name = pi.name
pe.references[0].grand_total = pi.grand_total
pe.references[0].outstanding_amount = pi.outstanding_amount
pe.references[0].allocated_amount = 100
pe.append(
"references",
{
"reference_doctype": pi_1.doctype,
"reference_name": pi_1.name,
"grand_total": pi_1.grand_total,
"outstanding_amount": pi_1.outstanding_amount,
"allocated_amount": 100,
},
)
pr_2 = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
pi.load_from_db()
self.assertEqual(pr_2.grand_total, pi.outstanding_amount)
def test_partial_paid_invoice_with_submitted_payment_entry(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)

View File

@@ -39,10 +39,12 @@ class TestPOSClosingEntry(IntegrationTestCase):
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv1.save()
pos_inv1.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()
pcv_doc = make_closing_entry_from_opening(opening_entry)
@@ -68,6 +70,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv.save()
pos_inv.submit()
pcv_doc = make_closing_entry_from_opening(opening_entry)
@@ -86,10 +89,12 @@ class TestPOSClosingEntry(IntegrationTestCase):
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv1.save()
pos_inv1.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()
# make return entry of pos_inv2
@@ -111,10 +116,12 @@ class TestPOSClosingEntry(IntegrationTestCase):
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv1.save()
pos_inv1.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()
pcv_doc = make_closing_entry_from_opening(opening_entry)
@@ -165,6 +172,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv1 = create_pos_invoice(rate=350, do_not_submit=1, pos_profile=pos_profile.name)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv1.save()
pos_inv1.submit()
# if in between a mandatory accounting dimension is added to the POS Profile then
@@ -226,6 +234,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
do_not_submit=True,
)
pos_inv.payments[0].amount = pos_inv.grand_total
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(
item_code=item_code,
@@ -236,11 +245,9 @@ class TestPOSClosingEntry(IntegrationTestCase):
do_not_submit=True,
)
pos_inv2.payments[0].amount = pos_inv2.grand_total
pos_inv2.save()
pos_inv2.submit()
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
self.assertEqual(batch_qty, 10)
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
self.assertEqual(batch_qty_with_pos, 0.0)
@@ -270,9 +277,6 @@ class TestPOSClosingEntry(IntegrationTestCase):
pcv_doc.reload()
pcv_doc.cancel()
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
self.assertEqual(batch_qty, 10)
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
self.assertEqual(batch_qty_with_pos, 0.0)

View File

@@ -8,7 +8,6 @@
"engine": "InnoDB",
"field_order": [
"customer_section",
"title",
"naming_series",
"customer",
"customer_name",
@@ -192,16 +191,6 @@
"fieldtype": "Section Break",
"options": "fa fa-user"
},
{
"allow_on_submit": 1,
"default": "{customer_name}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"label": "Title",
"no_copy": 1,
"print_hide": 1
},
{
"bold": 1,
"fieldname": "naming_series",
@@ -1584,7 +1573,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2024-11-26 13:10:50.309570",
"modified": "2025-01-06 15:03:19.957277",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
@@ -1635,7 +1624,6 @@
"sort_order": "DESC",
"states": [],
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
"title_field": "customer_name",
"track_changes": 1
}

View File

@@ -20,6 +20,10 @@ from erpnext.controllers.queries import item_query as _item_query
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.
@@ -161,7 +165,6 @@ class POSInvoice(SalesInvoice):
terms: DF.TextEditor | None
territory: DF.Link | None
timesheets: DF.Table[SalesInvoiceTimesheet]
title: DF.Data | None
to_date: DF.Date | None
total: DF.Currency
total_advance: DF.Currency
@@ -211,6 +214,7 @@ class POSInvoice(SalesInvoice):
self.validate_payment_amount()
self.validate_loyalty_transaction()
self.validate_company_with_pos_company()
self.validate_full_payment()
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
@@ -485,6 +489,20 @@ 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,7 +7,7 @@ import frappe
from frappe import _
from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice.pos_invoice import PartialPaymentValidationError, make_sales_return
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.stock.doctype.item.test_item import make_item
@@ -317,7 +317,7 @@ class TestPOSInvoice(IntegrationTestCase):
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2000, "default": 1}
)
pos.insert()
@@ -328,6 +328,11 @@ 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.paid_amount = -1000
pos_return1.submit()
pos_return1.reload()
@@ -342,6 +347,11 @@ 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.paid_amount = -1000
pos_return2.submit()
self.assertEqual(pos_return2.get("items")[0].qty, -1)
@@ -377,6 +387,15 @@ class TestPOSInvoice(IntegrationTestCase):
inv.payments = []
self.assertRaises(frappe.ValidationError, inv.insert)
def test_partial_payment(self):
pos_inv = create_pos_invoice(rate=10000, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 9000},
)
pos_inv.insert()
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
def test_serialized_item_transaction(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
@@ -589,7 +608,13 @@ class TestPOSInvoice(IntegrationTestCase):
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
)
inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000)
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},
)
inv.insert()
inv.submit()
lpe = frappe.get_doc(
"Loyalty Point Entry",
@@ -615,7 +640,13 @@ class TestPOSInvoice(IntegrationTestCase):
)
# add 10 loyalty points
create_pos_invoice(customer="Test Loyalty Customer", rate=10000)
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},
)
pos_inv.paid_amount = 10000
pos_inv.submit()
before_lp_details = get_loyalty_program_details_with_points(
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
@@ -649,10 +680,12 @@ class TestPOSInvoice(IntegrationTestCase):
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.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()
consolidate_pos_invoices()
@@ -684,6 +717,7 @@ class TestPOSInvoice(IntegrationTestCase):
"included_in_print_rate": 1,
},
)
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
@@ -700,6 +734,7 @@ class TestPOSInvoice(IntegrationTestCase):
"included_in_print_rate": 1,
},
)
pos_inv2.save()
pos_inv2.submit()
consolidate_pos_invoices()
@@ -752,6 +787,7 @@ class TestPOSInvoice(IntegrationTestCase):
"included_in_print_rate": 1,
},
)
pos_inv2.save()
pos_inv2.submit()
consolidate_pos_invoices()
@@ -782,7 +818,10 @@ class TestPOSInvoice(IntegrationTestCase):
# POS Invoice 1, for the batch without bundle
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},
)
pos_inv1.items[0].batch_no = batch_no
pos_inv1.save()
pos_inv1.submit()
@@ -798,8 +837,14 @@ class TestPOSInvoice(IntegrationTestCase):
# POS Invoice 2, for the batch with bundle
pos_inv2 = create_pos_invoice(
item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no
item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no, do_not_save=1
)
pos_inv2.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3000},
)
pos_inv2.save()
pos_inv2.submit()
pos_inv2.reload()
self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle)
@@ -834,6 +879,10 @@ class TestPOSInvoice(IntegrationTestCase):
pos_inv1 = create_pos_invoice(
item=item.name, rate=300, qty=1, do_not_submit=1, batch_no="TestBatch 01"
)
pos_inv1.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300},
)
pos_inv1.save()
pos_inv1.submit()
@@ -843,7 +892,8 @@ class TestPOSInvoice(IntegrationTestCase):
{
"item_code": item.name,
"warehouse": pos_inv2.items[0].warehouse,
"voucher_type": "Delivery Note",
"voucher_type": "POS Invoice",
"voucher_no": pos_inv2.name,
"qty": 2,
"avg_rate": 300,
"batches": frappe._dict({"TestBatch 01": 2}),

View File

@@ -12,7 +12,9 @@ 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
from erpnext.accounts.doctype.pos_profile.pos_profile import required_accounting_dimensions
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.controllers.taxes_and_totals import ItemWiseTaxDetail
@@ -293,22 +295,23 @@ class POSInvoiceMergeLog(Document):
invoice.disable_rounded_total = cint(
frappe.db.get_value("POS Profile", invoice.pos_profile, "disable_rounded_total")
)
accounting_dimensions = required_accounting_dimensions()
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, as_dict=1
"POS Profile", {"name": invoice.pos_profile}, accounting_dimensions_fields, as_dict=1
)
for dimension in accounting_dimensions:
dimension_value = dimension_values.get(dimension)
dimension_value = dimension_values.get(dimension.fieldname)
if not dimension_value:
if not dimension_value and (dimension.mandatory_for_pl or dimension.mandatory_for_bs):
frappe.throw(
_("Please set Accounting Dimension {} in {}").format(
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(dimension.label),
frappe.get_desk_link("POS Profile", invoice.pos_profile),
)
)
invoice.set(dimension, dimension_value)
invoice.set(dimension.fieldname, dimension_value)
if self.merge_invoices_based_on == "Customer Group":
invoice.flags.ignore_pos_profile = True

View File

@@ -40,14 +40,17 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
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_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()
consolidate_pos_invoices()
@@ -73,14 +76,17 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
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_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)
@@ -135,6 +141,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
)
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)
@@ -152,6 +159,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
)
inv2.insert()
inv2.payments[0].amount = inv.grand_total
inv2.save()
inv2.submit()
consolidate_pos_invoices()
@@ -291,7 +299,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
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": 1000})
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
inv3.insert()
inv3.submit()
@@ -299,8 +307,8 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.outstanding_amount, 800)
self.assertNotEqual(consolidated_invoice.status, "Paid")
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
self.assertEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
@@ -435,6 +443,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
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)
@@ -449,6 +458,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
do_not_submit=1,
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv2.save()
pos_inv2.submit()
consolidate_pos_invoices()

View File

@@ -25,11 +25,13 @@
"hide_unavailable_items",
"auto_add_item_to_cart",
"validate_stock_on_save",
"print_receipt_on_order_complete",
"column_break_16",
"update_stock",
"ignore_pricing_rule",
"allow_rate_change",
"allow_discount_change",
"disable_grand_total_to_default_mop",
"section_break_23",
"item_groups",
"column_break_25",
@@ -374,24 +376,36 @@
},
{
"fieldname": "utm_campaign",
"print_hide": 1,
"fieldtype": "Link",
"label": "Campaign",
"options": "UTM Campaign"
"options": "UTM Campaign",
"print_hide": 1
},
{
"fieldname": "utm_source",
"print_hide": 1,
"fieldtype": "Link",
"label": "Source",
"options": "UTM Source"
"options": "UTM Source",
"print_hide": 1
},
{
"fieldname": "utm_medium",
"print_hide": 1,
"fieldtype": "Link",
"label": "Medium",
"options": "UTM Campaign"
"options": "UTM Campaign",
"print_hide": 1
},
{
"default": "0",
"fieldname": "print_receipt_on_order_complete",
"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"
}
],
"icon": "icon-cog",
@@ -419,7 +433,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2024-06-28 10:51:48.543766",
"modified": "2025-01-29 13:12:30.796630",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
@@ -448,4 +462,4 @@
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -7,6 +7,10 @@ from frappe import _, msgprint, scrub, unscrub
from frappe.model.document import Document
from frappe.utils import get_link_to_form, now
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
class POSProfile(Document):
# begin: auto-generated types
@@ -35,6 +39,7 @@ 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
@@ -46,6 +51,7 @@ class POSProfile(Document):
letter_head: DF.Link | None
payments: DF.Table[POSPaymentMethod]
print_format: DF.Link | None
print_receipt_on_order_complete: DF.Check
select_print_heading: DF.Link | None
selling_price_list: DF.Link | None
tax_category: DF.Link | None
@@ -70,15 +76,19 @@ class POSProfile(Document):
self.validate_accounting_dimensions()
def validate_accounting_dimensions(self):
acc_dim_names = required_accounting_dimensions()
for acc_dim in acc_dim_names:
if not self.get(acc_dim):
acc_dims = get_checks_for_pl_and_bs_accounts()
for acc_dim in acc_dims:
if (
self.company == acc_dim.company
and not self.get(acc_dim.fieldname)
and (acc_dim.mandatory_for_pl or acc_dim.mandatory_for_bs)
):
frappe.throw(
_(
"{0} is a mandatory Accounting Dimension. <br>"
"Please set a value for {0} in Accounting Dimensions section."
).format(
unscrub(frappe.bold(acc_dim)),
frappe.bold(acc_dim.label),
),
title=_("Mandatory Accounting Dimension"),
)
@@ -216,23 +226,6 @@ def get_child_nodes(group_type, root):
)
def required_accounting_dimensions():
p = frappe.qb.DocType("Accounting Dimension")
c = frappe.qb.DocType("Accounting Dimension Detail")
acc_dim_doc = (
frappe.qb.from_(p)
.inner_join(c)
.on(p.name == c.parent)
.select(c.parent)
.where((c.mandatory_for_bs == 1) | (c.mandatory_for_pl == 1))
.where(p.disabled == 0)
).run(as_dict=1)
acc_dim_names = [scrub(d.parent) for d in acc_dim_doc]
return acc_dim_names
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):

View File

@@ -1,7 +1,10 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from collections import Counter
import frappe
from frappe import _
from frappe.model.document import Document
@@ -22,4 +25,14 @@ class POSSettings(Document):
# end: auto-generated types
def validate(self):
pass
self.validate_invoice_fields()
def validate_invoice_fields(self):
invoice_fields = [field.fieldname for field in self.invoice_fields]
duplicate_invoice_fields = {key for key, value in Counter(invoice_fields).items() if value > 1}
if len(duplicate_invoice_fields):
for field in duplicate_invoice_fields:
frappe.throw(
title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field)
)

View File

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

View File

@@ -60,6 +60,7 @@ class PricingRule(Document):
disable: DF.Check
discount_amount: DF.Currency
discount_percentage: DF.Float
dont_enforce_free_item_qty: DF.Check
for_price_list: DF.Link | None
free_item: DF.Link | None
free_item_rate: DF.Currency
@@ -415,8 +416,6 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
"parent": args.parent,
"parenttype": args.parenttype,
"child_docname": args.get("child_docname"),
"discount_percentage": 0.0,
"discount_amount": 0,
}
)
@@ -647,7 +646,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
if pricing_rule.margin_type in ["Percentage", "Amount"]:
item_details.margin_rate_or_amount = 0.0
item_details.margin_type = None
elif pricing_rule.get("free_item"):
elif pricing_rule.get("free_item") and not pricing_rule.get("dont_enforce_free_item_qty"):
item_details.remove_free_item = (
item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item")
)

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
{
"actions": [],
"autoname": "format:ACC-PPR-{#####}",
"beta": 1,
"creation": "2023-03-30 21:28:39.793927",
"default_view": "List",
"doctype": "DocType",
@@ -158,7 +157,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-08-27 14:48:56.715320",
"modified": "2025-01-08 08:22:14.798085",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Payment Reconciliation",
@@ -192,4 +191,4 @@
"sort_order": "DESC",
"states": [],
"title_field": "company"
}
}

View File

@@ -210,9 +210,9 @@ def trigger_reconciliation_for_queued_docs():
docs_to_trigger = []
unique_filters = set()
queue_size = 5
queue_size = frappe.db.get_single_value("Accounts Settings", "reconciliation_queue_size") or 5
fields = ["company", "party_type", "party", "receivable_payable_account"]
fields = ["company", "party_type", "party", "receivable_payable_account", "default_advance_account"]
def get_filters_as_tuple(fields, doc):
filters = ()

View File

@@ -1,7 +1,6 @@
{
"actions": [],
"autoname": "format:PPR-LOG-{##}",
"beta": 1,
"creation": "2023-03-13 15:00:09.149681",
"default_view": "List",
"doctype": "DocType",
@@ -110,7 +109,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-03-27 13:10:18.769659",
"modified": "2025-01-08 08:22:19.104975",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Payment Reconciliation Log",

View File

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

View File

@@ -20,6 +20,7 @@ class ProcessPaymentReconciliationLogAllocations(Document):
difference_account: DF.Link | None
difference_amount: DF.Currency
exchange_rate: DF.Float
gain_loss_posting_date: DF.Date | None
invoice_number: DF.DynamicLink
invoice_type: DF.Link
is_advance: DF.Data | None

View File

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

View File

@@ -336,6 +336,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return;
let payment_terms_template = this.frm.doc.payment_terms_template;
erpnext.utils.get_party_details(
this.frm,
"erpnext.accounts.party.get_party_details",
@@ -356,6 +358,12 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
// while duplicating, don't change payment terms
if (me.frm.doc.__run_link_triggers === false) {
me.frm.set_value("payment_terms_template", payment_terms_template);
me.frm.refresh_field("payment_terms_template");
}
}
);
}
@@ -372,6 +380,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
}
tax_withholding_category(frm) {
var me = this;
let filtered_taxes = (me.frm.doc.taxes || []).filter((row) => !row.is_tax_withholding_account);
me.frm.clear_table("taxes");
filtered_taxes.forEach((row) => {
me.frm.add_child("taxes", row);
});
me.frm.refresh_field("taxes");
}
credit_to() {
var me = this;
if (this.frm.doc.credit_to) {

View File

@@ -1625,7 +1625,7 @@
{
"default": "1",
"depends_on": "eval: doc.is_return && doc.return_against",
"description": "Debit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
"description": "Debit Note will update it's own outstanding amount, even if 'Return Against' is specified.",
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
"label": "Update Outstanding for Self"
@@ -1641,7 +1641,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2024-10-25 18:13:01.944477",
"modified": "2025-01-14 11:39:04.564610",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -10,7 +10,6 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate,
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
@@ -33,7 +32,7 @@ from erpnext.accounts.general_ledger import (
merge_similar_entries,
)
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update_voucher_outstanding
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
@@ -840,12 +839,12 @@ class PurchaseInvoice(BuyingController):
def update_supplier_outstanding(self, update_outstanding):
if update_outstanding == "No":
update_outstanding_amt(
self.credit_to,
"Supplier",
self.supplier,
self.doctype,
self.return_against if cint(self.is_return) and self.return_against else self.name,
update_voucher_outstanding(
voucher_type=self.doctype,
voucher_no=self.return_against if cint(self.is_return) and self.return_against else self.name,
account=self.credit_to,
party_type="Supplier",
party=self.supplier,
)
def get_gl_entries(self, warehouse_account=None):
@@ -1128,6 +1127,7 @@ class PurchaseInvoice(BuyingController):
exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
and item.net_rate == net_rate_map[item.pr_detail]
and item.item_code in stock_items
):
discrepancy_caused_by_exchange_rate_difference = (
item.qty * item.net_rate
@@ -1802,13 +1802,13 @@ class PurchaseInvoice(BuyingController):
self.remove(d)
## Add pending vouchers on which tax was withheld
for voucher_no, voucher_details in voucher_wise_amount.items():
for row in voucher_wise_amount:
self.append(
"tax_withheld_vouchers",
{
"voucher_name": voucher_no,
"voucher_type": voucher_details.get("voucher_type"),
"taxable_amount": voucher_details.get("amount"),
"voucher_name": row.voucher_name,
"voucher_type": row.voucher_type,
"taxable_amount": row.taxable_amount,
},
)

View File

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

View File

@@ -383,6 +383,53 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
def test_purchase_invoice_with_exchange_rate_difference_for_non_stock_item(self):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as create_purchase_invoice,
)
# Creating Purchase Invoice with USD currency
pr = frappe.new_doc("Purchase Receipt")
pr.currency = "USD"
pr.company = "_Test Company with perpetual inventory"
pr.conversion_rate = (70,)
pr.supplier = "_Test Supplier USD"
pr.append(
"items",
{
"item_code": "_Test Non Stock Item",
"qty": 1,
"rate": 100,
},
)
pr.append(
"items",
{"item_code": "_Test Item", "qty": 1, "rate": 5, "warehouse": "Stores - TCP1"},
)
pr.insert()
pr.submit()
# Createing purchase invoice against Purchase Receipt
pi = create_purchase_invoice(pr.name)
pi.conversion_rate = 80
pi.credit_to = "_Test Payable USD - TCP1"
pi.insert()
pi.submit()
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
# fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value(
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
)
discrepancy_caused_by_exchange_rate_diff = abs(
pi.items[1].base_net_amount - pr.items[1].base_net_amount
)
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(self.globalTestRecords["Purchase Invoice"][1])
pi.insert()

View File

@@ -16,6 +16,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
setup(doc) {
this.setup_posting_date_time_check();
super.setup(doc);
this.frm.make_methods = {
Dunning: this.make_dunning.bind(this),
"Invoice Discounting": this.make_invoice_discounting.bind(this),
};
}
company() {
super.company();
@@ -61,7 +65,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
refresh(doc, dt, dn) {
const me = this;
super.refresh();
if (this.frm.msgbox && this.frm.msgbox.$wrapper.is(":visible")) {
if (this.frm?.msgbox && this.frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
this.frm.msgbox.hide();
}
@@ -125,12 +129,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
},
__("Create")
);
this.frm.add_custom_button(
__("Invoice Discounting"),
function () {
this.frm.events.create_invoice_discounting(this.frm);
},
this.make_invoice_discounting.bind(this),
__("Create")
);
@@ -139,22 +140,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
.reduce((prev, current) => prev || current, false);
if (payment_is_overdue) {
this.frm.add_custom_button(
__("Dunning"),
() => {
this.frm.events.create_dunning(this.frm);
},
__("Create")
);
this.frm.add_custom_button(__("Dunning"), this.make_dunning.bind(this), __("Create"));
}
}
if (doc.docstatus === 1) {
this.frm.add_custom_button(
__("Maintenance Schedule"),
function () {
this.frm.cscript.make_maintenance_schedule();
},
this.make_maintenance_schedule.bind(this),
__("Create")
);
}
@@ -189,6 +182,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
}
make_invoice_discounting() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: this.frm,
});
}
make_dunning() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: this.frm,
});
}
make_maintenance_schedule() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
@@ -789,7 +796,11 @@ frappe.ui.form.on("Sales Invoice", {
}
},
<<<<<<< HEAD
onload: function (frm) {
=======
onload: function(frm) {
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
frm.redemption_conversion_factor = null;
},
@@ -888,26 +899,115 @@ frappe.ui.form.on("Sales Invoice", {
project: function (frm) {
if (frm.doc.project) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project,
frappe.call({
method: "is_auto_fetch_timesheet_enabled",
doc: frm.doc,
callback: function (r) {
if (cint(r.message)) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project,
});
}
},
});
}
},
<<<<<<< HEAD
<<<<<<< HEAD
=======
project: function(frm) {
if (frm.doc.project) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project
});
}
},
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
async add_timesheet_data(frm, kwargs) {
if (kwargs === "Sales Invoice") {
// called via frm.trigger()
kwargs = Object();
}
<<<<<<< HEAD
if (!Object.prototype.hasOwnProperty.call(kwargs, "project") && frm.doc.project) {
=======
if (!kwargs.hasOwnProperty("project") && frm.doc.project) {
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
kwargs.project = frm.doc.project;
}
const timesheets = await frm.events.get_timesheet_data(frm, kwargs);
return frm.events.set_timesheet_data(frm, timesheets);
<<<<<<< HEAD
=======
add_timesheet_row: function(frm, row, exchange_rate) {
frm.add_child('timesheets', {
'activity_type': row.activity_type,
'description': row.description,
'time_sheet': row.parent,
'billing_hours': row.billing_hours,
'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
'timesheet_detail': row.name
=======
},
async get_timesheet_data(frm, kwargs) {
return frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data",
args: kwargs
}).then(r => {
if (!r.exc && r.message.length > 0) {
return r.message
} else {
return []
}
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
});
},
set_timesheet_data: function(frm, timesheets) {
frm.clear_table("timesheets")
timesheets.forEach(timesheet => {
if (frm.doc.currency != timesheet.currency) {
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
from_currency: timesheet.currency,
to_currency: frm.doc.currency
},
callback: function(r) {
if (r.message) {
exchange_rate = r.message;
frm.events.append_time_log(frm, timesheet, exchange_rate);
}
}
});
} else {
frm.events.append_time_log(frm, timesheet, 1.0);
}
});
},
append_time_log: function(frm, time_log, exchange_rate) {
const row = frm.add_child("timesheets");
row.activity_type = time_log.activity_type;
row.description = time_log.description;
row.time_sheet = time_log.time_sheet;
row.from_time = time_log.from_time;
row.to_time = time_log.to_time;
row.billing_hours = time_log.billing_hours;
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
row.timesheet_detail = time_log.name;
frm.refresh_field("timesheets");
frm.trigger("calculate_timesheet_totals");
>>>>>>> b57521a337 (feat: add `total_billing_hours` to Sales Invoice)
},
<<<<<<< HEAD
async get_timesheet_data(frm, kwargs) {
return frappe
.call({
@@ -993,67 +1093,109 @@ frappe.ui.form.on("Sales Invoice", {
refresh: function (frm) {
if (frm.doc.docstatus === 0 && !frm.doc.is_return) {
frm.add_custom_button(__("Fetch Timesheet"), function () {
frm.add_custom_button(
__("Timesheet"),
function () {
let d = new frappe.ui.Dialog({
title: __("Fetch Timesheet"),
fields: [
{
label: __("From"),
fieldname: "from_time",
fieldtype: "Date",
reqd: 1,
},
{
fieldtype: "Column Break",
fieldname: "col_break_1",
},
{
label: __("To"),
fieldname: "to_time",
fieldtype: "Date",
reqd: 1,
},
{
label: __("Project"),
fieldname: "project",
fieldtype: "Link",
options: "Project",
default: frm.doc.project,
},
],
primary_action: function () {
const data = d.get_values();
frm.events.add_timesheet_data(frm, {
from_time: data.from_time,
to_time: data.to_time,
project: data.project,
});
d.hide();
},
primary_action_label: __("Get Timesheets"),
});
d.show();
},
__("Get Items From")
);
=======
calculate_timesheet_totals: function(frm) {
frm.set_value("total_billing_amount",
frm.doc.timesheets.reduce((a, b) => a + (b["billing_amount"] || 0.0), 0.0));
frm.set_value("total_billing_hours",
frm.doc.timesheets.reduce((a, b) => a + (b["billing_hours"] || 0.0), 0.0));
},
refresh: function(frm) {
if (frm.doc.docstatus===0 && !frm.doc.is_return) {
frm.add_custom_button(__("Fetch Timesheet"), function() {
let d = new frappe.ui.Dialog({
title: __("Fetch Timesheet"),
fields: [
{
label: __("From"),
fieldname: "from_time",
fieldtype: "Date",
reqd: 1,
"label" : __("From"),
"fieldname": "from_time",
"fieldtype": "Date",
"reqd": 1,
},
{
fieldtype: "Column Break",
fieldname: "col_break_1",
},
{
label: __("To"),
fieldname: "to_time",
fieldtype: "Date",
reqd: 1,
"label" : __("To"),
"fieldname": "to_time",
"fieldtype": "Date",
"reqd": 1,
},
{
label: __("Project"),
fieldname: "project",
fieldtype: "Link",
options: "Project",
default: frm.doc.project,
"label" : __("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
"default": frm.doc.project
},
],
primary_action: function () {
primary_action: function() {
const data = d.get_values();
frm.events.add_timesheet_data(frm, {
from_time: data.from_time,
to_time: data.to_time,
project: data.project,
project: data.project
});
d.hide();
},
primary_action_label: __("Get Timesheets"),
primary_action_label: __("Get Timesheets")
});
d.show();
});
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
}
if (frm.doc.is_debit_note) {
frm.set_df_property("return_against", "label", __("Adjustment Against"));
}
},
create_invoice_discounting: function (frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: frm,
});
},
create_dunning: function (frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: frm,
});
},
});
frappe.ui.form.on("Sales Invoice Timesheet", {
@@ -1077,7 +1219,34 @@ var set_timesheet_detail_rate = function (cdt, cdn, currency, timelog) {
});
};
<<<<<<< HEAD
var select_loyalty_program = function (frm, loyalty_programs) {
=======
create_invoice_discounting: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: frm
});
},
create_dunning: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: frm
});
}
});
frappe.ui.form.on("Sales Invoice Timesheet", {
timesheets_remove(frm, cdt, cdn) {
frm.trigger("calculate_timesheet_totals");
}
});
var select_loyalty_program = function(frm, loyalty_programs) {
>>>>>>> b57521a337 (feat: add `total_billing_hours` to Sales Invoice)
var dialog = new frappe.ui.Dialog({
title: __("Select Loyalty Program"),
fields: [

View File

@@ -7,7 +7,6 @@
"engine": "InnoDB",
"field_order": [
"customer_section",
"title",
"naming_series",
"customer",
"customer_name",
@@ -50,6 +49,18 @@
"set_target_warehouse",
"section_break_42",
"items",
<<<<<<< HEAD
=======
"pricing_rule_details",
"pricing_rules",
"packing_list",
"packed_items",
"product_bundle_help",
"time_sheet_list",
"timesheets",
"total_billing_amount",
"total_billing_hours",
>>>>>>> b57521a337 (feat: add `total_billing_hours` to Sales Invoice)
"section_break_30",
"total_qty",
"total_net_weight",
@@ -229,18 +240,6 @@
"hide_seconds": 1,
"options": "fa fa-user"
},
{
"allow_on_submit": 1,
"default": "{customer_name}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Title",
"no_copy": 1,
"print_hide": 1
},
{
"bold": 1,
"fieldname": "naming_series",
@@ -304,7 +303,8 @@
"oldfieldname": "project_name",
"oldfieldtype": "Link",
"options": "Project",
"print_hide": 1
"print_hide": 1,
"search_index": 1
},
{
"default": "0",
@@ -1989,6 +1989,7 @@
"read_only": 1
},
{
<<<<<<< HEAD
"default": "0",
"fieldname": "ignore_default_payment_terms_template",
"fieldtype": "Check",
@@ -1997,11 +1998,14 @@
"read_only": 1
},
{
=======
>>>>>>> b57521a337 (feat: add `total_billing_hours` to Sales Invoice)
"fieldname": "total_billing_hours",
"fieldtype": "Float",
"label": "Total Billing Hours",
"print_hide": 1,
"read_only": 1
<<<<<<< HEAD
},
{
"fieldname": "amount_eligible_for_commission",
@@ -2159,7 +2163,7 @@
{
"default": "1",
"depends_on": "eval: doc.is_return && doc.return_against",
"description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
"description": "Credit Note will update it's own outstanding amount, even if 'Return Against' is specified.",
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
"label": "Update Outstanding for Self",
@@ -2211,6 +2215,8 @@
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
=======
>>>>>>> b57521a337 (feat: add `total_billing_hours` to Sales Invoice)
}
],
"icon": "fa fa-file-text",
@@ -2223,7 +2229,15 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2024-11-26 12:34:09.110690",
<<<<<<< HEAD
<<<<<<< HEAD
"modified": "2025-02-06 15:59:54.636202",
=======
"modified": "2021-08-02 18:36:51.978581",
>>>>>>> b57521a337 (feat: add `total_billing_hours` to Sales Invoice)
=======
"modified": "2021-08-15 18:40:20.445127",
>>>>>>> 0f2f11cb33 (fix: typo)
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2275,7 +2289,7 @@
"sort_order": "DESC",
"states": [],
"timeline_field": "customer",
"title_field": "title",
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -29,7 +29,11 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
)
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import cancel_exchange_gain_loss_journal, get_account_currency
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
update_voucher_outstanding,
)
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_gl_entries_on_asset_disposal,
@@ -206,7 +210,6 @@ class SalesInvoice(SellingController):
terms: DF.TextEditor | None
territory: DF.Link | None
timesheets: DF.Table[SalesInvoiceTimesheet]
title: DF.Data | None
to_date: DF.Date | None
total: DF.Currency
total_advance: DF.Currency
@@ -323,9 +326,7 @@ class SalesInvoice(SellingController):
self.set_against_income_account()
self.validate_time_sheets_are_submitted()
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
if not self.is_return:
self.validate_serial_numbers()
else:
if self.is_return:
self.timesheets = []
self.update_packing_list()
self.set_billing_hours_and_amount()
@@ -364,7 +365,7 @@ class SalesInvoice(SellingController):
if self.update_stock:
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
elif asset.status in ("Scrapped", "Cancelled", "Capitalized", "Decapitalized") or (
elif asset.status in ("Scrapped", "Cancelled", "Capitalized") or (
asset.status == "Sold" and not self.is_return
):
frappe.throw(
@@ -449,6 +450,8 @@ class SalesInvoice(SellingController):
self.make_bundle_for_sales_purchase_return(table_name)
self.make_bundle_using_old_serial_batch_fields(table_name)
self.update_stock_reservation_entries()
self.update_stock_ledger()
# this sequence because outstanding may get -ve
@@ -558,6 +561,7 @@ class SalesInvoice(SellingController):
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.update_stock_reservation_entries()
self.repost_future_sle_and_gle()
self.db_set("status", "Cancelled")
@@ -1092,16 +1096,21 @@ class SalesInvoice(SellingController):
timesheet.billing_amount = ts_doc.total_billable_amount
def update_timesheet_billing_for_project(self):
if not self.timesheets and self.project:
if not self.timesheets and self.project and self.is_auto_fetch_timesheet_enabled():
self.add_timesheet_data()
else:
self.calculate_billing_amount_for_timesheet()
@frappe.whitelist()
def is_auto_fetch_timesheet_enabled(self):
return frappe.db.get_single_value("Projects Settings", "fetch_timesheet_in_sales_invoice")
@frappe.whitelist()
def add_timesheet_data(self):
self.set("timesheets", [])
if self.project:
for data in get_projectwise_timesheet_data(self.project):
<<<<<<< HEAD
self.append(
"timesheets",
{
@@ -1113,6 +1122,16 @@ class SalesInvoice(SellingController):
"description": data.description,
},
)
=======
self.append('timesheets', {
'time_sheet': data.time_sheet,
'billing_hours': data.billing_hours,
'billing_amount': data.billing_amount,
'timesheet_detail': data.name,
'activity_type': data.activity_type,
'description': data.description
})
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
self.calculate_billing_amount_for_timesheet()
@@ -1192,14 +1211,14 @@ class SalesInvoice(SellingController):
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(
self.debit_to,
"Customer",
self.customer,
self.doctype,
self.return_against if cint(self.is_return) and self.return_against else self.name,
update_voucher_outstanding(
voucher_type=self.doctype,
voucher_no=self.return_against
if cint(self.is_return) and self.return_against
else self.name,
account=self.debit_to,
party_type="Customer",
party=self.customer,
)
elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock):
@@ -1703,14 +1722,6 @@ class SalesInvoice(SellingController):
self.set("write_off_amount", reference_doc.get("write_off_amount"))
self.due_date = None
def validate_serial_numbers(self):
"""
validate serial number agains Delivery Note and Sales Invoice
"""
for item in self.items:
item.set_serial_no_against_delivery_note()
item.validate_serial_against_delivery_note()
def update_project(self):
unique_projects = list(set([d.project for d in self.get("items") if d.project]))
if self.project and self.project not in unique_projects:

View File

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

View File

@@ -4294,6 +4294,7 @@ class TestSalesInvoice(IntegrationTestCase):
si = create_sales_invoice(do_not_submit=True)
project = frappe.new_doc("Project")
project.company = "_Test Company"
project.project_name = "Test Total Billed Amount"
project.save()
@@ -4304,6 +4305,30 @@ class TestSalesInvoice(IntegrationTestCase):
doc = frappe.get_doc("Project", project.name)
self.assertEqual(doc.total_billed_amount, si.grand_total)
def test_pos_returns_with_party_account_currency(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
pos_profile = make_pos_profile()
pos_profile.payments = []
pos_profile.append("payments", {"default": 1, "mode_of_payment": "Cash"})
pos_profile.save()
pos = create_sales_invoice(
customer="_Test Customer USD",
currency="USD",
conversion_rate=86.595000000,
qty=2,
do_not_save=True,
)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.debit_to = "_Test Receivable USD - _TC"
pos.append("payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 20.35})
pos.save().submit()
pos_return = make_sales_return(pos.name)
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -8,7 +8,7 @@ from frappe.model.document import Document
from frappe.utils.data import cint
from erpnext.assets.doctype.asset.depreciation import get_disposal_account_and_cost_center
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class SalesInvoiceItem(Document):
@@ -128,39 +128,3 @@ class SalesInvoiceItem(Document):
self.income_account = disposal_account
if not self.cost_center:
self.cost_center = depreciation_cost_center
def set_serial_no_against_delivery_note(self):
"""Set serial no based on delivery note."""
if self.serial_no and self.delivery_note and self.qty != len(get_serial_nos(self.serial_no)):
self.serial_no = get_delivery_note_serial_no(self.item_code, self.qty, self.delivery_note)
def validate_serial_against_delivery_note(self):
"""Ensure the serial numbers in this Sales Invoice Item are same as in the linked Delivery Note."""
if not self.delivery_note or not self.dn_detail:
return
serial_nos = frappe.db.get_value("Delivery Note Item", self.dn_detail, "serial_no") or ""
dn_serial_nos = set(get_serial_nos(serial_nos))
serial_nos = self.serial_no or ""
si_serial_nos = set(get_serial_nos(serial_nos))
serial_no_diff = si_serial_nos - dn_serial_nos
if serial_no_diff:
dn_link = frappe.utils.get_link_to_form("Delivery Note", self.delivery_note)
msg = (
_("Row #{0}: The following serial numbers are not present in Delivery Note {1}:").format(
self.idx, dn_link
)
+ " "
+ ", ".join(frappe.bold(d) for d in serial_no_diff)
)
frappe.throw(msg=msg, title=_("Serial Nos Mismatch"))
if self.serial_no and cint(self.qty) != len(si_serial_nos):
frappe.throw(
_(
"Row #{0}: {1} serial numbers are required for Item {2}. You have provided {3} serial numbers."
).format(self.idx, self.qty, self.item_code, len(si_serial_nos))
)

View File

@@ -103,6 +103,7 @@
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"label": "Reference"
<<<<<<< HEAD
},
{
"fieldname": "project_name",
@@ -113,11 +114,17 @@
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
=======
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
}
],
"istable": 1,
"links": [],
<<<<<<< HEAD
"modified": "2024-03-27 13:10:36.562795",
=======
"modified": "2021-08-02 23:03:08.084930",
>>>>>>> 1110f88e5a (feat: refactor and enhance sales invoice timesheet)
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Timesheet",

View File

@@ -114,10 +114,10 @@ class Subscription(Document):
if self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
_current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start
elif date:
_current_invoice_start = date
elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start
else:
_current_invoice_start = nowdate()
@@ -414,8 +414,8 @@ class Subscription(Document):
if frappe.db.get_value("Supplier", self.party, "tax_withholding_category"):
invoice.apply_tds = 1
# Add party currency to invoice
invoice.currency = get_party_account_currency(self.party_type, self.party, self.company)
# Add currency to invoice
invoice.currency = frappe.db.get_value("Subscription Plan", {"name": self.plans[0].plan}, "currency")
# Add dimensions in invoice for subscription:
accounting_dimensions = get_accounting_dimensions()
@@ -634,9 +634,7 @@ class Subscription(Document):
"""
invoice = frappe.get_all(
self.invoice_document_type,
{
"subscription": self.name,
},
{"subscription": self.name, "docstatus": ("<", 2)},
limit=1,
order_by="to_date desc",
pluck="name",
@@ -675,6 +673,7 @@ class Subscription(Document):
self.invoice_document_type,
{
"subscription": self.name,
"docstatus": 1,
"status": ["!=", "Paid"],
},
)
@@ -697,7 +696,7 @@ class Subscription(Document):
self.status = "Cancelled"
self.cancelation_date = nowdate()
if to_generate_invoice:
if to_generate_invoice and self.cancelation_date >= self.current_invoice_start:
self.generate_invoice(self.current_invoice_start, self.cancelation_date)
self.save()

View File

@@ -479,6 +479,28 @@ class TestSubscription(IntegrationTestCase):
currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "currency")
self.assertEqual(currency, "USD")
@IntegrationTestCase.change_settings(
"Accounts Settings",
{"allow_multi_currency_invoices_against_single_party_account": 1},
)
def test_multi_currency_subscription_with_default_company_currency(self):
party = "Test Subscription Customer Multi Currency"
frappe.db.set_value("Customer", party, "default_currency", "USD")
subscription = create_subscription(
start_date="2018-01-01",
generate_invoice_at="Beginning of the current subscription period",
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1, "currency": "USD"}],
party=party,
)
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")
# Check the currency of the created invoice
currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "currency")
self.assertEqual(currency, "USD")
def test_subscription_recovery(self):
"""Test if Subscription recovers when start/end date run out of sync with created invoices."""
subscription = create_subscription(
@@ -590,6 +612,12 @@ def create_parties():
customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"})
customer.insert()
if not frappe.db.exists("Customer", "_Test Subscription Customer Multi Currency"):
customer = frappe.new_doc("Customer")
customer.customer_name = "Test Subscription Customer Multi Currency"
customer.default_currency = "USD"
customer.insert()
if not frappe.db.exists("Customer", "_Test Subscription Customer John Doe"):
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test Subscription Customer John Doe"

View File

@@ -87,6 +87,7 @@ def get_party_details(inv):
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
if inv.doctype == "Payment Entry":
inv.tax_withholding_net_total = inv.net_total
inv.base_tax_withholding_net_total = inv.net_total
pan_no = ""
parties = []
@@ -156,6 +157,9 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
}
)
if cint(tax_details.round_off_tax_amount):
inv.round_off_applicable_accounts_for_tax_withholding = tax_details.account_head
if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances, voucher_wise_amount
else:
@@ -266,7 +270,10 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no):
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
vouchers, voucher_wise_amount = get_invoice_vouchers(
parties, tax_details, inv.company, party_type=party_type
parties,
tax_details,
inv.company,
party_type=party_type,
)
payment_entry_vouchers = get_payment_entry_vouchers(
@@ -302,6 +309,10 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
tax_amount = 0
if party_type == "Supplier":
# if tds account is changed.
if not tax_deducted:
tax_deducted = is_tax_deducted_on_the_basis_of_inv(vouchers)
ldc = get_lower_deduction_certificate(inv.company, posting_date, tax_details, pan_no)
if tax_deducted:
net_total = inv.tax_withholding_net_total
@@ -319,7 +330,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
# once tds is deducted, not need to add vouchers in the invoice
voucher_wise_amount = {}
else:
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers)
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, voucher_wise_amount)
elif party_type == "Customer":
if tax_deducted:
@@ -336,14 +347,41 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
field = (
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total"
def is_tax_deducted_on_the_basis_of_inv(vouchers):
return frappe.db.exists(
"Purchase Taxes and Charges",
{
"parent": ["in", vouchers],
"is_tax_withholding_account": 1,
"parenttype": "Purchase Invoice",
"base_tax_amount_after_discount_amount": [">", 0],
},
)
voucher_wise_amount = {}
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
voucher_wise_amount = []
vouchers = []
ldcs = frappe.db.get_all(
"Lower Deduction Certificate",
filters={
"valid_from": [">=", tax_details.from_date],
"valid_upto": ["<=", tax_details.to_date],
"company": company,
"supplier": ["in", parties],
},
fields=["supplier", "valid_from", "valid_upto", "rate"],
)
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
field = [
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total",
"name",
"grand_total",
"posting_date",
]
filters = {
"company": company,
frappe.scrub(party_type): ["in", parties],
@@ -357,15 +395,29 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field])
invoices_details = frappe.get_all(doctype, filters=filters, fields=field)
for d in invoices_details:
vouchers.append(d.name)
voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}})
d = frappe._dict(
{
"voucher_name": d.name,
"voucher_type": doctype,
"taxable_amount": d.base_net_total,
"grand_total": d.grand_total,
"posting_date": d.posting_date,
}
)
if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]:
if ldc[0].supplier in parties and ldc[0].rate == 0:
d.update({"taxable_amount": 0})
vouchers.append(d.voucher_name)
voucher_wise_amount.append(d)
journal_entries_details = frappe.db.sql(
"""
SELECT j.name, ja.credit - ja.debit AS amount
SELECT j.name, ja.credit - ja.debit AS amount, ja.reference_type
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
WHERE
j.name = ja.parent
@@ -384,13 +436,20 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
tax_details.get("tax_withholding_category"),
company,
),
as_dict=1,
)
if journal_entries_details:
for d in journal_entries_details:
vouchers.append(d.name)
voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}})
for d in journal_entries_details:
vouchers.append(d.name)
voucher_wise_amount.append(
frappe._dict(
{
"voucher_name": d.name,
"voucher_type": "Journal Entry",
"taxable_amount": d.amount,
"reference_type": d.reference_type,
}
)
)
return vouchers, voucher_wise_amount
@@ -489,12 +548,24 @@ def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details):
return advance_tax_from_across_fiscal_year
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
def get_tds_amount(ldc, parties, inv, tax_details, voucher_wise_amount):
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
pi_grand_total = 0
pi_base_net_total = 0
jv_credit_amt = 0
pe_credit_amt = 0
for row in voucher_wise_amount:
if row.voucher_type == "Purchase Invoice":
pi_grand_total += row.get("grand_total", 0)
pi_base_net_total += row.get("taxable_amount", 0)
if row.voucher_type == "Journal Entry" and row.reference_type != "Purchase Invoice":
jv_credit_amt += row.get("taxable_amount", 0)
## for TDS to be deducted on advances
payment_entry_filters = {
pe_filters = {
"party_type": "Supplier",
"party": ("in", parties),
"docstatus": 1,
@@ -505,70 +576,49 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
"company": inv.company,
}
field = "sum(tax_withholding_net_total)"
consider_party_ledger_amt = cint(tax_details.consider_party_ledger_amount)
if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None)
field = "sum(grand_total)"
payment_entry_filters.pop("apply_tax_withholding_amount", None)
payment_entry_filters.pop("tax_withholding_category", None)
supp_inv_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
supp_jv_credit_amt = (
frappe.db.get_value(
"Journal Entry Account",
{
"parent": ("in", vouchers),
"docstatus": 1,
"party": ("in", parties),
"reference_type": ("!=", "Purchase Invoice"),
},
"sum(credit_in_account_currency - debit_in_account_currency)",
)
or 0.0
)
if consider_party_ledger_amt:
pe_filters.pop("apply_tax_withholding_amount", None)
pe_filters.pop("tax_withholding_category", None)
# Get Amount via payment entry
payment_entry_amounts = frappe.db.get_all(
payment_entries = frappe.db.get_all(
"Payment Entry",
filters=payment_entry_filters,
fields=["sum(unallocated_amount) as amount", "payment_type"],
group_by="payment_type",
filters=pe_filters,
fields=["name", "unallocated_amount as taxable_amount", "payment_type"],
)
supp_credit_amt = supp_jv_credit_amt
supp_credit_amt += inv.get("tax_withholding_net_total", 0)
for type in payment_entry_amounts:
if type.payment_type == "Pay":
supp_credit_amt += type.amount
else:
supp_credit_amt -= type.amount
for row in payment_entries:
value = row.taxable_amount if row.payment_type == "Pay" else -1 * row.taxable_amount
pe_credit_amt += value
voucher_wise_amount.append(
frappe._dict(
{
"voucher_name": row.name,
"voucher_type": "Payment Entry",
"taxable_amount": value,
}
)
)
threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
supp_credit_amt = jv_credit_amt + pe_credit_amt + inv.get("tax_withholding_net_total", 0)
tax_withholding_net_total = inv.get("base_tax_withholding_net_total", 0)
if inv.doctype != "Payment Entry":
tax_withholding_net_total = inv.get("base_tax_withholding_net_total", 0)
else:
tax_withholding_net_total = inv.get("tax_withholding_net_total", 0)
# if consider_party_ledger_amount is checked, then threshold will be based on grand total
amt_for_threshold = pi_grand_total if consider_party_ledger_amt else pi_base_net_total
if (threshold and tax_withholding_net_total >= threshold) or (
cumulative_threshold and (supp_credit_amt + supp_inv_credit_amt) >= cumulative_threshold
):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
net_total = (
frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)") or 0.0
)
supp_credit_amt += net_total
cumulative_threshold_breached = (
cumulative_threshold and (supp_credit_amt + amt_for_threshold) >= cumulative_threshold
)
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
tax_details.tax_on_excess_amount
):
supp_credit_amt = net_total + tax_withholding_net_total - cumulative_threshold
if (threshold and tax_withholding_net_total >= threshold) or (cumulative_threshold_breached):
supp_credit_amt += pi_base_net_total
if cumulative_threshold_breached and cint(tax_details.tax_on_excess_amount):
supp_credit_amt = pi_base_net_total + tax_withholding_net_total - cumulative_threshold
if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):
tds_amount = get_lower_deduction_amount(

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.utils import add_days, today
from frappe.utils import add_days, add_months, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.utils import get_fiscal_year
@@ -71,6 +71,49 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
for d in reversed(invoices):
d.cancel()
def test_tds_with_account_changed(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier", "tax_withholding_category", "Multi Account TDS Category"
)
invoices = []
# create invoices for lower than single threshold tax rate
for _ in range(2):
pi = create_purchase_invoice(supplier="Test TDS Supplier")
pi.submit()
invoices.append(pi)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
pi = create_purchase_invoice(supplier="Test TDS Supplier")
pi.submit()
# assert equal tax deduction on total invoice amount until now
self.assertEqual(pi.taxes_and_charges_deducted, 3000)
self.assertEqual(pi.grand_total, 7000)
invoices.append(pi)
# account changed
frappe.db.set_value(
"Tax Withholding Account",
{"parent": "Multi Account TDS Category"},
"account",
"_Test Account VAT - _TC",
)
# TDS should be on invoice only even though account is changed
pi = create_purchase_invoice(supplier="Test TDS Supplier", rate=5000)
pi.submit()
# assert equal tax deduction on total invoice amount until now
self.assertEqual(pi.taxes_and_charges_deducted, 500)
invoices.append(pi)
# delete invoices to avoid clashing
for d in reversed(invoices):
d.cancel()
def test_single_threshold_tds(self):
invoices = []
frappe.db.set_value(
@@ -536,6 +579,15 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
pi1.submit()
invoices.append(pi1)
pe = create_payment_entry(
payment_type="Pay", party_type="Supplier", party="Test TDS Supplier6", paid_amount=1000
)
pe.apply_tax_withholding_amount = 1
pe.tax_withholding_category = "Test Multi Invoice Category"
pe.save()
pe.submit()
invoices.append(pe)
pi2 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=9000, do_not_save=True)
pi2.apply_tds = 1
pi2.tax_withholding_category = "Test Multi Invoice Category"
@@ -551,6 +603,8 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
self.assertTrue(pi2.tax_withheld_vouchers[0].taxable_amount == pi1.net_total)
self.assertTrue(pi2.tax_withheld_vouchers[1].voucher_name == pi.name)
self.assertTrue(pi2.tax_withheld_vouchers[1].taxable_amount == pi.net_total)
self.assertTrue(pi2.tax_withheld_vouchers[2].voucher_name == pe.name)
self.assertTrue(pi2.tax_withheld_vouchers[2].taxable_amount == pe.paid_amount)
# cancel invoices to avoid clashing
for d in reversed(invoices):
@@ -622,6 +676,49 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
pi2.cancel()
pi3.cancel()
def test_ldc_at_0_rate(self):
frappe.db.set_value(
"Supplier",
"Test LDC Supplier",
{
"tax_withholding_category": "Test Service Category",
"pan": "ABCTY1234D",
},
)
fiscal_year = get_fiscal_year(today(), company="_Test Company")
valid_from = fiscal_year[1]
valid_upto = add_months(valid_from, 1)
create_lower_deduction_certificate(
supplier="Test LDC Supplier",
certificate_no="1AE0423AAJ",
tax_withholding_category="Test Service Category",
tax_rate=0,
limit=50000,
valid_from=valid_from,
valid_upto=valid_upto,
)
pi1 = create_purchase_invoice(
supplier="Test LDC Supplier", rate=35000, posting_date=valid_from, set_posting_time=True
)
pi1.submit()
self.assertEqual(pi1.taxes, [])
pi2 = create_purchase_invoice(
supplier="Test LDC Supplier",
rate=35000,
posting_date=add_days(valid_upto, 1),
set_posting_time=True,
)
pi2.submit()
self.assertEqual(len(pi2.taxes), 1)
# pi1 net total shouldn't be included as it lies within LDC at rate of '0'
self.assertEqual(pi2.taxes[0].tax_amount, 3500)
pi1.cancel()
pi2.cancel()
def set_previous_fy_and_tax_category(self):
test_company = "_Test Company"
category = "Cumulative Threshold TDS"
@@ -779,7 +876,8 @@ def create_purchase_invoice(**args):
pi = frappe.get_doc(
{
"doctype": "Purchase Invoice",
"posting_date": today(),
"set_posting_time": args.set_posting_time or False,
"posting_date": args.posting_date or today(),
"apply_tds": 0 if args.do_not_apply_tds else 1,
"supplier": args.supplier,
"company": "_Test Company",
@@ -1071,6 +1169,16 @@ def create_tax_withholding_category_records():
consider_party_ledger_amount=1,
)
create_tax_withholding_category(
category_name="Multi Account TDS Category",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=0,
cumulative_threshold=30000,
)
def create_tax_withholding_category(
category_name,
@@ -1107,7 +1215,9 @@ def create_tax_withholding_category(
).insert()
def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit):
def create_lower_deduction_certificate(
supplier, tax_withholding_category, tax_rate, certificate_no, limit, valid_from=None, valid_upto=None
):
fiscal_year = get_fiscal_year(today(), company="_Test Company")
if not frappe.db.exists("Lower Deduction Certificate", certificate_no):
frappe.get_doc(
@@ -1118,8 +1228,8 @@ def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_r
"certificate_no": certificate_no,
"tax_withholding_category": tax_withholding_category,
"fiscal_year": fiscal_year[0],
"valid_from": fiscal_year[1],
"valid_upto": fiscal_year[2],
"valid_from": valid_from or fiscal_year[1],
"valid_upto": valid_upto or fiscal_year[2],
"rate": tax_rate,
"certificate_limit": limit,
}

View File

@@ -36,7 +36,7 @@ def make_gl_entries(
make_acc_dimensions_offsetting_entry(gl_map)
validate_accounting_period(gl_map)
validate_disabled_accounts(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
gl_map = process_gl_map(gl_map, merge_entries, from_repost=from_repost)
if gl_map and len(gl_map) > 1:
if gl_map[0].voucher_type != "Period Closing Voucher":
create_payment_ledger_entry(
@@ -164,12 +164,12 @@ def validate_accounting_period(gl_map):
)
def process_gl_map(gl_map, merge_entries=True, precision=None):
def process_gl_map(gl_map, merge_entries=True, precision=None, from_repost=False):
if not gl_map:
return []
if gl_map[0].voucher_type != "Period Closing Voucher":
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision, from_repost)
if merge_entries:
gl_map = merge_similar_entries(gl_map, precision)
@@ -179,13 +179,17 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
return gl_map
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_repost=False):
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
# Validate budget against main cost center
validate_expense_against_budget(d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision))
if not from_repost:
validate_expense_against_budget(
d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
)
cost_center_allocation = get_cost_center_allocation_data(
gl_map[0]["company"], gl_map[0]["posting_date"], cost_center
)
@@ -677,11 +681,15 @@ def make_reverse_gl_entries(
debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
debit_in_transaction_currency = new_gle.get("debit_in_transaction_currency", 0)
credit_in_transaction_currency = new_gle.get("credit_in_transaction_currency", 0)
new_gle["debit"] = credit
new_gle["credit"] = debit
new_gle["debit_in_account_currency"] = credit_in_account_currency
new_gle["credit_in_account_currency"] = debit_in_account_currency
new_gle["debit_in_transaction_currency"] = credit_in_transaction_currency
new_gle["credit_in_transaction_currency"] = debit_in_transaction_currency
new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
new_gle["is_cancelled"] = 1

View File

@@ -621,34 +621,41 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
return due_date
def validate_due_date(posting_date, due_date, bill_date=None, template_name=None):
def validate_due_date(posting_date, due_date, bill_date=None, template_name=None, doctype=None):
if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
doctype_date = "Date"
if doctype == "Purchase Invoice":
doctype_date = "Supplier Invoice Date"
if doctype == "Sales Invoice":
doctype_date = "Posting Date"
frappe.throw(_("Due Date cannot be before {0}").format(doctype_date))
else:
if not template_name:
return
validate_due_date_with_template(posting_date, due_date, bill_date, template_name)
default_due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
"%Y-%m-%d"
def validate_due_date_with_template(posting_date, due_date, bill_date, template_name):
if not template_name:
return
default_due_date = format(get_due_date_from_template(template_name, posting_date, bill_date))
if not default_due_date:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
is_credit_controller = (
frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
)
if not default_due_date:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
is_credit_controller = (
frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
if is_credit_controller:
msgprint(
_("Note: Due Date exceeds allowed customer credit days by {0} day(s)").format(
date_diff(due_date, default_due_date)
)
)
if is_credit_controller:
msgprint(
_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)").format(
date_diff(due_date, default_due_date)
)
)
else:
frappe.throw(
_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date))
)
else:
frappe.throw(_("Due Date cannot be after {0}").format(formatdate(default_due_date)))
@frappe.whitelist()
@@ -681,7 +688,7 @@ def set_taxes(
):
from erpnext.accounts.doctype.tax_rule.tax_rule import get_party_details, get_tax_template
args = {party_type.lower(): party, "company": company}
args = {frappe.scrub(party_type): party, "company": company}
if tax_category:
args["tax_category"] = tax_category
@@ -701,10 +708,10 @@ def set_taxes(
else:
args.update(get_party_details(party, party_type))
if party_type in ("Customer", "Lead", "Prospect"):
if party_type in ("Customer", "Lead", "Prospect", "CRM Deal"):
args.update({"tax_type": "Sales"})
if party_type in ["Lead", "Prospect"]:
if party_type in ["Lead", "Prospect", "CRM Deal"]:
args["customer"] = None
del args[frappe.scrub(party_type)]
else:
@@ -770,7 +777,7 @@ def validate_account_party_type(self):
if self.party_type and self.party:
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type and (account_type not in ["Receivable", "Payable"]):
if account_type and (account_type not in ["Receivable", "Payable", "Equity"]):
frappe.throw(
_(
"Party Type and Party can only be set for Receivable / Payable account<br><br>" "{0}"

View File

@@ -44,7 +44,7 @@
{% endfor %}
<tr>
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
<td class="left" >{{ gl | sum(attribute='debit') }}</td>
<td class="left" >{{ gl | sum(attribute='debit') | round(2) }}</td>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Credit</strong></td>
@@ -61,7 +61,7 @@
{% endfor %}
<tr>
<td class="right" colspan="3"><strong>Total (credit) </strong></td>
<td class="left" >{{ gl | sum(attribute='credit') }}</td>
<td class="left" >{{ gl | sum(attribute='credit') | round(2) }}</td>
</tr>
<tr>
<td class="top-bottom" colspan="5"><b>Narration: </b>{{ gl[0].remarks }}</td>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -96,6 +96,7 @@ frappe.query_reports["Customer Ledger Summary"] = {
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "MultiSelectList",
options: "Cost Center",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
@@ -106,6 +107,7 @@ frappe.query_reports["Customer Ledger Summary"] = {
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),

View File

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

View File

@@ -71,9 +71,6 @@
</div>
<div style="text-align:center; font-size:13px;">
<b>
{% if(filters.party_type) { %}
[ {%= filters.party_type %} ]<br>
{% } %}
{%= frappe.datetime.str_to_user(filters.from_date) %}
{%= __("to") %}
{%= frappe.datetime.str_to_user(filters.to_date) %}<br><br>

View File

@@ -73,6 +73,7 @@ frappe.query_reports["General Ledger"] = {
fieldname: "party",
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;
@@ -151,6 +152,7 @@ frappe.query_reports["General Ledger"] = {
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "MultiSelectList",
options: "Cost Center",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
@@ -161,6 +163,7 @@ frappe.query_reports["General Ledger"] = {
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),

View File

@@ -208,6 +208,10 @@ def get_gl_entries(filters, accounting_dimensions):
def get_conditions(filters):
conditions = []
ignore_is_opening = frappe.db.get_single_value(
"Accounts Settings", "ignore_is_opening_check_for_reporting"
)
if filters.get("account"):
filters.account = get_accounts_with_children(filters.account)
if filters.account:
@@ -270,9 +274,15 @@ def get_conditions(filters):
or filters.get("party")
or filters.get("group_by") in ["Group by Account", "Group by Party"]
):
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
if not ignore_is_opening:
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
else:
conditions.append("posting_date >=%(from_date)s")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
if not ignore_is_opening:
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
else:
conditions.append("posting_date <=%(to_date)s")
if filters.get("project"):
conditions.append("project in %(project)s")

View File

@@ -67,6 +67,7 @@ frappe.query_reports["Gross Profit"] = {
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "MultiSelectList",
options: "Cost Center",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
@@ -77,6 +78,7 @@ frappe.query_reports["Gross Profit"] = {
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),

View File

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

View File

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

View File

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

View File

@@ -319,7 +319,7 @@ def get_columns(additional_table_columns, filters):
"width": 100,
},
{
"label": _("Tax Rate"),
"label": _("Rate"),
"fieldname": "rate",
"fieldtype": "Float",
"options": "currency",

View File

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

View File

@@ -78,6 +78,7 @@ frappe.query_reports["Supplier Ledger Summary"] = {
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "MultiSelectList",
options: "Cost Center",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
@@ -88,6 +89,7 @@ frappe.query_reports["Supplier Ledger Summary"] = {
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),

View File

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

View File

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

View File

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

View File

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

View File

@@ -730,6 +730,23 @@ def update_reference_in_payment_entry(
}
update_advance_paid = []
# Update Reconciliation effect date in reference
if payment_entry.book_advance_payments_in_separate_party_account:
if payment_entry.advance_reconciliation_takes_effect_on == "Advance Payment Date":
reconcile_on = payment_entry.posting_date
elif payment_entry.advance_reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
date_field = "posting_date"
if d.against_voucher_type in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
reconcile_on = frappe.db.get_value(d.against_voucher_type, d.against_voucher, date_field)
if getdate(reconcile_on) < getdate(payment_entry.posting_date):
reconcile_on = payment_entry.posting_date
elif payment_entry.advance_reconciliation_takes_effect_on == "Reconciliation Date":
reconcile_on = nowdate()
reference_details.update({"reconcile_effect_on": reconcile_on})
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
@@ -778,6 +795,8 @@ def update_reference_in_payment_entry(
frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict
)
# Ledgers will be reposted by Reconciliation tool
payment_entry.flags.ignore_reposting_on_reconciliation = True
if not do_not_save:
payment_entry.save(ignore_permissions=True)
return row, update_advance_paid
@@ -1661,7 +1680,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
if wh_details.account == account and not wh_details.is_group
]
total_stock_value = get_stock_value_on(related_warehouses, posting_date)
total_stock_value = get_stock_value_on(related_warehouses, posting_date, company=company)
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
@@ -2267,3 +2286,38 @@ def run_ledger_health_checks():
doc.general_and_payment_ledger_mismatch = True
doc.checked_on = run_date
doc.save()
def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15):
auto_reconciliation_job_trigger = auto_reconciliation_job_trigger or frappe.db.get_single_value(
"Accounts Settings", "auto_reconciliation_job_trigger"
)
method = "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_reconciliation_for_queued_docs"
sch_event = frappe.get_doc(
"Scheduler Event", {"scheduled_against": "Process Payment Reconciliation", "method": method}
)
if frappe.db.get_value("Scheduled Job Type", {"method": method}):
frappe.get_doc(
"Scheduled Job Type",
{
"method": method,
},
).update(
{
"cron_format": f"0/{auto_reconciliation_job_trigger} * * * *",
"scheduler_event": sch_event.name,
}
).save()
else:
frappe.get_doc(
{
"doctype": "Scheduled Job Type",
"method": method,
"scheduler_event": sch_event.name,
"cron_format": f"0/{auto_reconciliation_job_trigger} * * * *",
"create_log": True,
"stopped": False,
"frequency": "Cron",
}
).save()

View File

@@ -609,9 +609,7 @@ frappe.ui.form.on("Asset", {
frm.trigger("toggle_reference_doc");
if (frm.doc.purchase_receipt) {
if (frm.doc.item_code) {
frappe.db.get_doc("Purchase Receipt", frm.doc.purchase_receipt).then((pr_doc) => {
frm.events.set_values_from_purchase_doc(frm, "Purchase Receipt", pr_doc);
});
frm.events.set_values_from_purchase_doc(frm, "Purchase Receipt");
} else {
frm.set_value("purchase_receipt", "");
frappe.msgprint({
@@ -626,9 +624,7 @@ frappe.ui.form.on("Asset", {
frm.trigger("toggle_reference_doc");
if (frm.doc.purchase_invoice) {
if (frm.doc.item_code) {
frappe.db.get_doc("Purchase Invoice", frm.doc.purchase_invoice).then((pi_doc) => {
frm.events.set_values_from_purchase_doc(frm, "Purchase Invoice", pi_doc);
});
frm.events.set_values_from_purchase_doc(frm, "Purchase Invoice");
} else {
frm.set_value("purchase_invoice", "");
frappe.msgprint({
@@ -639,45 +635,38 @@ frappe.ui.form.on("Asset", {
}
},
set_values_from_purchase_doc: function (frm, doctype, purchase_doc) {
frm.set_value("company", purchase_doc.company);
if (purchase_doc.bill_date) {
frm.set_value("purchase_date", purchase_doc.bill_date);
} else {
frm.set_value("purchase_date", purchase_doc.posting_date);
}
if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) {
frm.set_value("available_for_use_date", frm.doc.purchase_date);
}
const item = purchase_doc.items.find((item) => item.item_code === frm.doc.item_code);
if (!item) {
let doctype_field = frappe.scrub(doctype);
frm.set_value(doctype_field, "");
frappe.msgprint({
title: __("Invalid {0}", [__(doctype)]),
message: __("The selected {0} does not contain the selected Asset Item.", [__(doctype)]),
indicator: "red",
});
}
frappe.db.get_value("Item", item.item_code, "is_grouped_asset", (r) => {
var asset_quantity = r.is_grouped_asset ? item.qty : 1;
var purchase_amount = flt(
item.valuation_rate * asset_quantity,
precision("gross_purchase_amount")
);
set_values_from_purchase_doc: (frm, doctype) => {
frappe.call({
method: "erpnext.assets.doctype.asset.asset.get_values_from_purchase_doc",
args: {
purchase_doc_name: frm.doc.purchase_receipt || frm.doc.purchase_invoice,
item_code: frm.doc.item_code,
doctype: doctype,
},
callback: (r) => {
if (r.message) {
let data = r.message;
frm.set_value("company", data.company);
frm.set_value("purchase_date", data.purchase_date);
frm.set_value("gross_purchase_amount", data.gross_purchase_amount);
frm.set_value("purchase_amount", data.gross_purchase_amount);
frm.set_value("asset_quantity", data.asset_quantity);
frm.set_value("cost_center", data.cost_center);
if (data.asset_location) {
frm.set_value("location", data.asset_location);
}
frm.set_value("gross_purchase_amount", purchase_amount);
frm.set_value("purchase_amount", purchase_amount);
frm.set_value("asset_quantity", asset_quantity);
frm.set_value("cost_center", item.cost_center || purchase_doc.cost_center);
if (item.asset_location) {
frm.set_value("location", item.asset_location);
}
if (doctype === "Purchase Receipt") {
frm.set_value("purchase_receipt_item", item.name);
} else if (doctype === "Purchase Invoice") {
frm.set_value("purchase_invoice_item", item.name);
}
if (doctype === "Purchase Receipt") {
frm.set_value("purchase_receipt_item", data.purchase_receipt_item);
} else {
frm.set_value("purchase_invoice_item", data.purchase_invoice_item);
}
let is_editable = !data.is_multiple_items; // if multiple items, then fields should be read-only
frm.set_df_property("gross_purchase_amount", "read_only", is_editable);
frm.set_df_property("asset_quantity", "read_only", is_editable);
}
},
});
},

View File

@@ -228,8 +228,7 @@
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
"options": "Company:company:default_currency",
"read_only_depends_on": "eval:!doc.is_existing_asset"
"options": "Company:company:default_currency"
},
{
"fieldname": "available_for_use_date",
@@ -353,7 +352,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nDecapitalized",
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nWork In Progress",
"read_only": 1
},
{
@@ -436,8 +435,7 @@
"default": "1",
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
"label": "Asset Quantity"
},
{
"fieldname": "depr_entry_posting_status",
@@ -507,17 +505,15 @@
},
{
"fieldname": "purchase_receipt_item",
"fieldtype": "Link",
"fieldtype": "Data",
"hidden": 1,
"label": "Purchase Receipt Item",
"options": "Purchase Receipt Item"
"label": "Purchase Receipt Item"
},
{
"fieldname": "purchase_invoice_item",
"fieldtype": "Link",
"fieldtype": "Data",
"hidden": 1,
"label": "Purchase Invoice Item",
"options": "Purchase Invoice Item"
"label": "Purchase Invoice Item"
},
{
"fieldname": "insurance_details_tab",
@@ -592,7 +588,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-11-29 14:25:56.436124",
"modified": "2025-02-11 16:01:56.140904",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -95,9 +95,9 @@ class Asset(AccountsController):
purchase_amount: DF.Currency
purchase_date: DF.Date | None
purchase_invoice: DF.Link | None
purchase_invoice_item: DF.Link | None
purchase_invoice_item: DF.Data | None
purchase_receipt: DF.Link | None
purchase_receipt_item: DF.Link | None
purchase_receipt_item: DF.Data | None
split_from: DF.Link | None
status: DF.Literal[
"Draft",
@@ -111,7 +111,7 @@ class Asset(AccountsController):
"Issue",
"Receipt",
"Capitalized",
"Decapitalized",
"Work In Progress",
]
supplier: DF.Link | None
total_asset_cost: DF.Currency
@@ -121,6 +121,7 @@ class Asset(AccountsController):
def validate(self):
self.validate_precision()
self.set_purchase_doc_row_item()
self.validate_asset_values()
self.validate_asset_and_reference()
self.validate_item()
@@ -200,6 +201,38 @@ class Asset(AccountsController):
def after_delete(self):
add_asset_activity(self.name, _("Asset deleted"))
def set_purchase_doc_row_item(self):
if self.is_existing_asset or self.is_composite_asset:
return
self.purchase_amount = self.gross_purchase_amount
purchase_doc_type = "Purchase Receipt" if self.purchase_receipt else "Purchase Invoice"
purchase_doc = self.purchase_receipt or self.purchase_invoice
if not purchase_doc:
return
linked_item = self.get_linked_item(purchase_doc_type, purchase_doc)
if linked_item:
if purchase_doc_type == "Purchase Receipt":
self.purchase_receipt_item = linked_item
else:
self.purchase_invoice_item = linked_item
def get_linked_item(self, purchase_doc_type, purchase_doc):
purchase_doc = frappe.get_doc(purchase_doc_type, purchase_doc)
for item in purchase_doc.items:
if self.asset_quantity > 1:
if item.base_net_amount == self.gross_purchase_amount and item.qty == self.asset_quantity:
return item.name
elif item.qty == self.asset_quantity:
return item.name
else:
if item.base_net_rate == self.gross_purchase_amount and item.qty == self.asset_quantity:
return item.name
def validate_asset_and_reference(self):
if self.purchase_invoice or self.purchase_receipt:
reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt"
@@ -1126,6 +1159,30 @@ def has_active_capitalization(asset):
return active_capitalizations > 0
@frappe.whitelist()
def get_values_from_purchase_doc(purchase_doc_name, item_code, doctype):
purchase_doc = frappe.get_doc(doctype, purchase_doc_name)
matching_items = [item for item in purchase_doc.items if item.item_code == item_code]
if not matching_items:
frappe.throw(_(f"Selected {doctype} does not contain the Item Code {item_code}"))
first_item = matching_items[0]
is_multiple_items = len(matching_items) > 1
return {
"company": purchase_doc.company,
"purchase_date": purchase_doc.get("bill_date") or purchase_doc.get("posting_date"),
"gross_purchase_amount": flt(first_item.base_net_amount),
"asset_quantity": first_item.qty,
"cost_center": first_item.cost_center or purchase_doc.get("cost_center"),
"asset_location": first_item.get("asset_location"),
"is_multiple_items": is_multiple_items,
"purchase_receipt_item": first_item.name if doctype == "Purchase Receipt" else None,
"purchase_invoice_item": first_item.name if doctype == "Purchase Invoice" else None,
}
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)

View File

@@ -1,5 +1,6 @@
frappe.listview_settings["Asset"] = {
add_fields: ["status"],
add_fields: ["status", "docstatus"],
has_indicator_for_draft: 1,
get_indicator: function (doc) {
if (doc.status === "Fully Depreciated") {
return [__("Fully Depreciated"), "green", "status,=,Fully Depreciated"];
@@ -7,8 +8,10 @@ frappe.listview_settings["Asset"] = {
return [__("Partially Depreciated"), "grey", "status,=,Partially Depreciated"];
} else if (doc.status === "Sold") {
return [__("Sold"), "green", "status,=,Sold"];
} else if (["Capitalized", "Decapitalized"].includes(doc.status)) {
return [__(doc.status), "grey", "status,=," + doc.status];
} else if (doc.status === "Work In Progress") {
return [__("Work In Progress"), "orange", "status,=,Work In Progress"];
} else if (doc.status === "Capitalized") {
return [__("Capitalized"), "grey", "status,=,Capitalized"];
} else if (doc.status === "Scrapped") {
return [__("Scrapped"), "grey", "status,=,Scrapped"];
} else if (doc.status === "In Maintenance") {
@@ -21,7 +24,7 @@ frappe.listview_settings["Asset"] = {
return [__("Receipt"), "green", "status,=,Receipt"];
} else if (doc.status === "Submitted") {
return [__("Submitted"), "blue", "status,=,Submitted"];
} else if (doc.status === "Draft") {
} else if (doc.status === "Draft" || doc.docstatus === 0) {
return [__("Draft"), "red", "status,=,Draft"];
}
},

View File

@@ -436,7 +436,7 @@ def scrap_asset(asset_name, scrap_date=None):
if asset.docstatus != 1:
frappe.throw(_("Asset {0} must be submitted").format(asset.name))
elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized", "Decapitalized"):
elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized"):
frappe.throw(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status))
today_date = getdate(today())
@@ -448,9 +448,9 @@ def scrap_asset(asset_name, scrap_date=None):
notes = _("This schedule was created when Asset {0} was scrapped.").format(
get_link_to_form(asset.doctype, asset.name)
)
depreciate_asset(asset, date, notes)
asset.reload()
if asset.status != "Fully Depreciated":
depreciate_asset(asset, date, notes)
asset.reload()
depreciation_series = frappe.get_cached_value("Company", asset.company, "series_for_depreciation_entry")

View File

@@ -1752,6 +1752,10 @@ def create_asset(**args):
},
)
if asset.is_composite_asset:
asset.gross_purchase_amount = 0
asset.purchase_amount = 0
if not args.do_not_save:
try:
asset.insert(ignore_if_duplicate=True)

View File

@@ -36,11 +36,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
me.setup_warehouse_query();
me.frm.set_query("target_item_code", function () {
if (me.frm.doc.entry_type == "Capitalization") {
return erpnext.queries.item({ is_stock_item: 0, is_fixed_asset: 1 });
} else {
return erpnext.queries.item({ is_stock_item: 1, is_fixed_asset: 0 });
}
return erpnext.queries.item({ is_stock_item: 0, is_fixed_asset: 1 });
});
me.frm.set_query("target_asset", function () {
@@ -51,7 +47,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
me.frm.set_query("asset", "asset_items", function () {
var filters = {
status: ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]],
status: ["not in", ["Draft", "Scrapped", "Sold", "Capitalized"]],
docstatus: 1,
};

View File

@@ -8,30 +8,26 @@
"engine": "InnoDB",
"field_order": [
"title",
"company",
"naming_series",
"entry_type",
"target_item_name",
"target_is_fixed_asset",
"target_has_batch_no",
"target_has_serial_no",
"column_break_9",
"capitalization_method",
"target_item_code",
"target_asset_location",
"target_item_name",
"target_asset",
"target_asset_name",
"target_warehouse",
"target_qty",
"target_stock_uom",
"target_batch_no",
"target_serial_no",
"column_break_5",
"finance_book",
"target_asset_location",
"column_break_9",
"company",
"posting_date",
"posting_time",
"set_posting_time",
"finance_book",
"target_batch_no",
"target_serial_no",
"amended_from",
"target_is_fixed_asset",
"target_has_batch_no",
"target_has_serial_no",
"section_break_16",
"stock_items",
"stock_items_total",
@@ -58,12 +54,12 @@
"label": "Title"
},
{
"depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')",
"depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || doc.capitalization_method=='Create a new composite asset'",
"fieldname": "target_item_code",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Item Code",
"mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'",
"mandatory_depends_on": "eval:doc.capitalization_method=='Create a new composite asset'",
"options": "Item"
},
{
@@ -84,22 +80,18 @@
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
"depends_on": "eval:(doc.target_asset && !doc.__islocal) || doc.capitalization_method=='Choose a WIP composite asset'",
"fieldname": "target_asset",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Asset",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'",
"mandatory_depends_on": "eval:doc.capitalization_method=='Choose a WIP composite asset'",
"no_copy": 1,
"options": "Asset",
"read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')"
"read_only_depends_on": "eval:doc.capitalization_method=='Create a new composite asset'"
},
{
"depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
"depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.capitalization_method=='Choose a WIP composite asset')",
"fetch_from": "target_asset.asset_name",
"fieldname": "target_asset_name",
"fieldtype": "Data",
@@ -162,7 +154,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length))",
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"label": "Consumed Stock Items"
@@ -173,14 +165,6 @@
"label": "Stock Items",
"options": "Asset Capitalization Stock Item"
},
{
"depends_on": "eval:doc.entry_type=='Decapitalization'",
"fieldname": "target_warehouse",
"fieldtype": "Link",
"label": "Target Warehouse",
"mandatory_depends_on": "eval:doc.entry_type=='Decapitalization'",
"options": "Warehouse"
},
{
"depends_on": "target_has_batch_no",
"fieldname": "target_batch_no",
@@ -190,20 +174,9 @@
},
{
"default": "1",
"depends_on": "eval:doc.entry_type=='Decapitalization'",
"fieldname": "target_qty",
"fieldtype": "Float",
"label": "Target Qty",
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
},
{
"depends_on": "eval:doc.entry_type=='Decapitalization'",
"fetch_from": "target_item_code.stock_uom",
"fieldname": "target_stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
"label": "Target Qty"
},
{
"default": "0",
@@ -241,16 +214,6 @@
"label": "Assets",
"options": "Asset Capitalization Asset Item"
},
{
"default": "Capitalization",
"fieldname": "entry_type",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Entry Type",
"options": "Capitalization\nDecapitalization",
"reqd": 1
},
{
"fieldname": "stock_items_total",
"fieldtype": "Currency",
@@ -272,7 +235,7 @@
"options": "Finance Book"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.service_items && doc.service_items.length))",
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
"fieldname": "service_expenses_section",
"fieldtype": "Section Break",
"label": "Service Expenses"
@@ -337,26 +300,24 @@
"read_only": 1
},
{
"depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
"depends_on": "eval:doc.capitalization_method=='Create a new composite asset'",
"fieldname": "target_asset_location",
"fieldtype": "Link",
"label": "Target Asset Location",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
"mandatory_depends_on": "eval:doc.capitalization_method=='Create a new composite asset'",
"options": "Location"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"fieldname": "capitalization_method",
"fieldtype": "Select",
"label": "Capitalization Method",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
"options": "\nCreate a new composite asset\nChoose a WIP composite asset"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:06:33.080441",
"modified": "2025-01-08 13:14:33.008458",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization",

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