Compare commits

..

341 Commits

Author SHA1 Message Date
diptanilsaha
4b2633a971 ci: set TZ: 'Asia/Kolkata' on Python Tests 2026-04-22 03:45:24 +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
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
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
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
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
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
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
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
MochaMind
a76336e3d9 fix: sync translations from crowdin (#54181) 2026-04-11 21:07:20 +02:00
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
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
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
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
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
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
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
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
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
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
350 changed files with 91637 additions and 78833 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

@@ -56,6 +56,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

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

@@ -26,8 +26,13 @@
],
"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-02-23 01:19:49.589393",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Category",

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

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

@@ -1292,6 +1292,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)

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

@@ -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",
@@ -475,11 +475,6 @@
"options": "Stock Entry",
"read_only": 1
},
{
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription"
},
{
"allow_on_submit": 1,
"fieldname": "auto_repeat",
@@ -651,6 +646,11 @@
"fieldname": "tax_withholding_tab",
"fieldtype": "Tab Break",
"label": "Tax Withholding"
},
{
"fieldname": "auto_repeat_section",
"fieldtype": "Section Break",
"label": "Auto Repeat"
}
],
"icon": "fa fa-file-text",

View File

@@ -354,8 +354,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)
@@ -1550,35 +1553,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.user_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

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

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

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

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

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

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"
@@ -1462,7 +1464,7 @@
{
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section"
"label": "Subscription"
},
{
"allow_on_submit": 1,
@@ -1480,10 +1482,6 @@
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "column_break_140",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "auto_repeat",
@@ -1619,12 +1617,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-03-30 12:15:57.253316",
"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

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

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

@@ -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 {
@@ -610,6 +697,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 +1152,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",
@@ -2321,6 +2324,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 +2355,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2026-03-09 17:15:30.931929",
"modified": "2026-03-30 12:17:16.201016",
"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

@@ -2246,13 +2246,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 +2887,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

@@ -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,6 +2,7 @@
# See license.txt
import datetime
from unittest.mock import patch
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
@@ -18,7 +19,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"""
@@ -3542,6 +3542,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
@@ -3998,18 +4039,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,
)
@@ -648,8 +647,11 @@ class TaxWithholdingController:
# 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 +690,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 +1057,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

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

View File

@@ -1199,6 +1199,7 @@ class ReceivablePayableReport:
fieldtype="Dynamic Link",
options="party_type",
width=180,
sticky=(self.party_naming_by not in ["Naming Series", "Auto Name"]),
)
if self.account_type == "Receivable":
label = _("Receivable Account")
@@ -1213,6 +1214,7 @@ class ReceivablePayableReport:
fieldtype="Link",
options="Account",
width=180,
sticky=True,
)
if self.party_naming_by == "Naming Series":
@@ -1226,6 +1228,7 @@ class ReceivablePayableReport:
label=label,
fieldname=fieldname,
fieldtype="Data",
sticky=True,
)
if self.account_type == "Receivable":
@@ -1309,7 +1312,7 @@ class ReceivablePayableReport:
if self.filters.show_remarks:
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200)
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120, sticky=False):
if not fieldname:
fieldname = scrub(label)
if fieldtype == "Currency":
@@ -1318,7 +1321,14 @@ class ReceivablePayableReport:
width = 90
self.columns.append(
dict(label=label, fieldname=fieldname, fieldtype=fieldtype, options=options, width=width)
dict(
label=label,
fieldname=fieldname,
fieldtype=fieldtype,
options=options,
width=width,
sticky=sticky,
)
)
def setup_ageing_columns(self):

View File

@@ -10,7 +10,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.tests.utils import ERPNextTestSuite
class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_customer()
@@ -120,12 +120,12 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
report = execute(filters)
expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]]
expected_data = [[100, 30], [100, 50], [100, 20]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced])
self.assertFalse(row.get("remarks"))
# check invoice grand total, invoiced, paid and outstanding column's value after payment
self.create_payment_entry(si.name)
report = execute(filters)
@@ -178,11 +178,11 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
report = execute(filters)
expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]]
expected_data = [[100, 30], [100, 50], [100, 20]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced])
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
cr_note = self.create_credit_note(si.name, do_not_submit=True)
@@ -225,9 +225,10 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
report = execute(filters)
row = report[1][0]
expected_data = [8000, 8000, "No Remarks"] # Data in company currency
expected_data = [8000, 8000] # Data in company currency
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced])
self.assertFalse(row.get("remarks"))
# CASE 2: Transaction currency and party account currency are the same
self.create_customer(
@@ -258,18 +259,20 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
report = execute(filters)
row = report[1][0]
expected_data = [100, 100, "No Remarks"] # Data in Part Account Currency
expected_data = [100, 100] # Data in Part Account Currency
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced])
self.assertFalse(row.get("remarks"))
# View in Company currency
filters.pop("in_party_currency")
report = execute(filters)
row = report[1][0]
expected_data = [8000, 8000, "No Remarks"] # Data in Company Currency
expected_data = [8000, 8000] # Data in Company Currency
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced])
self.assertFalse(row.get("remarks"))
def test_accounts_receivable_with_partial_payment(self):
filters = {
@@ -285,11 +288,12 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
report = execute(filters)
expected_data = [[200, 60, "No Remarks"], [200, 100, "No Remarks"], [200, 40, "No Remarks"]]
expected_data = [[200, 60], [200, 100], [200, 40]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced])
self.assertFalse(row.get("remarks"))
# check invoice grand total, invoiced, paid and outstanding column's value after payment
self.create_payment_entry(si.name)
@@ -348,11 +352,12 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
report = execute(filters)
expected_data = [100, 100, "No Remarks"]
expected_data = [100, 100]
self.assertEqual(len(report[1]), 1)
row = report[1][0]
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced])
self.assertFalse(row.get("remarks"))
# check invoice grand total, invoiced, paid and outstanding column's value after payment
self.create_payment_entry(si.name)
@@ -767,7 +772,7 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
def test_party_account_filter(self):
si1 = self.create_sales_invoice()
self.customer2 = frappe.get_doc(
jane = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
@@ -776,9 +781,9 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
}
).insert()
self.customer = jane.name
si2 = self.create_sales_invoice(do_not_submit=True)
si2.posting_date = add_days(today(), -1)
si2.customer = self.customer2.name
si2.currency = "USD"
si2.conversion_rate = 80
si2.debit_to = self.debtors_usd
@@ -986,7 +991,7 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
self.assertEqual(expected_data, report_output)
def test_future_payments_on_foreign_currency(self):
self.customer2 = frappe.get_doc(
jane = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
@@ -994,10 +999,10 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
"default_currency": "USD",
}
).insert()
self.customer = jane.name
si = self.create_sales_invoice(do_not_submit=True)
si.posting_date = add_days(today(), -1)
si.customer = self.customer2.name
si.currency = "USD"
si.conversion_rate = 80
si.debit_to = self.debtors_usd

View File

@@ -149,6 +149,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
fieldtype="Dynamic Link",
options="party_type",
width=180,
sticky=(self.party_naming_by not in ["Naming Series", "Auto Name"]),
)
if self.party_naming_by == "Naming Series":
@@ -156,6 +157,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
label=_("Supplier Name") if self.account_type == "Payable" else _("Customer Name"),
fieldname="party_name",
fieldtype="Data",
sticky=True,
)
credit_debit_label = "Credit Note" if self.account_type == "Receivable" else "Debit Note"

View File

@@ -8,7 +8,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.tests.utils import ERPNextTestSuite
class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.maxDiff = None
self.create_company()

View File

@@ -9,7 +9,7 @@ from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.tests.utils import ERPNextTestSuite
class TestCustomerLedgerSummary(AccountsTestMixin, ERPNextTestSuite):
class TestCustomerLedgerSummary(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_customer()

View File

@@ -48,6 +48,9 @@ class Deferred_Item:
Generate report data for output
"""
ret_data = frappe._dict({"name": self.item_name})
ret_data.service_start_date = self.service_start_date
ret_data.service_end_date = self.service_end_date
ret_data.amount = self.base_net_amount
for period in self.period_total:
ret_data[period.key] = period.total
ret_data.indent = 1
@@ -205,6 +208,9 @@ class Deferred_Invoice:
for item in self.uniq_items:
self.items.append(Deferred_Item(item, self, [x for x in items if x.item == item]))
# roll-up amount from all deferred items
self.amount_total = sum(item.base_net_amount for item in self.items)
def calculate_invoice_revenue_expense_for_period(self):
"""
calculate deferred revenue/expense for all items in invoice
@@ -232,7 +238,7 @@ class Deferred_Invoice:
generate report data for invoice, includes invoice total
"""
ret_data = []
inv_total = frappe._dict({"name": self.name})
inv_total = frappe._dict({"name": self.name, "amount": self.amount_total})
for x in self.period_total:
inv_total[x.key] = x.total
inv_total.indent = 0
@@ -386,6 +392,24 @@ class Deferred_Revenue_and_Expense_Report:
def get_columns(self):
columns = []
columns.append({"label": _("Name"), "fieldname": "name", "fieldtype": "Data", "read_only": 1})
columns.append(
{
"label": _("Service Start Date"),
"fieldname": "service_start_date",
"fieldtype": "Date",
"read_only": 1,
}
)
columns.append(
{
"label": _("Service End Date"),
"fieldname": "service_end_date",
"fieldtype": "Date",
"read_only": 1,
}
)
columns.append({"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "read_only": 1})
for period in self.period_list:
columns.append(
{
@@ -415,6 +439,8 @@ class Deferred_Revenue_and_Expense_Report:
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
total_row["amount"] = sum(inv.amount_total for inv in self.deferred_invoices)
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)

View File

@@ -142,6 +142,7 @@ def prepare_data(accounts, filters, company_currency, dimension_list):
total = 0
row = {
"account": d.name,
"is_group": d.is_group,
"parent_account": d.parent_account,
"indent": d.indent,
"from_date": filters.from_date,

View File

@@ -74,6 +74,7 @@ frappe.query_reports["General Ledger"] = {
label: __("Party"),
fieldtype: "MultiSelectList",
options: "party_type",
depends_on: "party_type",
get_data: function (txt) {
if (!frappe.query_report.filters) return;

View File

@@ -676,13 +676,20 @@ def get_columns(filters):
"options": "GL Entry",
"hidden": 1,
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
{
"label": _("Posting Date"),
"fieldname": "posting_date",
"fieldtype": "Date",
"width": 120,
"sticky": True,
},
{
"label": _("Account"),
"fieldname": "account",
"fieldtype": "Link",
"options": "Account",
"width": 180,
"sticky": True,
},
{
"label": _("Debit ({0})").format(currency),
@@ -724,7 +731,7 @@ def get_columns(filters):
"options": "transaction_currency",
},
{
"label": "Transaction Currency",
"label": _("Transaction Currency"),
"fieldname": "transaction_currency",
"fieldtype": "Link",
"options": "Currency",

View File

@@ -578,7 +578,11 @@ class GrossProfitGenerator:
# get buying rate
if flt(row.qty):
row.buying_rate = flt(row.buying_amount / flt(row.qty), self.float_precision)
row.buying_rate = (
flt(row.buying_amount / flt(row.qty), self.float_precision)
if not row.delivered_by_supplier
else None
)
row.base_rate = flt(row.base_amount / flt(row.qty), self.float_precision)
else:
if self.is_not_invoice_row(row):
@@ -630,7 +634,8 @@ class GrossProfitGenerator:
returned_item_row.qty += row.qty
returned_item_row.base_amount += row.base_amount
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
if not row.delivered_by_supplier:
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
def get_average_rate_based_on_group_by(self):
for key in list(self.grouped):
@@ -769,7 +774,28 @@ class GrossProfitGenerator:
# IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc
if item_code in self.non_stock_items and (row.project or row.cost_center):
if (
row.delivered_by_supplier
and row.so_detail
and (
po_details := frappe.get_all(
"Purchase Order Item",
filters={"sales_order_item": row.so_detail, "docstatus": 1},
pluck="name",
)
)
):
from frappe.query_builder.functions import Sum
table = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(table)
.select(Sum(table.qty * table.base_net_rate))
.where((table.po_detail.isin(po_details)) & (table.docstatus == 1))
)
return flt(query.run()[0][0])
elif item_code in self.non_stock_items and (row.project or row.cost_center):
# Issue 6089-Get last purchasing rate for non-stock item
item_rate = self.get_last_purchase_rate(item_code, row)
return flt(row.qty) * item_rate
@@ -951,6 +977,7 @@ class GrossProfitGenerator:
SalesInvoice.is_return,
SalesInvoiceItem.cost_center,
SalesInvoiceItem.serial_and_batch_bundle,
SalesInvoiceItem.delivered_by_supplier,
)
if self.filters.group_by == "Sales Person":

View File

@@ -731,6 +731,31 @@ class TestGrossProfit(ERPNextTestSuite):
self.assertEqual(total[7], 1000.0)
self.assertEqual(total[8], 100.0)
def test_drop_ship(self):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order, make_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
item = make_item("_Test Drop Ship Item", properties={"is_stock_item": 1, "delivered_by_supplier": 1})
so = make_sales_order(item=item.name, qty=10, rate=100)
po = make_purchase_order(so.name, selected_items=[so.items[0]])[0]
po.items[0].rate = 80
po.supplier = "_Test Supplier"
po.submit()
make_purchase_invoice(po.name).submit()
si = make_sales_invoice(so.name).submit()
filters = frappe._dict(
company=si.company, from_date=si.posting_date, to_date=si.posting_date, group_by="Invoice"
)
_, data = execute(filters=filters)
self.assertEqual(data[1].buying_amount, 800)
self.assertIsNone(data[1].buying_rate)
self.assertEqual(data[1]["gross_profit_%"], 20)
def make_sales_person(sales_person_name="_Test Sales Person"):
if not frappe.db.exists("Sales Person", {"sales_person_name": sales_person_name}):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.tests.utils import ERPNextTestSuite
class TestTaxWithholdingDetails(AccountsTestMixin, ERPNextTestSuite):
class TestTaxWithholdingDetails(ERPNextTestSuite, AccountsTestMixin):
def setUp(self):
self.create_company()
self.clear_old_entries()

View File

@@ -14,7 +14,6 @@ class TestTrialBalance(ERPNextTestSuite):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_fiscal_year
self.company = create_company()
create_cost_center(
cost_center_name="Test Cost Center",
company="Trial Balance Company",
@@ -26,7 +25,16 @@ class TestTrialBalance(ERPNextTestSuite):
parent_account="Temporary Accounts - TBC",
)
self.fiscal_year = get_fiscal_year(today(), company="Trial Balance Company")[0]
create_accounting_dimension()
dim = frappe.get_doc("Accounting Dimension", "Branch")
dim.append(
"dimension_defaults",
{
"company": "Trial Balance Company",
"automatically_post_balancing_accounting_entry": 1,
"offsetting_account": "Offsetting - TBC",
},
)
dim.save()
def test_offsetting_entries_for_accounting_dimensions(self):
"""
@@ -45,7 +53,7 @@ class TestTrialBalance(ERPNextTestSuite):
branch2.insert(ignore_if_duplicate=True)
si = create_sales_invoice(
company=self.company,
company="Trial Balance Company",
debit_to="Debtors - TBC",
cost_center="Test Cost Center - TBC",
income_account="Sales - TBC",
@@ -57,60 +65,7 @@ class TestTrialBalance(ERPNextTestSuite):
si.submit()
filters = frappe._dict(
{"company": self.company, "fiscal_year": self.fiscal_year, "branch": ["Location 1"]}
{"company": "Trial Balance Company", "fiscal_year": self.fiscal_year, "branch": ["Location 1"]}
)
total_row = execute(filters)[1][-1]
self.assertEqual(total_row["debit"], total_row["credit"])
def create_company(**args):
args = frappe._dict(args)
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": args.company_name or "Trial Balance Company",
"country": args.country or "India",
"default_currency": args.currency or "INR",
"parent_company": args.get("parent_company"),
"is_group": args.get("is_group"),
}
)
company.insert(ignore_if_duplicate=True)
return company.name
def create_accounting_dimension(**args):
args = frappe._dict(args)
document_type = args.document_type or "Branch"
if frappe.db.exists("Accounting Dimension", document_type):
accounting_dimension = frappe.get_doc("Accounting Dimension", document_type)
accounting_dimension.disabled = 0
else:
accounting_dimension = frappe.new_doc("Accounting Dimension")
accounting_dimension.document_type = document_type
accounting_dimension.insert()
accounting_dimension.set("dimension_defaults", [])
accounting_dimension.append(
"dimension_defaults",
{
"company": args.company or "Trial Balance Company",
"automatically_post_balancing_accounting_entry": 1,
"offsetting_account": args.offsetting_account or "Offsetting - TBC",
},
)
accounting_dimension.save()
def disable_dimension(**args):
args = frappe._dict(args)
document_type = args.document_type or "Branch"
dimension = frappe.get_doc("Accounting Dimension", document_type)
dimension.disabled = 1
dimension.save()
def clear_dimension_defaults(dimension_name):
accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name)
accounting_dimension.dimension_defaults = []
accounting_dimension.save()

View File

@@ -1,5 +1,6 @@
{
"add_total_row": 0,
"add_translate_data": 0,
"columns": [],
"creation": "2023-06-27 16:40:15.109554",
"disabled": 0,
@@ -9,8 +10,7 @@
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"letter_head": "LetterHead",
"modified": "2023-06-27 16:40:32.493725",
"modified": "2026-04-20 16:55:11.962163",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Voucher-wise Balance",
@@ -29,5 +29,6 @@
{
"role": "Auditor"
}
]
}
],
"timeout": 0
}

View File

@@ -229,23 +229,3 @@ class AccountsTestMixin:
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_price_list(self):
pl_name = "Mixin Price List"
if not frappe.db.exists("Price List", pl_name):
self.price_list = (
frappe.get_doc(
{
"doctype": "Price List",
"currency": "INR",
"enabled": True,
"selling": True,
"buying": True,
"price_list_name": pl_name,
}
)
.insert()
.name
)
else:
self.price_list = frappe.get_doc("Price List", pl_name).name

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