Compare commits

...

473 Commits

Author SHA1 Message Date
rohitwaghchaure
06ffe52d6e Merge pull request #54681 from rohitwaghchaure/fixed-support-66529
fix: incorrect expense account book in purchase return
2026-05-01 08:12:31 +05:30
Raffael Meyer
c120cc7ed1 fix: add missing fields in set_currency_labels (#54689) 2026-05-01 03:54:14 +02:00
Raffael Meyer
25be38e23c fix: Backfill not_applicable on Item Tax Template Details for German companies (#54682) 2026-04-30 19:21:24 +00:00
Rohit Waghchaure
2a720e7008 fix: incorrect expense account book in purchase return 2026-04-30 20:36:20 +05:30
Raffael Meyer
f38eca9124 fix: mark item tax templates as not applicable (#54673)
* fix: mark item tax templates as not applicable

For new German charts of accounts, mark accounts for different tax rates as *Not Applicable* in **Item Tax Templates**.

* fix: wrong applicable rate 19 in template 7
2026-04-30 11:44:08 +00:00
rohitwaghchaure
ad89f88c93 Merge pull request #54664 from rohitwaghchaure/fixed-support-66924
fix: show in and out qty in the stock ledger report for stock recos
2026-04-30 14:13:42 +05:30
Trusted Computer
78f654765d fix: correct titles set to {customer_name} or {supplier_name} text strings (#54656) 2026-04-30 10:28:14 +02:00
Hemil-Sangani
231dd1856f fix(project): use user.email for invitations and skip disabled users. (#54561)
* fix(project): use user.email for invitations and skip disabled users.

* Update erpnext/projects/doctype/project/project.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(project): remove duplicate loop causing indentation error

* fix(project): resolve pre-commit hook failure

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-30 07:53:32 +00:00
Rohit Waghchaure
da081254a6 fix: show in and out qty in the stock ledger report for stock recos 2026-04-30 13:16:13 +05:30
Raffael Meyer
c543d15f3c feat: copy terms attachments to transactions (#53403) 2026-04-29 21:14:58 +00:00
Khushi Rawat
ddf0e35009 Merge pull request #54658 from khushi8112/skip-rescheduling-for-fully-depreciated-asset-sale
fix: skip depreciation rescheduling when asset is fully depreciated on sale
2026-04-30 02:34:10 +05:30
khushi8112
88b82383f5 fix: skip rescheduling only for asset being disposed 2026-04-30 02:11:57 +05:30
khushi8112
c4155b6c81 fix: skip depreciation rescheduling when asset is fully depreciated on sale 2026-04-30 02:01:57 +05:30
Mihir Kandoi
a04c028522 fix: correct project filter in buying doctypes (#54644) 2026-04-29 11:27:47 +00:00
diptanilsaha
5c5a5361bc fix(payment_entry): convert the date args to string type before escaping in get_outstanding_reference_documents (#54639) 2026-04-29 16:38:57 +05:30
Mihir Kandoi
060defcc2b fix: dont show serial/batch button when PR is submitted (#54642) 2026-04-29 16:36:31 +05:30
Mihir Kandoi
d0d8cff48f fix: py error on sales forecast doctype (#54641)
fix: py error on sales forecase doctype
2026-04-29 10:49:05 +00:00
Nishka Gosalia
844f3dbc0b feat(ux): Naming series dialog (#54554) 2026-04-29 14:45:10 +05:30
Khushi Rawat
43937acd8b fix(UX): Item master form cleanup (#54538)
* fix: UI improvements for item form

* fix: add descriptions and tooltips to all checkboxes

* feat: show toast notification when item price is created

* fix: do not use selling rate for opening stock entry

* fix: add descriptions and tooltips to item default fields

* fix(test): give valuation rate for opening stock entry creation

* fix: moving naming series toggle before the return

* refactor: more changes in the form UI
2026-04-29 14:44:55 +05:30
Mihir Kandoi
503b5bf140 perf: max recursion depth error in serial no (#54629) 2026-04-29 08:34:08 +00:00
rohitwaghchaure
3542087003 Merge pull request #54567 from barredterra/sn-ledger-status
fix: show correct status in Serial No Ledger
2026-04-29 12:42:54 +05:30
Pandiyan P
d68801e73a fix(selling): blanket order ordered qty recalculation on sales order status change (#54593) 2026-04-29 11:57:40 +05:30
Nishka Gosalia
addec3aa8f Merge pull request #53295 from aerele/project-not-copied-from-first-item-row 2026-04-29 11:38:07 +05:30
MochaMind
b001884f9d fix: sync translations from crowdin (#54607) 2026-04-29 01:52:16 +05:30
Ravibharathi
d1a80d40c4 fix: avoid double reduction of pe reference outstanding (#54193)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-29 01:46:55 +05:30
Ravibharathi
a8030c9713 fix: filter overdue purchase order items by company (#54099) 2026-04-29 00:58:01 +05:30
Mihir Kandoi
54f20de7e3 fix: duplicate entries being shown in batch exists in future transact… (#54604)
fix: duplicate entries being shown in batch exists in future transactions msg
2026-04-28 16:28:53 +00:00
diptanilsaha
f8893b04d5 refactor(sms_center): replaced raw SQL queries with Query Builder (#54600) 2026-04-28 15:15:46 +00:00
Lakshit Jain
1bade56e37 Merge pull request #54362 from frappe/ignore-opening-check
fix: filter opening entries after closing voucher
2026-04-28 18:43:25 +05:30
Lakshit Jain
a2b96799ff Merge pull request #54517 from vorasmit/exclude-pcv
fix: always exclude pcv entries except for closing account head
2026-04-28 18:42:42 +05:30
Smit Vora
d0f0e38e8d Merge pull request #54479 from Abdeali099/cash-flow-fixes 2026-04-28 17:30:32 +05:30
Smit Vora
590f2ffe28 test: include both accounts to test sum = 0 2026-04-28 16:45:26 +05:30
diptanilsaha
084c7f72f0 fix(get_stock_balance): validate inventory dimension fieldnames (#54587) 2026-04-28 16:41:22 +05:30
Smit Vora
84aa54c540 test: pcv is excluded from PL accounts 2026-04-28 16:30:02 +05:30
Smit Vora
5fc3ca1d4b test: opening entries after period closing 2026-04-28 16:02:32 +05:30
diptanilsaha
d62fa3c464 fix(payment_entry): escape arguments on invoice and order fetching sql queries (#54582) 2026-04-28 15:55:45 +05:30
diptanilsaha
07337ba9da chore(sidebar): moved Inactive Customers from CRM to Selling Workspace Sidbar (#54578) 2026-04-28 09:31:51 +00:00
Mihir Kandoi
2088a01c19 fix: update status of quotation in patch (#54577) 2026-04-28 09:20:41 +00:00
ravibharathi656
68cc518497 fix: copy project to new item row from parent 2026-04-28 13:10:48 +05:30
Sudharsanan Ashok
6f9089dd5b fix(manufacturing): remove conversion factor for stock qty (#54525) 2026-04-28 10:45:54 +05:30
Vinay Mishra
63edd5ddc6 fix: negative quantity check in validate_item_qty (#54559)
Fix negative quantity check in validate_item_qty

When saving a Blanket Order with a blank qty field in the items table, the following error is raised:

TypeError: '<' not supported between instances of 'NoneType' and 'int'

Root cause: The validate_item_qty method compares d.qty < 0 directly. When the qty field is left empty, its value is None, and Python cannot compare None with an integer.

Fix
Wrap d.qty with flt(), which safely converts None (and any non-numeric value) to 0.0 before the comparison.

# Before
if d.qty < 0:

# After
if flt(d.qty) < 0:
2026-04-28 05:12:49 +00:00
barredterra
2b3e047143 fix: show correct status in Serial No Ledger 2026-04-27 21:41:38 +02:00
barredterra
cb2e6e1e2e refactor: extract SN status logic 2026-04-27 21:41:12 +02:00
MochaMind
37e3493ec4 fix: sync translations from crowdin (#54520) 2026-04-27 20:53:04 +02:00
Mihir Kandoi
601581d6f8 fix: debit credit not equal in purchase transactions for multi currency (#54456) 2026-04-27 20:30:41 +05:30
ruthra kumar
837cdc9cc3 Merge pull request #54509 from ruthra-kumar/hide_toggleable_fields
fix: hide feature flag controlled fields on install
2026-04-27 14:43:11 +05:30
Mihir Kandoi
5281d60f2d fix: correct display depends on condition (#54548) 2026-04-27 09:07:36 +00:00
Mihir Kandoi
0aadd1e3a5 fix: make inv dimen reqd only in delivery note (#54546) 2026-04-27 08:28:55 +00:00
Pandiyan P
60a6b38c31 fix(stock): remove validation for transfer_qty field (#54542) 2026-04-27 06:56:30 +00:00
Mihir Kandoi
be2a4b7b2a refactor: quality inspection item query (#54511) 2026-04-27 10:45:25 +05:30
MochaMind
5c839f60e4 chore: update POT file (#54536) 2026-04-26 18:55:27 +02:00
rohitwaghchaure
6e77a45c05 Merge pull request #54514 from aerele/fix/incoming-rate-issue
fix(stock): set incoming rate as zero for outward sle
2026-04-26 10:06:35 +05:30
rohitwaghchaure
2a6ddc7f67 Merge pull request #54530 from aerele/fix/support-#66029
fix(stock): show item code in serial and batch selector dialog
2026-04-26 10:04:41 +05:30
Sudharsanan11
fee5bcadb2 fix(stock): add stock entry in batch master connection 2026-04-26 00:05:19 +05:30
Sudharsanan11
f572bc51e1 fix(stock): show item code in serial and batch selector dialog 2026-04-26 00:05:19 +05:30
Nishka Gosalia
fba33b7e7a refactor(UX): selling settings form (#54412)
refactor(UX): Selling settings form cleanup
2026-04-25 15:27:32 +05:30
diptanilsaha
ebca389136 fix(PCV): set correct filters of from_date and to_date on General Ledger Report on clicking Ledger button (#54522) 2026-04-25 00:03:38 +05:30
Smit Vora
c94b8c41f3 chore: comment 2026-04-24 19:32:00 +05:30
mahsem
e517eeaaa2 feat: danish_bosnian_address_template (#54093) 2026-04-24 14:54:37 +02:00
Khushi Rawat
c3931d4e29 Merge pull request #53843 from Shllokkk/ap-print-format
feat: Accounts Payable print template revamp and print format introduction
2026-04-24 17:51:45 +05:30
Khushi Rawat
0b9fdcd8cd Merge pull request #53870 from Shllokkk/arap-summary-print-formats
feat: AR and AP summary reports print template revamp and print format introduction
2026-04-24 17:42:52 +05:30
Khushi Rawat
b4e941835b Merge pull request #53822 from Shllokkk/ar-print-format
feat: Accounts Receivable print template revamp and print format introduction
2026-04-24 17:41:13 +05:30
Khushi Rawat
9132f0fc4a Merge pull request #53762 from Shllokkk/gl-print-format
feat: General ledger print template revamp and print format introduction
2026-04-24 17:39:45 +05:30
Sudharsanan11
ce37530e70 fix(stock): set incoming rate as zero for outward sle 2026-04-24 17:29:13 +05:30
ruthra kumar
889fdf2f11 fix: hide feature flag controlled fields on install 2026-04-24 17:13:36 +05:30
Smit Vora
5518e8c99f Merge pull request #54480 from ljain112/fix-change-customer 2026-04-24 13:19:32 +05:30
Smit Vora
419b9b3279 Merge pull request #54476 from ljain112/fix-tds-threshhold
fix: ensure tax withholding entries respect date range of category
2026-04-24 13:18:25 +05:30
Khushi Rawat
a9e6f8efd8 Merge pull request #53314 from aerele/budget-validation-on-cancel
fix: skip budget validation when cancelling GL entries
2026-04-24 12:14:09 +05:30
Mihir Kandoi
0e20e35842 fix: preserve inventory dimensions when raw materials are reset (#54440)
* fix: preserve inventory dimensions when raw materials are reset

* test: add test case
2026-04-23 17:16:12 +00:00
Raffael Meyer
b4107b8fd5 test(Code List): check content, not filename (#54490) 2026-04-23 15:40:22 +00:00
Raffael Meyer
a165b240a7 fix(edi): hardcode "Code List" DocType in importer (#54488) 2026-04-23 13:48:18 +00:00
Abdeali Chharchhodawala
f6639db0e9 feat: enhance account category with root type (#53190) 2026-04-23 17:34:37 +05:30
Abdeali Chharchhodawala
c35221852a feat: Add XLSX styling support to custom financial report templates (#52612) 2026-04-23 17:15:41 +05:30
Abdeali Chharchhoda
3854d2cbf6 chore: minor fix 2026-04-23 17:13:01 +05:30
Sudharsanan Ashok
ab19b16fe2 fix(stock): show available qty in warehouse link field (#54474) 2026-04-23 17:08:54 +05:30
Abdeali Chharchhoda
1fd6c3ba1a fix: update account identification to avoid using name_field in financial statements 2026-04-23 17:05:33 +05:30
Abdeali Chharchhoda
4274c2aba3 fix: add filter labels and required filters for financial report validation 2026-04-23 16:29:38 +05:30
Abdeali Chharchhoda
79d6a51e1e fix: update fiscal year filter to use mandatory_depends_on instead of reqd 2026-04-23 15:54:42 +05:30
ljain112
4eb9107e22 fix: update type hint for get_item_tax_template function 2026-04-23 15:50:03 +05:30
Abdeali Chharchhoda
5a915cb45e fix: ensure fiscal year is checked before validating date filters in financial statements 2026-04-23 15:43:20 +05:30
Smit Vora
b8c3765b85 Merge pull request #54449 from vorasmit/tds-reports-refactor
refactor: tax witholding report
2026-04-23 14:57:31 +05:30
ljain112
9ead8d4e3f fix: ensure tax withholding entries respect date range of category 2026-04-23 13:35:46 +05:30
Raffael Meyer
7f8fa7cf5e ci: test correctness pattern (#54186) 2026-04-22 22:00:42 +02:00
rohitwaghchaure
fd4cedf5e4 Merge pull request #54471 from rohitwaghchaure/fixed-delivery-schedule
fix: delivery schedule in the sales order
2026-04-22 22:02:15 +05:30
Rohit Waghchaure
435db260ee fix: delivery schedule in the sales order 2026-04-22 21:36:51 +05:30
Mihir Kandoi
f5357c233d fix: py error on stock ageing report (#54467) 2026-04-22 19:45:12 +05:30
diptanilsaha
0d2da6d86c ci: fix timezone for python mariadb tests (#54464) 2026-04-22 17:46:28 +05:30
Smit Vora
0349e7a0b8 fix: always exclude pcv entries except for closing account head 2026-04-22 16:09:42 +05:30
Smit Vora
7ae91cac01 fix: summing of values could be zero even if values exist 2026-04-22 13:27:36 +05:30
Smit Vora
b925469c4d fix: add party type for dynamic link support 2026-04-22 12:06:38 +05:30
Smit Vora
f0ea20e579 refactor: make report extensible by regional apps 2026-04-22 12:04:35 +05:30
ruthra kumar
3faeb1609b Merge pull request #54447 from ruthra-kumar/test_remove_raw_sql_delete_on_setup
refactor(test): remove explicit sql delete calls
2026-04-22 11:12:59 +05:30
ruthra kumar
b16dd3f2dd refactor(test): remove explicit sql delete calls 2026-04-22 10:33:25 +05:30
MochaMind
ffae7e42d3 fix: sync translations from crowdin (#54454) 2026-04-22 00:26:57 +05:30
Smit Vora
b5550f747e test: None is better than zero, as no values exist 2026-04-21 19:33:29 +05:30
Shllokkk
f6adef45bf Merge pull request #54307 from aerele/fix/populate_project_from_pe
fix(accounts): fetch project name from payment entry to journal entry
2026-04-21 18:57:45 +05:30
Smit Vora
07b023a934 refactor: updated key for withholding_date 2026-04-21 18:45:26 +05:30
Smit Vora
53666974a3 refactor: better label for entity type 2026-04-21 18:29:50 +05:30
Smit Vora
c3e7f7f02f refactor: how data is built 2026-04-21 18:04:02 +05:30
ruthra kumar
75a068aea8 Merge pull request #54446 from ruthra-kumar/wrong_type_hint_in_pos
fix: incorrect type hint
2026-04-21 17:56:01 +05:30
Smit Vora
6dca96b423 refactor: use consistent report column names 2026-04-21 17:28:56 +05:30
Smit Vora
f6eb844d20 Merge pull request #54422 from ljain112/fix-test-tds-report 2026-04-21 17:21:40 +05:30
Smit Vora
6d727c90b6 Merge pull request #54272 from ljain112/parrenttype 2026-04-21 17:20:56 +05:30
Smit Vora
d8fc9444ea Merge pull request #54344 from ljain112/project-filter-ar-ap 2026-04-21 17:15:37 +05:30
Mihir Kandoi
e65b9fc2ae fix: sales order is not valid when creating WO from MR from PP (#54435) 2026-04-21 09:47:02 +00:00
ruthra kumar
1995fcfdd8 fix: incorrect type hint 2026-04-21 13:57:12 +05:30
MochaMind
c2590c174d fix: sync translations from crowdin (#54358) 2026-04-21 09:24:40 +05:30
diptanilsaha
11fc3e5495 refactor: Sales Partner Commission Summary and Sales Partner Transaction Summary report (#54268) 2026-04-21 03:12:22 +00:00
Khushi Rawat
0edee23e53 Merge pull request #54131 from khushi8112/journal-entry-custom-remark-toggle
feat: use single remark field with custom remark toggle
2026-04-21 00:45:46 +05:30
Ravibharathi
f9232b209c Merge pull request #54415 from aerele/fix/clear-condions-table
fix: clear conditions table when calculate_based_on is set to Fixed
2026-04-20 19:22:25 +05:30
ravibharathi656
d6bb0ae093 fix: clear shipping rule conditions for fixed shipping rule 2026-04-20 19:01:37 +05:30
sarathibalamurugan
d73920be12 fix: clear conditions table when calculate_based_on is set to Fixed 2026-04-20 19:01:37 +05:30
ljain112
6545bcbbd9 refactor: fix test cases in tax withholding details report 2026-04-20 18:15:10 +05:30
Nihantra C. Patel
bc8f63b6dd Merge pull request #54419 from Nihantra-Patel/fix-default-letterhead-report-validation
fix: set letter_head_for letterhead and remove unknown letterhead from report
2026-04-20 17:25:51 +05:30
diptanilsaha
f4e77f63dd test(BootStrapTestData): create sales_partner test data while bootstrapping (#54416) 2026-04-20 11:41:02 +00:00
Nihantra Patel
a2ec597e3d fix: set letter_head_for for letterhead 2026-04-20 16:58:35 +05:30
diptanilsaha
6c51e4cd1f fix(pos_invoice_item): fetch grant_commission from item_code (#54413) 2026-04-20 11:21:10 +00:00
rohitwaghchaure
f89448709f Merge pull request #54350 from rohitwaghchaure/feat-backflush-based-on-in-bom
feat: backflush based on in BOM
2026-04-20 16:36:36 +05:30
Rohit Waghchaure
877d99c5a5 feat: backflush based on in BOM 2026-04-20 16:02:18 +05:30
ruthra kumar
1614d33e5c Merge pull request #54406 from ruthra-kumar/remove_dead_test_code
refactor(test): move contact and address creation to bootstrap
2026-04-20 14:16:04 +05:30
ruthra kumar
336be9d820 refactor(test): set instance variables before calling utility method 2026-04-20 13:53:03 +05:30
rohitwaghchaure
742c5f1822 Merge pull request #53708 from aerele/fetch_item_tax_template
fix: fetch get_item_tax_template while update items
2026-04-20 12:17:22 +05:30
rohitwaghchaure
0a8c195ab9 Merge pull request #54378 from rohitwaghchaure/exclude-group-warehouse
fix: exclude group warehouse in the report
2026-04-20 11:38:21 +05:30
Deepesh Garg
53d7c9fd9c Merge pull request #53756 from iamkhanraheel/fix/hr-default_role_permission
fix(hrms): default permission for HR roles
2026-04-20 11:20:12 +05:30
ruthra kumar
7f021fb705 refactor(test): move create_test_contact_and_address to bootstrap 2026-04-20 11:07:20 +05:30
ruthra kumar
e2b554e151 chore(test): remove dead test code 2026-04-20 10:37:31 +05:30
MochaMind
c1a469478d chore: update POT file (#54402) 2026-04-19 14:02:58 +02:00
Ahmed AbuKhatwa
d61b5fd5f6 fix(dashboard-trends): set default fiscal year and company before val… (#54339)
* fix(dashboard-trends): set default fiscal year and company before validating filters Ensure  and  are populated with default values

* fix(dashboard-trends): ensure fiscal_year and company are properly set before validation to avoid empty filter issues

* Update erpnext/controllers/trends.py

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 08:10:44 +00:00
Mihir Kandoi
28f3429a54 fix: recalculate operating costs if workstation type is changed (#54390)
* fix: recalculate operating costs if workstation type is changed

* fix: do not overwrite op costs on every save
2026-04-19 07:43:48 +00:00
yasmine ben ismail
af98963fa8 fix(readme): correct HTML issues and improve accessibility (#54267)
- Fix invalid width attribute (80xp → 80px)
- Remove invalid nested <p> tag in centered header section
- Add missing alt attribute to hero image for accessibility
- Improve external link security by recommending rel="noopener noreferrer"
2026-04-19 07:40:00 +00:00
Jaganath-Tridots
82438d6c72 Fix : None handling in pricing rule free item quantity calculation (#54375)
* fix(pricing_rule): handle None qty in transaction_qty calculation

* Update erpnext/accounts/doctype/pricing_rule/utils.py

---------

Co-authored-by: Jagan <jagan@DESKTOP-HPDMQ06.localdomain>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 07:39:39 +00:00
Ravibharathi
1c65cc1088 fix: validate south africa company in vat audit report (#54030)
* fix: validate south africa company in vat audit report

* fix: use qb to get invoice data

* fix: validate company region in south africa vat settings
2026-04-19 13:06:15 +05:30
Nishka Gosalia
23768ae0a5 fix: Disallow negative rates in Purchase invoice (#54254) 2026-04-19 12:55:05 +05:30
yasmine ben ismail
3d87f3c070 fix(contributing): fix typos, grammar, and inconsistent casing (#54384) 2026-04-19 07:18:12 +00:00
yasmine ben ismail
43e5dfc3ca Revise feature request template for clarity and structure (#54386)
* Revise feature request template for clarity and structure

- Updated `about` field to mention "enhancement"
- Fixed typo: "many many requests" → "many requests"
- Fixed typo: "urgent need to" → "urgent need of"
- Added "Before Submitting" checklist to reduce duplicate issues
- Improved problem statement placeholder with a role-based example
- Added Environment section for ERPNext and Frappe version info

* chore: fix case

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 07:17:22 +00:00
yasmine ben ismail
e6f17e0447 Fix typos and enhance phrasing in README (#54387)
Fixed typo: "80xp" → "80px" in logo image tag
- Fixed typo: "many many requests" → "many requests"
- Fixed capitalization: "Javascript" → "JavaScript"
- Fixed capitalization: "Learning and community" → "Learning and Community"
- Fixed capitalization: "Open-Source ERP system" → "Open-Source ERP System"
- Fixed capitalization: "Frappe user" → "Frappe user"
- Fixed broken documentation link to use consistent URL
- Added missing Oxford commas
- Added missing "the" before "create-site"
- Improved phrasing: "with peace of mind" → "reliably and securely"
- Improved phrasing: "ad-hoc activities" → "other daily operations"
- Improved phrasing: "already set up sandbox" → "pre-configured sandbox"
- Improved phrasing: "It takes care of" → "It handles"
2026-04-19 07:15:49 +00:00
Mihir Kandoi
d6b379b936 fix: use qty instead of stock qty dropship gross profit report (#54389) 2026-04-19 06:40:59 +00:00
Mihir Kandoi
40bcaa7bc3 fix: dropship logic should come above non stock logic in gross profit… (#54383)
fix: dropship logic should come above non stock logic in gross profit report
2026-04-18 16:16:26 +00:00
Rohit Waghchaure
31fe6a378c fix: exclude group warehouse in the report 2026-04-18 17:41:53 +05:30
Mihir Kandoi
3ef6c24f07 fix: zero valuation rate popup on SI (#54376) 2026-04-18 11:44:49 +00:00
Pandiyan P
b93f2350ee fix: fetch item tax template from item group when creating item (#54258) 2026-04-18 11:58:33 +05:30
Lakshit Jain
453fe376ab feat: add support for 'not applicable' tax in item tax templates (#50898)
* feat: add support for 'not applicable' tax in item tax templates

* refactor: remove unused imports

* fix: import NOT_APPLICABLE_TAX in get_item_tax_map function

* fix: add item wise tax details for not applicable taxes

* test: added test case for `not_applicable`

* fix: do not create item wise tax details for not applicable tax

* fix: ensure tax rate is set to 0 for not applicable tax rows

* refactor: changes as per review

* test: update selling settings

* test: correct settings

* fix: return both net and current tax amounts for not applicable tax
2026-04-18 11:34:36 +05:30
vorasmit
3c8a066484 fix: filter opening entries in first year in custom financial statement 2026-04-17 22:27:48 +05:30
rohitwaghchaure
b0fd152896 Merge pull request #54355 from aerele/fix/support-65791
fix(manufacturing): handle empty list in query builder
2026-04-17 21:21:29 +05:30
rohitwaghchaure
0359a3ed0b Merge pull request #54354 from rohitwaghchaure/fixed-negative-batch-report
fix: negative batch report showing same batch-warehouse multiple times
2026-04-17 21:10:47 +05:30
sarathibalamurugan
9eeb819106 test: add test for project name in exchange gain loss entry 2026-04-17 18:44:44 +05:30
sarathibalamurugan
d9b255b952 fix(accounts): fetch project name from payment entry to journal entry 2026-04-17 18:43:50 +05:30
Jatin3128
ba01d66c24 fix: changed qty validation from qty field to stock_qty (#54352) 2026-04-17 13:13:13 +00:00
Pandiyan37
9e5d94c1e6 fix(manufacturing): handle empty list in query builder 2026-04-17 18:26:34 +05:30
Rohit Waghchaure
700572980d fix: negative batch report showing same batch-warehouse multiple times 2026-04-17 18:21:57 +05:30
iamkhanraheel
2018a90ad8 fix: default company perms for HR manager 2026-04-17 17:13:51 +05:30
Nishka Gosalia
9ecbf57a84 Merge pull request #54074 from nishkagosalia/gh-53442 2026-04-17 16:09:44 +05:30
iamkhanraheel
d26cd69fe5 fix: remove unwanted perm for HR user role 2026-04-17 15:39:42 +05:30
MochaMind
be711eacde fix: sync translations from crowdin (#54334) 2026-04-17 11:49:12 +02:00
ljain112
0cad511136 test: add test with project not in payment entry 2026-04-17 15:18:57 +05:30
nishkagosalia
eb89903dec fix: Table row in dialog should not have delete row option 2026-04-17 15:16:40 +05:30
Nishka Gosalia
6ee4b46be0 Merge pull request #54345 from nishkagosalia/batch-form-cleanup 2026-04-17 15:13:57 +05:30
nishkagosalia
de747fe625 refactor(UX): Batch Form Cleanup 2026-04-17 14:31:42 +05:30
ljain112
d51dbf5254 fix: add project filter to accounts payable and receivable reports 2026-04-17 14:00:01 +05:30
rohitwaghchaure
f555183ab6 Merge pull request #54342 from rohitwaghchaure/fixed-reqd-fg_warehouse
fix: make Target Warehouse mandatory on UI for WO
2026-04-17 13:23:39 +05:30
Rohit Waghchaure
2a8267e10a fix: make Target Warehouse mandatory on UI 2026-04-17 13:17:06 +05:30
Mihir Kandoi
5f4641e55b fix: hide operations field in bom creator if phantom (#54336) 2026-04-16 15:36:14 +00:00
Mihir Kandoi
cac7a358dd feat: make fg phantom-able in bom creator (#54332) 2026-04-16 13:25:54 +00:00
ruthra kumar
ee50767e42 Merge pull request #54327 from ruthra-kumar/setup_default_for_repost_on_fresh_install
fix(test): missing repost allowed defaults
2026-04-16 16:02:18 +05:30
ruthra kumar
257865deb2 fix(test): missing repost allowed defaults 2026-04-16 15:36:15 +05:30
Mihir Kandoi
e04a2e6da2 refactor: add category field to uom (#54290) 2026-04-16 09:03:12 +00:00
Venkatesh
97efd51fb8 feat: add option to create production plan from sales order (#53662)
Co-authored-by: sudarsan2001 <frankel9675@gmail.com>
2026-04-16 13:49:28 +05:30
ruthra kumar
40012f6617 Merge pull request #54301 from ruthra-kumar/merge_repost_settings_to_accounts_settings
refactor(ux): merge repost settings to accounts settings
2026-04-16 12:08:50 +05:30
ruthra kumar
6a04c159ca refactor: delete redundent repost setting 2026-04-16 11:35:23 +05:30
ruthra kumar
940d3cfe0a refactor: limit reposting to only supported doctypes 2026-04-16 11:35:23 +05:30
ruthra kumar
ece85c770f refactor: remove redundant field from filter 2026-04-16 11:35:23 +05:30
ruthra kumar
3093409933 refactor(ux): better error message 2026-04-16 11:35:23 +05:30
ruthra kumar
b8207d5ed1 refactor(test): use new source for repost setting 2026-04-16 11:35:23 +05:30
ruthra kumar
d5c58277cb refactor: move allowed doctypes to accounts settings
- dropped 'allowed' field
2026-04-16 11:35:20 +05:30
ruthra kumar
ff2d536943 Merge pull request #54172 from Shllokkk/acc-dimension-fix
fix: move make_dimension_in_accounting_doctypes from after_insert to on_update
2026-04-16 11:05:51 +05:30
ruthra kumar
2f4bb23125 Merge pull request #52923 from aerele/fix/taxable-amount-conversion-rate
fix(taxes_and_totals): apply conversion_rate to taxable_amount in get_itemised_tax
2026-04-16 10:52:13 +05:30
Dharanidharan2813
2e577ed25b fix(taxes_and_totals): apply conversion_rate to taxable_amount in get_itemised_tax 2026-04-16 10:30:20 +05:30
MochaMind
2e1d426c78 fix: sync translations from crowdin (#54312) 2026-04-15 16:11:43 +00:00
NaviN
9e9c8a07a7 Merge pull request #54306 from aerele/fix/expand_details_in_customer_quick_entry
fix: non-collapsible in customer quick entry
2026-04-15 17:26:13 +05:30
PKSowmiya05
53e120269d fix: non-collapsible in customer quick entry 2026-04-15 17:17:51 +05:30
ruthra kumar
89ebf48544 refactor: merge reposting settings to accounts settings 2026-04-15 16:31:02 +05:30
ruthra kumar
4198dd643d refactor(company): don't force set service expense account on save (#54275) 2026-04-15 15:33:39 +05:30
Smit Vora
ab61a757e3 Merge pull request #54241 from ljain112/fix-rounded-total 2026-04-15 15:23:14 +05:30
ruthra kumar
299e141cee refactor(test): set dependant value in company master 2026-04-15 12:29:29 +05:30
Mihir Kandoi
af6974893b fix: add portal user ownership check to supplier quotation (#54298) 2026-04-15 11:21:08 +05:30
rohitwaghchaure
0969ec4186 Merge pull request #54279 from rohitwaghchaure/fixed-banner-for-serial-batch
fix: banner to enable serial / batch feature
2026-04-14 23:14:30 +05:30
Vishnu Priya Baskaran
ce2670b252 Revert "fix: sync paid and received amount" (#54238) 2026-04-14 22:16:00 +05:30
MochaMind
b083121421 fix: sync translations from crowdin (#54259) 2026-04-14 16:46:06 +02:00
iamkhanraheel
41103a0622 fix: default perm for HR manager & HR user 2026-04-14 18:30:41 +05:30
Rohit Waghchaure
08e8cc8575 fix: banner to enable serial / batch feature 2026-04-14 18:26:35 +05:30
Sudharsanan Ashok
b6b7e8e2f6 fix(stock): remove float precision to fix precision issue (#54284) 2026-04-14 16:42:17 +05:30
Mihir Kandoi
1cc2b159dd fix: handle multi uom conversion factor for manufacture entry (#54285) 2026-04-14 10:43:47 +00:00
Mihir Kandoi
5ff2ae5a83 fix: fetch correct expense account for operations in stock entry (#54278) 2026-04-14 09:55:07 +00:00
Mihir Kandoi
ea0d53e2f3 fix: add drop ship logic in gross profit report (#54220) 2026-04-14 09:29:01 +00:00
ruthra kumar
927f40b296 refactor(company): don't force set service expense account on save 2026-04-14 14:46:46 +05:30
Mihir Kandoi
f37bf62824 fix: wrong operation time calculation (#53796) 2026-04-14 14:42:02 +05:30
ljain112
3aeb7d6b01 fix(purchase_register): filter tax rows by parenttype in invoice tax map query 2026-04-14 12:31:10 +05:30
Raffael Meyer
8a72d7fafe fix(edi): restrict Code List imports to files and trusted backend URLs (#54137) 2026-04-13 21:44:24 +05:30
Sudharsanan Ashok
2f025272d7 fix(stock): update bin to zero when no previous sle exists (#54236) 2026-04-13 21:04:14 +05:30
rohitwaghchaure
488747eb5d Merge pull request #54257 from rohitwaghchaure/fixed-github-65007
fix: not able to submit the PO
2026-04-13 19:43:34 +05:30
Rohit Waghchaure
1e43c37452 fix: not able to submit the PO 2026-04-13 17:10:37 +05:30
ljain112
e2ac476587 chore: spelling mistake 2026-04-13 16:51:20 +05:30
Khushi Rawat
6c5788dfba Merge pull request #54190 from khushi8112/company-address-permission-check
fix: add permission validation when prompting company details for incomplete letterhead data
2026-04-13 15:24:04 +05:30
Nishka Gosalia
aa9d35b28a Merge pull request #54249 from nishkagosalia/gh-53595 2026-04-13 15:21:30 +05:30
khushi8112
84e5272f5d fix: append row level user remarks in gl map 2026-04-13 15:15:52 +05:30
khushi8112
697f521e14 feat: use single remark field with custom remark toggle 2026-04-13 15:15:45 +05:30
Khushi Rawat
c805324a99 Merge pull request #54244 from khushi8112/journal-entry-get-against-jv-sql-injection
fix: replace raw SQL with qb in get_against_jv to prevent SQL injection
2026-04-13 15:12:49 +05:30
nishkagosalia
3e2b40ad4a refactor(UX): Stock ledger serial and batch number fields 2026-04-13 14:47:22 +05:30
khushi8112
c133f7156d fix: replace raw SQL with qb in get_against_jv to prevent SQL injection 2026-04-13 14:47:21 +05:30
Sudarshan
a9bb3e2315 fix: make operation mandatory when any sub operation row is added (#54245) 2026-04-13 07:38:22 +00:00
Khushi Rawat
577a7591c7 Merge pull request #54176 from khushi8112/payment-entry-list-reconciliation-indicator
feat: show reconciled/unreconciled indicator in list view
2026-04-13 12:13:23 +05:30
ljain112
f8d278b733 fix: reset base_rounded_total when rounded_total resets 2026-04-13 11:48:08 +05:30
ruthra kumar
451e4fbb21 Merge pull request #54237 from ruthra-kumar/bold_group_accounts
refactor: boldface for group accounts in financial statements
2026-04-13 11:40:23 +05:30
ruthra kumar
545e9e069a refactor: boldface for group accounts in financial statements 2026-04-13 11:16:49 +05:30
MochaMind
3617a9b674 fix: sync translations from crowdin (#54234) 2026-04-12 19:11:36 +02:00
MochaMind
39d93f35e0 chore: update POT file (#54228) 2026-04-12 10:12:49 +00:00
Shllokkk
44e0b36093 fix: minor changes in print templates 2026-04-12 13:14:24 +05:30
Shllokkk
915fcc0166 fix: minor changes in print template 2026-04-12 13:07:41 +05:30
Shllokkk
e3019c827c fix: minor changes in print template 2026-04-12 13:00:51 +05:30
MochaMind
a76336e3d9 fix: sync translations from crowdin (#54181) 2026-04-11 21:07:20 +02:00
Shllokkk
e8d08df044 fix: changes to gl print template 2026-04-11 23:06:01 +05:30
mgicking-bmi
3e5d18c5c4 Fix(selling): enable selling_settings creation through fixtures (#54177) 2026-04-11 05:12:00 +00:00
Mihir Kandoi
2f5fa3b207 fix: batch/serial should use parent's posting datetime for naming (#54206) 2026-04-10 18:59:16 +00:00
Sambhav Saxena
1dcfd9174f Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (#54187) 2026-04-10 18:12:13 +00:00
rohitwaghchaure
1e64f392bb Merge pull request #54182 from nishkagosalia/st-64901-2
fix: account change in warehouse
2026-04-10 19:41:14 +05:30
nishkagosalia
777d9161cc fix: account change in warehouse 2026-04-10 18:54:43 +05:30
Praveenkumar Dhanasekar
887d2a8379 fix: update return value in workstation list view indicator (#54198) 2026-04-10 16:49:05 +05:30
Mihir Kandoi
9cdfe74de6 fix: remove unneccessary function for serial no status updation (#54191) 2026-04-10 10:36:42 +00:00
Trusted Computer
bd9427623f refactor: bring back titles on sales transactions and make them optional and visible on purchase transactions (#52633)
* fix: correct wrong PO titles

* refactor: restore title fields to sales transaction doctypes

* refactor: change title fields to optional fields with no default in purchase transactional doctypes

* chore: re-save doctype definitions

- updates modified timestamps
- regenerates type hints

---------

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2026-04-10 13:09:32 +05:30
khushi8112
256a258b38 fix: add permission validation when prompting company details for incomplete letterhead data 2026-04-10 12:53:45 +05:30
iamkhanraheel
f02b3b6166 fix: default perm for HR manager & HR user 2026-04-09 19:15:45 +05:30
Mihir Kandoi
9bc5a30ea4 Revert "fix: update_nsm only in warehouse creation" (#54178) 2026-04-09 13:04:38 +00:00
khushi8112
a48a29410e fix: refresh after unreconcile 2026-04-09 18:05:39 +05:30
khushi8112
7eded60892 feat: show reconciled/unreconciled indicator in list view 2026-04-09 17:40:03 +05:30
Shllokkk
ee067e6015 fix: move make_dimension_in_accounting_doctypes from after_insert to on_update 2026-04-09 16:27:54 +05:30
Nishka Gosalia
b0e3fa3979 fix: update_nsm only in warehouse creation (#54165) 2026-04-09 10:27:22 +00:00
Khushi Rawat
514c86cf4b Merge pull request #54142 from khushi8112/blank-remarks-in-invoices
fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices
2026-04-09 14:54:23 +05:30
khushi8112
56416d18d3 fix(test): Remove usage of No remark as remark in tests 2026-04-09 14:23:52 +05:30
rohitwaghchaure
90a1d32098 Merge pull request #54161 from rohitwaghchaure/fixed-posting-time-riv
fix: set default posting time in RIV
2026-04-09 13:55:44 +05:30
Rohit Waghchaure
a7ece65536 fix: set default posting time in RIV 2026-04-09 13:31:34 +05:30
Aarol D'Souza
f5dda90f26 Merge pull request #54129 from AarDG10/refactor-util-use
refactor: update reset password method name
2026-04-09 11:52:15 +05:30
mergify[bot]
f09001a25e Merge branch 'develop' into refactor-util-use 2026-04-09 05:58:56 +00:00
rohitwaghchaure
d7254bba47 Merge pull request #54132 from rohitwaghchaure/fixed-reposting-file-not-updating
fix: last SLE not updated in the file
2026-04-09 08:22:11 +05:30
Mihir Kandoi
71a17cfda9 fix: inventory dimension patch (#54147) 2026-04-09 02:26:35 +00:00
Mihir Kandoi
7f0751539b fix: inventory dimension patch (#54141) 2026-04-09 01:45:48 +00:00
Nishka Gosalia
7ef48a966a feat: Allowing operation level quality inspection check in BOM (#53859)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-09 01:41:45 +00:00
khushi8112
2515bf3aff fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices 2026-04-09 01:54:35 +05:30
diptanilsaha
d15cd08e72 refactor(lost_opportunity_report): replaced raw_sql with query builder (#54136) 2026-04-08 23:38:43 +05:30
Rohit Waghchaure
38ed425ee2 fix: last SLE not updated in the file 2026-04-08 20:43:23 +05:30
NaviN
ef454822d7 fix(sales invoice): toggle Get Items From button based on is_return and POS view (#52594) 2026-04-08 20:42:58 +05:30
Mihir Kandoi
6e44b8913e fix: inventory dimensions should not be mandatory unnecesarily (#54064) 2026-04-08 19:09:56 +05:30
Sudharsanan Ashok
9d16d06504 fix(manufacturing): check remaining qty to calculate operating cost (#53983) 2026-04-08 17:20:49 +05:30
Nishka Gosalia
31319cb6ee fix: quality inspection item code fetch perm issue (#54121)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-08 17:20:09 +05:30
AarDG10
c4d74483e1 refactor: update reset password method name 2026-04-08 17:19:43 +05:30
Sudharsanan Ashok
086122f650 fix(stock): ignore delivery note on delivery trip on_cancel trigger (#54120) 2026-04-08 16:36:02 +05:30
rohitwaghchaure
f9e2696745 Merge pull request #54102 from rohitwaghchaure/fixed-precision-issue
fix: hardcoded precision causing decimal issues
2026-04-08 12:19:21 +05:30
Khushi Rawat
f183885829 Merge pull request #54103 from aerele/asset-movement-field-state-after-save
fix: preserve asset movement field properties after save
2026-04-08 11:57:19 +05:30
MochaMind
eb04706fee fix: sync translations from crowdin (#54105) 2026-04-07 23:12:50 +05:30
Abdeali Chharchhodawala
76a7781283 refactor: financial report template enhancements (#52687) 2026-04-07 22:18:31 +05:30
Ahmed AbuKhatwa
89560d4691 fix(promotional_scheme): toggle enable state between Buying and Selli… (#54110)
Co-authored-by: AhmedAbukhatwa <Ahmedabukhatwa1@gmail.com>
2026-04-07 21:52:24 +05:30
Rohit Waghchaure
90fd6f2e40 fix: hardcoded precision causing decimal issues 2026-04-07 19:48:18 +05:30
Vishnu Priya Baskaran
edba5f3a06 fix: sync paid and received amount (#53039) 2026-04-07 18:33:31 +05:30
mergify[bot]
6da5dff26c fix: skip validate_stock_accounts in Journal Entry when perpetual inventory is disabled (backport #53554) (backport #53558) (#54104)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Saeed Kola <mohammedsaeedk@gmail.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-07 12:28:41 +00:00
ravibharathi656
4a004a2a82 fix: preserve asset movement field properties after save 2026-04-07 16:54:44 +05:30
Smit Vora
187542bfa5 Merge pull request #53964 from vorasmit/disassembly-by-se 2026-04-07 14:17:16 +05:30
Smit Vora
98dfd64f63 fix: remove unnecessary param, and use value from self 2026-04-07 13:17:22 +05:30
Shllokkk
4228885f1e fix: minor bug fixes for ar print template 2026-04-07 13:01:18 +05:30
Shllokkk
e6a32a9d02 feat: introduce print format for Accounts Receivable report 2026-04-07 13:01:18 +05:30
Shllokkk
ffc59ebc9c fix: improve design and refactor ar print template 2026-04-07 13:01:18 +05:30
Khushi Rawat
b4be235bbf Merge pull request #53394 from aerele/fix-asset-filter
fix: remove null from link_filters
2026-04-07 12:29:44 +05:30
Sakthivel Murugan S
00c94afa78 fix: task gantt popup text not visible in light theme (#53882) 2026-04-07 12:08:52 +05:30
Mihir Kandoi
17853931d6 fix: divide sub-assembly cost by qty to get per-unit rate in BOM Creator (#54090)
Co-authored-by: Ravindu Gajanayaka <ravindu2012@users.noreply.github.com>
2026-04-07 05:33:05 +00:00
Smit Vora
f13d37fbf9 test: enhance tests as per review comments 2026-04-07 10:54:34 +05:30
Smit Vora
b892139342 test: maintain sufficient stock for scrap item 2026-04-07 10:27:41 +05:30
Smit Vora
ab1fc22431 fix: set bom details on disassembly; abs batch qty 2026-04-07 10:00:30 +05:30
Smit Vora
93ad48bc1b fix: process loss with bom path disassembly 2026-04-07 09:59:54 +05:30
Amine
13cc07237e docs: fix typo, grammar and numbering in README.md (#54008) 2026-04-07 09:41:16 +05:30
mahsem
4282b9c68a fix: dif_inward_from_outward_workspace_sidebar (#54083) 2026-04-07 09:26:23 +05:30
Lakshit Jain
6d11a08cdd fix: add tax_id handling in Tax Withholding Entry (#53598)
* fix: add tax_id handling in Tax Withholding Entry

* fix: update get_tax_id_for_party to handle absence of tax_id in payment and journal entries

* fix: refactor tax_id handling in Tax Withholding Entry and Details

* test: correct function address
2026-04-06 22:27:32 +05:30
Nishka Gosalia
66780543bd fix: transactions where update stock is 0 should not create SLEs (#54035) 2026-04-06 20:25:51 +05:30
Smit Vora
ea392b2009 fix: validate work order consistency in stock entry 2026-04-06 20:08:38 +05:30
MochaMind
80272de0df fix: sync translations from crowdin (#53955) 2026-04-06 13:07:08 +00:00
Smit Vora
54342539c3 Merge pull request #53973 from vorasmit/persist-toggle 2026-04-06 17:26:19 +05:30
rohitwaghchaure
eebc0d2ad3 Merge pull request #54050 from rohitwaghchaure/fixed-github-51874
fix: GL entries for different exchange rate in the purchase invoice
2026-04-06 17:21:50 +05:30
Amine
fa814c0b71 fix: fix formatting and missing bullet point in TRADEMARK_POLICY.md (#54054) 2026-04-06 17:13:04 +05:30
Amine
83235b90d7 fix: fix incorrect issue link numbers in sponsors.md (#54055) 2026-04-06 17:12:26 +05:30
Mihir Kandoi
9b60d8e711 fix: remove title field from purchase receipt (#54051) 2026-04-06 17:09:00 +05:30
Rohit Waghchaure
a953709640 fix: GL entries for different exchange rate in the purchase invoice 2026-04-06 17:00:02 +05:30
Khushi Rawat
adea316dd2 Merge pull request #54052 from khushi8112/add-print-hide-to-fields
fix: print hide unnecessary fields
2026-04-06 16:13:26 +05:30
Deepesh Garg
77578e41e5 Merge pull request #54033 from krishna-254/fix-user-permission-error-on-status-change
fix: resolve user permission error on status change by updating user …
2026-04-06 15:49:47 +05:30
mahsem
1e90b9a148 feat: croatian_address_template (#53888) 2026-04-06 15:41:23 +05:30
Krishna Shirsath
c6695b613c fix: resolve user permission error on status change by updating user enabled status directly 2026-04-06 15:27:39 +05:30
khushi8112
8f83616b60 fix: print hide unnecessary fields 2026-04-06 14:35:15 +05:30
Sagar Vora
99da4f5147 Merge pull request #54042 from sagarvora/fix/discount-amount-validation-on-save
fix: skip discount amount validation when not saving
2026-04-06 13:00:22 +05:30
Sagar Vora
135cb5fd67 test: add test for discount amount on partial purchase receipt
Co-authored-by: ravibharathi656 <131471282+ravibharathi656@users.noreply.github.com>
2026-04-06 12:38:46 +05:30
Sagar Vora
0975583388 fix: skip discount amount validation when not saving 2026-04-06 12:33:49 +05:30
Gajendra Nishad
b9ef061911 fix: show current stock qty in Stock Entry PDF (#53761) 2026-04-06 10:50:13 +05:30
Amine
7a2759b2f0 fix: fix typos, grammar and numbering in CONTRIBUTING.md (#54027) 2026-04-06 04:25:24 +00:00
rohitwaghchaure
ccd017c737 Merge pull request #54004 from rohitwaghchaure/fixed-do-not-repost-gl
fix: do not repost GL if no change in valuation
2026-04-06 09:07:29 +05:30
Rohit Waghchaure
bb53cce228 fix: do not repost GL if no change in valuation 2026-04-05 22:54:51 +05:30
Vishnu Priya Baskaran
9417f55b7d fix: update min date based on transaction_date (#53803) 2026-04-05 20:59:13 +05:30
MochaMind
eca3ec114c chore: update POT file (#54018) 2026-04-05 15:59:23 +02:00
rohitwaghchaure
92dc95570d Merge pull request #54005 from rohitwaghchaure/fixed-github-53991
fix: screen freezes if consumed qty set in SCR
2026-04-05 13:19:49 +05:30
Pandiyan P
74b11710cc fix(manufacturing): handle null cur_dialog in BOM work order dialog (#54011) 2026-04-05 12:47:18 +05:30
ervishnucs
ad22256b2d fix: resolve item tax template from item group in update items 2026-04-05 11:16:37 +05:30
Rohit Waghchaure
dd7be2b370 fix: screen freezes if consumed qty set in SCR 2026-04-04 13:25:43 +05:30
rohitwaghchaure
f322dc9d69 Merge pull request #53994 from aerele/fix/update-sabe-stock-queue
fix(stock): update stock queue in SABE for return entries
2026-04-04 12:51:34 +05:30
kavin-114
e537896df8 test(stock): add unit test to update stock queue for return 2026-04-03 17:37:19 +05:30
kavin-114
0af8077bcc fix(stock): update stock queue in SABE for return entries 2026-04-03 17:37:05 +05:30
vorasmit
a71e8bb116 fix: use get_value 2026-04-03 16:19:43 +05:30
Mihir Kandoi
efd716e53d fix: remove reference in serial/batch when document is cancelled (#53979) 2026-04-02 07:51:20 +00:00
vorasmit
71fd18bdf9 fix: avg stock entries for disassembly from WO 2026-04-01 23:56:44 +05:30
vorasmit
3cf1ce8360 fix: manufacture entry with group_by support 2026-04-01 23:42:36 +05:30
Smit Vora
a6d41151ff test: disassembly for scrap / secondary item 2026-04-01 16:55:15 +05:30
Smit Vora
2be8313819 fix: handle disassembly for secondary / scrap items 2026-04-01 16:55:15 +05:30
Smit Vora
1693698fed test: disassembly of items with batch and serial numbers 2026-04-01 16:55:15 +05:30
Smit Vora
d32977e3a9 test: additional items in stock entry considered with disassembly 2026-04-01 16:55:15 +05:30
Smit Vora
6988e2cbbc test: disassemble with source stock entry reference 2026-04-01 16:55:15 +05:30
Smit Vora
342a14d340 test: disassembly from wo 2026-04-01 16:55:15 +05:30
Smit Vora
13b019ab8e fix: set serial and batch from source stock entry - on disassemble 2026-04-01 16:55:15 +05:30
Smit Vora
d3d6b5c660 fix: correct warehouse preference for disassemble 2026-04-01 16:55:15 +05:30
Smit Vora
2e4e8bcaa7 fix: auto-set source_stock_entry 2026-04-01 16:55:15 +05:30
Smit Vora
1ed0124ad7 fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order 2026-04-01 16:55:15 +05:30
Smit Vora
6394dead72 fix: validate qty that can be disassembled from source stock entry. 2026-04-01 16:55:15 +05:30
Smit Vora
dba82720b6 fix: support creating disassembly (without link of WO) 2026-04-01 16:55:15 +05:30
Smit Vora
b64f86148c fix: custom button to disassemble manufactured stock entry with work order 2026-04-01 16:55:15 +05:30
Smit Vora
b47dfacb3e fix: set_query for source stock entry 2026-04-01 16:55:15 +05:30
Smit Vora
68e97808c5 fix: disassembly prompt with source stock entry field 2026-04-01 16:55:14 +05:30
Smit Vora
d4baa9a74a fix: create source_stock_entry to refer to original manufacturing entry 2026-04-01 16:55:13 +05:30
rohitwaghchaure
7846548a1b Merge pull request #53963 from rohitwaghchaure/fixed-github-53743
fix: hide fields related to track Semi-Finished Goods if feature has disabled
2026-04-01 16:37:13 +05:30
Shllokkk
86ee9959a2 fix: minor bugs in print templates 2026-04-01 12:32:15 +05:30
Rohit Waghchaure
399faf0ced fix: hide fields related to track Semi-Finished Goods if feature has disabled 2026-04-01 12:05:41 +05:30
Smit Vora
da778edf48 fix(ux): refresh grid to correctly persist the state of fields 2026-04-01 08:43:56 +05:30
Shllokkk
fbe5d128a8 feat: sticky column in various reports (#53960)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
Co-authored-by: mihir-kandoi <kandoimihir@gmail.com>
2026-03-31 23:38:19 +05:30
Mihir Kandoi
d76ddf7271 fix: include rejected qty in tax (purchase receipt) (#53624) 2026-03-31 15:29:56 +00:00
Lakshit Jain
1e85d72127 Merge pull request #53961 from ljain112/fix-taxe-rounding
fix: ensure accurate rounding for item-wise tax and taxable amounts
2026-03-31 19:49:39 +05:30
Mihir Kandoi
ef18b5cd93 Revert "chore: initiate release twice in a week" (#53944) 2026-03-31 19:01:12 +05:30
Nishka Gosalia
7ff3dc0ac4 Merge pull request #53965 from nishkagosalia/gh-53962 2026-03-31 18:07:43 +05:30
nishkagosalia
e9e510a76e fix: Party Field only visibile when party type selected 2026-03-31 17:52:41 +05:30
ljain112
b73b161cbe test: improve test case 2026-03-31 17:46:46 +05:30
ljain112
9b37f2d95c fix: ensure accurate rounding for item-wise tax and taxable amounts 2026-03-31 17:23:01 +05:30
Khushi Rawat
f5bf95ca65 Merge pull request #53811 from khushi8112/customer-group-is-group-validation
fix: prevent selection of group type customer group in customer master
2026-03-31 16:46:25 +05:30
rohitwaghchaure
4013092271 Merge pull request #53953 from rohitwaghchaure/fixed-github-53597
fix: rejected serial no field showing even if serial / batch feature disabled
2026-03-31 16:11:12 +05:30
Rohit Waghchaure
c2f419ac3d fix: rejected serial no field showing even if serial / batch feature not enabled 2026-03-31 16:09:00 +05:30
Ejaaz Khan
eaa4f7bb55 Merge pull request #53949 from iamejaaz/sticky-erpnext-reports
feat: sticky columns in reports
2026-03-31 16:04:31 +05:30
khushi8112
75fa2b2277 fix(test): do not use is_group enabled customer group in test 2026-03-31 15:42:08 +05:30
Ejaaz Khan
df753676c6 fix: semgrep translation issue 2026-03-31 15:34:46 +05:30
Ejaaz Khan
03e4df7a1a feat: sticky columns in reports
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-31 15:12:58 +05:30
Mihir Kandoi
f1529a05b2 fix: do not show inv dimension unnecessarily in stock entry (#53946) 2026-03-31 09:42:21 +00:00
ruthra kumar
f452ad3ce2 Merge pull request #53795 from ruthra-kumar/use_erpnext_testsuite_across_repo
refactor(test): enforce ERPNextTestSuite across repo
2026-03-31 15:01:58 +05:30
khushi8112
6068dc959f fix: prevent selection of group type customer group in customer master 2026-03-31 14:40:59 +05:30
Khushi Rawat
dce5e46599 Merge pull request #53939 from khushi8112/ux-improvement-for-opening-invoice-tool
fix: dynamic labels on invoice type change
2026-03-31 12:06:45 +05:30
Nishka Gosalia
0696bd2082 chore: remove inter warehouse transfer settings (#53860) 2026-03-31 11:13:26 +05:30
khushi8112
820bd15e1e fix: dynamic labels on invoice type change 2026-03-31 01:53:28 +05:30
MochaMind
e7614e2290 fix: sync translations from crowdin (#53864) 2026-03-30 18:11:38 +00:00
Ankush Menat
f97877a60a fix(UX): Store weekly off at the end of holiday list (#53833)
Perhaps these two should be stored separately too?
2026-03-30 21:28:40 +05:30
Smit Vora
28ac0effff Merge pull request #53925 from ljain112/fix-item-tax-rounding 2026-03-30 20:46:21 +05:30
ljain112
fc8437c499 test: update item-wise tax detail test for high conversion rates 2026-03-30 20:04:53 +05:30
Smit Vora
a9edd3f132 Merge pull request #53406 from ljain112/fix-item-valaution-deduct 2026-03-30 19:53:55 +05:30
Smit Vora
a18196f584 fix(taxes): improve tax calculation accuracy and update test assertions 2026-03-30 19:37:59 +05:30
diptanilsaha
7d7a1efadb fix(bank_account): added validation to fetch bank account details using get_bank_account_details (#53926) 2026-03-30 18:51:27 +05:30
ljain112
3449ab063a fix(tests): update item code and quantity in tax detail test case 2026-03-30 18:37:07 +05:30
Jatin3128
d827ab3d2e Merge pull request #53922 from Jatin3128/toggle-ps
feat(Payment Request): Added a toggle for using the payment schedule
2026-03-30 18:28:30 +05:30
Jatin3128
8ec15b537e feat(Payment Request): Added a toggle for using the payment schedule 2026-03-30 18:05:42 +05:30
ljain112
7f87a5e5c6 fix(taxes): increase rounding threshold for tax breakup calculations 2026-03-30 17:58:47 +05:30
diptanilsaha
c41730dfee fix(opening_invoice_creation_tool): sanitize summary content for dashboard (#53917) 2026-03-30 17:52:27 +05:30
diptanilsaha
fa5238ba12 fix(item_dashboard): escaping warehouse, item_code, stock_uom and item_name on get_data (#53904) 2026-03-30 09:30:51 +00:00
rohitwaghchaure
b9f26a1f31 Merge pull request #53906 from rohitwaghchaure/fixed-support-63613
fix: purchase invoice missing item
2026-03-30 14:44:37 +05:30
diptanilsaha
eda64cbd4d fix(warehouse_capacity_dashboard): removed escape from template (#53907) 2026-03-30 09:01:34 +00:00
Rohit Waghchaure
af994c1a22 fix: purchase invoice missing item 2026-03-30 14:07:31 +05:30
rohitwaghchaure
91aaabdd31 Merge pull request #53902 from rohitwaghchaure/fixed-code-cleanup-reposting
fix: item-wh reposting, code cleanup
2026-03-30 14:01:07 +05:30
Rohit Waghchaure
e0ca34ae39 fix: item-wh reposting, code cleanup 2026-03-30 13:39:24 +05:30
diptanilsaha
ddeb9775ed fix(warehouse_capacity_dashboard): escaping warehouse, item_code and company on get_data (#53894) 2026-03-30 07:46:33 +00:00
Sudharsanan Ashok
ad25c6d163 fix(stock): add warehouse filter to pick work order raw materials (#53748) 2026-03-30 13:15:48 +05:30
rohitwaghchaure
71a1dda958 Merge pull request #53799 from aerele/fix/lcv-company-validation
fix(stock): update company validation for expense account in lcv
2026-03-30 13:03:36 +05:30
Sudharsanan11
875a2e4947 fix(test): enable perpetual inventory 2026-03-30 12:16:48 +05:30
Sudharsanan Ashok
d28474a450 fix(stock): ignore qty validation for pick list (#53871) 2026-03-30 06:37:26 +00:00
Sudharsanan Ashok
f3a794384a fix(manufacturing): update the qty precision (#53874) 2026-03-29 21:51:36 +05:30
ervishnucs
97e7916b66 fix: resolve item tax template from item group in update items 2026-03-29 21:35:26 +05:30
MochaMind
893eb8c77a chore: update POT file (#53876) 2026-03-29 14:56:50 +02:00
rohitwaghchaure
0e0a7f3563 Merge pull request #53878 from rohitwaghchaure/fixed-maintain-reposting-state
fix: maintain state during reposting
2026-03-29 16:12:48 +05:30
Rohit Waghchaure
f8738a791b fix: maintain state during reposting 2026-03-29 15:53:46 +05:30
Kaushal Shriwas
6badf00313 fix: change shipment parcel dimension fields from Int to Float (#53867) 2026-03-29 06:48:51 +00:00
Shllokkk
5bbcb73808 fix: revamp print formats for accounts receivable summary and accounts payable summary reports 2026-03-29 02:02:59 +05:30
diptanilsaha
998469d5c7 refactor: setup wizard stages and demo data creation (#53866) 2026-03-29 00:43:58 +05:30
rohitwaghchaure
5c95f3347b Merge pull request #53853 from rohitwaghchaure/fixed-reposting-dependent-sles
fix: corrected logic to retry reposting if timeout occurs after dependant SLE processing
2026-03-27 21:25:44 +05:30
Rohit Waghchaure
90b9ab0bc8 fix: corrected logic to retry reposting if timeout occurs after dependent SLE processing 2026-03-27 21:04:35 +05:30
Mihir Kandoi
935eea6463 fix: invalid dynamic link filter for address doctype (#53849) 2026-03-27 12:23:58 +00:00
Shllokkk
2bf9d41797 feat: add print format for accounts payable report 2026-03-27 16:03:20 +05:30
Mihir Kandoi
6008fa710d fix: validate if quantity greater than 0 in item dashboard (#53846) 2026-03-27 10:32:35 +00:00
ruthra kumar
47bb728f65 fix: sync translations from crowdin (#53841)
* fix: Italian translations

* fix: Swedish translations
2026-03-27 15:56:27 +05:30
MochaMind
aef6c959ea fix: Swedish translations 2026-03-27 14:12:13 +05:30
MochaMind
bddc7a3e4a fix: Italian translations 2026-03-27 14:11:56 +05:30
rohitwaghchaure
54c6948174 Merge pull request #53839 from rohitwaghchaure/fixed-job-card-timer-issue
fix: timer not showing in job card
2026-03-27 13:50:14 +05:30
ruthra kumar
e45af4345f Merge pull request #53837 from ruthra-kumar/semgrep_to_prevent_test_regression
ci: semgrep to prevent test regression
2026-03-27 13:41:47 +05:30
Rohit Waghchaure
58dbb3d638 fix: timer not showing in job card 2026-03-27 13:06:34 +05:30
ruthra kumar
be4496e4ab ci: semgrep to prevent test regression 2026-03-27 12:49:28 +05:30
Shllokkk
c051536182 refactor: revamp print template for accounts payable report 2026-03-27 12:32:17 +05:30
ruthra kumar
2aecf0103a refactor(test): remove AccountsTestMixin from Sales Order 2026-03-27 12:12:48 +05:30
rohitwaghchaure
e6cac26640 Merge pull request #53812 from rohitwaghchaure/fixed-pick-correct-dependent-sle
fix: pick correct dependant sle during reposting
2026-03-27 12:12:48 +05:30
ruthra kumar
d2ee967383 refactor(test): remove AccountsTestMixin from reactivity 2026-03-27 11:56:52 +05:30
ruthra kumar
0b6546ea06 refactor(test): remove AccountsTestMixin from distributed discount 2026-03-27 11:56:30 +05:30
Rohit Waghchaure
8e8ee56e64 fix: pick correct dependant sle during reposting 2026-03-27 11:50:34 +05:30
ruthra kumar
13505ddcfb test: fixed test case (backport #53826) (#53834)
test: fixed test case

(cherry picked from commit 10f58112ae)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-03-27 11:47:12 +05:30
Nishka Gosalia
5d4ac95e7a Merge pull request #53704 from nishkagosalia/gh-53512-fixes 2026-03-27 11:38:52 +05:30
ruthra kumar
2b37d7514d refactor(test): move logic from AccountsTestMixin to ERPNextTestSuite 2026-03-27 11:20:11 +05:30
Rohit Waghchaure
8368feb9df test: fixed test case
(cherry picked from commit 10f58112ae)
2026-03-27 05:49:25 +00:00
nishkagosalia
3a78af7f42 fix: test case 2026-03-27 11:11:19 +05:30
nishkagosalia
3bedc6cf7e chore: Dropping bom stock report and bom stock calculated report 2026-03-27 11:11:18 +05:30
nishkagosalia
c1874cb7d5 fix: change in functionality 2026-03-27 11:11:18 +05:30
nishkagosalia
5d088350dc feat: Bom stock analysis report 2026-03-27 11:11:18 +05:30
ruthra kumar
f3148e052c refactor(test): erpnext testsuite should be primary superclass 2026-03-27 11:00:25 +05:30
Shllokkk
d987688058 fix: support translated search in get_party_type and refactor raw sql to qb 2026-03-27 10:49:34 +05:30
ruthra kumar
c283c1c472 Merge pull request #53074 from Jatin3128/subscription-section-hide
feat: add setting to hide Subscription references across doctypes
2026-03-27 10:37:59 +05:30
ruthra kumar
6566acbe23 Merge pull request #53429 from Shllokkk/deferred-rev-fix
feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report
2026-03-27 10:30:12 +05:30
ruthra kumar
d6755c3d14 Merge pull request #53343 from Shllokkk/email-campaign-fix
fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes
2026-03-27 10:26:49 +05:30
Shllokkk
0d4f56bf84 refactor: table body data rendering cleanup 2026-03-26 21:56:51 +05:30
Pandiyan P
5b1fa81451 fix(accounts): set supplier name as title field in Purchase Invoice (#53710)
fix(accounts): update title field in purchase order and purchase invoice
2026-03-26 19:00:10 +05:30
Mihir Kandoi
8164d195fc fix: flaky currency exchange test (#53813) 2026-03-26 12:23:55 +00:00
iamkhanraheel
5ec66169a7 fix: default permission for HR manager role 2026-03-26 17:23:54 +05:30
Shllokkk
9660debe28 fix: improve filter details render logic to avoid showing duplicate information 2026-03-26 16:18:51 +05:30
rohitwaghchaure
f382b30b4e Merge pull request #53216 from aerele/fix/legacy-recon-in-ageing
fix(stock): handle legacy single sle recon entries
2026-03-26 15:37:21 +05:30
rohitwaghchaure
15996952f6 Merge pull request #52152 from rohitwaghchaure/refactor-reposting-feature
Refactor reposting feature
2026-03-26 15:12:20 +05:30
Rohit Waghchaure
daa2420996 refactor: storing of current status of reposting 2026-03-26 14:49:39 +05:30
Krishna Pramod Shirsath
f37b6fde72 Merge pull request #53245 from krishna-254/feat/employee-milestone-indicators
feat: employee milestone indicators
2026-03-26 13:40:20 +05:30
Mihir Kandoi
afa66e4785 fix: keep from and to time blank until added explicitly (#53798) 2026-03-26 13:05:54 +05:30
Sudharsanan11
913168e8b6 fix(stock): update company validation for expense account in lcv 2026-03-26 12:56:59 +05:30
kavin-114
7e6bbcc3fb fix(stock): handle legacy single sle recon entries 2026-03-26 12:46:06 +05:30
Krishna Shirsath
9715637c80 Merge remote-tracking branch 'origin/develop' into feat/employee-milestone-indicators 2026-03-26 10:05:23 +05:30
Mihir Kandoi
3f74733942 fix: purchase invoice for internal transfers should not require PO (#53791) 2026-03-25 16:18:51 +00:00
diptanilsaha
e136bfbb61 fix(contract_template): restrict create, write and delete access only to System Manager (#53787) 2026-03-25 14:43:45 +00:00
MochaMind
00b780362b fix: sync translations from crowdin (#53475) 2026-03-25 11:47:01 +01:00
diptanilsaha
1d202fe739 Merge pull request #53779 from diptanilsaha/template_fixes 2026-03-25 14:56:36 +05:30
diptanilsaha
bc6561cdd0 fix(templates): using correct syntax of include in projects.html 2026-03-25 14:55:05 +05:30
diptanilsaha
d9760bbf4f fix(templates): escape attachment file_url and file_name in order.html and projects.html 2026-03-25 14:46:50 +05:30
Sudharsanan Ashok
a821c6669f fix(manufacturing): update condition for base hour rate calculation (#53753) 2026-03-25 11:37:38 +05:30
Pandiyan P
d43d308e2f fix(manufacturing): apply work order status filter in job card (#53766) 2026-03-25 11:20:04 +05:30
Mihir Kandoi
90cd957d6e fix: do not check for sub assembly reference for rm of fg (#53758) 2026-03-24 17:09:49 +00:00
iamkhanraheel
7b0bfe76cc fix: default permission for HR User role 2026-03-24 19:57:42 +05:30
ruthra kumar
14a46bf920 Merge pull request #53657 from ruthra-kumar/move_commits_inside_test_guard_clause
refactor(test): move remaining commits inside test guard
2026-03-24 17:43:17 +05:30
ruthra kumar
bc2b8da597 refactor(test): process statement of acc remove commit 2026-03-24 17:21:31 +05:30
ruthra kumar
fd2b76a4d2 refactor(test): move location creation to bootstrap in asset movement 2026-03-24 17:21:31 +05:30
ruthra kumar
8fd65d7afa refactor(test): make stock entry deterministic 2026-03-24 17:21:31 +05:30
ruthra kumar
2c53cf3902 refactor(test): make asset capitalization deterministic 2026-03-24 17:21:31 +05:30
ruthra kumar
d3cf8cb851 refactor(test): make ledger merge deterministic 2026-03-24 17:21:31 +05:30
ruthra kumar
77f41e120d refactor(test): SLA move company creation to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
426b7db3c8 refactor(test): move webform custom dt creation to boostrap 2026-03-24 17:21:31 +05:30
ruthra kumar
934740205a refactor(test): move custom doctype data setup to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
4454af8efd refactor(test): move tax category custom field creation to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
11fb00c21d refactor(test): move trial company creation to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
31ce09204f refactor(test): move purchase invoice dimension setup to bootstrap 2026-03-24 17:21:31 +05:30
Nishka Gosalia
6c354895d6 Merge pull request #53738 from nishkagosalia/item-form-cleanup 2026-03-24 16:28:10 +05:30
nishkagosalia
be55082751 refactor: item master ux improvements 2026-03-24 15:55:45 +05:30
ervishnucs
a518a735f3 fix: remove null from link_filters 2026-03-24 15:43:20 +05:30
Khushi Rawat
ec003342a0 Merge pull request #53588 from khushi8112/default-print-format-for-quotation
feat: default print format for Quotation
2026-03-24 15:36:34 +05:30
Shllokkk
3ba36212b0 refactor: clean and standardize print template for general ledger report 2026-03-24 15:32:13 +05:30
khushi8112
da41057cd6 fix: add quotation print format in the list 2026-03-24 13:03:24 +05:30
ruthra kumar
9ed072ac83 refactor(test): move company setup to bootstrap 2026-03-24 11:57:45 +05:30
ruthra kumar
342ce65401 refactor(test): move dimension setup to test data bootstrap
and remove create_dimension() and disable_dimension()
2026-03-24 11:57:43 +05:30
ruthra kumar
ed76d6699a refactor(test): move commits inside test guard clause 2026-03-24 11:57:02 +05:30
Rohit Waghchaure
20787ef5da refactor: reposting for better peformance 2026-03-23 20:36:02 +05:30
Jatin3128
226aafa8cf feat: add setting to hide Subscription references across doctypes 2026-03-23 19:49:01 +05:30
ervishnucs
03c9d16ca6 fix: fetch get_item_tax_template while update items 2026-03-23 16:45:20 +05:30
Krishna Shirsath
3e56b8d71d fix(employee): removed milestone lable and remove unnecessary margins 2026-03-23 14:47:54 +05:30
Shllokkk
8e5692d8a3 feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report 2026-03-13 21:31:37 +05:30
ljain112
e68f149d3a fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt. 2026-03-13 11:53:08 +05:30
Krishna Shirsath
831b1d3a79 feat(employee): Refactor milestone indicator functions to accept 'today' parameter 2026-03-12 14:19:15 +05:30
Shllokkk
56f597f5ad fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes 2026-03-12 00:54:40 +05:30
Krishna Shirsath
e4a0d2ab0b feat(employee): Enhance milestone indicators for birthdays and work anniversaries 2026-03-11 22:38:42 +05:30
nareshkannasln
fa34ebea94 fix: skip BudgetValidation when cancelling GL entries 2026-03-11 11:59:14 +05:30
Krishna Shirsath
be819eb876 fix(employee): correct work anniversary message pluralization 2026-03-09 16:06:55 +05:30
Krishna Pramod Shirsath
2b8a4a9b5f Merge branch 'frappe:develop' into feat/employee-milestone-indicators 2026-03-09 13:06:20 +05:30
Krishna Shirsath
7e8a830f42 feat(employee): Add birthdays and work anniversaries indicator in form 2026-03-09 13:01:52 +05:30
450 changed files with 126214 additions and 103500 deletions

View File

@@ -1,4 +1,4 @@
### Introduction (first timers)
### Introduction (for first timers)
Thank you for your interest in raising an Issue with ERPNext. An Issue could mean a bug report or a request for a missing feature. By raising a bug report, you are contributing to the development of ERPNext and this is the first step of participating in the community. Bug reports are very helpful for developers as they quickly fix the issue before other users start facing it.
@@ -6,31 +6,31 @@ Feature requests are also a great way to take the product forward. New ideas can
When you are raising an Issue, you should keep a few things in mind. Remember that the developer does not have access to your machine so you must give all the information you can while raising an Issue. If you are suggesting a feature, you should be very clear about what you want.
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that , then the right place is the forum [https://discuss.frappe.io](https://discuss.frappe.io/c/erpnext/6).
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that, then the right place is the forum [https://discuss.frappe.io](https://discuss.frappe.io/c/erpnext/6).
### Reply and Closing Policy
If your issue is not clear or does not meet the guidelines, then it will be closed. If it is closed, please supply the information asked and re-open it.
If your issue is not clear or does not meet the guidelines, then it will be closed. If it is closed, please supply the requested information and re-open it.
### General Issue Guidelines
1. **Search existing Issues:** Before raising a Issue, search if it has been raised before. Maybe add a 👍 or give additional help by creating a mockup if it is not already created.
1. **Report each issue separately:** Don't club multiple, unreleated issues in one note.
1. **Brief:** Please don't include long explanations. Use screenshots and bullet points instead of descriptive paragraphs.
1. **Search existing Issues:** Before raising an Issue, search if it has been raised before. Maybe add a 👍 or give additional help by creating a mockup if it is not already created.
2. **Report each issue separately:** Don't club multiple, unrelated issues in one note.
3. **Brief:** Please don't include long explanations. Use screenshots and bullet points instead of descriptive paragraphs.
### Bug Report Guidelines
1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it.
1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version
1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit"
1. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LiceCAP to take a screencast in `gif`.
2. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version.
3. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit".
4. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LICEcap to take a screencast in `.gif` format.
### Feature Request Guidelines
1. **Clarity:** Clearly specify how do you want the feature to behave. Don't just say "I would like multiple PDF formats", say that "Ability to add multiple print formats for customers with different languages".
1. **Solution:** Try and identify how the feature should look like.
1. **Mockups:** Mockups are a great way to explain your requirement.
1. **Clarity:** Clearly specify how you want the feature to behave. Don't just say "I would like multiple PDF formats", instead say "Ability to add multiple print formats for customers with different languages".
2. **Solution:** Try to identify what the feature should look like.
3. **Mockups:** Mockups are a great way to explain your requirement.
### What if my Issue is closed
### What if my issue is closed
Don't worry, take the feedback, supply the correct information and re-open it!

View File

@@ -1,6 +1,6 @@
---
name: Feature request
about: Suggest an idea to improve ERPNext
about: Suggest an idea or enhancement for ERPNext
title: ''
labels: feature-request
assignees: ''
@@ -17,17 +17,21 @@ Welcome to ERPNext issue tracker! Before creating an issue, please heed the foll
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
Please keep in mind that we get many many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
Please keep in mind that we get many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
If you're in urgent need to a feature, please try the following channels to get paid developments done quickly:
If you're in urgent need of a feature, please try the following channels to get paid developments done quickly:
1. Certified ERPNext partners: https://erpnext.com/partners
2. Developer community on ERPNext forums: https://discuss.frappe.io/c/framework/5
3. Telegram group for ERPNext/Frappe development work: https://t.me/erpnext_opps
-->
## Before Submitting
- [ ] I searched existing issues and confirmed this is not a duplicate
- [ ] This is a feature request, not a bug or support question
- [ ] For support: https://discuss.frappe.io/c/erpnext/6
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is. Ex. As a [role], I have to [painful task] because [missing feature].
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
@@ -35,5 +39,17 @@ A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Impact**
<!-- Check one: -->
- [ ] Blocks critical workflow — no viable workaround
- [ ] Significant friction — workaround exists but is painful
- [ ] Nice to have — minor improvement
**Additional context**
Add any other context or screenshots about the feature request here.
**Environment**
- ERPNext Version: <!-- Find this in Help > About, e.g. v15.12.0 -->
- Frappe Version: <!-- Find this in Help > About, e.g. v15.10.0 -->
- Deployment: <!-- Frappe Cloud / Self-hosted / ERPNext Cloud -->

View File

@@ -8,8 +8,8 @@ permissions:
on:
schedule:
# 9:30 UTC => 3 PM IST
- cron: "30 9 * * 1,4"
# 9:30 UTC => 3 PM IST Tuesday
- cron: "30 9 * * 2"
workflow_dispatch:
jobs:

View File

@@ -43,3 +43,6 @@ jobs:
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
- name: Semgrep for Test Correctness
run: semgrep ci --include=**/test_*.py --config ./semgrep/test-correctness.yml

View File

@@ -41,6 +41,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
TZ: 'Asia/Kolkata'
NODE_ENV: "production"
WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}
@@ -56,6 +57,7 @@ jobs:
mysql:
image: mariadb:10.6
env:
TZ: 'Asia/Kolkata'
MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306

View File

@@ -1,12 +1,12 @@
<div align="center">
<a href="https://frappe.io/erpnext">
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80px"/>
</a>
<h2>ERPNext</h2>
<p align="center">
<div align="center">
<p>Powerful, Intuitive and Open-Source ERP</p>
</p>
</div>
[![Learn on Frappe School](https://img.shields.io/badge/Frappe%20School-Learn%20ERPNext-blue?style=flat-square)](https://frappe.school)<br><br>
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
@@ -15,7 +15,7 @@
</div>
<div align="center">
<img src="./erpnext/public/images/v16/hero_image.png"/>
<img src="./erpnext/public/images/v16/hero_image.png" alt="ERPNext Hero Image"/>
</div>
<div align="center">
@@ -28,19 +28,19 @@
## ERPNext
100% Open-Source ERP system to help you run your business.
100% Open-Source ERP System to help you run your business.
### Motivation
Running a business is a complex task - handling invoices, tracking stock, managing personnel and even more ad-hoc activities. In a market where software is sold separately to manage each of these tasks, ERPNext does all of the above and more, for free.
Running a business is a complex task - handling invoices, tracking stock, managing personnel, and other daily operations. In a market where software is sold separately to manage each of these tasks, ERPNext does all of the above and more, for free.
### Key Features
- **Accounting**: All the tools you need to manage cash flow in one place, right from recording transactions to summarizing and analyzing financial reports.
- **Order Management**: Track inventory levels, replenish stock, and manage sales orders, customers, suppliers, shipments, deliverables, and order fulfillment.
- **Manufacturing**: Simplifies the production cycle, helps track material consumption, exhibits capacity planning, handles subcontracting, and more!
- **Asset Management**: From purchase to perishment, IT infrastructure to equipment. Cover every branch of your organization, all in one centralized system.
- **Projects**: Delivery both internal and external Projects on time, budget and Profitability. Track tasks, timesheets, and issues by project.
- **Asset Management**: From purchase to disposal, IT infrastructure to equipment. Covers every branch of your organization, all in one centralized system.
- **Projects**: Deliver both internal and external projects on time, budget and profitability. Track tasks, timesheets, and issues by project.
<details open>
@@ -53,7 +53,7 @@ Running a business is a complex task - handling invoices, tracking stock, managi
### Under the Hood
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and JavaScript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
- [**Frappe UI**](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface. The Frappe UI library provides a variety of components that can be used to build single-page applications on top of the Frappe Framework.
@@ -61,12 +61,12 @@ Running a business is a complex task - handling invoices, tracking stock, managi
### Managed Hosting
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications with peace of mind.
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly, and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications reliably and securely.
It takes care of installation, setup, upgrades, monitoring, maintenance and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
It handles installation, setup, upgrades, monitoring, maintenance, and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
<div>
<a href="https://erpnext-demo.frappe.cloud/app/home" target="_blank">
<a href="https://erpnext-demo.frappe.cloud/app/home" target="_blank" rel="noopener noreferrer">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
@@ -78,7 +78,7 @@ It takes care of installation, setup, upgrades, monitoring, maintenance and supp
### Self-Hosted
#### Docker
See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for full documentation & FAQ on docker setup
See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for full documentation & FAQ on Docker setup
#### Prerequisites
@@ -90,7 +90,7 @@ See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for f
#### Demo setup
The fastest way to try ERPNext is to play in an already set up sandbox, in your browser, click the button below:
The fastest way to try ERPNext is to play in a pre-configured sandbox, in your browser, click the button below:
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/main/pwd.yml">
<img src="https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png" alt="Try in PWD"/>
@@ -114,7 +114,7 @@ Then run:
```sh
docker compose -f pwd.yml up -d
```
Wait for a couple of minutes for ERPNext site to be created or check `create-site` container logs before opening browser on port `8080`. (username: `Administrator`, password: `admin`)
Wait for a couple of minutes for ERPNext site to be created or check the `create-site` container logs before opening browser on port `8080`. (username: `Administrator`, password: `admin`)
See [Frappe Docker](https://github.com/frappe/frappe_docker/blob/main/docs/01-getting-started/03-arm64.md) for ARM based docker setup
@@ -124,7 +124,7 @@ See [Frappe Docker](https://github.com/frappe/frappe_docker/blob/main/docs/01-ge
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the Frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
### Local
@@ -153,20 +153,20 @@ To setup the repository locally follow the steps mentioned below:
4. Open the URL `http://erpnext.localhost:8000/app` in your browser, you should see the app running
## Learning and community
## Learning and Community
1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.frappe.io/c/erpnext/6) - Engage with community of ERPNext users and service providers.
3. [Discussion Forum](https://discuss.frappe.io/c/erpnext/6) - Engage with the community of ERPNext users and service providers.
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users.
## Contributing
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
2. [Translations](https://crowdin.com/project/frappe)
2. [Report Security Vulnerabilities](https://erpnext.com/security)
3. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
4. [Translations](https://crowdin.com/project/frappe)
## Logo and Trademark Policy

View File

@@ -18,8 +18,9 @@ We will grant permission to use the ERPNext name and logo for projects that meet
- The primary purpose of your project is to promote the spread and improvement of the ERPNext software.
- Your project is non-commercial in nature (it can make money to cover its costs or contribute to non-profit entities, but it cannot be run as a for-profit project or business).
Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
- If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
- Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
Use of the ERPNext name and logo is additionally allowed in the following situations:

View File

@@ -5,8 +5,7 @@ frappe.ui.form.on("Account", {
setup: function (frm) {
frm.add_fetch("parent_account", "report_type", "report_type");
frm.add_fetch("parent_account", "root_type", "root_type");
},
onload: function (frm) {
frm.set_query("parent_account", function (doc) {
return {
filters: {
@@ -15,7 +14,18 @@ frappe.ui.form.on("Account", {
},
};
});
frm.set_query("account_category", function () {
if (!frm.doc.root_type) return;
return {
filters: {
root_type: ["in", [frm.doc.root_type, ""]],
},
};
});
},
refresh: function (frm) {
frm.toggle_display("account_name", frm.is_new());
@@ -58,12 +68,20 @@ frappe.ui.form.on("Account", {
}
}
},
account_type: function (frm) {
if (frm.doc.is_group == 0) {
frm.toggle_display(["tax_rate"], frm.doc.account_type == "Tax");
frm.toggle_display("warehouse", frm.doc.account_type == "Stock");
}
},
root_type: function (frm) {
if (frm.doc.account_category) {
frm.set_value("account_category", "");
}
},
add_toolbar_buttons: function (frm) {
frm.add_custom_button(
__("Chart of Accounts"),

View File

@@ -203,7 +203,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2025-08-02 06:26:44.657146",
"modified": "2026-04-14 18:14:42.202065",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
@@ -256,6 +256,14 @@
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"role": "HR User",
"select": 1
},
{
"role": "HR Manager",
"select": 1
}
],
"row_format": "Dynamic",

View File

@@ -320,72 +320,6 @@ class TestAccount(ERPNextTestSuite):
self.assertEqual(balance, 0)
def _make_test_records(verbose=None):
from frappe.tests.utils import make_test_objects
accounts = [
# [account_name, parent_account, is_group]
["_Test Bank", "Bank Accounts", 0, "Bank", None],
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
["_Test Cash", "Cash In Hand", 0, "Cash", None],
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
["_Test Employee Advance", "Current Liabilities", 0, None, None],
["_Test Account Tax Assets", "Current Assets", 1, None, None],
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account S&H Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account CST", "Direct Expenses", 0, "Tax", None],
["_Test Account Discount", "Direct Expenses", 0, None, None],
["_Test Write Off", "Indirect Expenses", 0, None, None],
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
["_Test Account Sales", "Direct Income", 0, None, None],
# related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, "Depreciation", None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
# Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None],
["_Test Payable", "Current Liabilities", 0, "Payable", None],
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"],
]
for company, abbr in [
["_Test Company", "_TC"],
["_Test Company 1", "_TC1"],
["_Test Company with perpetual inventory", "TCP1"],
]:
test_objects = make_test_objects(
"Account",
[
{
"doctype": "Account",
"account_name": account_name,
"parent_account": parent_account + " - " + abbr,
"company": company,
"is_group": is_group,
"account_type": account_type,
"account_currency": currency,
}
for account_name, parent_account, is_group, account_type, currency in accounts
],
)
return test_objects
def get_inventory_account(company, warehouse=None):
account = None
if warehouse:

View File

@@ -7,6 +7,8 @@
"engine": "InnoDB",
"field_order": [
"account_category_name",
"root_type",
"column_break_qluu",
"description"
],
"fields": [
@@ -14,6 +16,7 @@
"fieldname": "account_category_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Account Category Name",
"reqd": 1,
"unique": 1
@@ -22,12 +25,29 @@
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "column_break_qluu",
"fieldtype": "Column Break"
},
{
"fieldname": "root_type",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-10-15 03:19:47.171349",
"links": [
{
"link_doctype": "Account",
"link_fieldname": "account_category"
}
],
"modified": "2026-03-05 06:49:34.430723",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Category",
@@ -64,7 +84,7 @@
}
],
"row_format": "Dynamic",
"search_fields": "account_category_name, description",
"search_fields": "account_category_name, root_type",
"sort_field": "creation",
"sort_order": "DESC",
"states": []

View File

@@ -21,6 +21,7 @@ class AccountCategory(Document):
account_category_name: DF.Data
description: DF.SmallText | None
root_type: DF.Literal["", "Asset", "Liability", "Income", "Expense", "Equity"]
# end: auto-generated types
def after_rename(self, old_name, new_name, merge):

View File

@@ -82,7 +82,7 @@ class AccountingDimension(Document):
else:
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
def after_insert(self):
def on_update(self):
if frappe.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:

View File

@@ -9,9 +9,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestAccountingDimension(ERPNextTestSuite):
def setUp(self):
create_dimension()
def test_dimension_against_sales_invoice(self):
si = create_sales_invoice(do_not_save=1)
@@ -76,63 +73,3 @@ class TestAccountingDimension(ERPNextTestSuite):
si.save()
self.assertRaises(frappe.ValidationError, si.submit)
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
dimension = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
}
)
dimension.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Department",
"default_dimension": "_Test Department - _TC",
},
)
dimension.insert()
dimension.save()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Location",
}
)
dimension1.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
},
)
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 1
dimension1.save()
dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.disabled = 1
dimension2.save()

View File

@@ -3,9 +3,6 @@
import frappe
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
from erpnext.tests.utils import ERPNextTestSuite
@@ -13,7 +10,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestAccountingDimensionFilter(ERPNextTestSuite):
def setUp(self):
create_dimension()
create_accounting_dimension_filter()
self.invoice_list = []

View File

@@ -2,7 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on("Accounts Settings", {
refresh: function (frm) {},
refresh: function (frm) {
frm.set_query("document_type", "repost_allowed_types", function (doc, cdt, cdn) {
return {
filters: {
name: ["in", frappe.boot.sysdefaults.repost_allowed_doctypes],
},
};
});
},
enable_immutable_ledger: function (frm) {
if (!frm.doc.enable_immutable_ledger) {
return;

View File

@@ -16,6 +16,7 @@
"invoicing_features_section",
"check_supplier_invoice_uniqueness",
"automatically_fetch_payment_terms",
"enable_subscription",
"column_break_17",
"enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account",
@@ -62,9 +63,12 @@
"reconciliation_queue_size",
"column_break_resa",
"exchange_gain_loss_posting_date",
"repost_section",
"repost_allowed_types",
"payment_options_section",
"enable_loyalty_point_program",
"column_break_ctam",
"fetch_payment_schedule_in_payment_request",
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
@@ -688,16 +692,39 @@
"fieldname": "enable_accounting_dimensions",
"fieldtype": "Check",
"label": "Enable Accounting Dimensions"
},
{
"default": "1",
"description": "Enable Subscription tracking in invoice",
"fieldname": "enable_subscription",
"fieldtype": "Check",
"label": "Enable Subscription"
},
{
"default": "1",
"fieldname": "fetch_payment_schedule_in_payment_request",
"fieldtype": "Check",
"label": "Fetch Payment Schedule In Payment Request"
},
{
"fieldname": "repost_section",
"fieldtype": "Section Break",
"label": "Repost"
},
{
"fieldname": "repost_allowed_types",
"fieldtype": "Table",
"label": "Allowed Doctypes",
"options": "Repost Allowed Types"
}
],
"grid_page_length": 50,
"hide_toolbar": 0,
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-02-27 01:04:09.415288",
"modified": "2026-04-13 15:30:28.729627",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -10,6 +10,9 @@ 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.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.utils import sync_auto_reconcile_config
SELLING_DOCTYPES = [
@@ -44,6 +47,8 @@ class AccountsSettings(Document):
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
add_taxes_from_item_tax_template: DF.Check
add_taxes_from_taxes_and_charges_template: DF.Check
allow_multi_currency_invoices_against_single_party_account: DF.Check
@@ -72,7 +77,9 @@ class AccountsSettings(Document):
enable_immutable_ledger: DF.Check
enable_loyalty_point_program: DF.Check
enable_party_matching: DF.Check
enable_subscription: DF.Check
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
fetch_payment_schedule_in_payment_request: DF.Check
fetch_valuation_rate_for_internal_transaction: DF.Check
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
@@ -85,6 +92,7 @@ class AccountsSettings(Document):
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
repost_allowed_types: DF.Table[RepostAllowedTypes]
role_allowed_to_over_bill: DF.Link | None
role_to_notify_on_depreciation_failure: DF.Link | None
role_to_override_stop_action: DF.Link | None
@@ -135,10 +143,15 @@ class AccountsSettings(Document):
toggle_loyalty_point_program_section(not self.enable_loyalty_point_program)
clear_cache = True
if old_doc.enable_subscription != self.enable_subscription:
toggle_subscription_sections(not self.enable_subscription)
clear_cache = True
if clear_cache:
frappe.clear_cache()
self.validate_and_sync_auto_reconcile_config()
self.update_property_for_accounting_dimension()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
@@ -185,6 +198,17 @@ class AccountsSettings(Document):
title=_("Auto Tax Settings Error"),
)
def update_property_for_accounting_dimension(self):
doctypes = [entry.document_type for entry in self.repost_allowed_types]
if not doctypes:
return
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
doctypes += get_child_docs(doctypes)
set_allow_on_submit_for_dimension_fields(doctypes)
@frappe.whitelist()
def drop_ar_sql_procedures(self):
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
@@ -215,6 +239,12 @@ def toggle_loyalty_point_program_section(hide):
create_property_setter_for_hiding_field(doctype, "loyalty_points_redemption", hide)
def toggle_subscription_sections(hide):
subscription_doctypes = frappe.get_hooks("subscription_doctypes")
for doctype in subscription_doctypes:
create_property_setter_for_hiding_field(doctype, "subscription_section", hide)
def create_property_setter_for_hiding_field(doctype, field_name, hide):
make_property_setter(
doctype,
@@ -224,3 +254,12 @@ def create_property_setter_for_hiding_field(doctype, field_name, hide):
"Check",
validate_fields_for_doctype=False,
)
def set_allow_on_submit_for_dimension_fields(doctypes):
for dt in doctypes:
meta = frappe.get_meta(dt)
for dimension in get_accounting_dimensions():
df = meta.get_field(dimension)
if df and not df.allow_on_submit:
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -15,7 +15,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.tests.utils import ERPNextTestSuite
class TestAdvancePaymentLedgerEntry(AccountsTestMixin, ERPNextTestSuite):
class TestAdvancePaymentLedgerEntry(ERPNextTestSuite, AccountsTestMixin):
"""
Integration tests for AdvancePaymentLedgerEntry.
Use this class for testing interactions between multiple components.

View File

@@ -116,6 +116,7 @@ def get_default_company_bank_account(company, party_type, party):
@frappe.whitelist()
def get_bank_account_details(bank_account: str):
frappe.has_permission("Bank Account", doc=bank_account, ptype="read", throw=True)
return frappe.get_cached_value(
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
)

View File

@@ -15,7 +15,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.tests.utils import ERPNextTestSuite
class TestBankReconciliationTool(AccountsTestMixin, ERPNextTestSuite):
class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_customer()

View File

@@ -382,7 +382,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
frappe.get_doc(
{
"doctype": "Customer",
"customer_group": "All Customer Groups",
"customer_group": "Individual",
"customer_type": "Company",
"customer_name": "Poore Simon's",
}
@@ -413,7 +413,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
frappe.get_doc(
{
"doctype": "Customer",
"customer_group": "All Customer Groups",
"customer_group": "Individual",
"customer_type": "Company",
"customer_name": "Fayva",
}

View File

@@ -2,10 +2,11 @@
# See license.txt
import frappe
from frappe.tests import UnitTestCase
from erpnext.tests.utils import ERPNextTestSuite
class TestBankTransactionFees(UnitTestCase):
class TestBankTransactionFees(ERPNextTestSuite):
def test_included_fee_throws(self):
"""A fee that's part of a withdrawal cannot be bigger than the
withdrawal itself."""

View File

@@ -126,7 +126,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2025-01-22 10:46:42.904001",
"modified": "2026-04-14 18:15:27.367298",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
@@ -173,11 +173,20 @@
"role": "Employee",
"select": 1,
"share": 1
},
{
"role": "HR User",
"select": 1
},
{
"role": "HR Manager",
"select": 1
}
],
"row_format": "Dynamic",
"search_fields": "parent_cost_center, is_group",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "ASC",
"states": []
}
}

View File

@@ -13,7 +13,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.tests.utils import ERPNextTestSuite
class TestExchangeRateRevaluation(AccountsTestMixin, ERPNextTestSuite):
class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_usd_receivable_account()

View File

@@ -6,7 +6,7 @@ import json
import math
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from functools import reduce
from functools import cache, reduce
from typing import Any, Union
import frappe
@@ -15,6 +15,7 @@ from frappe.database.operator_map import OPERATOR_MAP
from frappe.query_builder import Case
from frappe.query_builder.functions import Sum
from frappe.utils import cstr, date_diff, flt, getdate
from frappe.utils.xlsxutils import XLSXMetadata, XLSXStyleBuilder
from pypika.terms import Bracket, LiteralValue
from erpnext import get_company_currency
@@ -38,6 +39,9 @@ from erpnext.accounts.report.financial_statements import (
)
from erpnext.accounts.utils import get_children, get_currency_precision
DEFAULT_BULLET_PREFIX = ""
SEGMENT_PREFIX = "seg_"
# ============================================================================
# DATA MODELS
# ============================================================================
@@ -141,7 +145,7 @@ class SegmentData:
@property
def id(self) -> str:
return f"seg_{self.index}"
return f"{SEGMENT_PREFIX}{self.index}"
@dataclass
@@ -222,14 +226,38 @@ class FinancialReportEngine:
return context.get_result()
def _validate_filters(self, filters: dict[str, Any]) -> None:
required_filters = ["report_template", "period_start_date", "period_end_date"]
filter_labels = {
"report_template": _("Report Template"),
"filter_based_on": _("Filter Based On"),
"period_start_date": _("Start Date"),
"period_end_date": _("End Date"),
"from_fiscal_year": _("Start Year"),
"to_fiscal_year": _("End Year"),
}
required_filters_by_basis = {
"Date Range": ("period_start_date", "period_end_date"),
"Fiscal Year": ("from_fiscal_year", "to_fiscal_year"),
}
required_filters = ["report_template", "filter_based_on"]
required_filters.extend(required_filters_by_basis.get(filters.get("filter_based_on"), ()))
for filter_key in required_filters:
if not filters.get(filter_key):
frappe.throw(_("Missing required filter: {0}").format(filter_key))
frappe.throw(
title=_("Missing Required Filter"),
msg=_("Missing required filter: {0}").format(
frappe.bold(filter_labels.get(filter_key, filter_key))
),
)
if filters.get("presentation_currency"):
frappe.msgprint(_("Currency filters are currently unsupported in Custom Financial Report."))
frappe.msgprint(
title=_("Unsupported Feature"),
msg=_("Currency filters are currently unsupported in Custom Financial Report."),
indicator="orange",
)
# Margin view is dependent on first row being an income account. Hence not supported.
# Way to implement this would be using calculated rows with formulas.
@@ -464,6 +492,7 @@ class FinancialQueryBuilder:
self.periods = periods
self.company = filters.get("company")
self.account_meta = {} # {name: {account_name, account_number}}
self.ignore_opening_entries = False
def fetch_account_balances(self, accounts: list[dict]) -> dict[str, AccountData]:
"""
@@ -501,6 +530,8 @@ class FinancialQueryBuilder:
"""
Return opening balances for *all accounts* defaulting to zero.
"""
self.ignore_opening_entries = False
if frappe.get_single_value("Accounts Settings", "ignore_account_closing_balance"):
return self._get_opening_balances_from_gl(accounts)
@@ -520,9 +551,9 @@ class FinancialQueryBuilder:
if last_closing_voucher:
closing_voucher = last_closing_voucher[0]
closing_data = self._get_closing_balances(accounts, closing_voucher.name)
self.ignore_opening_entries = True # Else it will double count
if sum(closing_data.values()) != 0.0:
return self._rebase_closing_balances(closing_data, closing_voucher.period_end_date)
return self._rebase_closing_balances(closing_data, closing_voucher.period_end_date)
return self._get_opening_balances_from_gl(accounts)
@@ -616,7 +647,12 @@ class FinancialQueryBuilder:
.groupby(gl_table.account)
)
if not frappe.get_single_value("Accounts Settings", "ignore_is_opening_check_for_reporting"):
ignore_is_opening = frappe.get_single_value(
"Accounts Settings", "ignore_is_opening_check_for_reporting"
)
if self.ignore_opening_entries and not ignore_is_opening:
# This filter here applies to all accounts (BS & PL)
# However, in legacy query, this filter only applies to BS accounts
query = query.where(gl_table.is_opening == "No")
# Add period-specific columns
@@ -680,11 +716,18 @@ class FinancialQueryBuilder:
account_data.unaccumulate_values()
def _apply_standard_filters(self, query, table, doctype: str = "GL Entry"):
if self.filters.get("ignore_closing_entries"):
if doctype == "GL Entry":
query = query.where(table.voucher_type != "Period Closing Voucher")
else:
query = query.where(table.is_period_closing_voucher_entry == 0)
# Exclude PCV-generated entries except those posted to a closing-account-head
# so BS retained earnings survive while P&L reversal entries are filtered out
pcv = frappe.qb.DocType("Period Closing Voucher")
closing_heads = frappe.qb.from_(pcv).select(pcv.closing_account_head).where(pcv.docstatus == 1)
if doctype == "GL Entry":
is_pcv = table.voucher_type == "Period Closing Voucher"
else:
# Account Closing Balance
is_pcv = table.is_period_closing_voucher_entry == 1
query = query.where(~is_pcv | table.account.isin(closing_heads))
if self.filters.get("project"):
projects = self.filters.get("project")
@@ -1392,7 +1435,8 @@ class FormattingEngine:
condition=lambda rd: getattr(rd.row, "italic_text", False), format_properties={"italic": True}
),
FormattingRule(
condition=lambda rd: rd.is_detail_row, format_properties={"is_detail": True, "prefix": ""}
condition=lambda rd: rd.is_detail_row,
format_properties={"is_detail": True, "prefix": DEFAULT_BULLET_PREFIX},
),
FormattingRule(
condition=lambda rd: getattr(rd.row, "warn_if_negative", False),
@@ -1838,3 +1882,124 @@ class GrowthViewTransformer:
return 0.0
else:
return flt(((current_value - previous_value) / abs(previous_value)) * 100, 2)
# ============================================================================
# XLSX EXPORT STYLING
# ============================================================================
def get_xlsx_styles(metadata: XLSXMetadata) -> dict | None:
"""
Generate XLSX styles for financial report templates.
NOTE: Currently only custom report generated with "Report Template" filter will have styles applied.
"""
# skip styling
if not metadata.filters.get("report_template"):
return
builder = XLSXStyleBuilder(metadata, default_styling=False)
builder.apply_default_styles(currency_formatting=False)
# currency is fixed for all columns (only if report template filter is applied)
currency = get_company_currency(metadata.filters.get("company"))
styles = {
"bold": builder.register_style({"bold": True}),
"italic": builder.register_style({"italic": True}),
"warning": builder.register_style({"font_color": "#dc3545"}), # text-danger
}
fieldtype_formats = {
"Int": builder.register_style({"num_format": "General"}),
"Float": builder.register_style({"num_format": builder.get_number_format("Float")}),
"Percent": builder.register_style({"num_format": builder.get_number_format("Percent")}),
"Currency": builder.register_style({"num_format": builder.get_number_format("Currency", currency)}),
}
# quick access for hot loop
style_cell = builder.style_cell
@cache
def get_color_style(color: str) -> int:
return builder.register_style({"font_color": color})
@cache
def get_prefix_style(prefix: str) -> int:
prefix = f"{prefix or DEFAULT_BULLET_PREFIX}@"
return builder.register_style({"num_format": prefix})
@cache
def get_indent_style(indent: int) -> int:
return builder.register_style({"align": "left", "indent": indent})
# column level styling of currency columns
for col_idx, col in metadata.column_map.items():
if col.get("fieldtype") != "Currency":
continue
builder.style_column(col_idx, fieldtype_formats["Currency"])
# cell level styling
for row_idx, row in metadata.row_map.items():
# skip total row
if metadata.has_total_row and row_idx == builder.last_row_index:
continue
is_segmented = (row.get("_segment_info", {}).get("total_segments", 1) or 1) > 1
segment_values = row.get("segment_values", {}) or {}
for col_idx, col in metadata.column_map.items():
fieldname = col.get("fieldname")
is_account = fieldname == "account"
# determine formatting bucket
if is_segmented and fieldname.startswith(SEGMENT_PREFIX):
formatting = row.copy()
_, seg_idx, seg_fieldname = fieldname.split("_", 2)
is_account = seg_fieldname == "account"
formatting.update(segment_values.get(f"{SEGMENT_PREFIX}{seg_idx}", {}) or {})
else:
formatting = row # default formatting bucket.
if not is_account and formatting.get("is_blank_line"):
continue
col_fieldtype = col.get("fieldtype")
cell_fieldtype = formatting.get("fieldtype") or col_fieldtype
cell_value = row.get(fieldname)
if cell_value in (None, ""):
continue
# account column and other fieldtype styling
if is_account:
if formatting.get("is_detail") or (prefix := formatting.get("prefix")):
style_cell(row_idx, col_idx, get_prefix_style(prefix))
# custom indentation (different segment might have different indentation levels)
if is_segmented and (indent := formatting.get("indent")) and indent > 0:
style_cell(row_idx, col_idx, get_indent_style(indent))
else:
if col_fieldtype != cell_fieldtype and cell_fieldtype in fieldtype_formats:
style_cell(row_idx, col_idx, fieldtype_formats[cell_fieldtype])
# text styles
for style_key in ("bold", "italic"):
if formatting.get(style_key):
style_cell(row_idx, col_idx, styles[style_key])
# color styles
if (
formatting.get("warn_if_negative")
and cell_fieldtype in frappe.model.numeric_fieldtypes
and flt(cell_value) < 0
):
style_cell(row_idx, col_idx, styles["warning"])
elif color := formatting.get("color"):
style_cell(row_idx, col_idx, get_color_style(color))
return builder.result

View File

@@ -3,6 +3,8 @@
frappe.ui.form.on("Financial Report Template", {
refresh(frm) {
if (frm.is_new() || frm.doc.rows.length === 0) return;
// add custom button to view missed accounts
frm.add_custom_button(__("View Account Coverage"), function () {
let selected_rows = frm.get_field("rows").grid.get_selected_children();
@@ -20,7 +22,7 @@ frappe.ui.form.on("Financial Report Template", {
});
},
validate(frm) {
after_save(frm) {
if (!frm.doc.rows || frm.doc.rows.length === 0) {
frappe.msgprint(__("At least one row is required for a financial report template"));
}
@@ -34,14 +36,6 @@ frappe.ui.form.on("Financial Report Row", {
update_formula_label(frm, row.data_source);
update_formula_description(frm, row.data_source);
if (row.data_source !== "Account Data") {
frappe.model.set_value(cdt, cdn, "balance_type", "");
}
if (["Blank Line", "Column Break", "Section Break"].includes(row.data_source)) {
frappe.model.set_value(cdt, cdn, "calculation_formula", "");
}
set_up_filters_editor(frm, cdt, cdn);
},
@@ -322,6 +316,8 @@ function update_formula_description(frm, data_source) {
const list_style = `style="margin-bottom: var(--margin-sm); color: var(--text-muted); font-size: 0.9em;"`;
const note_style = `style="margin-bottom: 0; color: var(--text-muted); font-size: 0.9em;"`;
const tip_style = `style="margin-bottom: 0; color: var(--text-color); font-size: 0.85em;"`;
const code_style = `style="background: var(--bg-light-gray); padding: var(--padding-xs); border-radius: var(--border-radius); font-size: 0.85em; width: max-content; margin-bottom: var(--margin-sm);"`;
const pre_style = `style="margin: 0; border-radius: var(--border-radius)"`;
let description_html = "";
@@ -382,8 +378,13 @@ function update_formula_description(frm, data_source) {
<li><code>my_app.financial_reports.get_kpi_data</code></li>
</ul>
<h6 ${subtitle_style}>Method Signature:</h6>
<div ${code_style}>
<pre ${pre_style}>def get_custom_data(filters, periods, row): <br>&nbsp; # filters: dict — report filters (company, period, etc.) <br>&nbsp; # periods: list[dict] — period definitions <br>&nbsp; # row: dict — the current report row <br><br>&nbsp; return [1000.0, 1200.0, 1150.0] # one value per period</pre>
</div>
<h6 ${subtitle_style}>Return Format:</h6>
<p ${text_style}>Numbers for each period: <code>[1000.0, 1200.0, 1150.0]</code></p>
<p ${text_style}>A list of numbers, one for each period: <code>[1000.0, 1200.0, 1150.0]</code></p>
</div>`;
} else if (data_source === "Blank Line") {
description_html = `

View File

@@ -1,6 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:template_name",
"creation": "2025-08-02 04:44:15.184541",
"doctype": "DocType",
@@ -31,7 +30,8 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Report Type",
"options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement"
"options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement",
"reqd": 1
},
{
"depends_on": "eval:frappe.boot.developer_mode",
@@ -66,7 +66,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-11-14 00:11:03.508139",
"modified": "2026-02-23 01:04:05.797161",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Financial Report Template",

View File

@@ -31,6 +31,19 @@ class FinancialReportTemplate(Document):
template_name: DF.Data
# end: auto-generated types
def before_validate(self):
self.clear_hidden_fields()
def clear_hidden_fields(self):
style_data_sources = {"Blank Line", "Column Break", "Section Break"}
for row in self.rows:
if row.data_source != "Account Data":
row.balance_type = None
if row.data_source in style_data_sources:
row.calculation_formula = None
def validate(self):
validator = TemplateValidator(self)
result = validator.validate()

View File

@@ -67,8 +67,8 @@ class ValidationResult:
self.warnings.append(issue)
def notify_user(self) -> None:
warnings = "<br><br>".join(str(w) for w in self.warnings)
errors = "<br><br>".join(str(e) for e in self.issues)
warnings = "<br><br>".join(str(w) for w in self.warnings if w)
errors = "<br><br>".join(str(e) for e in self.issues if e)
if warnings:
frappe.msgprint(warnings, title=_("Warnings"), indicator="orange")
@@ -96,9 +96,8 @@ class TemplateValidator:
result.merge(validator.validate(self.template))
# Run row-level validations
account_fields = {field.fieldname for field in frappe.get_meta("Account").fields}
for row in self.template.rows:
result.merge(self.formula_validator.validate(row, account_fields))
result.merge(self.formula_validator.validate(row))
return result
@@ -380,7 +379,8 @@ class AccountFilterValidator(Validator):
"""Validates account filter expressions used in Account Data rows"""
def __init__(self, account_fields: set | None = None):
self.account_fields = account_fields or set(frappe.get_meta("Account")._valid_columns)
self.account_meta = frappe.get_meta("Account")
self.account_fields = account_fields or set(self.account_meta._valid_columns)
def validate(self, row) -> ValidationResult:
result = ValidationResult()
@@ -400,7 +400,11 @@ class AccountFilterValidator(Validator):
try:
filter_config = json.loads(row.calculation_formula)
error = self._validate_filter_structure(filter_config, self.account_fields)
error = self._validate_filter_structure(
filter_config,
self.account_fields,
row.advanced_filtering,
)
if error:
result.add_error(
@@ -422,7 +426,12 @@ class AccountFilterValidator(Validator):
return result
def _validate_filter_structure(self, filter_config, account_fields: set) -> str | None:
def _validate_filter_structure(
self,
filter_config,
account_fields: set,
advanced_filtering: bool = False,
) -> str | None:
# simple condition: [field, operator, value]
if isinstance(filter_config, list):
if len(filter_config) != 3:
@@ -433,8 +442,10 @@ class AccountFilterValidator(Validator):
if not isinstance(field, str) or not isinstance(operator, str):
return "Field and operator must be strings"
display = (field if advanced_filtering else self.account_meta.get_label(field)) or field
if field not in account_fields:
return f"Field '{field}' is not a valid account field"
return f"Field '{display}' is not a valid Account field"
if operator.casefold() not in OPERATOR_MAP:
return f"Invalid operator '{operator}'"
@@ -457,7 +468,7 @@ class AccountFilterValidator(Validator):
# recursive
for condition in conditions:
error = self._validate_filter_structure(condition, account_fields)
error = self._validate_filter_structure(condition, account_fields, advanced_filtering)
if error:
return error
else:
@@ -473,7 +484,7 @@ class FormulaValidator(Validator):
self.calculation_validator = CalculationFormulaValidator(reference_codes)
self.account_filter_validator = AccountFilterValidator()
def validate(self, row, account_fields: set) -> ValidationResult:
def validate(self, row) -> ValidationResult:
result = ValidationResult()
if not row.calculation_formula:
@@ -483,9 +494,6 @@ class FormulaValidator(Validator):
return self.calculation_validator.validate(row)
elif row.data_source == "Account Data":
# Update account fields if provided
if account_fields:
self.account_filter_validator.account_fields = account_fields
return self.account_filter_validator.validate(row)
elif row.data_source == "Custom API":

View File

@@ -8,6 +8,7 @@ from erpnext.accounts.doctype.financial_report_template.financial_report_engine
DependencyResolver,
FilterExpressionParser,
FinancialQueryBuilder,
FinancialReportEngine,
FormulaCalculator,
)
from erpnext.accounts.doctype.financial_report_template.test_financial_report_template import (
@@ -1292,6 +1293,7 @@ class TestFilterExpressionParser(FinancialReportTemplateTestCase):
self.data_source = "Account Data"
self.idx = 1
self.reverse_sign = 0
self.advanced_filtering = True
return MockReportRow(formula, reference_code)
@@ -1948,6 +1950,159 @@ class TestFinancialQueryBuilder(FinancialReportTemplateTestCase):
jv_2023.cancel()
def test_opening_entries_roll_into_opening_after_period_closing(self):
"""
Sequence:
1. is_opening JV of 3000 in current year (FY 2024)
2. is_opening JV of 5000 in next year (FY 2025)
3. Period Closing Voucher for previous year (FY 2023)
Expected (BS report for FY 2024):
opening of FY 2024 = 3000 + 5000 = 8000
(all is_opening entries roll into opening irrespective of fiscal year,
on top of the PCV carry-forward — here PCV closing for cash is 0).
"""
company = "_Test Company"
cash_account = "_Test Cash - _TC"
# Opening JVs cannot post against P&L accounts; use a Balance Sheet offset.
opening_offset_account = "Temporary Opening - _TC"
pcv = None
jv_current_year = None
jv_next_year = None
original_pcv_setting = frappe.db.get_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv"
)
try:
# Step 1: opening JV in current year (FY 2024) — must be posted before PCV
# exists, else `validate_against_pcv` rejects it.
jv_current_year = make_journal_entry(
account1=cash_account,
account2=opening_offset_account,
amount=3000,
posting_date="2024-06-15",
company=company,
save=False,
)
jv_current_year.is_opening = "Yes"
jv_current_year.insert()
jv_current_year.submit()
# Step 2: opening JV in next year (FY 2025)
jv_next_year = make_journal_entry(
account1=cash_account,
account2=opening_offset_account,
amount=5000,
posting_date="2025-06-15",
company=company,
save=False,
)
jv_next_year.is_opening = "Yes"
jv_next_year.insert()
jv_next_year.submit()
# Step 3: book Period Closing Voucher for previous year (FY 2023)
closing_account = frappe.db.get_value(
"Account",
{
"company": company,
"root_type": "Liability",
"is_group": 0,
"account_type": ["not in", ["Payable", "Receivable"]],
},
"name",
)
fy_2023 = get_fiscal_year("2023-06-15", company=company)
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": "2023-12-31",
"period_start_date": fy_2023[1],
"period_end_date": fy_2023[2],
"company": company,
"fiscal_year": fy_2023[0],
"cost_center": "_Test Cost Center - _TC",
"closing_account_head": closing_account,
"remarks": "Test Period Closing",
}
)
pcv.insert()
pcv.submit()
pcv.reload()
# Run BS report for FY 2024
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-01-01",
"period_end_date": "2024-12-31",
"filter_based_on": "Date Range",
"periodicity": "Yearly",
"ignore_closing_entries": True,
}
periods = [{"key": "2024", "from_date": "2024-01-01", "to_date": "2024-12-31"}]
query_builder = FinancialQueryBuilder(filters, periods)
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
frappe._dict(
{
"name": opening_offset_account,
"account_name": "Temporary Opening",
"account_number": "1900",
}
),
]
balances_data = query_builder.fetch_account_balances(accounts)
cash_data = balances_data.get(cash_account)
offset_data = balances_data.get(opening_offset_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
self.assertIsNotNone(offset_data, "Offset account should exist in results")
year_2024_cash = cash_data.get_period("2024")
year_2024_offset = offset_data.get_period("2024")
self.assertIsNotNone(year_2024_cash, "FY 2024 period should exist for cash")
self.assertIsNotNone(year_2024_offset, "FY 2024 period should exist for offset")
# All is_opening JVs (current + next year) roll into FY 2024 opening
self.assertEqual(
year_2024_cash.opening,
8000.0,
"FY 2024 cash opening must combine is_opening JVs from current and next year",
)
self.assertEqual(
year_2024_offset.opening,
-8000.0,
"FY 2024 offset opening must combine is_opening JVs from current and next year",
)
self.assertEqual(
year_2024_cash.movement, 0.0, "Opening JVs must not be counted as period movement"
)
self.assertEqual(year_2024_cash.closing, 8000.0, "Closing = opening when no non-opening movement")
finally:
frappe.db.set_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv", original_pcv_setting or 0
)
if pcv:
pcv.reload()
if pcv.docstatus == 1:
pcv.cancel()
if jv_next_year and jv_next_year.docstatus == 1:
jv_next_year.cancel()
if jv_current_year and jv_current_year.docstatus == 1:
jv_current_year.cancel()
def test_account_with_gl_entries_but_no_prior_closing_balance(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
@@ -2021,3 +2176,210 @@ class TestFinancialQueryBuilder(FinancialReportTemplateTestCase):
finally:
jv.cancel()
def test_pl_pcv_exclusion_and_growth_view_year_over_year(self):
"""
Sequence:
1. Expense JV 2000 in FY 2024, PCV for FY 2024
→ assert FY 2024 movement = 2000 via FinancialQueryBuilder
2. Expense JV 3000 in FY 2025, PCV for FY 2025
3. Run FinancialReportEngine with selected_view="Growth"
→ assert col_2024 = 2000 (raw), col_2025 = 50.0 (% growth)
"""
company = "_Test Company"
expense_account = "Administrative Expenses - _TC"
bank_account = "_Test Bank - _TC"
template = None
pcv_2024 = None
pcv_2025 = None
jv_2024 = None
jv_2025 = None
original_pcv_setting = frappe.db.get_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv"
)
try:
closing_account = frappe.db.get_value(
"Account",
{
"company": company,
"root_type": "Liability",
"is_group": 0,
"account_type": ["not in", ["Payable", "Receivable"]],
},
"name",
)
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
accounts = [
frappe._dict(
{
"name": expense_account,
"account_name": "Administrative Expenses",
"account_number": "5001",
}
),
]
# --- Step 1: FY 2024 expense + PCV, assert PCV reversal excluded ---
jv_2024 = make_journal_entry(
account1=expense_account,
account2=bank_account,
amount=2000,
posting_date="2024-06-15",
company=company,
submit=True,
)
fy_2024 = get_fiscal_year("2024-06-15", company=company)
pcv_2024 = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": "2024-12-31",
"period_start_date": fy_2024[1],
"period_end_date": fy_2024[2],
"company": company,
"fiscal_year": fy_2024[0],
"cost_center": "_Test Cost Center - _TC",
"closing_account_head": closing_account,
"remarks": "Test PCV FY 2024",
}
)
pcv_2024.insert()
pcv_2024.submit()
pcv_2024.reload()
builder_2024 = FinancialQueryBuilder(
{
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-01-01",
"period_end_date": "2024-12-31",
"filter_based_on": "Date Range",
"periodicity": "Yearly",
},
[{"key": "2024", "from_date": "2024-01-01", "to_date": "2024-12-31"}],
)
data_2024 = builder_2024.fetch_account_balances(accounts)
expense_2024 = data_2024.get(expense_account)
self.assertIsNotNone(expense_2024, "Expense account must appear in FY 2024 results")
year_2024 = expense_2024.get_period("2024")
self.assertEqual(
year_2024.movement,
2000.0,
"FY 2024 expense movement must equal real expense (PCV reversal excluded)",
)
# --- Step 2: FY 2025 expense + PCV ---
jv_2025 = make_journal_entry(
account1=expense_account,
account2=bank_account,
amount=3000,
posting_date="2025-06-15",
company=company,
submit=True,
)
fy_2025 = get_fiscal_year("2025-06-15", company=company)
pcv_2025 = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": "2025-12-31",
"period_start_date": fy_2025[1],
"period_end_date": fy_2025[2],
"company": company,
"fiscal_year": fy_2025[0],
"cost_center": "_Test Cost Center - _TC",
"closing_account_head": closing_account,
"remarks": "Test PCV FY 2025",
}
)
pcv_2025.insert()
pcv_2025.submit()
pcv_2025.reload()
# --- Step 3: full pipeline with Growth view across both years ---
template_name = f"Test Growth Template {frappe.generate_hash()[:8]}"
template = frappe.get_doc(
{
"doctype": "Financial Report Template",
"template_name": template_name,
"report_type": "Profit and Loss Statement",
"rows": [
{
"reference_code": "EXP_ADMIN",
"display_name": "Administrative Expenses",
"indentation_level": 0,
"data_source": "Account Data",
"balance_type": "Closing Balance",
"calculation_formula": f'["name", "=", "{expense_account}"]',
},
],
}
)
template.insert()
filters = frappe._dict(
{
"company": company,
"report_template": template_name,
"from_fiscal_year": fy_2024[0],
"to_fiscal_year": fy_2025[0],
"period_start_date": "2024-01-01",
"period_end_date": "2025-12-31",
"filter_based_on": "Date Range",
"periodicity": "Yearly",
"accumulated_values": 0,
"selected_view": "Growth",
}
)
_columns, formatted_data, _msg, _chart = FinancialReportEngine().execute(filters)
expense_row = next(
(row for row in formatted_data if row.get("account_name") == "Administrative Expenses"),
None,
)
self.assertIsNotNone(expense_row, "Administrative Expenses row must appear in growth view")
period_keys = expense_row.get("_segment_info", {}).get("period_keys", [])
self.assertEqual(len(period_keys), 2, "Yearly view must yield exactly two periods")
first_period_key, second_period_key = period_keys
# First column: raw absolute value (FY 2024 expense)
self.assertEqual(
flt(expense_row[first_period_key]),
2000.0,
"First column in growth view must keep raw FY 2024 expense value",
)
# Second column: ((3000 - 2000) / 2000) * 100 = 50.0
self.assertEqual(
flt(expense_row[second_period_key]),
50.0,
"Second column must be % growth FY 2024 → FY 2025",
)
finally:
frappe.db.set_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv", original_pcv_setting or 0
)
if pcv_2025:
pcv_2025.reload()
if pcv_2025.docstatus == 1:
pcv_2025.cancel()
if jv_2025 and jv_2025.docstatus == 1:
jv_2025.cancel()
if pcv_2024:
pcv_2024.reload()
if pcv_2024.docstatus == 1:
pcv_2024.cancel()
if jv_2024 and jv_2024.docstatus == 1:
jv_2024.cancel()
if template and frappe.db.exists("Financial Report Template", template.name):
frappe.delete_doc("Financial Report Template", template.name, force=1)

View File

@@ -489,4 +489,5 @@ def rename_temporarily_named_docs(doctype):
for hook in frappe.get_hooks(hook_type):
frappe.call(hook, newname=newname, oldname=oldname)
frappe.db.commit()
if not frappe.in_test:
frappe.db.commit()

View File

@@ -47,3 +47,12 @@ frappe.ui.form.on("Item Tax Template", {
});
},
});
frappe.ui.form.on("Item Tax Template Detail", {
not_applicable: function (frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.not_applicable) {
frappe.model.set_value(cdt, cdn, "tax_rate", 0);
}
},
});

View File

@@ -27,8 +27,15 @@ class ItemTaxTemplate(Document):
# end: auto-generated types
def validate(self):
self.set_zero_rate_for_not_applicable_tax()
self.validate_tax_accounts()
def set_zero_rate_for_not_applicable_tax(self):
"""Ensure tax_rate is 0 for any row marked as not applicable."""
for row in self.get("taxes"):
if row.not_applicable:
row.tax_rate = 0
def autoname(self):
if self.company and self.title:
abbr = frappe.get_cached_value("Company", self.company, "abbr")

View File

@@ -6,7 +6,8 @@
"engine": "InnoDB",
"field_order": [
"tax_type",
"tax_rate"
"tax_rate",
"not_applicable"
],
"fields": [
{
@@ -21,20 +22,30 @@
"fieldname": "tax_rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Tax Rate"
"label": "Tax Rate",
"read_only_depends_on": "eval:doc.not_applicable"
},
{
"default": "0",
"description": "Check if this tax is not applicable to items (distinct from 0% rate)",
"fieldname": "not_applicable",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Not Applicable"
}
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:55.735360",
"modified": "2025-12-26 17:19:18.791891",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template Detail",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -14,6 +14,7 @@ class ItemTaxTemplateDetail(Document):
if TYPE_CHECKING:
from frappe.types import DF
not_applicable: DF.Check
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data

View File

@@ -648,7 +648,7 @@ $.extend(erpnext.journal_entry, {
reqd: 1,
default: frm.doc.posting_date,
},
{ fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark") },
{ fieldtype: "Small Text", fieldname: "remark", label: __("Remark") },
{
fieldtype: "Select",
fieldname: "naming_series",
@@ -665,8 +665,14 @@ $.extend(erpnext.journal_entry, {
var values = dialog.get_values();
frm.set_value("posting_date", values.posting_date);
frm.set_value("user_remark", values.user_remark);
frm.set_value("naming_series", values.naming_series);
if (values.remark) {
frm.set_value("custom_remark", 1);
frm.set_value("remark", values.remark);
} else {
frm.set_value("custom_remark", 0);
frm.set_value("remark", "");
}
// clear table is used because there might've been an error while adding child
// and cleanup didn't happen

View File

@@ -39,7 +39,7 @@
"clearance_date",
"column_break_oizh",
"user_remark",
"subscription_section",
"auto_repeat_section",
"auto_repeat",
"tax_withholding_tab",
"section_tax_withholding_entry",
@@ -78,6 +78,7 @@
"from_template",
"title",
"column_break3",
"custom_remark",
"remark",
"mode_of_payment",
"party_not_required"
@@ -202,6 +203,7 @@
{
"fieldname": "user_remark",
"fieldtype": "Small Text",
"hidden": 1,
"label": "User Remark",
"no_copy": 1,
"oldfieldname": "user_remark",
@@ -315,7 +317,7 @@
"no_copy": 1,
"oldfieldname": "remark",
"oldfieldtype": "Small Text",
"read_only": 1
"read_only_depends_on": "eval: !doc.custom_remark"
},
{
"depends_on": "eval:doc.voucher_type== \"Inter Company Journal Entry\"",
@@ -475,11 +477,6 @@
"options": "Stock Entry",
"read_only": 1
},
{
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription"
},
{
"allow_on_submit": 1,
"fieldname": "auto_repeat",
@@ -651,6 +648,17 @@
"fieldname": "tax_withholding_tab",
"fieldtype": "Tab Break",
"label": "Tax Withholding"
},
{
"fieldname": "auto_repeat_section",
"fieldtype": "Section Break",
"label": "Auto Repeat"
},
{
"default": "0",
"fieldname": "custom_remark",
"fieldtype": "Check",
"label": "Custom Remark"
}
],
"icon": "fa fa-file-text",
@@ -665,7 +673,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2026-03-09 17:15:26.569327",
"modified": "2026-04-08 14:19:30.870894",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -62,6 +62,7 @@ class JournalEntry(AccountsController):
cheque_no: DF.Data | None
clearance_date: DF.Date | None
company: DF.Link
custom_remark: DF.Check
difference: DF.Currency
due_date: DF.Date | None
finance_book: DF.Link | None
@@ -354,8 +355,11 @@ class JournalEntry(AccountsController):
frappe.throw(_("Account {0} should be of type Expense").format(d.account))
def validate_stock_accounts(self):
if self.voucher_type == "Periodic Accounting Entry":
# Skip validation for periodic accounting entry
if (
not erpnext.is_perpetual_inventory_enabled(self.company)
or self.voucher_type == "Periodic Accounting Entry"
):
# Skip validation for periodic accounting entry and Perpetual Inventory Disabled Company.
return
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
@@ -1024,8 +1028,8 @@ class JournalEntry(AccountsController):
if self.flags.skip_remarks_creation:
return
if self.user_remark:
r.append(_("Note: {0}").format(self.user_remark))
if self.get("custom_remark"):
return
if self.cheque_no:
if self.cheque_date:
@@ -1550,35 +1554,42 @@ def get_payment_entry(ref_doc, args):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_against_jv(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_against_jv(
doctype: str,
txt: str,
searchfield: str,
start: int,
page_len: int,
filters: dict,
):
if not frappe.db.has_column("Journal Entry", searchfield):
return []
return frappe.db.sql(
f"""
SELECT jv.name, jv.posting_date, jv.user_remark
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name
AND jv_detail.account = %(account)s
AND IFNULL(jv_detail.party, '') = %(party)s
AND (
jv_detail.reference_type IS NULL
OR jv_detail.reference_type = ''
)
AND jv.docstatus = 1
AND jv.`{searchfield}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(limit)s offset %(offset)s
""",
dict(
account=filters.get("account"),
party=cstr(filters.get("party")),
txt=f"%{txt}%",
offset=start,
limit=page_len,
),
JournalEntry = frappe.qb.DocType("Journal Entry")
JournalEntryAccount = frappe.qb.DocType("Journal Entry Account")
query = (
frappe.qb.from_(JournalEntry)
.join(JournalEntryAccount)
.on(JournalEntryAccount.parent == JournalEntry.name)
.select(JournalEntry.name, JournalEntry.posting_date, JournalEntry.remark)
.where(JournalEntryAccount.account == filters.get("account"))
.where(JournalEntryAccount.reference_type.isnull() | (JournalEntryAccount.reference_type == ""))
.where(JournalEntry.docstatus == 1)
.where(JournalEntry[searchfield].like(f"%{txt}%"))
.orderby(JournalEntry.name, order=frappe.qb.desc)
.limit(page_len)
.offset(start)
)
party = filters.get("party")
if party:
query = query.where(JournalEntryAccount.party == party)
else:
query = query.where(JournalEntryAccount.party.isnull() | (JournalEntryAccount.party == ""))
return query.run()
@frappe.whitelist()
def get_outstanding(args: str | dict):

View File

@@ -1,5 +1,5 @@
frappe.listview_settings["Journal Entry"] = {
add_fields: ["voucher_type", "posting_date", "total_debit", "company", "user_remark"],
add_fields: ["voucher_type", "posting_date", "total_debit", "company", "remark"],
get_indicator: function (doc) {
if (doc.docstatus === 1) {
return [__(doc.voucher_type), "blue", `voucher_type,=,${doc.voucher_type}`];

View File

@@ -413,9 +413,9 @@ class TestJournalEntry(ERPNextTestSuite):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
# Configure Repost Accounting Ledger for JVs
settings = frappe.get_doc("Repost Accounting Ledger Settings")
if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]:
settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True})
settings = frappe.get_doc("Accounts Settings")
if "Journal Entry" not in [x.document_type for x in settings.repost_allowed_types]:
settings.append("repost_allowed_types", {"document_type": "Journal Entry"})
settings.save()
# Create JV with defaut cost center - _Test Cost Center
@@ -523,7 +523,7 @@ class TestJournalEntry(ERPNextTestSuite):
jv = frappe.new_doc("Journal Entry")
jv.posting_date = nowdate()
jv.company = "_Test Company"
jv.user_remark = "test"
jv.remark = "test"
jv.extend(
"accounts",
[
@@ -592,6 +592,14 @@ class TestJournalEntry(ERPNextTestSuite):
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
def test_custom_remark(self):
# When custom_remark is enabled, remark should not be auto-overwritten on save
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
jv.custom_remark = 1
jv.remark = "My custom remark text"
jv.insert()
self.assertEqual(jv.remark, "My custom remark text")
def test_credit_limit_for_customer(self):
customer = make_customer("_Test New Customer")
set_credit_limit("_Test New Customer", "_Test Company", 50)
@@ -620,7 +628,7 @@ def make_journal_entry(
jv = frappe.new_doc("Journal Entry")
jv.posting_date = posting_date or nowdate()
jv.company = company or "_Test Company"
jv.user_remark = "test"
jv.remark = "test"
jv.multi_currency = 1
jv.set(
"accounts",

View File

@@ -10,7 +10,7 @@ from erpnext.accounts.utils import run_ledger_health_checks
from erpnext.tests.utils import ERPNextTestSuite
class TestLedgerHealth(AccountsTestMixin, ERPNextTestSuite):
class TestLedgerHealth(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_customer()

View File

@@ -71,14 +71,16 @@ def start_merge(docname):
ledger_merge.account,
)
row.db_set("merged", 1)
frappe.db.commit()
if not frappe.in_test:
frappe.db.commit()
successful_merges += 1
frappe.publish_realtime(
"ledger_merge_progress",
{"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total},
)
except Exception:
frappe.db.rollback()
if not frappe.in_test:
frappe.db.rollback()
ledger_merge.log_error("Ledger merge failed")
finally:
if successful_merges == total:

View File

@@ -48,7 +48,7 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-08-16 19:22:42.942264",
"modified": "2026-04-14 18:16:47.795986",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Mode of Payment",
@@ -68,12 +68,21 @@
"read": 1,
"report": 1,
"role": "Accounts User"
},
{
"role": "HR User",
"select": 1
},
{
"role": "HR Manager",
"select": 1
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"translated_doctype": 1
}
}

View File

@@ -70,9 +70,7 @@ frappe.ui.form.on("Opening Invoice Creation Tool", {
});
});
if (frm.doc.create_missing_party) {
frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
}
frm.trigger("update_party_labels");
},
setup_company_filters: function (frm) {
@@ -127,7 +125,9 @@ frappe.ui.form.on("Opening Invoice Creation Tool", {
frappe.model.set_value(row.doctype, row.name, "party", "");
frappe.model.set_value(row.doctype, row.name, "party_name", "");
});
frm.clear_table("invoices");
frm.refresh_fields();
frm.trigger("update_party_labels");
},
make_dashboard: function (frm) {
@@ -175,6 +175,32 @@ frappe.ui.form.on("Opening Invoice Creation Tool", {
}
frm.refresh_field("invoices");
},
update_party_labels: function (frm) {
let is_sales = frm.doc.invoice_type == "Sales";
frm.fields_dict["invoices"].grid.update_docfield_property(
"party",
"label",
is_sales ? "Customer ID" : "Supplier ID"
);
frm.fields_dict["invoices"].grid.update_docfield_property(
"party_name",
"label",
is_sales ? "Customer Name" : "Supplier Name"
);
frm.set_df_property(
"create_missing_party",
"description",
is_sales
? __("If party does not exist, create it using the Customer Name field.")
: __("If party does not exist, create it using the Supplier Name field.")
);
frm.refresh_field("invoices");
frm.refresh_field("create_missing_party");
},
});
frappe.ui.form.on("Opening Invoice Creation Tool Item", {

View File

@@ -7,10 +7,11 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section_break_ynel",
"company",
"create_missing_party",
"column_break_3",
"invoice_type",
"create_missing_party",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -25,11 +26,11 @@
"in_list_view": 1,
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
"default": "0",
"description": "If party does not exist, create it using the Party Name field.",
"fieldname": "create_missing_party",
"fieldtype": "Check",
"label": "Create Missing Party"
@@ -79,12 +80,17 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_ynel",
"fieldtype": "Section Break",
"hide_border": 1
}
],
"hide_toolbar": 1,
"issingle": 1,
"links": [],
"modified": "2026-03-23 00:32:15.600086",
"modified": "2026-03-31 01:47:20.360352",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Opening Invoice Creation Tool",

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _, scrub
from frappe.model.document import Document
from frappe.utils import flt, nowdate
from frappe.utils import escape_html, flt, nowdate
from frappe.utils.background_jobs import enqueue, is_job_enqueued
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -86,6 +86,11 @@ class OpeningInvoiceCreationTool(Document):
)
prepare_invoice_summary(doctype, invoices)
invoices_summary_companies = list(invoices_summary.keys())
for company in invoices_summary_companies:
invoices_summary[escape_html(company)] = invoices_summary.pop(company)
return invoices_summary, max_count
def validate_company(self):
@@ -274,7 +279,8 @@ def start_import(invoices):
doc.flags.ignore_mandatory = True
doc.insert(set_name=invoice_number)
doc.submit()
frappe.db.commit()
if not frappe.in_test:
frappe.db.commit()
names.append(doc.name)
except Exception:
errors += 1

View File

@@ -3,9 +3,6 @@
import frappe
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
)
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account,
)
@@ -13,11 +10,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestOpeningInvoiceCreationTool(ERPNextTestSuite):
def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
create_dimension()
def make_invoices(
self,
invoice_type="Sales",
@@ -182,26 +174,13 @@ def get_opening_invoice_creation_dict(**args):
return invoice_dict
def make_company():
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
return frappe.get_doc("Company", "_Test Opening Invoice Company")
company = frappe.new_doc("Company")
company.company_name = "_Test Opening Invoice Company"
company.abbr = "_TOIC"
company.default_currency = "INR"
company.country = "Pakistan"
company.insert()
return company
def make_customer(customer=None):
customer_name = customer or "Opening Customer"
customer = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": customer_name,
"customer_group": "All Customer Groups",
"customer_group": "Individual",
"customer_type": "Company",
"territory": "All Territories",
}

View File

@@ -89,6 +89,7 @@
"remarks",
"base_in_words",
"is_opening",
"title",
"column_break_16",
"letter_head",
"print_heading",
@@ -96,10 +97,9 @@
"bank_account_no",
"payment_order",
"in_words",
"subscription_section",
"auto_repeat",
"amended_from",
"title"
"auto_repeat_section",
"auto_repeat"
],
"fields": [
{
@@ -503,11 +503,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section"
},
{
"allow_on_submit": 1,
"fieldname": "auto_repeat",
@@ -781,6 +776,11 @@
"fieldname": "override_tax_withholding_entries",
"fieldtype": "Check",
"label": "Edit Tax Withholding Entries"
},
{
"fieldname": "auto_repeat_section",
"fieldtype": "Section Break",
"label": "Auto Repeat"
}
],
"grid_page_length": 50,

View File

@@ -2308,22 +2308,20 @@ def get_outstanding_reference_documents(args: str | dict, validate: bool = False
# Get positive outstanding sales /purchase invoices
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={} and voucher_no={}".format(
frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
)
condition = f" and voucher_type={frappe.db.escape(args['voucher_type'])} and voucher_no={frappe.db.escape(args['voucher_no'])}"
common_filter.append(ple.voucher_type == args["voucher_type"])
common_filter.append(ple.voucher_no == args["voucher_no"])
# Add cost center condition
if args.get("cost_center"):
condition += " and cost_center='%s'" % args.get("cost_center")
condition += f" and cost_center={frappe.db.escape(args.get('cost_center'))}"
accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center"))
# dynamic dimension filters
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if args.get(dim.fieldname):
condition += f" and {dim.fieldname}='{args.get(dim.fieldname)}'"
condition += f" and {dim.fieldname}={frappe.db.escape(args.get(dim.fieldname))}"
accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
date_fields_dict = {
@@ -2332,18 +2330,19 @@ def get_outstanding_reference_documents(args: str | dict, validate: bool = False
}
for fieldname, date_fields in date_fields_dict.items():
from_date = frappe.db.escape(str(args.get(date_fields[0]))) if args.get(date_fields[0]) else None
to_date = frappe.db.escape(str(args.get(date_fields[1]))) if args.get(date_fields[1]) else None
if args.get(date_fields[0]) and args.get(date_fields[1]):
condition += " and {} between '{}' and '{}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
condition += f" and {fieldname} between {from_date} and {to_date}"
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
elif args.get(date_fields[0]):
# if only from date is supplied
condition += f" and {fieldname} >= '{args.get(date_fields[0])}'"
condition += f" and {fieldname} >= {from_date}"
posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
elif args.get(date_fields[1]):
# if only to date is supplied
condition += f" and {fieldname} <= '{args.get(date_fields[1])}'"
condition += f" and {fieldname} <= {to_date}"
posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
if args.get("company"):
@@ -2563,7 +2562,7 @@ def get_orders_to_be_billed(
active_dimensions = get_dimensions(True)[0]
for dim in active_dimensions:
if filters.get(dim.fieldname):
condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'"
condition += f" and {dim.fieldname}={frappe.db.escape(filters.get(dim.fieldname))}"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"

View File

@@ -1,4 +1,20 @@
frappe.listview_settings["Payment Entry"] = {
add_fields: ["unallocated_amount", "docstatus"],
get_indicator: function (doc) {
if (doc.docstatus === 2) {
return [__("Cancelled"), "red", "docstatus,=,2"];
}
if (doc.docstatus === 0) {
return [__("Draft"), "orange", "docstatus,=,0"];
}
if (flt(doc.unallocated_amount) > 0) {
return [__("Unreconciled"), "orange", "docstatus,=,1|unallocated_amount,>,0"];
}
return [__("Reconciled"), "green", "docstatus,=,1|unallocated_amount,=,0"];
},
onload: function (listview) {
if (listview.page.fields_dict.party_type) {
listview.page.fields_dict.party_type.get_query = function () {

View File

@@ -195,6 +195,30 @@ class TestPaymentEntry(ERPNextTestSuite):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 100)
def test_reference_outstanding_amount_on_advance_pull(self):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
so = make_sales_order(qty=1, rate=1000)
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Cash - _TC")
pe.paid_amount = pe.received_amount = 500
pe.references[0].allocated_amount = 500
pe.insert()
pe.submit()
so.reload()
self.assertEqual(so.advance_paid, 500)
si = make_sales_invoice(so.name)
si.allocate_advances_automatically = 1
si.save()
self.assertEqual(si.get("advances")[0].allocated_amount, 500)
self.assertEqual(si.get("advances")[0].reference_name, pe.name)
si.submit()
pe.load_from_db()
self.assertEqual(pe.references[0].reference_name, si.name)
self.assertEqual(pe.references[0].outstanding_amount, si.outstanding_amount)
def test_payment_entry_against_pi(self):
pi = make_purchase_invoice(
supplier="_Test Supplier USD",
@@ -2105,6 +2129,37 @@ class TestPaymentEntry(ERPNextTestSuite):
self.assertEqual(ref.voucher_no, so.name)
self.assertIsNotNone(ref.payment_term)
def test_project_name_in_exchange_gain_loss_entry(self):
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
)
from erpnext.projects.doctype.project.test_project import make_project
si.project = make_project({"project_name": "_Test Project for Exchange Gain Loss Entry"}).name
si.submit()
pe = get_payment_entry("Sales Invoice", si.name)
pe.source_exchange_rate = 100
pe.insert()
pe.submit()
rows = frappe.get_all(
"Journal Entry Account",
or_filters=[{"reference_name": pe.name}, {"reference_name": si.name}],
fields=["project"],
)
self.assertEqual(len(rows), 2)
self.assertEqual(rows[0].project, si.project)
self.assertEqual(rows[1].project, si.project)
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -183,7 +183,7 @@
"depends_on": "eval:doc.is_a_subscription",
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section"
"label": "Subscription"
},
{
"fieldname": "subscription_plans",
@@ -478,7 +478,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-01-13 12:53:00.963274",
"modified": "2026-02-27 19:11:03.308896",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",

View File

@@ -102,8 +102,8 @@ class PaymentRequest(Document):
subscription_plans: DF.Table[SubscriptionPlanDetail]
swift_number: DF.ReadOnly | None
transaction_date: DF.Date | None
# end: auto-generated types
def on_discard(self):
self.db_set("status", "Cancelled")
@@ -750,7 +750,8 @@ def make_payment_request(**args):
pr.submit()
if args.order_type == "Shopping Cart":
frappe.db.commit()
if not frappe.in_test:
frappe.db.commit()
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = pr.get_payment_url()

View File

@@ -46,8 +46,8 @@ frappe.ui.form.on("Period Closing Voucher", {
function () {
frappe.route_options = {
voucher_no: frm.doc.name,
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
from_date: frm.doc.period_start_date,
to_date: frm.doc.period_end_date,
company: frm.doc.company,
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,

View File

@@ -17,9 +17,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
def test_closing_entry(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
@@ -69,9 +66,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.assertEqual(pcv_gle, expected_gle)
def test_cost_center_wise_posting(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
@@ -135,9 +129,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
)
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
@@ -189,9 +180,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.assertSequenceEqual(pcv_gle, expected_gle)
def test_gl_entries_restrictions(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
@@ -212,10 +200,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.assertRaises(frappe.ValidationError, jv1.submit)
def test_closing_balance_with_dimensions_and_test_reposting_entry(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
company = create_company()
cost_center1 = create_cost_center("Test Cost Center 1")
cost_center2 = create_cost_center("Test Cost Center 2")

View File

@@ -3,10 +3,6 @@
import frappe
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
disable_dimension,
)
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
@@ -161,7 +157,6 @@ class TestPOSClosingEntry(ERPNextTestSuite):
test case to check whether we can create POS Closing Entry without mandatory accounting dimension
"""
create_dimension()
location = frappe.get_doc("Accounting Dimension", "Location")
location.dimension_defaults[0].mandatory_for_bs = True
location.save()
@@ -197,7 +192,6 @@ class TestPOSClosingEntry(ERPNextTestSuite):
)
accounting_dimension_department.mandatory_for_bs = 0
accounting_dimension_department.save()
disable_dimension()
def test_merging_into_sales_invoice_for_batched_item(self):
frappe.flags.print_message = False
@@ -206,7 +200,6 @@ class TestPOSClosingEntry(ERPNextTestSuite):
)
from erpnext.stock.doctype.batch.batch import get_batch_qty
frappe.db.sql("delete from `tabPOS Invoice`")
item_doc = make_item(
"_Test Item With Batch FOR POS Merge Test",
properties={

View File

@@ -26,6 +26,8 @@
"due_date",
"amended_from",
"return_against",
"section_break_clmv",
"title",
"accounting_dimensions_section",
"project",
"dimension_col_break",
@@ -187,7 +189,7 @@
"subscription_section",
"from_date",
"to_date",
"column_break_140",
"auto_repeat_section",
"auto_repeat",
"update_auto_repeat_reference",
"against_income_account"
@@ -662,6 +664,7 @@
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
"label": "Total Billing Amount",
"options": "currency",
"print_hide": 1,
"read_only": 1
},
@@ -1462,7 +1465,7 @@
{
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section"
"label": "Subscription"
},
{
"allow_on_submit": 1,
@@ -1480,10 +1483,6 @@
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "column_break_140",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "auto_repeat",
@@ -1533,6 +1532,7 @@
"fieldname": "amount_eligible_for_commission",
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"options": "Company:company:default_currency",
"read_only": 1
},
{
@@ -1619,12 +1619,29 @@
{
"fieldname": "column_break_bhao",
"fieldtype": "Column Break"
},
{
"fieldname": "auto_repeat_section",
"fieldtype": "Section Break",
"label": "Auto Repeat"
},
{
"fieldname": "section_break_clmv",
"fieldtype": "Section Break"
},
{
"allow_on_submit": 1,
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"no_copy": 1,
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2026-02-10 14:23:07.181782",
"modified": "2026-05-01 02:37:30.580568",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -173,6 +173,7 @@ 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
@@ -754,7 +755,7 @@ class POSInvoice(SalesInvoice):
return profile
@frappe.whitelist()
def set_missing_values(self, for_validate: bool = False):
def set_missing_values(self, for_validate: bool | None = False):
profile = self.set_pos_fields(for_validate)
if not self.debit_to:
@@ -1026,7 +1027,7 @@ def get_pos_reserved_qty_from_table(child_table, item_code, warehouse):
@frappe.whitelist()
def make_sales_return(source_name: str, target_doc: Document | None = None):
def make_sales_return(source_name: str, target_doc: Document | str | None = None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return make_return_doc("POS Invoice", source_name, target_doc)

View File

@@ -34,7 +34,6 @@ class POSInvoiceTestMixin(ERPNextTestSuite):
frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0)
frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice")
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`")
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")

View File

@@ -33,7 +33,6 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
consolidate_pos_invoices,
)
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270})
@@ -63,7 +62,6 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
consolidate_pos_invoices,
)
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
@@ -122,7 +120,7 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
item = "Test Selling Price Validation"
make_item(item, {"is_stock_item": 1})
make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300)
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})

View File

@@ -811,6 +811,7 @@
},
{
"default": "0",
"fetch_from": "item_code.grant_commission",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
@@ -857,7 +858,7 @@
],
"istable": 1,
"links": [],
"modified": "2025-11-12 18:11:11.818015",
"modified": "2026-04-20 16:16:12.322024",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",

View File

@@ -15,7 +15,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestPricingRule(ERPNextTestSuite):
def setUp(self):
delete_existing_pricing_rules()
setup_pricing_rule_data()
self.enterClassContext(self.change_settings("Selling Settings", validate_selling_price=0))
@@ -1584,16 +1583,6 @@ def setup_pricing_rule_data():
).insert()
def delete_existing_pricing_rules():
for doctype in [
"Pricing Rule",
"Pricing Rule Item Code",
"Pricing Rule Item Group",
"Pricing Rule Brand",
]:
frappe.db.sql(f"delete from `tab{doctype}`")
def make_item_price(item, price_list_name, item_price):
frappe.get_doc(
{

View File

@@ -661,7 +661,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if pricing_rule.is_recursive:
transaction_qty = sum(
[
row.qty
flt(row.qty)
for row in doc.items
if not row.is_free_item
and row.item_code == args.item_code

View File

@@ -566,10 +566,10 @@ def send_emails(document_name: str, from_scheduler: bool = False, posting_date:
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
doc.add_comment("Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now()))
if doc.report == "General Ledger":
doc.db_set("to_date", new_to_date, commit=True)
doc.db_set("from_date", new_from_date, commit=True)
frappe.db.set_value(doc.doctype, doc.name, "to_date", new_to_date)
frappe.db.set_value(doc.doctype, doc.name, "from_date", new_from_date)
else:
doc.db_set("posting_date", new_to_date, commit=True)
frappe.db.set_value(doc.doctype, doc.name, "posting_date", new_to_date)
return True
else:
return False

View File

@@ -14,7 +14,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.tests.utils import ERPNextTestSuite
class TestProcessStatementOfAccounts(AccountsTestMixin, ERPNextTestSuite):
class TestProcessStatementOfAccounts(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0)
letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey")

View File

@@ -21,10 +21,12 @@ frappe.ui.form.on("Promotional Scheme", {
selling: function (frm) {
frm.trigger("set_options_for_applicable_for");
frm.toggle_enable("buying", !frm.doc.selling);
},
buying: function (frm) {
frm.trigger("set_options_for_applicable_for");
frm.toggle_enable("selling", !frm.doc.buying);
},
set_options_for_applicable_for: function (frm) {

View File

@@ -443,13 +443,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
items_add(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, [
"expense_account",
"discount_account",
"cost_center",
"project",
]);
const row = frappe.get_doc(cdt, cdn);
const field_copy = ["expense_account", "discount_account", "cost_center"];
if (doc.project) {
frappe.model.set_value(cdt, cdn, "project", doc.project);
} else {
field_copy.push("project");
}
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
}
on_submit() {
@@ -558,12 +559,6 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function
};
};
cur_frm.fields_dict["items"].grid.get_field("project").get_query = function (doc, cdt, cdn) {
return {
filters: [["Project", "status", "not in", "Completed, Cancelled"]],
};
};
frappe.ui.form.on("Purchase Invoice", {
setup: function (frm) {
frm.custom_make_buttons = {

View File

@@ -8,7 +8,6 @@
"email_append_to": 1,
"engine": "InnoDB",
"field_order": [
"title",
"naming_series",
"supplier",
"supplier_name",
@@ -28,6 +27,8 @@
"update_billed_amount_in_purchase_receipt",
"apply_tds",
"amended_from",
"section_break_ecfi",
"title",
"supplier_invoice_details",
"bill_no",
"column_break_15",
@@ -181,11 +182,12 @@
"unrealized_profit_loss_account",
"subscription_section",
"subscription",
"auto_repeat",
"update_auto_repeat_reference",
"column_break_114",
"from_date",
"to_date",
"automation_section",
"auto_repeat",
"update_auto_repeat_reference",
"printing_settings",
"letter_head",
"group_same_items",
@@ -211,10 +213,8 @@
"fields": [
{
"allow_on_submit": 1,
"default": "{supplier_name}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"label": "Title",
"no_copy": 1,
"print_hide": 1
@@ -1686,6 +1686,16 @@
"fieldname": "totals_section",
"fieldtype": "Section Break",
"label": "Totals"
},
{
"collapsible": 1,
"fieldname": "automation_section",
"fieldtype": "Section Break",
"label": "Automation"
},
{
"fieldname": "section_break_ecfi",
"fieldtype": "Section Break"
}
],
"grid_page_length": 50,
@@ -1693,7 +1703,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2026-03-17 20:44:00.221219",
"modified": "2026-03-30 12:16:40.157755",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1756,6 +1766,6 @@
"sort_order": "DESC",
"states": [],
"timeline_field": "supplier",
"title_field": "title",
"title_field": "supplier_name",
"track_changes": 1
}

View File

@@ -334,9 +334,6 @@ class PurchaseInvoice(BuyingController):
if self.bill_date:
self.remarks += " " + _("dated {0}").format(formatdate(self.bill_date))
else:
self.remarks = _("No Remarks")
def set_missing_values(self, for_validate=False):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
@@ -618,12 +615,13 @@ class PurchaseInvoice(BuyingController):
frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account)
def po_required(self):
if frappe.db.get_single_value("Buying Settings", "po_required") == "Yes":
if frappe.get_value(
if (
frappe.db.get_single_value("Buying Settings", "po_required") == "Yes"
and not self.is_internal_transfer()
and not frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
):
return
)
):
for d in self.get("items"):
if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
@@ -984,6 +982,10 @@ class PurchaseInvoice(BuyingController):
if provisional_accounting_for_non_stock_items:
self.get_provisional_accounts()
adjust_incoming_rate = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
for item in self.get("items"):
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
if item.item_code:
@@ -1162,7 +1164,11 @@ class PurchaseInvoice(BuyingController):
)
# check if the exchange rate has changed
if item.get("purchase_receipt") and self.auto_accounting_for_stock:
if (
not adjust_incoming_rate
and item.get("purchase_receipt")
and self.auto_accounting_for_stock
):
if (
exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
@@ -1199,6 +1205,7 @@ class PurchaseInvoice(BuyingController):
item=item,
)
)
if (
self.auto_accounting_for_stock
and self.is_opening == "No"

View File

@@ -350,6 +350,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
make_purchase_invoice as create_purchase_invoice,
)
original_value = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
@@ -368,14 +374,19 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
# 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}, "credit"
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
)
discrepancy_caused_by_exchange_rate_diff = abs(
pi.items[0].base_net_amount - pr.items[0].base_net_amount
)
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
frappe.db.set_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value
)
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,
@@ -2189,11 +2200,6 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
def test_offsetting_entries_for_accounting_dimensions(self):
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.report.trial_balance.test_trial_balance import (
clear_dimension_defaults,
create_accounting_dimension,
disable_dimension,
)
create_account(
account_name="Offsetting",
@@ -2201,7 +2207,16 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
parent_account="Temporary Accounts - _TC",
)
create_accounting_dimension(company="_Test Company", offsetting_account="Offsetting - _TC")
dim = frappe.get_doc("Accounting Dimension", "Branch")
dim.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Branch",
"offsetting_account": "Offsetting - _TC",
},
)
dim.save()
branch1 = frappe.new_doc("Branch")
branch1.branch = "Location 1"
@@ -2238,14 +2253,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
voucher_type="Purchase Invoice",
additional_columns=["branch"],
)
clear_dimension_defaults("Branch")
disable_dimension()
def test_repost_accounting_entries(self):
# update repost settings
settings = frappe.get_doc("Repost Accounting Ledger Settings")
if not [x for x in settings.allowed_types if x.document_type == "Purchase Invoice"]:
settings.append("allowed_types", {"document_type": "Purchase Invoice", "allowed": True})
settings = frappe.get_doc("Accounts Settings")
if "Purchase Invoice" not in [x.document_type for x in settings.repost_allowed_types]:
settings.append("repost_allowed_types", {"document_type": "Purchase Invoice"})
settings.save()
pi = make_purchase_invoice(

View File

@@ -190,6 +190,7 @@
"fieldtype": "Float",
"label": "Received Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
@@ -206,7 +207,8 @@
{
"fieldname": "rejected_qty",
"fieldtype": "Float",
"label": "Rejected Qty"
"label": "Rejected Qty",
"print_hide": 1
},
{
"depends_on": "eval:doc.uom != doc.stock_uom",
@@ -226,6 +228,7 @@
"fieldtype": "Link",
"label": "UOM",
"options": "UOM",
"print_hide": 1,
"reqd": 1
},
{
@@ -261,14 +264,16 @@
"depends_on": "price_list_rate",
"fieldname": "discount_percentage",
"fieldtype": "Percent",
"label": "Discount on Price List Rate (%)"
"label": "Discount on Price List Rate (%)",
"print_hide": 1
},
{
"depends_on": "price_list_rate",
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Discount Amount",
"options": "currency"
"options": "currency",
"print_hide": 1
},
{
"fieldname": "col_break3",
@@ -401,12 +406,14 @@
{
"fieldname": "weight_per_unit",
"fieldtype": "Float",
"label": "Weight Per Unit"
"label": "Weight Per Unit",
"print_hide": 1
},
{
"fieldname": "total_weight",
"fieldtype": "Float",
"label": "Total Weight",
"print_hide": 1,
"read_only": 1
},
{
@@ -417,7 +424,8 @@
"fieldname": "weight_uom",
"fieldtype": "Link",
"label": "Weight UOM",
"options": "UOM"
"options": "UOM",
"print_hide": 1
},
{
"depends_on": "eval:parent.update_stock",
@@ -429,7 +437,8 @@
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Accepted Warehouse",
"options": "Warehouse"
"options": "Warehouse",
"print_hide": 1
},
{
"fieldname": "rejected_warehouse",
@@ -674,7 +683,8 @@
"fieldname": "asset_location",
"fieldtype": "Link",
"label": "Asset Location",
"options": "Location"
"options": "Location",
"print_hide": 1
},
{
"fieldname": "po_detail",
@@ -730,7 +740,6 @@
"label": "Valuation Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1,
"read_only": 1
},
@@ -796,6 +805,7 @@
"fieldtype": "Link",
"label": "Asset Category",
"options": "Asset Category",
"print_hide": 1,
"read_only": 1
},
{
@@ -828,6 +838,7 @@
"label": "Rate of Stock UOM",
"no_copy": 1,
"options": "currency",
"print_hide": 1,
"read_only": 1
},
{
@@ -866,6 +877,7 @@
"fieldtype": "Currency",
"label": "Rate With Margin",
"options": "currency",
"print_hide": 1,
"read_only": 1
},
{
@@ -892,7 +904,8 @@
"default": "1",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Consider for Tax Withholding"
"label": "Consider for Tax Withholding",
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
@@ -918,7 +931,8 @@
"fieldname": "wip_composite_asset",
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
"options": "Asset",
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
@@ -930,7 +944,8 @@
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
"label": "Use Serial No / Batch Fields",
"print_hide": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
@@ -977,7 +992,8 @@
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
"options": "currency",
"print_hide": 1
},
{
"fieldname": "tax_withholding_category",
@@ -991,7 +1007,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2026-02-15 21:07:49.455930",
"modified": "2026-04-07 15:40:45.687554",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -217,7 +217,6 @@ def get_allowed_types_from_settings(child_doc: bool = False):
x.document_type
for x in frappe.db.get_all(
"Repost Allowed Types",
filters={"allowed": True},
fields=["document_type"],
distinct=True,
)
@@ -272,14 +271,13 @@ def validate_docs_for_voucher_types(doc_voucher_types):
if disallowed_types := voucher_types.difference(allowed_types):
message = "are" if len(disallowed_types) > 1 else "is"
frappe.throw(
_("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format(
_(
"{0} {1} not allowed to be reposted. You can enable it by adding it '{2}' table in {3}."
).format(
frappe.bold(comma_and(list(disallowed_types))),
message,
frappe.bold(
frappe.utils.get_link_to_form(
"Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings"
)
),
frappe.bold("Allowed Doctype"),
frappe.utils.get_link_to_form("Accounts Settings"),
)
)
@@ -289,8 +287,6 @@ def validate_docs_for_voucher_types(doc_voucher_types):
def get_repost_allowed_types(
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
):
filters = {"allowed": True}
if txt:
filters.update({"document_type": ("like", f"%{txt}%")})

View File

@@ -9,29 +9,25 @@ from frappe.utils import add_days, nowdate, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt
from erpnext.tests.utils import ERPNextTestSuite
class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
class TestRepostAccountingLedger(ERPNextTestSuite):
def setUp(self):
self.create_company()
self.create_customer()
self.create_item()
frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0)
update_repost_settings()
def test_01_basic_functions(self):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
item="_Test Item",
company="_Test Company",
customer="_Test Customer",
debit_to="Debtors - _TC",
parent_cost_center="Main - _TC",
cost_center="Main - _TC",
rate=100,
)
@@ -48,7 +44,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
# Test Validation Error
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.company = "_Test Company"
ral.delete_cancelled_entries = True
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.append(
@@ -65,7 +61,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
ral.save()
# manually set an incorrect debit amount in DB
gle = frappe.db.get_all("GL Entry", filters={"voucher_no": si.name, "account": self.debit_to})
gle = frappe.db.get_all("GL Entry", filters={"voucher_no": si.name, "account": "Debtors - _TC"})
frappe.db.set_value("GL Entry", gle[0], "debit", 90)
gl = qb.DocType("GL Entry")
@@ -94,23 +90,23 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
def test_02_deferred_accounting_valiations(self):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
item="_Test Item",
company="_Test Company",
customer="_Test Customer",
debit_to="Debtors - _TC",
parent_cost_center="Main - _TC",
cost_center="Main - _TC",
rate=100,
do_not_submit=True,
)
si.items[0].enable_deferred_revenue = True
si.items[0].deferred_revenue_account = self.deferred_revenue
si.items[0].deferred_revenue_account = "Deferred Revenue - _TC"
si.items[0].service_start_date = nowdate()
si.items[0].service_end_date = add_days(nowdate(), 90)
si.save().submit()
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.company = "_Test Company"
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
self.assertRaises(frappe.ValidationError, ral.save)
@@ -118,35 +114,35 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
def test_04_pcv_validation(self):
# Clear old GL entries so PCV can be submitted.
gl = frappe.qb.DocType("GL Entry")
qb.from_(gl).delete().where(gl.company == self.company).run()
qb.from_(gl).delete().where(gl.company == "_Test Company").run()
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
item="_Test Item",
company="_Test Company",
customer="_Test Customer",
debit_to="Debtors - _TC",
parent_cost_center="Main - _TC",
cost_center="Main - _TC",
rate=100,
)
fy = get_fiscal_year(today(), company=self.company)
fy = get_fiscal_year(today(), company="_Test Company")
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": today(),
"period_start_date": fy[1],
"period_end_date": today(),
"company": self.company,
"company": "_Test Company",
"fiscal_year": fy[0],
"cost_center": self.cost_center,
"closing_account_head": self.retained_earnings,
"cost_center": "Main - _TC",
"closing_account_head": "Retained Earnings - _TC",
"remarks": "test",
}
)
pcv.save().submit()
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.company = "_Test Company"
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
self.assertRaises(frappe.ValidationError, ral.save)
@@ -156,12 +152,12 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
def test_03_deletion_flag_and_preview_function(self):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
item="_Test Item",
company="_Test Company",
customer="_Test Customer",
debit_to="Debtors - _TC",
parent_cost_center="Main - _TC",
cost_center="Main - _TC",
rate=100,
)
@@ -170,7 +166,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
# with deletion flag set
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.company = "_Test Company"
ral.delete_cancelled_entries = True
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
@@ -181,12 +177,12 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
def test_05_without_deletion_flag(self):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
item="_Test Item",
company="_Test Company",
customer="_Test Customer",
debit_to="Debtors - _TC",
parent_cost_center="Main - _TC",
cost_center="Main - _TC",
rate=100,
)
@@ -195,7 +191,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
# without deletion flag set
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.company = "_Test Company"
ral.delete_cancelled_entries = False
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
@@ -207,19 +203,24 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
def test_06_repost_purchase_receipt(self):
from erpnext.accounts.doctype.account.test_account import create_account
if not frappe.db.set_value("Company", "_Test Company", "service_expense_account"):
frappe.db.set_value(
"Company", "_Test Company", "service_expense_account", "Marketing Expenses - _TC"
)
provisional_account = create_account(
account_name="Provision Account",
parent_account="Current Liabilities - _TC",
company=self.company,
company="_Test Company",
)
another_provisional_account = create_account(
account_name="Another Provision Account",
parent_account="Current Liabilities - _TC",
company=self.company,
company="_Test Company",
)
company = frappe.get_doc("Company", self.company)
company = frappe.get_doc("Company", "_Test Company")
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
@@ -229,7 +230,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
item = make_item(properties={"is_stock_item": 0})
pr = make_purchase_receipt(company=self.company, item_code=item.name, rate=1000.0, qty=1.0)
pr = make_purchase_receipt(company="_Test Company", item_code=item.name, rate=1000.0, qty=1.0)
pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
expected_pr_gles = [
{"account": provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
@@ -246,7 +247,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite):
)
repost_doc = frappe.new_doc("Repost Accounting Ledger")
repost_doc.company = self.company
repost_doc.company = "_Test Company"
repost_doc.delete_cancelled_entries = True
repost_doc.append("vouchers", {"voucher_type": pr.doctype, "voucher_no": pr.name})
repost_doc.save().submit()
@@ -279,7 +280,8 @@ def update_repost_settings():
"Journal Entry",
"Purchase Receipt",
]
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
for x in allowed_types:
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
repost_settings.save()
settings = frappe.get_doc("Accounts Settings")
for _type in allowed_types:
if _type not in [x.document_type for x in settings.repost_allowed_types]:
settings.append("repost_allowed_types", {"document_type": _type})
settings.save()

View File

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

View File

@@ -1,53 +0,0 @@
{
"actions": [],
"creation": "2023-11-07 09:57:20.619939",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"allowed_types"
],
"fields": [
{
"fieldname": "allowed_types",
"fieldtype": "Table",
"label": "Allowed Doctypes",
"options": "Repost Allowed Types"
}
],
"grid_page_length": 50,
"hide_toolbar": 0,
"in_create": 1,
"issingle": 1,
"links": [],
"modified": "2026-03-16 13:28:21.312607",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Administrator",
"select": 1,
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"read": 1,
"role": "System Manager",
"select": 1,
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -1,45 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
class RepostAccountingLedgerSettings(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
allowed_types: DF.Table[RepostAllowedTypes]
# end: auto-generated types
def validate(self):
self.update_property_for_accounting_dimension()
def update_property_for_accounting_dimension(self):
doctypes = [entry.document_type for entry in self.allowed_types if entry.allowed]
if not doctypes:
return
doctypes += get_child_docs(doctypes)
set_allow_on_submit_for_dimension_fields(doctypes)
def set_allow_on_submit_for_dimension_fields(doctypes):
for dt in doctypes:
meta = frappe.get_meta(dt)
for dimension in get_accounting_dimensions():
df = meta.get_field(dimension)
if df and not df.allow_on_submit:
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -1,11 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from erpnext.tests.utils import ERPNextTestSuite
class TestRepostAccountingLedgerSettings(ERPNextTestSuite):
pass

View File

@@ -6,9 +6,7 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"column_break_sfzb",
"allowed"
"document_type"
],
"fields": [
{
@@ -17,29 +15,20 @@
"in_list_view": 1,
"label": "Doctype",
"options": "DocType"
},
{
"default": "0",
"fieldname": "allowed",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Allowed"
},
{
"fieldname": "column_break_sfzb",
"fieldtype": "Column Break"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:32.415806",
"modified": "2026-04-14 16:53:16.806714",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Allowed Types",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -14,7 +14,6 @@ class RepostAllowedTypes(Document):
if TYPE_CHECKING:
from frappe.types import DF
allowed: DF.Check
document_type: DF.Link | None
parent: DF.Data
parentfield: DF.Data

View File

@@ -165,13 +165,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
);
}
}
// Show buttons only when pos view is active
if (cint(doc.docstatus == 0) && this.frm.page.current_view_name !== "pos" && !doc.is_return) {
this.frm.cscript.sales_order_btn();
this.frm.cscript.delivery_note_btn();
this.frm.cscript.quotation_btn();
}
this.toggle_get_items();
this.set_default_print_format();
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
@@ -260,6 +254,93 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
}
}
toggle_get_items() {
const buttons = ["Sales Order", "Quotation", "Timesheet", "Delivery Note"];
buttons.forEach((label) => {
this.frm.remove_custom_button(label, "Get Items From");
});
if (cint(this.frm.doc.docstatus) !== 0 || this.frm.page.current_view_name === "pos") {
return;
}
if (!this.frm.doc.is_return) {
this.frm.cscript.sales_order_btn();
this.frm.cscript.quotation_btn();
this.frm.cscript.timesheet_btn();
}
this.frm.cscript.delivery_note_btn();
}
timesheet_btn() {
var me = this;
me.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,
},
{
label: __("Item Code"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
get_query: () => {
return {
query: "erpnext.controllers.queries.item_query",
filters: {
is_sales_item: 1,
customer: me.frm.doc.customer,
has_variants: 0,
},
};
},
},
{
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: me.frm.doc.project,
},
],
primary_action: function () {
const data = d.get_values();
me.frm.events.add_timesheet_data(me.frm, {
from_time: data.from_time,
to_time: data.to_time,
project: data.project,
item_code: data.item_code,
});
d.hide();
},
primary_action_label: __("Get Timesheets"),
});
d.show();
},
__("Get Items From")
);
}
sales_order_btn() {
var me = this;
@@ -331,6 +412,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
this.$delivery_note_btn = this.frm.add_custom_button(
__("Delivery Note"),
function () {
if (!me.frm.doc.customer) {
frappe.throw({
title: __("Mandatory"),
message: __("Please Select a Customer"),
});
}
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
source_doctype: "Delivery Note",
@@ -343,7 +430,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
var filters = {
docstatus: 1,
company: me.frm.doc.company,
is_return: 0,
is_return: me.frm.doc.is_return,
};
if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
return {
@@ -465,12 +552,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
}
items_add(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, [
"income_account",
"discount_account",
"cost_center",
]);
const row = frappe.get_doc(cdt, cdn);
const field_copy = ["income_account", "discount_account", "cost_center"];
if (doc.project) {
frappe.model.set_value(cdt, cdn, "project", doc.project);
} else {
field_copy.push("project");
}
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
}
set_dynamic_labels() {
@@ -610,6 +699,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
apply_tds(frm) {
this.frm.clear_table("tax_withholding_entries");
}
is_return() {
this.toggle_get_items();
}
};
// for backward compatibility: combine new and previous states
@@ -1061,71 +1154,6 @@ frappe.ui.form.on("Sales Invoice", {
},
refresh: function (frm) {
if (frm.doc.docstatus === 0 && !frm.doc.is_return) {
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,
},
{
label: __("Item Code"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
get_query: () => {
return {
query: "erpnext.controllers.queries.item_query",
filters: {
is_sales_item: 1,
customer: frm.doc.customer,
has_variants: 0,
},
};
},
},
{
fieldtype: "Column Break",
fieldname: "col_break_1",
},
{
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,
item_code: data.item_code,
});
d.hide();
},
primary_action_label: __("Get Timesheets"),
});
d.show();
},
__("Get Items From")
);
}
if (frm.doc.is_debit_note) {
frm.set_df_property("return_against", "label", __("Adjustment Against"));
}

View File

@@ -33,6 +33,8 @@
"is_created_using_pos",
"pos_closing_entry",
"has_subcontracted",
"section_break_sgnf",
"title",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -214,10 +216,11 @@
"language",
"subscription_section",
"subscription",
"from_date",
"auto_repeat",
"column_break_140",
"from_date",
"to_date",
"automation_section",
"auto_repeat",
"update_auto_repeat_reference",
"utm_analytics_section",
"utm_source",
@@ -1147,6 +1150,7 @@
"hide_seconds": 1,
"label": "Rounding Adjustment",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1159,6 +1163,7 @@
"label": "Rounded Total",
"oldfieldname": "rounded_total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -2321,6 +2326,24 @@
{
"fieldname": "column_break_rdks",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "automation_section",
"fieldtype": "Section Break",
"label": "Automation"
},
{
"fieldname": "section_break_sgnf",
"fieldtype": "Section Break"
},
{
"allow_on_submit": 1,
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"no_copy": 1,
"print_hide": 1
}
],
"grid_page_length": 50,
@@ -2334,7 +2357,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2026-03-09 17:15:30.931929",
"modified": "2026-05-01 02:37:29.742764",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -226,6 +226,7 @@ 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
@@ -1102,9 +1103,6 @@ class SalesInvoice(SellingController):
if self.po_date:
self.remarks += " " + _("dated {0}").format(formatdate(self.po_date))
else:
self.remarks = _("No Remarks")
def validate_auto_set_posting_time(self):
# Don't auto set the posting date and time if invoice is amended
if self.is_new() and self.amended_from:

View File

@@ -2025,10 +2025,6 @@ class TestSalesInvoice(ERPNextTestSuite):
)
def test_multiple_uom_in_selling(self):
frappe.db.sql(
"""delete from `tabItem Price`
where price_list='_Test Price List' and item_code='_Test Item'"""
)
item_price = frappe.new_doc("Item Price")
item_price.price_list = "_Test Price List"
item_price.item_code = "_Test Item"
@@ -2246,13 +2242,6 @@ class TestSalesInvoice(ERPNextTestSuite):
@ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True})
def test_rounding_adjustment_3(self):
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension
# Dimension creates custom field, which does an implicit DB commit as it is a DDL command
# Ensure dimension don't have any mandatory fields
create_dimension()
# rollback from tearDown() happens till here
si = create_sales_invoice(do_not_save=True)
si.items = []
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
@@ -2894,7 +2883,7 @@ class TestSalesInvoice(ERPNextTestSuite):
si.submit()
# Check if adjustment entry is created
self.assertTrue(
self.assertFalse(
frappe.db.exists(
"GL Entry",
{

View File

@@ -207,6 +207,7 @@
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"print_hide": 1,
"read_only": 1
},
{
@@ -310,7 +311,8 @@
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Discount Amount",
"options": "currency"
"options": "currency",
"print_hide": 1
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
@@ -853,6 +855,7 @@
"fieldtype": "Currency",
"label": "Rate of Stock UOM",
"no_copy": 1,
"print_hide": 1,
"options": "currency",
"read_only": 1
},
@@ -869,6 +872,7 @@
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"print_hide": 1,
"read_only": 1
},
{
@@ -926,7 +930,8 @@
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
"label": "Use Serial No / Batch Fields",
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
@@ -941,7 +946,8 @@
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
"options": "currency",
"print_hide": 1
},
{
"fieldname": "available_quantity_section",
@@ -1010,7 +1016,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2026-02-23 14:37:14.853941",
"modified": "2026-02-24 14:37:16.853941",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -9,8 +9,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestShareTransfer(ERPNextTestSuite):
def setUp(self):
frappe.db.sql("delete from `tabShare Transfer`")
frappe.db.sql("delete from `tabShare Balance`")
share_transfers = [
{
"doctype": "Share Transfer",

View File

@@ -25,6 +25,10 @@ frappe.ui.form.on("Shipping Rule", {
},
calculate_based_on: function (frm) {
frm.trigger("toggle_reqd");
if (frm.doc.calculate_based_on === "Fixed") {
frm.clear_table("conditions");
frm.refresh_field("conditions");
}
},
toggle_reqd: function (frm) {
frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === "Fixed");

View File

@@ -58,6 +58,11 @@ class ShippingRule(Document):
self.validate_overlapping_shipping_rule_conditions()
def validate_from_to_values(self):
if self.calculate_based_on == "Fixed":
if self.conditions:
self.set("conditions", [])
return
zero_to_values = []
for d in self.get("conditions"):

View File

@@ -772,7 +772,8 @@ def process_all(subscription: list, posting_date: DateTimeLikeObject | None = No
try:
subscription = frappe.get_doc("Subscription", subscription_name)
subscription.process(posting_date)
frappe.db.commit()
if not frappe.in_test:
frappe.db.commit()
except frappe.ValidationError:
frappe.db.rollback()
subscription.log_error("Subscription failed")

View File

@@ -128,6 +128,7 @@ class TaxWithholdingDetails:
self.party_type = party_type
self.party = party
self.company = company
self.tax_id = get_tax_id_for_party(self.party_type, self.party)
def get(self) -> list:
"""
@@ -161,6 +162,7 @@ class TaxWithholdingDetails:
disable_cumulative_threshold=doc.disable_cumulative_threshold,
disable_transaction_threshold=doc.disable_transaction_threshold,
taxable_amount=0,
tax_id=self.tax_id,
)
# ldc (only if valid based on posting date)
@@ -181,17 +183,13 @@ class TaxWithholdingDetails:
if self.party_type != "Supplier":
return ldc_details
# NOTE: This can be a configurable option
# To check if filter by tax_id is needed
tax_id = get_tax_id_for_party(self.party_type, self.party)
# ldc details
ldc_records = self.get_valid_ldc_records(tax_id)
ldc_records = self.get_valid_ldc_records(self.tax_id)
if not ldc_records:
return ldc_details
ldc_names = [ldc.name for ldc in ldc_records]
ldc_utilization_map = self.get_ldc_utilization_by_category(ldc_names, tax_id)
ldc_utilization_map = self.get_ldc_utilization_by_category(ldc_names, self.tax_id)
# map
for ldc in ldc_records:
@@ -254,4 +252,5 @@ class TaxWithholdingDetails:
@allow_regional
def get_tax_id_for_party(party_type, party):
return None
# cannot use tax_id from doc because payment and journal entry do not have tax_id field.\
return frappe.db.get_value(party_type, party, "tax_id")

View File

@@ -2,10 +2,10 @@
# See license.txt
import datetime
from unittest.mock import patch
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.utils import add_days, add_months, today
from frappe.utils import add_days, add_months, getdate, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.utils import get_fiscal_year
@@ -18,7 +18,6 @@ class TestTaxWithholdingCategory(ERPNextTestSuite):
# create relevant supplier, etc
create_records()
create_tax_withholding_category_records()
make_pan_no_field()
def validate_tax_withholding_entries(self, doctype, docname, expected_entries):
"""Validate tax withholding entries for a document"""
@@ -1922,7 +1921,6 @@ class TestTaxWithholdingCategory(ERPNextTestSuite):
def set_previous_fy_and_tax_category(self):
test_company = "_Test Company"
category = "Cumulative Threshold TDS"
def add_company_to_fy(fy, company):
if not [x.company for x in fy.companies if x.company == company]:
@@ -1948,20 +1946,6 @@ class TestTaxWithholdingCategory(ERPNextTestSuite):
)
self.prev_fy.save()
# setup tax withholding category for previous fiscal year
cat = frappe.get_doc("Tax Withholding Category", category)
cat.append(
"rates",
{
"from_date": self.prev_fy.year_start_date,
"to_date": self.prev_fy.year_end_date,
"tax_withholding_rate": 10,
"single_threshold": 0,
"cumulative_threshold": 30000,
},
)
cat.save()
def test_tds_across_fiscal_year(self):
"""
Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year
@@ -1972,6 +1956,14 @@ class TestTaxWithholdingCategory(ERPNextTestSuite):
supplier = "Test TDS Supplier"
# Cumulative threshold 30000 and tax rate 10%
category = "Cumulative Threshold TDS"
create_tax_withholding_category(
category_name=category,
rate=10,
from_date=self.prev_fy.year_start_date,
to_date=self.prev_fy.year_end_date,
account="TDS - _TC",
cumulative_threshold=30000,
)
frappe.db.set_value(
"Supplier",
supplier,
@@ -2043,6 +2035,158 @@ class TestTaxWithholdingCategory(ERPNextTestSuite):
self.assertEqual(pi2.taxes, [])
self.assertEqual(payment.taxes[0].tax_amount, 6000)
def test_threshold_resets_in_new_fiscal_year(self):
"""
Threshold entries from a previous FY must not carry over into the new FY.
"""
self.set_previous_fy_and_tax_category()
invoices = []
supplier = "Test TDS Supplier"
category = "Cumulative Threshold TDS"
create_tax_withholding_category(
category_name=category,
rate=10,
from_date=self.prev_fy.year_start_date,
to_date=self.prev_fy.year_end_date,
account="TDS - _TC",
cumulative_threshold=30000,
)
self.setup_party_with_category("Supplier", supplier, category)
prev_fy_date = add_days(self.prev_fy.year_end_date, -10)
# Previous FY: 3 invoices to cross the 30000 cumulative threshold
for _ in range(3):
pi = create_purchase_invoice(supplier=supplier, posting_date=prev_fy_date, set_posting_time=True)
pi.submit()
invoices.append(pi)
# Third invoice crosses the threshold - 3000 TDS deducted across all three
self.validate_tax_deduction(invoices[-1], 3000)
# Current FY: 10000 invoice - must be Under Withheld, threshold resets
pi_curr = create_purchase_invoice(supplier=supplier)
pi_curr.submit()
invoices.append(pi_curr)
self.validate_tax_deduction(pi_curr, 0)
self.validate_tax_withholding_entries(
"Purchase Invoice",
pi_curr.name,
[
self.get_tax_withholding_entry(
tax_withholding_category=category,
party_type="Supplier",
party=supplier,
taxable_doctype="Purchase Invoice",
taxable_name=pi_curr.name,
tax_rate=10.0,
taxable_amount=10000.0,
withholding_amount=0.0,
status="Under Withheld",
withholding_doctype=None,
withholding_name=None,
under_withheld_reason=None,
)
],
)
self.cleanup_invoices(invoices)
def test_tax_on_excess_threshold_resets_in_new_fiscal_year(self):
"""
For tax-on-excess categories, unused threshold must reset each FY.
"""
self.set_previous_fy_and_tax_category()
invoices = []
supplier = "Test TDS Supplier3"
category = "New TDS Category"
create_tax_withholding_category(
category_name=category,
rate=10,
from_date=self.prev_fy.year_start_date,
to_date=self.prev_fy.year_end_date,
account="TDS - _TC",
cumulative_threshold=30000,
tax_on_excess_amount=1,
round_off_tax_amount=1,
)
self.setup_party_with_category("Supplier", supplier, category)
prev_fy_date = add_days(self.prev_fy.year_end_date, -10)
for _ in range(2):
pi = create_purchase_invoice(supplier=supplier, posting_date=prev_fy_date, set_posting_time=True)
pi.submit()
invoices.append(pi)
pi3 = create_purchase_invoice(
supplier=supplier, rate=20000, posting_date=prev_fy_date, set_posting_time=True
)
pi3.submit()
invoices.append(pi3)
self.validate_tax_deduction(pi3, 1000)
self.validate_tax_withholding_entries(
"Purchase Invoice",
pi3.name,
[
self.get_tax_withholding_entry(
tax_withholding_category=category,
party_type="Supplier",
party=supplier,
taxable_doctype="Purchase Invoice",
taxable_name=pi3.name,
tax_rate=10.0,
taxable_amount=10000.0,
withholding_amount=0.0,
status="Settled",
withholding_doctype="Purchase Invoice",
withholding_name=pi3.name,
under_withheld_reason="Threshold Exemption",
),
self.get_tax_withholding_entry(
tax_withholding_category=category,
party_type="Supplier",
party=supplier,
taxable_doctype="Purchase Invoice",
taxable_name=pi3.name,
tax_rate=10.0,
taxable_amount=10000.0,
withholding_amount=1000.0,
status="Settled",
withholding_doctype="Purchase Invoice",
withholding_name=pi3.name,
under_withheld_reason=None,
),
],
)
# no excess, so no TDS
pi_curr = create_purchase_invoice(supplier=supplier, rate=30000)
pi_curr.submit()
invoices.append(pi_curr)
self.validate_tax_deduction(pi_curr, 0)
self.validate_tax_withholding_entries(
"Purchase Invoice",
pi_curr.name,
[
self.get_tax_withholding_entry(
tax_withholding_category=category,
party_type="Supplier",
party=supplier,
taxable_doctype="Purchase Invoice",
taxable_name=pi_curr.name,
tax_rate=10.0,
taxable_amount=30000.0,
withholding_amount=0.0,
status="Settled",
withholding_doctype="Purchase Invoice",
withholding_name=pi_curr.name,
under_withheld_reason="Threshold Exemption",
),
],
)
self.cleanup_invoices(invoices)
@ERPNextTestSuite.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1})
def test_tds_payment_entry_cancellation(self):
"""
@@ -3542,6 +3686,47 @@ class TestTaxWithholdingCategory(ERPNextTestSuite):
entry.withholding_amount = 5001 # Should be 5000 (10% of 50000)
self.assertRaisesRegex(frappe.ValidationError, "Withholding Amount.*does not match", pi.save)
def test_tax_id_is_set_in_all_generated_entries_from_party_doctype(self):
self.setup_party_with_category("Supplier", "Test TDS Supplier3", "New TDS Category")
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_id", "ABCTY1234D")
pi = create_purchase_invoice(supplier="Test TDS Supplier3", rate=40000)
pi.submit()
entries = frappe.get_all(
"Tax Withholding Entry",
filters={"parenttype": "Purchase Invoice", "parent": pi.name},
fields=["name", "tax_id"],
)
self.assertTrue(entries)
self.assertTrue(all(entry.tax_id == "ABCTY1234D" for entry in entries))
def test_threshold_considers_two_parties_with_same_tax_id_with_overrided_hook(self):
self.setup_party_with_category("Supplier", "Test TDS Supplier1", "Cumulative Threshold TDS")
self.setup_party_with_category("Supplier", "Test TDS Supplier2", "Cumulative Threshold TDS")
with patch(
"erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category.get_tax_id_for_party",
return_value="AAAPL1234C",
):
pi1 = create_purchase_invoice(supplier="Test TDS Supplier1", rate=20000)
pi1.submit()
pi2 = create_purchase_invoice(supplier="Test TDS Supplier2", rate=20000)
pi2.submit()
entries = frappe.get_all(
"Tax Withholding Entry",
filters={"parenttype": "Purchase Invoice", "parent": pi2.name},
fields=["status", "withholding_amount"],
)
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].status, "Settled")
self.assertEqual(entries[0].withholding_amount, 2000.0)
def create_purchase_invoice(**args):
# return sales invoice doc object
@@ -3956,7 +4141,7 @@ def create_tax_withholding_category(
tax_deduction_basis="Net Total",
):
if not frappe.db.exists("Tax Withholding Category", category_name):
frappe.get_doc(
doc = frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": category_name,
@@ -3977,6 +4162,22 @@ def create_tax_withholding_category(
"accounts": [{"company": "_Test Company", "account": account}],
}
).insert()
else:
doc = frappe.get_doc("Tax Withholding Category", category_name)
if not any(getdate(r.from_date) == getdate(from_date) for r in doc.rates):
doc.append(
"rates",
{
"from_date": from_date,
"to_date": to_date,
"tax_withholding_rate": rate,
"single_threshold": single_threshold,
"cumulative_threshold": cumulative_threshold,
},
)
doc.save()
return doc
def create_lower_deduction_certificate(
@@ -3998,18 +4199,3 @@ def create_lower_deduction_certificate(
"certificate_limit": limit,
}
).insert()
def make_pan_no_field():
pan_field = {
"Supplier": [
{
"fieldname": "pan",
"label": "PAN",
"fieldtype": "Data",
"translatable": 0,
}
]
}
create_custom_fields(pan_field, update=1)

View File

@@ -346,7 +346,6 @@ class TaxWithholdingEntry(Document):
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
TaxWithholdingDetails,
get_tax_id_for_party,
)
@@ -643,13 +642,17 @@ class TaxWithholdingController:
.where(entry.tax_withholding_category == category.name)
.where(entry.company == self.doc.company)
.where(entry.docstatus == 1)
.where(entry.taxable_date.between(category.from_date, category.to_date))
.groupby(entry.status)
)
# NOTE: This can be a configurable option
# To check if filter by tax_id is needed
tax_id = get_tax_id_for_party(self.party_type, self.party)
query = query.where(entry.tax_id == tax_id) if tax_id else query.where(entry.party == self.party)
query = (
query.where(entry.tax_id == category.tax_id)
if category.tax_id
else query.where(entry.party == self.party)
)
return query
@@ -688,6 +691,7 @@ class TaxWithholdingController:
"company": self.doc.company,
"party_type": self.party_type,
"party": self.party,
"tax_id": category.tax_id,
"tax_withholding_category": category.name,
"tax_withholding_group": category.tax_withholding_group,
"tax_rate": category.tax_rate,
@@ -1054,6 +1058,7 @@ class TaxWithholdingController:
"party_type": self.party_type,
"party": self.party,
"company": self.doc.company,
"tax_id": category.tax_id,
}
)
return entry

View File

@@ -14,7 +14,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.tests.utils import ERPNextTestSuite
class TestUnreconcilePayment(AccountsTestMixin, ERPNextTestSuite):
class TestUnreconcilePayment(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_customer()

View File

@@ -1,118 +1,147 @@
[
{
"account_category_name": "Cash and Cash Equivalents",
"root_type": "Asset",
"description": "Cash on hand, demand deposits, and short-term highly liquid investments readily convertible to cash with original maturities of three months or less. Examples: Cash in hand, bank current accounts, money market funds, treasury bills \u22643 months."
},
{
"account_category_name": "Cost of Goods Sold",
"root_type": "Expense",
"description": "Direct costs attributable to cost of goods sold. Examples: Raw materials, stock in trade."
},
{
"account_category_name": "Current Tax Liabilities",
"root_type": "Liability",
"description": "Income tax obligations for current and prior periods. Examples: Provision for income tax, advance tax paid, tax deducted at source."
},
{
"account_category_name": "Finance Costs",
"root_type": "Expense",
"description": "Interest and financing-related expenses. Examples: Interest on borrowings, bank charges, lease interest, foreign exchange losses."
},
{
"account_category_name": "Intangible Assets",
"root_type": "Asset",
"description": "Identifiable non-monetary assets without physical substance. Examples: Software, patents, trademarks, licenses, development costs."
},
{
"account_category_name": "Investment Income",
"root_type": "Income",
"description": "Returns generated from financial investments and cash management. Examples: Interest income, dividend income, rental income, fair value gains."
},
{
"account_category_name": "Long-term Borrowings",
"root_type": "Liability",
"description": "Interest-bearing debt obligations with maturity beyond one year. Examples: Term loans, bonds, debentures, mortgages."
},
{
"account_category_name": "Long-term Investments",
"root_type": "Asset",
"description": "Investments held for strategic purposes or extended periods. Examples: Equity investments, bonds, associates, joint ventures, deposits."
},
{
"account_category_name": "Long-term Provisions",
"root_type": "Liability",
"description": "Present obligations beyond one year with uncertain timing/amount. Examples: Asset retirement obligations, environmental remediation, legal settlements."
},
{
"account_category_name": "Operating Expenses",
"root_type": "Expense",
"description": "Costs incurred in ordinary business operations excluding direct costs. Examples: Selling expenses, administrative costs, marketing, utilities, rent."
},
{
"account_category_name": "Other Current Assets",
"root_type": "Asset",
"description": "Current assets not classified elsewhere including prepaid expenses and advances. Examples: Prepaid insurance, prepaid rent, advance to suppliers, security deposits recoverable within one year."
},
{
"account_category_name": "Other Current Liabilities",
"root_type": "Liability",
"description": "Short-term obligations not classified elsewhere. Examples: Accrued expenses, statutory liabilities, employee payables."
},
{
"account_category_name": "Other Direct Costs",
"root_type": "Expense",
"description": "Direct costs excluding cost of goods sold. Examples: Direct labor, manufacturing overhead, freight inward."
},
{
"account_category_name": "Other Non-current Assets",
"root_type": "Asset",
"description": "Long-term assets not classified elsewhere. Examples: Security deposits, long-term prepayments, advances for capital goods."
},
{
"account_category_name": "Other Non-current Liabilities",
"root_type": "Liability",
"description": "Long-term obligations not classified elsewhere. Examples: Long-term deposits, deferred income, government grants."
},
{
"account_category_name": "Other Operating Income",
"root_type": "Income",
"description": "Incidental income related to business operations but not core revenue. Examples: Scrap sales, government grants, insurance claims, foreign exchange gains."
},
{
"account_category_name": "Other Payables",
"root_type": "Liability",
"description": "Non-trade payables and obligations to parties other than suppliers. Examples: Employee payables, accrued expenses, customer advances, security deposits received."
},
{
"account_category_name": "Other Receivables",
"root_type": "Asset",
"description": "Non-trade amounts due to the entity excluding financing arrangements. Examples: Employee advances, insurance claims, tax refunds, deposits recoverable."
},
{
"account_category_name": "Reserves and Surplus",
"root_type": "Equity",
"description": "Accumulated profits and other reserves created from profits or share premium. Examples: General reserves, retained earnings, statutory reserves, share premium."
},
{
"account_category_name": "Revenue from Operations",
"root_type": "Income",
"description": "Income from primary business activities in ordinary course. Examples: Sales of goods, service revenue, commission income, royalty income."
},
{
"account_category_name": "Share Capital",
"root_type": "Equity",
"description": "Nominal value of issued and paid-up equity shares. Examples: Common stock, ordinary shares, preference shares."
},
{
"account_category_name": "Short-term Borrowings",
"root_type": "Liability",
"description": "Interest-bearing debt obligations due within one year. Examples: Bank overdrafts, short-term loans, current portion of long-term debt."
},
{
"account_category_name": "Short-term Investments",
"root_type": "Asset",
"description": "Financial instruments held for short-term investment purposes, readily convertible to cash. Examples: Marketable securities, fixed deposits >3 months, mutual funds."
},
{
"account_category_name": "Short-term Provisions",
"root_type": "Liability",
"description": "Present obligations due within one year with uncertain timing or amount. Examples: Warranty provisions, legal claims, restructuring costs."
},
{
"account_category_name": "Stock Assets",
"root_type": "Asset",
"description": "Inventory and stock-related assets including raw materials, work in progress, finished goods, and stock in trade. Examples: Raw materials, finished goods, trading merchandise, consumables."
},
{
"account_category_name": "Tangible Assets",
"root_type": "Asset",
"description": "Physical assets used in business operations including property, plant, and equipment. Examples: Land, buildings, machinery, equipment, vehicles, furniture, capital work in progress."
},
{
"account_category_name": "Tax Expense",
"root_type": "Expense",
"description": "Current and deferred income tax obligations. Examples: Current tax provision, deferred tax expense, withholding taxes."
},
{
"account_category_name": "Trade Payables",
"root_type": "Liability",
"description": "Amounts owed to suppliers. Examples: Supplier invoices, accrued purchases, bills payable."
},
{
"account_category_name": "Trade Receivables",
"root_type": "Asset",
"description": "Amounts due from customers for goods sold or services provided in ordinary course of business. Examples: Accounts receivable, notes receivable from customers, unbilled revenue."
}
]
]

View File

@@ -35,7 +35,8 @@ def make_gl_entries(
):
if gl_map:
if (
not cint(frappe.get_single_value("Accounts Settings", "use_legacy_budget_controller"))
not cancel
and not cint(frappe.get_single_value("Accounts Settings", "use_legacy_budget_controller"))
and gl_map[0].voucher_type != "Period Closing Voucher"
):
bud_val = BudgetValidation(gl_map=gl_map)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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