Compare commits

..

351 Commits

Author SHA1 Message Date
Frappe PR Bot
7b494dc9e8 chore(release): Bumped to Version 16.17.0
# [16.17.0](https://github.com/frappe/erpnext/compare/v16.16.0...v16.17.0) (2026-05-05)

### Bug Fixes

* accounts and account types in German CoA "SKR 03" (backport [#54711](https://github.com/frappe/erpnext/issues/54711)) ([#54713](https://github.com/frappe/erpnext/issues/54713)) ([982810a](982810a700))
* add missing fields in set_currency_labels (backport [#54689](https://github.com/frappe/erpnext/issues/54689)) ([#54690](https://github.com/frappe/erpnext/issues/54690)) ([bca893a](bca893a508))
* Backfill `not_applicable` on Item Tax Template Details for German companies (backport [#54682](https://github.com/frappe/erpnext/issues/54682)) ([#54686](https://github.com/frappe/erpnext/issues/54686)) ([a22d773](a22d773341))
* copy project from first row to new rows (backport [#53295](https://github.com/frappe/erpnext/issues/53295)) ([#54620](https://github.com/frappe/erpnext/issues/54620)) ([e24ab72](e24ab72c0d))
* correct project filter in buying doctypes (backport [#54644](https://github.com/frappe/erpnext/issues/54644)) ([#54652](https://github.com/frappe/erpnext/issues/54652)) ([86cf256](86cf256358))
* correct titles set to {customer_name} or {supplier_name} text strings (backport [#54656](https://github.com/frappe/erpnext/issues/54656)) ([#54669](https://github.com/frappe/erpnext/issues/54669)) ([38cfeb1](38cfeb1bb7))
* dont show serial/batch button when PR is submitted (backport [#54642](https://github.com/frappe/erpnext/issues/54642)) ([#54646](https://github.com/frappe/erpnext/issues/54646)) ([6dbc17d](6dbc17d71a))
* error when creating quotation from CRM (backport [#54722](https://github.com/frappe/erpnext/issues/54722)) ([#54725](https://github.com/frappe/erpnext/issues/54725)) ([2cd4c1a](2cd4c1a052))
* hide payment and payment request buttons based on permissions in invoices and orders (backport [#53920](https://github.com/frappe/erpnext/issues/53920)) ([#54736](https://github.com/frappe/erpnext/issues/54736)) ([e60490d](e60490dceb))
* incorrect expense account book in purchase return (backport [#54681](https://github.com/frappe/erpnext/issues/54681)) ([#54693](https://github.com/frappe/erpnext/issues/54693)) ([0dade2c](0dade2c38c))
* mark item tax templates as not applicable (backport [#54673](https://github.com/frappe/erpnext/issues/54673)) ([#54677](https://github.com/frappe/erpnext/issues/54677)) ([126e13b](126e13be25))
* **payment_entry:** convert the date args to string type before escaping in `get_outstanding_reference_documents` (backport [#54639](https://github.com/frappe/erpnext/issues/54639)) ([#54648](https://github.com/frappe/erpnext/issues/54648)) ([19a8ebe](19a8ebe8a5))
* **project:** use user.email for invitations and skip disabled users. (backport [#54561](https://github.com/frappe/erpnext/issues/54561)) ([#54667](https://github.com/frappe/erpnext/issues/54667)) ([288cdf3](288cdf3bf0))
* py error on sales forecast doctype (backport [#54641](https://github.com/frappe/erpnext/issues/54641)) ([#54643](https://github.com/frappe/erpnext/issues/54643)) ([7bd360a](7bd360aa29))
* Remove bom stock report link from manufacturing workspace ([0f27881](0f27881fed))
* **selling:** blanket order ordered qty recalculation on sales order status change (backport [#54593](https://github.com/frappe/erpnext/issues/54593)) ([#54623](https://github.com/frappe/erpnext/issues/54623)) ([9db03bc](9db03bc520))
* set valid_from in created Item Price (backport [#54696](https://github.com/frappe/erpnext/issues/54696)) ([#54700](https://github.com/frappe/erpnext/issues/54700)) ([bbb4e79](bbb4e79d0a))
* show correct status in Serial No Ledger (backport [#54567](https://github.com/frappe/erpnext/issues/54567)) ([#54626](https://github.com/frappe/erpnext/issues/54626)) ([d6f2ff6](d6f2ff6b87))
* show in and out qty in the stock ledger report for stock recos ([d27cf48](d27cf48b19))
* skip depreciation rescheduling when asset is fully depreciated on sale ([d3c893d](d3c893d08b))
* skip rescheduling only for asset being disposed ([07a957c](07a957c164))
* use RecoverableErrors isinstance check for repost timeout status (backport [#54543](https://github.com/frappe/erpnext/issues/54543)) ([#54649](https://github.com/frappe/erpnext/issues/54649)) ([b300159](b3001595ab))

### Features

* copy terms attachments to transactions (backport [#53403](https://github.com/frappe/erpnext/issues/53403)) ([#54661](https://github.com/frappe/erpnext/issues/54661)) ([bd932da](bd932da08b))
* **ux:** Naming series dialog ([#54554](https://github.com/frappe/erpnext/issues/54554)) ([48ebb4c](48ebb4ca61))

### Performance Improvements

* max recursion depth error in serial no (backport [#54629](https://github.com/frappe/erpnext/issues/54629)) ([#54631](https://github.com/frappe/erpnext/issues/54631)) ([808214f](808214fd95))
2026-05-05 16:32:20 +00:00
diptanilsaha
ed69dafbe8 Merge pull request #54740 from frappe/version-16-hotfix 2026-05-05 22:00:39 +05:30
Nishka Gosalia
c985f94009 Merge pull request #54743 from frappe/mergify/bp/version-16-hotfix/pr-54732
fix: Remove bom stock report link from manufacturing workspace (backport #54732)
2026-05-05 16:44:55 +05:30
nishkagosalia
0f27881fed fix: Remove bom stock report link from manufacturing workspace
(cherry picked from commit f86568b078)
2026-05-05 10:51:13 +00:00
mergify[bot]
e60490dceb fix: hide payment and payment request buttons based on permissions in invoices and orders (backport #53920) (#54736)
Co-authored-by: Sakthivel Murugan S <129778327+ssakthivelmurugan@users.noreply.github.com>
Co-authored-by: ravibharathi656 <ravibharathi656@gmail.com>
fix: hide payment and payment request buttons based on permissions in invoices and orders (#53920)
2026-05-05 12:17:57 +05:30
mergify[bot]
2cd4c1a052 fix: error when creating quotation from CRM (backport #54722) (#54725)
fix: error when creating quotation from CRM (#54722)

(cherry picked from commit 2d3190effb)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-04 16:04:03 +00:00
mergify[bot]
982810a700 fix: accounts and account types in German CoA "SKR 03" (backport #54711) (#54713)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: accounts and account types in German CoA "SKR 03" (#54711)
2026-05-03 17:49:02 +00:00
MochaMind
18006b978f chore: update POT file (#54710) 2026-05-03 14:24:28 +02:00
mergify[bot]
bbb4e79d0a fix: set valid_from in created Item Price (backport #54696) (#54700)
* fix: set valid_from in created Item Price (#54696)

Co-authored-by: Kaajal-Chhattani <kaajal.chhattani@aurigait.com>
(cherry picked from commit 6246a9aa6e)

# Conflicts:
#	erpnext/stock/get_item_details.py

* chore: resolve conflicts

---------

Co-authored-by: Kaajalchhattani <89331214+Kaajalchhattani@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-02 16:45:52 +00:00
mergify[bot]
bca893a508 fix: add missing fields in set_currency_labels (backport #54689) (#54690)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: add missing fields in set_currency_labels (#54689)
2026-05-01 14:39:39 +02:00
mergify[bot]
0dade2c38c fix: incorrect expense account book in purchase return (backport #54681) (#54693)
fix: incorrect expense account book in purchase return

(cherry picked from commit 2a720e7008)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-05-01 12:47:05 +05:30
mergify[bot]
a22d773341 fix: Backfill not_applicable on Item Tax Template Details for German companies (backport #54682) (#54686)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: Backfill `not_applicable` on Item Tax Template Details for German companies (#54682)
2026-05-01 04:29:06 +02:00
mergify[bot]
126e13be25 fix: mark item tax templates as not applicable (backport #54673) (#54677)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: mark item tax templates as not applicable (#54673)
2026-04-30 17:52:24 +02:00
mergify[bot]
288cdf3bf0 fix(project): use user.email for invitations and skip disabled users. (backport #54561) (#54667)
fix(project): use user.email for invitations and skip disabled users. (#54561)

* fix(project): use user.email for invitations and skip disabled users.

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



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

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

---------


(cherry picked from commit 231dd1856f)

Co-authored-by: Hemil-Sangani <hemil@sanskartechnolab.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-30 14:35:12 +05:30
rohitwaghchaure
2422237c1a Merge pull request #54671 from frappe/mergify/bp/version-16-hotfix/pr-54664
fix: show in and out qty in the stock ledger report for stock recos (backport #54664)
2026-04-30 14:34:23 +05:30
mergify[bot]
38cfeb1bb7 fix: correct titles set to {customer_name} or {supplier_name} text strings (backport #54656) (#54669)
Co-authored-by: Trusted Computer <75872475+trustedcomputer@users.noreply.github.com>
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
fix: correct titles set to {customer_name} or {supplier_name} text strings (#54656)
2026-04-30 08:52:23 +00:00
Rohit Waghchaure
d27cf48b19 fix: show in and out qty in the stock ledger report for stock recos
(cherry picked from commit da081254a6)
2026-04-30 08:44:26 +00:00
Khushi Rawat
c232f1f450 Merge pull request #54659 from frappe/mergify/bp/version-16-hotfix/pr-54658
fix: skip depreciation rescheduling when asset is fully depreciated on sale (backport #54658)
2026-04-30 11:31:15 +05:30
mergify[bot]
bd932da08b feat: copy terms attachments to transactions (backport #53403) (#54661)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2026-04-29 23:43:28 +02:00
khushi8112
07a957c164 fix: skip rescheduling only for asset being disposed
(cherry picked from commit 88b82383f5)
2026-04-29 21:05:17 +00:00
khushi8112
d3c893d08b fix: skip depreciation rescheduling when asset is fully depreciated on sale
(cherry picked from commit c4155b6c81)
2026-04-29 21:05:17 +00:00
mergify[bot]
b3001595ab fix: use RecoverableErrors isinstance check for repost timeout status (backport #54543) (#54649)
fix: use RecoverableErrors isinstance check for repost timeout status

When a Repost Item Valuation job is killed by an RQ worker timeout
(JobTimeoutException raised via SIGALRM), the existing status detection
relied solely on traceback string matching for 'timeout' or 'Deadlock'.

This is unreliable because SIGALRM can interrupt a C-extension call
(e.g. inside pypika's copy.copy()) before Python records the exception
in the traceback. In that case the traceback shows only the interrupted
frame -- not JobTimeoutException -- so the job is permanently marked
'Failed' instead of 'In Progress', preventing the scheduler from
automatically retrying it.

RecoverableErrors = (JobTimeoutException, QueryDeadlockError,
QueryTimeoutError) is already defined at the top of this file and is
already used further down in the same except block to suppress email
notifications. Extend its use to also guard the status decision.

The traceback string fallback is kept as a secondary check for
forward compatibility with other timeout signals.

Fixes: jobs permanently stuck as 'Failed' after RQ worker timeout,
requiring manual re-queue to resume reposting.

(cherry picked from commit a49e2de866)

Co-authored-by: Assem Bahnasy <bahnasyassem@gmail.com>
2026-04-29 12:02:04 +00:00
mergify[bot]
86cf256358 fix: correct project filter in buying doctypes (backport #54644) (#54652)
fix: correct project filter in buying doctypes (#54644)

(cherry picked from commit a04c028522)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-29 17:28:12 +05:30
mergify[bot]
19a8ebe8a5 fix(payment_entry): convert the date args to string type before escaping in get_outstanding_reference_documents (backport #54639) (#54648)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(payment_entry): convert the date args to string type before escaping in `get_outstanding_reference_documents` (#54639)
2026-04-29 11:45:24 +00:00
mergify[bot]
6dbc17d71a fix: dont show serial/batch button when PR is submitted (backport #54642) (#54646)
fix: dont show serial/batch button when PR is submitted (#54642)

(cherry picked from commit 060defcc2b)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-29 11:32:37 +00:00
mergify[bot]
7bd360aa29 fix: py error on sales forecast doctype (backport #54641) (#54643)
fix: py error on sales forecast doctype (#54641)

fix: py error on sales forecase doctype
(cherry picked from commit d0d8cff48f)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-29 11:13:18 +00:00
Nishka Gosalia
2e438011da Merge pull request #54635 from frappe/mergify/bp/version-16-hotfix/pr-54554 2026-04-29 15:21:22 +05:30
Nishka Gosalia
48ebb4ca61 feat(ux): Naming series dialog (#54554)
(cherry picked from commit 844f3dbc0b)
2026-04-29 09:15:45 +00:00
mergify[bot]
808214fd95 perf: max recursion depth error in serial no (backport #54629) (#54631)
perf: max recursion depth error in serial no (#54629)

(cherry picked from commit 503b5bf140)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-29 08:53:07 +00:00
mergify[bot]
d6f2ff6b87 fix: show correct status in Serial No Ledger (backport #54567) (#54626)
* refactor: extract SN status logic

(cherry picked from commit cb2e6e1e2e)

* fix: show correct status in Serial No Ledger

(cherry picked from commit 2b3e047143)

---------

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2026-04-29 13:55:18 +05:30
mergify[bot]
9db03bc520 fix(selling): blanket order ordered qty recalculation on sales order status change (backport #54593) (#54623)
fix(selling): blanket order ordered qty recalculation on sales order status change (#54593)

(cherry picked from commit d68801e73a)

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
2026-04-29 06:47:55 +00:00
mergify[bot]
e24ab72c0d fix: copy project from first row to new rows (backport #53295) (#54620)
fix: copy project to new item row from parent

(cherry picked from commit 68cc518497)

Co-authored-by: ravibharathi656 <ravibharathi656@gmail.com>
2026-04-29 11:55:46 +05:30
Frappe PR Bot
4d5c665e22 chore(release): Bumped to Version 16.16.0
# [16.16.0](https://github.com/frappe/erpnext/compare/v16.15.1...v16.16.0) (2026-04-28)

### Bug Fixes

* **`get_stock_balance`:** validate inventory dimension fieldnames (backport [#54587](https://github.com/frappe/erpnext/issues/54587)) ([#54589](https://github.com/frappe/erpnext/issues/54589)) ([9f04fcc](9f04fcc190))
* add filter labels and required filters for financial report validation ([e6f0bb6](e6f0bb66e2))
* add party type for dynamic link support ([c6d4802](c6d4802857))
* always exclude pcv entries except for closing account head ([446c111](446c111653))
* avoid double reduction of pe reference outstanding (backport [#54193](https://github.com/frappe/erpnext/issues/54193)) ([#54613](https://github.com/frappe/erpnext/issues/54613)) ([5de4b01](5de4b013ea))
* correct display depends on condition ([#54556](https://github.com/frappe/erpnext/issues/54556)) ([0df38a8](0df38a841e))
* debit credit not equal in purchase transactions for multi currency (backport [#54456](https://github.com/frappe/erpnext/issues/54456)) ([#54564](https://github.com/frappe/erpnext/issues/54564)) ([d9a9a5b](d9a9a5bcde))
* delivery schedule in the sales order ([386f499](386f49978e))
* duplicate entries being shown in batch exists in future transact… (backport [#54604](https://github.com/frappe/erpnext/issues/54604)) ([#54606](https://github.com/frappe/erpnext/issues/54606)) ([1111771](11117710d3))
* **edi:** restrict Code List imports to files and trusted backend URLs (backport [#54137](https://github.com/frappe/erpnext/issues/54137)) ([#54266](https://github.com/frappe/erpnext/issues/54266)) ([2a244d1](2a244d162b)), closes [#54488](https://github.com/frappe/erpnext/issues/54488)
* ensure fiscal year is checked before validating date filters in financial statements ([fba7871](fba78711cc))
* ensure tax withholding entries respect date range of category ([719d982](719d982a07))
* filter opening entries in first year in custom financial statement ([6bd6e62](6bd6e62c8c))
* filter overdue purchase order items by company (backport [#54099](https://github.com/frappe/erpnext/issues/54099)) ([#54611](https://github.com/frappe/erpnext/issues/54611)) ([8f8bf13](8f8bf13b41))
* hide feature flag controlled fields on install ([45dc2c4](45dc2c40fd))
* make inv dimen reqd only in delivery note (backport [#54546](https://github.com/frappe/erpnext/issues/54546)) ([#54552](https://github.com/frappe/erpnext/issues/54552)) ([d56df96](d56df96f73))
* **manufacturing:** remove conversion factor for stock qty (backport [#54525](https://github.com/frappe/erpnext/issues/54525)) ([#54573](https://github.com/frappe/erpnext/issues/54573)) ([f14751d](f14751d538))
* negative quantity check in validate_item_qty (backport [#54559](https://github.com/frappe/erpnext/issues/54559)) ([#54572](https://github.com/frappe/erpnext/issues/54572)) ([f7fa394](f7fa394aea))
* **payment_entry:** escape arguments on invoice and order fetching sql queries (backport [#54582](https://github.com/frappe/erpnext/issues/54582)) ([#54586](https://github.com/frappe/erpnext/issues/54586)) ([5289aa0](5289aa0ab3))
* **PCV:** set correct filters of `from_date` and `to_date` on General Ledger Report on clicking `Ledger` button (backport [#54522](https://github.com/frappe/erpnext/issues/54522)) ([#54524](https://github.com/frappe/erpnext/issues/54524)) ([f3996fb](f3996fb971))
* preserve inventory dimensions when raw materials are reset (backport [#54440](https://github.com/frappe/erpnext/issues/54440)) ([#54493](https://github.com/frappe/erpnext/issues/54493)) ([456e99b](456e99b352))
* py error on stock ageing report (backport [#54467](https://github.com/frappe/erpnext/issues/54467)) ([#54469](https://github.com/frappe/erpnext/issues/54469)) ([090aab3](090aab33fb))
* skip BudgetValidation when cancelling GL entries ([1b14673](1b146738c4))
* **stock:** add stock entry in batch master connection ([62bbe28](62bbe28a72))
* **stock:** remove validation for transfer_qty field (backport [#54542](https://github.com/frappe/erpnext/issues/54542)) ([#54545](https://github.com/frappe/erpnext/issues/54545)) ([cc85370](cc85370d54))
* **stock:** set incoming rate as zero for outward sle (backport [#54514](https://github.com/frappe/erpnext/issues/54514)) ([#54533](https://github.com/frappe/erpnext/issues/54533)) ([cabea2f](cabea2f288))
* **stock:** show available qty in warehouse link field (backport [#54474](https://github.com/frappe/erpnext/issues/54474)) ([#54484](https://github.com/frappe/erpnext/issues/54484)) ([f7b87ed](f7b87ed0e3))
* **stock:** show item code in serial and batch selector dialog ([85d1eb8](85d1eb8379))
* summing of values could be zero even if values exist ([d51ce66](d51ce66cb2))
* update account identification to avoid using name_field in financial statements ([7b60ec8](7b60ec8457))
* update fiscal year filter to use mandatory_depends_on instead of reqd ([6570796](6570796fba))
* update status of quotation in patch (backport [#54577](https://github.com/frappe/erpnext/issues/54577)) ([#54580](https://github.com/frappe/erpnext/issues/54580)) ([134e4b7](134e4b7446))

### Features

* add setting to hide Subscription references across doctypes ([#54576](https://github.com/frappe/erpnext/issues/54576)) ([15b6633](15b6633fc3))
* Add XLSX styling support to custom financial report templates ([#52612](https://github.com/frappe/erpnext/issues/52612)) ([055ff56](055ff56ce4))
* Add XLSX styling support to custom financial report templates (backport [#52612](https://github.com/frappe/erpnext/issues/52612)) ([#54485](https://github.com/frappe/erpnext/issues/54485)) ([df3fbed](df3fbeded2))
* danish_bosnian_address_template (backport [#54093](https://github.com/frappe/erpnext/issues/54093)) ([#54516](https://github.com/frappe/erpnext/issues/54516)) ([5c0d2cb](5c0d2cb474))
* enhance account category with root type ([#53190](https://github.com/frappe/erpnext/issues/53190)) ([96bab08](96bab08ae0))
2026-04-28 21:03:29 +00:00
diptanilsaha
e09487d140 Merge pull request #54583 from frappe/version-16-hotfix 2026-04-29 02:31:54 +05:30
mergify[bot]
5de4b013ea fix: avoid double reduction of pe reference outstanding (backport #54193) (#54613)
* fix: avoid double reduction of pe reference outstanding (#54193)

Co-authored-by: diptanilsaha <diptanil@frappe.io>
(cherry picked from commit d1a80d40c4)

# Conflicts:
#	erpnext/accounts/utils.py

* chore: remove type hints for function parameters

---------

Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-28 20:41:42 +00:00
mergify[bot]
8f8bf13b41 fix: filter overdue purchase order items by company (backport #54099) (#54611)
Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
fix: filter overdue purchase order items by company (#54099)
2026-04-29 01:25:31 +05:30
mergify[bot]
11117710d3 fix: duplicate entries being shown in batch exists in future transact… (backport #54604) (#54606)
fix: duplicate entries being shown in batch exists in future transact… (#54604)

fix: duplicate entries being shown in batch exists in future transactions msg
(cherry picked from commit 54f20de7e3)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-28 22:23:13 +05:30
diptanilsaha
90b07b3db5 Merge branch 'version-16' into version-16-hotfix 2026-04-28 21:49:53 +05:30
Trusted Computer
0d498baa10 refactor: bring back titles on sales transactions and make them optional and visible on purchase transactions (backport #52633) (#54601)
* refactor: bring back titles on sales transactions and make them optional and visible on purchase transactions

* fix: update timestamp in json to UTC
2026-04-28 21:12:41 +05:30
mergify[bot]
deef1696d6 refactor(sms_center): replaced raw SQL queries with Query Builder (backport #54600) (#54603)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-28 15:41:59 +00:00
Lakshit Jain
c6ee18b4d4 Merge pull request #54599 from frappe/mergify/bp/version-16-hotfix/pr-54362
fix: filter opening entries after closing voucher (backport #54362)
2026-04-28 19:04:45 +05:30
Lakshit Jain
2f88fa6731 Merge pull request #54598 from frappe/mergify/bp/version-16-hotfix/pr-54517
fix: always exclude pcv entries except for closing account head (backport #54517)
2026-04-28 19:03:09 +05:30
Lakshit Jain
9c5c87b354 Merge pull request #54594 from frappe/mergify/bp/version-16-hotfix/pr-54479
fix:  Handle mandantory filters for financial statements report (backport #54479)
2026-04-28 18:45:05 +05:30
Smit Vora
64a724baea test: include both accounts to test sum = 0
(cherry picked from commit 590f2ffe28)
2026-04-28 13:13:46 +00:00
Smit Vora
7f32c3aca7 test: opening entries after period closing
(cherry picked from commit 5fc3ca1d4b)
2026-04-28 13:13:46 +00:00
Smit Vora
4675921077 chore: comment
(cherry picked from commit c94b8c41f3)
2026-04-28 13:13:46 +00:00
Smit Vora
d51ce66cb2 fix: summing of values could be zero even if values exist
(cherry picked from commit 7ae91cac01)
2026-04-28 13:13:45 +00:00
vorasmit
6bd6e62c8c fix: filter opening entries in first year in custom financial statement
(cherry picked from commit 3c8a066484)
2026-04-28 13:13:45 +00:00
Smit Vora
fa901946ce test: pcv is excluded from PL accounts
(cherry picked from commit 84aa54c540)
2026-04-28 13:13:18 +00:00
Smit Vora
446c111653 fix: always exclude pcv entries except for closing account head
(cherry picked from commit 0349e7a0b8)
2026-04-28 13:13:18 +00:00
Abdeali Chharchhoda
859b24dd95 chore: minor fix
(cherry picked from commit 3854d2cbf6)
2026-04-28 12:01:21 +00:00
Abdeali Chharchhoda
7b60ec8457 fix: update account identification to avoid using name_field in financial statements
(cherry picked from commit 1fd6c3ba1a)
2026-04-28 12:01:21 +00:00
Abdeali Chharchhoda
e6f0bb66e2 fix: add filter labels and required filters for financial report validation
(cherry picked from commit 4274c2aba3)
2026-04-28 12:01:21 +00:00
Abdeali Chharchhoda
6570796fba fix: update fiscal year filter to use mandatory_depends_on instead of reqd
(cherry picked from commit 79d6a51e1e)
2026-04-28 12:01:20 +00:00
Abdeali Chharchhoda
fba78711cc fix: ensure fiscal year is checked before validating date filters in financial statements
(cherry picked from commit 5a915cb45e)
2026-04-28 12:01:20 +00:00
mergify[bot]
9f04fcc190 fix(get_stock_balance): validate inventory dimension fieldnames (backport #54587) (#54589)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(`get_stock_balance`): validate inventory dimension fieldnames (#54587)
2026-04-28 11:35:16 +00:00
mergify[bot]
5289aa0ab3 fix(payment_entry): escape arguments on invoice and order fetching sql queries (backport #54582) (#54586)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(payment_entry): escape arguments on invoice and order fetching sql queries (#54582)
2026-04-28 10:48:41 +00:00
ruthra kumar
185ef4e273 Merge pull request #54553 from frappe/mergify/bp/version-16-hotfix/pr-54509
fix: hide feature flag controlled fields on install (backport #54509)
2026-04-28 16:04:42 +05:30
ruthra kumar
45dc2c40fd fix: hide feature flag controlled fields on install
(cherry picked from commit 889fdf2f11)
2026-04-28 15:44:28 +05:30
mergify[bot]
386a373c9b chore(sidebar): moved Inactive Customers from CRM to Selling Workspace Sidebar (backport #54578) (#54581)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-28 15:41:36 +05:30
mergify[bot]
134e4b7446 fix: update status of quotation in patch (backport #54577) (#54580)
fix: update status of quotation in patch (#54577)

(cherry picked from commit 2088a01c19)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-28 09:44:16 +00:00
Jatin3128
15b6633fc3 feat: add setting to hide Subscription references across doctypes (#54576) 2026-04-28 13:09:50 +05:30
mergify[bot]
f14751d538 fix(manufacturing): remove conversion factor for stock qty (backport #54525) (#54573)
fix(manufacturing): remove conversion factor for stock qty (#54525)

(cherry picked from commit 6f9089dd5b)

Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
2026-04-28 05:38:29 +00:00
mergify[bot]
f7fa394aea fix: negative quantity check in validate_item_qty (backport #54559) (#54572)
fix: negative quantity check in validate_item_qty (#54559)

Fix negative quantity check in validate_item_qty

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

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

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

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

# Before
if d.qty < 0:

# After
if flt(d.qty) < 0:

(cherry picked from commit 63edd5ddc6)

Co-authored-by: Vinay Mishra <39999379+vinaymishraofficial@users.noreply.github.com>
2026-04-28 05:33:55 +00:00
mergify[bot]
d9a9a5bcde fix: debit credit not equal in purchase transactions for multi currency (backport #54456) (#54564)
fix: debit credit not equal in purchase transactions for multi currency (#54456)

(cherry picked from commit 601581d6f8)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-27 15:23:05 +00:00
Mihir Kandoi
0df38a841e fix: correct display depends on condition (#54556) 2026-04-27 10:08:47 +00:00
mergify[bot]
d56df96f73 fix: make inv dimen reqd only in delivery note (backport #54546) (#54552)
fix: make inv dimen reqd only in delivery note (#54546)

(cherry picked from commit 0aadd1e3a5)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-27 15:10:44 +05:30
mergify[bot]
cc85370d54 fix(stock): remove validation for transfer_qty field (backport #54542) (#54545)
fix(stock): remove validation for transfer_qty field (#54542)

(cherry picked from commit 60a6b38c31)

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
2026-04-27 07:20:16 +00:00
mergify[bot]
ac9aa7f154 refactor: quality inspection item query (backport #54511) (#54540)
* refactor: quality inspection item query (#54511)

(cherry picked from commit be2a4b7b2a)

# Conflicts:
#	erpnext/stock/doctype/quality_inspection/quality_inspection.py

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-27 05:50:37 +00:00
MochaMind
23cac0df83 chore: update POT file (#54535) 2026-04-26 18:55:31 +02:00
mergify[bot]
5c0d2cb474 feat: danish_bosnian_address_template (backport #54093) (#54516)
feat: danish_bosnian_address_template (#54093)

(cherry picked from commit e517eeaaa2)

Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com>
2026-04-26 21:06:52 +05:30
mergify[bot]
cabea2f288 fix(stock): set incoming rate as zero for outward sle (backport #54514) (#54533)
fix(stock): set incoming rate as zero for outward sle

(cherry picked from commit ce37530e70)

Co-authored-by: Sudharsanan11 <sudharsananashok1975@gmail.com>
2026-04-26 20:24:52 +05:30
rohitwaghchaure
4c95daaca5 Merge pull request #54531 from frappe/mergify/bp/version-16-hotfix/pr-54530
fix(stock): show item code in serial and batch selector dialog (backport #54530)
2026-04-26 10:25:51 +05:30
Sudharsanan11
62bbe28a72 fix(stock): add stock entry in batch master connection
(cherry picked from commit fee5bcadb2)
2026-04-26 04:35:42 +00:00
Sudharsanan11
85d1eb8379 fix(stock): show item code in serial and batch selector dialog
(cherry picked from commit f572bc51e1)
2026-04-26 04:35:42 +00:00
mergify[bot]
8de9ac4e34 refactor(UX): selling settings form (backport #54412) (#54527) 2026-04-25 18:01:07 +05:30
mergify[bot]
f3996fb971 fix(PCV): set correct filters of from_date and to_date on General Ledger Report on clicking Ledger button (backport #54522) (#54524)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(PCV): set correct filters of `from_date` and `to_date` on General Ledger Report on clicking `Ledger` button (#54522)
2026-04-25 00:08:27 +05:30
mergify[bot]
764c775e19 refactor: tax witholding report (backport #54449) (backport #54477) (#54519)
* refactor: use consistent report column names

(cherry picked from commit 6dca96b423)
(cherry picked from commit 9276cd7343)

* refactor: how data is built

(cherry picked from commit c3e7f7f02f)
(cherry picked from commit be0e58fb23)

* refactor: better label for entity type

(cherry picked from commit 53666974a3)
(cherry picked from commit fffaf834fd)

* refactor: updated key for withholding_date

(cherry picked from commit 07b023a934)
(cherry picked from commit e6cfdb8e4d)

* test: None is better than zero, as no values exist

(cherry picked from commit b5550f747e)
(cherry picked from commit 40466be9ef)

* refactor: make report extensible by regional apps

(cherry picked from commit f0ea20e579)
(cherry picked from commit 6392126ca5)

* fix: add party type for dynamic link support

(cherry picked from commit b925469c4d)
(cherry picked from commit c6d4802857)

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
2026-04-24 14:39:42 +00:00
Frappe PR Bot
66ec6a4d20 chore(release): Bumped to Version 16.15.1
## [16.15.1](https://github.com/frappe/erpnext/compare/v16.15.0...v16.15.1) (2026-04-24)

### Bug Fixes

* preserve inventory dimensions when raw materials are reset (backport [#54440](https://github.com/frappe/erpnext/issues/54440)) (backport [#54493](https://github.com/frappe/erpnext/issues/54493)) ([#54513](https://github.com/frappe/erpnext/issues/54513)) ([610735d](610735d1c5))
2026-04-24 12:35:07 +00:00
mergify[bot]
610735d1c5 fix: preserve inventory dimensions when raw materials are reset (backport #54440) (backport #54493) (#54513)
* fix: preserve inventory dimensions when raw materials are reset (backport #54440) (#54493)

fix: preserve inventory dimensions when raw materials are reset (#54440)

* fix: preserve inventory dimensions when raw materials are reset

* test: add test case

(cherry picked from commit 0e20e35842)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit 456e99b352)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-24 12:33:34 +00:00
Khushi Rawat
8a5fa64e1d Merge pull request #54504 from frappe/mergify/bp/version-16-hotfix/pr-53314
fix: skip budget validation when cancelling GL entries (backport #53314)
2026-04-24 17:39:05 +05:30
Smit Vora
aecf2c1c0e Merge pull request #54505 from frappe/mergify/bp/version-16-hotfix/pr-54476
fix: ensure tax withholding entries respect date range of category (backport #54476)
2026-04-24 13:39:54 +05:30
ljain112
719d982a07 fix: ensure tax withholding entries respect date range of category
(cherry picked from commit 9ead8d4e3f)
2026-04-24 07:48:53 +00:00
nareshkannasln
1b146738c4 fix: skip BudgetValidation when cancelling GL entries
(cherry picked from commit fa34ebea94)
2026-04-24 06:45:16 +00:00
mergify[bot]
c4010b0581 ci: fix timezone for python mariadb tests (backport #54464) (#54465)
ci: fix timezone for python mariadb tests (#54464)

(cherry picked from commit 0d2da6d86c)

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-23 22:31:16 +00:00
mergify[bot]
456e99b352 fix: preserve inventory dimensions when raw materials are reset (backport #54440) (#54493)
fix: preserve inventory dimensions when raw materials are reset (#54440)

* fix: preserve inventory dimensions when raw materials are reset

* test: add test case

(cherry picked from commit 0e20e35842)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-23 17:37:04 +00:00
mergify[bot]
2a244d162b fix(edi): restrict Code List imports to files and trusted backend URLs (backport #54137) (#54266)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(edi): restrict Code List imports to files and trusted backend URLs (#54137)
fix(edi): hardcode "Code List" DocType in importer (#54488)
2026-04-23 15:36:35 +00:00
mergify[bot]
ddc9ea16cc ci: test correctness pattern (backport #54186) (#54473)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2026-04-23 13:29:15 +00:00
mergify[bot]
f7b87ed0e3 fix(stock): show available qty in warehouse link field (backport #54474) (#54484)
fix(stock): show available qty in warehouse link field (#54474)

(cherry picked from commit ab19b16fe2)

Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
2026-04-23 18:19:28 +05:30
Smit Vora
01b22254e7 Merge pull request #54486 from frappe/mergify/bp/version-16-hotfix/pr-53190 2026-04-23 17:54:28 +05:30
Smit Vora
df3fbeded2 feat: Add XLSX styling support to custom financial report templates (backport #52612) (#54485)
Co-authored-by: Abdeali Chharchhodawala <99460106+Abdeali099@users.noreply.github.com>
2026-04-23 17:50:52 +05:30
Abdeali Chharchhodawala
96bab08ae0 feat: enhance account category with root type (#53190)
(cherry picked from commit f6639db0e9)
2026-04-23 12:05:07 +00:00
Abdeali Chharchhodawala
055ff56ce4 feat: Add XLSX styling support to custom financial report templates (#52612)
(cherry picked from commit c35221852a)
2026-04-23 11:46:01 +00:00
Smit Vora
4f8184ec70 Merge pull request #54477 from frappe/mergify/bp/version-16-hotfix/pr-54449 2026-04-23 15:18:46 +05:30
Smit Vora
c6d4802857 fix: add party type for dynamic link support
(cherry picked from commit b925469c4d)
2026-04-23 09:27:53 +00:00
Smit Vora
6392126ca5 refactor: make report extensible by regional apps
(cherry picked from commit f0ea20e579)
2026-04-23 09:27:53 +00:00
Smit Vora
40466be9ef test: None is better than zero, as no values exist
(cherry picked from commit b5550f747e)
2026-04-23 09:27:53 +00:00
Smit Vora
e6cfdb8e4d refactor: updated key for withholding_date
(cherry picked from commit 07b023a934)
2026-04-23 09:27:53 +00:00
Smit Vora
fffaf834fd refactor: better label for entity type
(cherry picked from commit 53666974a3)
2026-04-23 09:27:52 +00:00
Smit Vora
be0e58fb23 refactor: how data is built
(cherry picked from commit c3e7f7f02f)
2026-04-23 09:27:52 +00:00
Smit Vora
9276cd7343 refactor: use consistent report column names
(cherry picked from commit 6dca96b423)
2026-04-23 09:27:52 +00:00
rohitwaghchaure
dd2763aabc Merge pull request #54472 from frappe/mergify/bp/version-16-hotfix/pr-54471
fix: delivery schedule in the sales order (backport #54471)
2026-04-22 22:11:01 +05:30
Rohit Waghchaure
386f49978e fix: delivery schedule in the sales order
(cherry picked from commit 435db260ee)
2026-04-22 16:33:12 +00:00
mergify[bot]
090aab33fb fix: py error on stock ageing report (backport #54467) (#54469)
fix: py error on stock ageing report (#54467)

(cherry picked from commit f5357c233d)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-22 14:41:13 +00:00
ruthra kumar
99bc2c174b Merge pull request #54463 from frappe/mergify/bp/version-16-hotfix/pr-54447
refactor(test): remove explicit sql delete calls (backport #54447)
2026-04-22 11:34:10 +05:30
ruthra kumar
91a748d9bf refactor(test): remove explicit sql delete calls
(cherry picked from commit b16dd3f2dd)
2026-04-22 05:43:48 +00:00
Frappe PR Bot
c99b9e1b64 chore(release): Bumped to Version 16.15.0
# [16.15.0](https://github.com/frappe/erpnext/compare/v16.14.0...v16.15.0) (2026-04-22)

### Bug Fixes

* **accounts:** fetch project name from payment entry to journal entry (backport [#54307](https://github.com/frappe/erpnext/issues/54307)) ([#54453](https://github.com/frappe/erpnext/issues/54453)) ([62a9a76](62a9a761b7))
* add portal user ownership check to supplier quotation (backport [#54298](https://github.com/frappe/erpnext/issues/54298)) ([#54300](https://github.com/frappe/erpnext/issues/54300)) ([d7da5b0](d7da5b047d))
* add project filter to accounts payable and receivable reports (backport [#54344](https://github.com/frappe/erpnext/issues/54344)) ([#54442](https://github.com/frappe/erpnext/issues/54442)) ([57cd2a0](57cd2a06e8))
* append row level user remarks in gl map ([aa359ad](aa359aded4))
* changed qty validation from qty field to stock_qty (backport [#54352](https://github.com/frappe/erpnext/issues/54352)) ([#54357](https://github.com/frappe/erpnext/issues/54357)) ([fa76e8a](fa76e8ac7f))
* clear conditions table when calculate_based_on is set to Fixed ([7849733](78497336c7))
* clear shipping rule conditions for fixed shipping rule ([319d769](319d769c6f))
* **dashboard-trends:** set default fiscal year and company before val… (backport [#54339](https://github.com/frappe/erpnext/issues/54339)) ([#54400](https://github.com/frappe/erpnext/issues/54400)) ([b1825c0](b1825c0cbe))
* default company perms for HR manager ([47abaf7](47abaf70b2))
* default perm for HR manager & HR user ([95213fb](95213fb9b8))
* default perm for HR manager & HR user ([a7b1fec](a7b1fec21d))
* default permission for HR manager role ([534891a](534891aac4))
* default permission for HR User role ([0d6d64f](0d6d64ff05))
* Disallow negative rates in Purchase invoice (backport [#54254](https://github.com/frappe/erpnext/issues/54254)) ([#54393](https://github.com/frappe/erpnext/issues/54393)) ([cac9073](cac907383b))
* dropship logic should come above non stock logic in gross profit… (backport [#54383](https://github.com/frappe/erpnext/issues/54383)) ([#54385](https://github.com/frappe/erpnext/issues/54385)) ([78aaf6c](78aaf6c7e8))
* fetch item tax template from item group when creating item (backport [#54258](https://github.com/frappe/erpnext/issues/54258)) ([#54368](https://github.com/frappe/erpnext/issues/54368)) ([3914d5d](3914d5d1b7))
* hide operations field in bom creator if phantom (backport [#54336](https://github.com/frappe/erpnext/issues/54336)) ([#54337](https://github.com/frappe/erpnext/issues/54337)) ([b252ad4](b252ad49b7))
* make Target Warehouse mandatory on UI ([46f5de0](46f5de0b1c))
* **manufacturing:** handle empty list in query builder ([d2cc549](d2cc549696))
* move make_dimension_in_accounting_doctypes from after_insert to on_update ([f287edd](f287edd8c2))
* negative batch report showing same batch-warehouse multiple times ([493f36b](493f36b3ce))
* non-collapsible in customer quick entry ([101f68c](101f68c8e8))
* **pos_invoice_item:** fetch `grant_commission` from `item_code` (backport [#54413](https://github.com/frappe/erpnext/issues/54413)) ([#54418](https://github.com/frappe/erpnext/issues/54418)) ([dd6d4d1](dd6d4d1910))
* **purchase_register:** filter tax rows by parenttype in invoice tax map query (backport [#54272](https://github.com/frappe/erpnext/issues/54272)) ([#54444](https://github.com/frappe/erpnext/issues/54444)) ([01aff64](01aff6492c))
* recalculate operating costs if workstation type is changed (backport [#54390](https://github.com/frappe/erpnext/issues/54390)) ([#54398](https://github.com/frappe/erpnext/issues/54398)) ([cfcba1f](cfcba1fcf2))
* remove unwanted perm for HR user role ([4940aeb](4940aeb712))
* reset base_rounded_total when rounded_total resets (backport [#54241](https://github.com/frappe/erpnext/issues/54241)) ([#54304](https://github.com/frappe/erpnext/issues/54304)) ([45052ce](45052ce8a7))
* resolve conflict ([9e6300b](9e6300bf76))
* sales order is not valid when creating WO from MR from PP (backport [#54435](https://github.com/frappe/erpnext/issues/54435)) ([#54436](https://github.com/frappe/erpnext/issues/54436)) ([5397b7d](5397b7da25))
* Table row in dialog should not have delete row option ([5916e57](5916e570af))
* **taxes_and_totals:** apply conversion_rate to taxable_amount in get_itemised_tax ([d506e57](d506e574d2))
* **test:** missing repost allowed defaults ([d49c343](d49c34389b))
* use qty instead of stock qty dropship gross profit report (backport [#54389](https://github.com/frappe/erpnext/issues/54389)) ([#54391](https://github.com/frappe/erpnext/issues/54391)) ([7556550](7556550158))
* validate south africa company in vat audit report (backport [#54030](https://github.com/frappe/erpnext/issues/54030)) ([#54394](https://github.com/frappe/erpnext/issues/54394)) ([aa2cba9](aa2cba9780))
* zero valuation rate popup on SI (backport [#54376](https://github.com/frappe/erpnext/issues/54376)) ([#54377](https://github.com/frappe/erpnext/issues/54377)) ([104eac2](104eac21e8))

### Features

* add option to create production plan from sales order (backport [#53662](https://github.com/frappe/erpnext/issues/53662)) ([#54323](https://github.com/frappe/erpnext/issues/54323)) ([b487f69](b487f69b59))
* add support for 'not applicable' tax in item tax templates ([#50898](https://github.com/frappe/erpnext/issues/50898)) ([52a4ca9](52a4ca9c41))
* backflush based on in BOM ([2c73e37](2c73e37f80))
* make fg phantom-able in bom creator (backport [#54332](https://github.com/frappe/erpnext/issues/54332)) ([#54333](https://github.com/frappe/erpnext/issues/54333)) ([10dbfd3](10dbfd310f))
* use single remark field with custom remark toggle ([27c5dab](27c5dab7e4))
2026-04-22 00:21:02 +00:00
diptanilsaha
a9747213f5 Merge pull request #54437 from frappe/version-16-hotfix 2026-04-22 05:49:31 +05:30
mergify[bot]
5923618df3 refactor(test): move contact and address creation to bootstrap (backport #54406) (#54410)
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-04-22 00:24:36 +05:30
MochaMind
193a44f298 chore: update POT file (#54401) 2026-04-22 00:16:52 +05:30
Khushi Rawat
d9731d7c72 Merge pull request #54427 from frappe/mergify/bp/version-16-hotfix/pr-54131
feat: use single remark field with custom remark toggle (backport #54131)
2026-04-22 00:05:24 +05:30
mergify[bot]
457adcee95 refactor: fix test cases in tax withholding details report (backport #54422) (#54445)
Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-21 23:58:57 +05:30
mergify[bot]
01aff6492c fix(purchase_register): filter tax rows by parenttype in invoice tax map query (backport #54272) (#54444)
fix(purchase_register): filter tax rows by parenttype in invoice tax map query

(cherry picked from commit 3aeb7d6b01)

Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-21 23:58:25 +05:30
mergify[bot]
57cd2a06e8 fix: add project filter to accounts payable and receivable reports (backport #54344) (#54442)
Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-21 23:56:35 +05:30
mergify[bot]
62a9a761b7 fix(accounts): fetch project name from payment entry to journal entry (backport #54307) (#54453)
Co-authored-by: sarathibalamurugan <sarathigreen4@gmail.com>
2026-04-21 23:54:56 +05:30
Khushi Rawat
9e6300bf76 fix: resolve conflict 2026-04-21 16:15:21 +05:30
mergify[bot]
5397b7da25 fix: sales order is not valid when creating WO from MR from PP (backport #54435) (#54436)
fix: sales order is not valid when creating WO from MR from PP (#54435)

(cherry picked from commit e65b9fc2ae)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-21 15:44:26 +05:30
mergify[bot]
37d080bdb4 refactor: Sales Partner Commission Summary and Sales Partner Transaction Summary report (backport #54268) (#54431)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-21 09:21:10 +05:30
khushi8112
aa359aded4 fix: append row level user remarks in gl map
(cherry picked from commit 84e5272f5d)
2026-04-20 19:16:59 +00:00
khushi8112
27c5dab7e4 feat: use single remark field with custom remark toggle
(cherry picked from commit 697f521e14)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.json
2026-04-20 19:16:58 +00:00
Ravibharathi
4e05277695 Merge pull request #54424 from frappe/mergify/bp/version-16-hotfix/pr-54415
fix: clear conditions table when calculate_based_on is set to Fixed (backport #54415)
2026-04-20 19:44:19 +05:30
ravibharathi656
319d769c6f fix: clear shipping rule conditions for fixed shipping rule
(cherry picked from commit d6bb0ae093)
2026-04-20 13:53:25 +00:00
sarathibalamurugan
78497336c7 fix: clear conditions table when calculate_based_on is set to Fixed
(cherry picked from commit d73920be12)
2026-04-20 13:53:25 +00:00
rohitwaghchaure
d5ea039e07 Merge pull request #54414 from frappe/mergify/bp/version-16-hotfix/pr-54350
feat: backflush based on in BOM (backport #54350)
2026-04-20 17:57:33 +05:30
mergify[bot]
4bb30a9157 test(BootStrapTestData): create sales_partner test data while bootstrapping (backport #54416) (#54421)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-20 12:03:23 +00:00
mergify[bot]
dd6d4d1910 fix(pos_invoice_item): fetch grant_commission from item_code (backport #54413) (#54418)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(pos_invoice_item): fetch `grant_commission` from `item_code` (#54413)
2026-04-20 11:47:15 +00:00
Rohit Waghchaure
2c73e37f80 feat: backflush based on in BOM
(cherry picked from commit 877d99c5a5)
2026-04-20 11:07:19 +00:00
Raheel Khan
3bee79b90d Merge pull request #54407 from frappe/mergify/bp/version-16-hotfix/pr-53756
fix(hrms): default permission for HR roles (backport #53756)
2026-04-20 12:21:31 +05:30
iamkhanraheel
47abaf70b2 fix: default company perms for HR manager
(cherry picked from commit 2018a90ad8)
2026-04-20 05:50:43 +00:00
iamkhanraheel
4940aeb712 fix: remove unwanted perm for HR user role
(cherry picked from commit d26cd69fe5)
2026-04-20 05:50:42 +00:00
iamkhanraheel
95213fb9b8 fix: default perm for HR manager & HR user
(cherry picked from commit 41103a0622)
2026-04-20 05:50:42 +00:00
iamkhanraheel
a7b1fec21d fix: default perm for HR manager & HR user
(cherry picked from commit f02b3b6166)
2026-04-20 05:50:42 +00:00
iamkhanraheel
534891aac4 fix: default permission for HR manager role
(cherry picked from commit 5ec66169a7)
2026-04-20 05:50:42 +00:00
iamkhanraheel
0d6d64ff05 fix: default permission for HR User role
(cherry picked from commit 7b0bfe76cc)
2026-04-20 05:50:41 +00:00
mergify[bot]
fa76e8ac7f fix: changed qty validation from qty field to stock_qty (backport #54352) (#54357)
fix: changed qty validation from qty field to stock_qty (#54352)

(cherry picked from commit ba01d66c24)

Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
2026-04-20 10:54:04 +05:30
ruthra kumar
abed348121 Merge pull request #54366 from frappe/mergify/bp/version-16-hotfix/pr-50898
feat: add support for 'not applicable' tax in item tax templates (backport #50898)
2026-04-20 10:17:00 +05:30
mergify[bot]
b1825c0cbe fix(dashboard-trends): set default fiscal year and company before val… (backport #54339) (#54400)
* 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>
(cherry picked from commit d61b5fd5f6)

# Conflicts:
#	erpnext/controllers/trends.py

* chore: fix conflicts

---------

Co-authored-by: Ahmed AbuKhatwa <82771130+AhmedAbokhatwa@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 09:52:45 +00:00
mergify[bot]
cfcba1fcf2 fix: recalculate operating costs if workstation type is changed (backport #54390) (#54398)
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

(cherry picked from commit 28f3429a54)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 08:06:09 +00:00
mergify[bot]
d542a72da5 Fix : None handling in pricing rule free item quantity calculation (backport #54375) (#54396)
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

---------



(cherry picked from commit 82438d6c72)

Co-authored-by: Jaganath-Tridots <jaganath@tridotstech.com>
Co-authored-by: Jagan <jagan@DESKTOP-HPDMQ06.localdomain>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 08:02:03 +00:00
mergify[bot]
aa2cba9780 fix: validate south africa company in vat audit report (backport #54030) (#54394)
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

(cherry picked from commit 1c65cc1088)

Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
2026-04-19 07:57:13 +00:00
mergify[bot]
cac907383b fix: Disallow negative rates in Purchase invoice (backport #54254) (#54393)
fix: Disallow negative rates in Purchase invoice (#54254)

(cherry picked from commit 23768ae0a5)

Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
2026-04-19 07:46:21 +00:00
mergify[bot]
7556550158 fix: use qty instead of stock qty dropship gross profit report (backport #54389) (#54391)
fix: use qty instead of stock qty dropship gross profit report (#54389)

(cherry picked from commit d6b379b936)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 12:52:15 +05:30
mergify[bot]
78aaf6c7e8 fix: dropship logic should come above non stock logic in gross profit… (backport #54383) (#54385)
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
(cherry picked from commit 40bcaa7bc3)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-18 16:37:43 +00:00
mergify[bot]
104eac21e8 fix: zero valuation rate popup on SI (backport #54376) (#54377)
fix: zero valuation rate popup on SI (#54376)

(cherry picked from commit 3ef6c24f07)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-18 12:05:36 +00:00
mergify[bot]
3914d5d1b7 fix: fetch item tax template from item group when creating item (backport #54258) (#54368)
fix: fetch item tax template from item group when creating item (#54258)

(cherry picked from commit b93f2350ee)

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
2026-04-18 06:49:44 +00:00
Lakshit Jain
52a4ca9c41 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

(cherry picked from commit 453fe376ab)
2026-04-18 06:05:36 +00:00
rohitwaghchaure
de66fd0c58 Merge pull request #54361 from frappe/mergify/bp/version-16-hotfix/pr-54355
fix(manufacturing): handle empty list in query builder (backport #54355)
2026-04-17 21:43:14 +05:30
rohitwaghchaure
450b4c2f5f Merge pull request #54360 from frappe/mergify/bp/version-16-hotfix/pr-54354
fix: negative batch report showing same batch-warehouse multiple times (backport #54354)
2026-04-17 21:32:28 +05:30
Pandiyan37
d2cc549696 fix(manufacturing): handle empty list in query builder
(cherry picked from commit 9e5d94c1e6)
2026-04-17 15:52:18 +00:00
Rohit Waghchaure
493f36b3ce fix: negative batch report showing same batch-warehouse multiple times
(cherry picked from commit 700572980d)
2026-04-17 15:42:03 +00:00
Nishka Gosalia
cd605d35c5 Merge pull request #54348 from frappe/mergify/bp/version-16-hotfix/pr-54074
fix: Table row in dialog should not have delete row option (backport #54074)
2026-04-17 17:07:40 +05:30
nishkagosalia
5916e570af fix: Table row in dialog should not have delete row option
(cherry picked from commit eb89903dec)
2026-04-17 10:40:26 +00:00
Nishka Gosalia
4d300f7d34 Merge pull request #54347 from frappe/mergify/bp/version-16-hotfix/pr-54345 2026-04-17 16:08:41 +05:30
nishkagosalia
36cc39ddc6 refactor(UX): Batch Form Cleanup
(cherry picked from commit de747fe625)
2026-04-17 09:44:19 +00:00
rohitwaghchaure
afd25508d6 Merge pull request #54343 from frappe/mergify/bp/version-16-hotfix/pr-54342
fix: make Target Warehouse mandatory on UI for WO (backport #54342)
2026-04-17 13:27:35 +05:30
Rohit Waghchaure
46f5de0b1c fix: make Target Warehouse mandatory on UI
(cherry picked from commit 2a8267e10a)
2026-04-17 07:54:10 +00:00
mergify[bot]
b252ad49b7 fix: hide operations field in bom creator if phantom (backport #54336) (#54337) 2026-04-16 16:11:45 +00:00
mergify[bot]
10dbfd310f feat: make fg phantom-able in bom creator (backport #54332) (#54333) 2026-04-16 19:17:51 +05:30
ruthra kumar
07bcaab33b Merge pull request #54328 from frappe/mergify/bp/version-16-hotfix/pr-54327
fix(test): missing repost allowed defaults (backport #54327)
2026-04-16 17:20:04 +05:30
ruthra kumar
9d969d5af5 Merge pull request #54321 from frappe/mergify/bp/version-16-hotfix/pr-54301
refactor(ux): merge repost settings to accounts settings (backport #54301)
2026-04-16 17:19:48 +05:30
ruthra kumar
2e7c4776d4 refactor: delete redundent repost setting
(cherry picked from commit 6a04c159ca)
2026-04-16 16:55:38 +05:30
ruthra kumar
5a2933df8f refactor: limit reposting to only supported doctypes
(cherry picked from commit 940d3cfe0a)
2026-04-16 16:55:38 +05:30
ruthra kumar
fa5e4dee17 refactor: remove redundant field from filter
(cherry picked from commit ece85c770f)
2026-04-16 16:55:38 +05:30
ruthra kumar
3ba400a02b refactor(ux): better error message
(cherry picked from commit 3093409933)
2026-04-16 16:55:38 +05:30
ruthra kumar
5c064331cb refactor(test): use new source for repost setting
(cherry picked from commit b8207d5ed1)
2026-04-16 16:55:38 +05:30
ruthra kumar
151864079b refactor: move allowed doctypes to accounts settings
- dropped 'allowed' field

(cherry picked from commit d5c58277cb)
2026-04-16 16:55:37 +05:30
ruthra kumar
f785f36ad6 refactor: merge reposting settings to accounts settings
(cherry picked from commit 89ebf48544)
2026-04-16 16:55:26 +05:30
ruthra kumar
d49c34389b fix(test): missing repost allowed defaults
(cherry picked from commit 257865deb2)
2026-04-16 10:33:04 +00:00
mergify[bot]
ead7744f81 refactor: add category field to uom (backport #54290) (#54325)
* refactor: add category field to uom (#54290)

(cherry picked from commit e04a2e6da2)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-16 10:16:28 +00:00
mergify[bot]
b487f69b59 feat: add option to create production plan from sales order (backport #53662) (#54323)
Co-authored-by: sudarsan2001 <frankel9675@gmail.com>
Co-authored-by: Venkatesh <47534423+venkat102@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-16 15:16:34 +05:30
ruthra kumar
d74e632934 Merge pull request #54318 from frappe/mergify/bp/version-16-hotfix/pr-54172
fix: move make_dimension_in_accounting_doctypes from after_insert to on_update (backport #54172)
2026-04-16 11:29:50 +05:30
ruthra kumar
29ba701432 Merge pull request #54316 from frappe/mergify/bp/version-16-hotfix/pr-52923
fix(taxes_and_totals): apply conversion_rate to taxable_amount in get_itemised_tax (backport #52923)
2026-04-16 11:12:15 +05:30
Shllokkk
f287edd8c2 fix: move make_dimension_in_accounting_doctypes from after_insert to on_update
(cherry picked from commit ee067e6015)
2026-04-16 05:37:36 +00:00
Dharanidharan2813
d506e574d2 fix(taxes_and_totals): apply conversion_rate to taxable_amount in get_itemised_tax
(cherry picked from commit 2e577ed25b)
2026-04-16 05:22:40 +00:00
mergify[bot]
45052ce8a7 fix: reset base_rounded_total when rounded_total resets (backport #54241) (#54304)
* fix: reset base_rounded_total when rounded_total resets

(cherry picked from commit f8d278b733)

# Conflicts:
#	erpnext/controllers/tests/test_taxes_and_totals.py

* chore: spelling mistake

(cherry picked from commit e2ac476587)

* chore: resolve conflicts

---------

Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-16 10:39:17 +05:30
NaviN
4f9f90738a Merge pull request #54309 from frappe/mergify/bp/version-16-hotfix/pr-54306
fix: non-collapsible in customer quick entry (backport #54306)
2026-04-15 17:32:56 +05:30
PKSowmiya05
101f68c8e8 fix: non-collapsible in customer quick entry
(cherry picked from commit 53e120269d)
2026-04-15 11:56:54 +00:00
ruthra kumar
ffebb86846 refactor(company): don't force set service expense account on save (backport #54275) (#54305)
* refactor(company): don't force set service expense account on save

(cherry picked from commit 927f40b296)

* refactor(test): set dependant value in company master

(cherry picked from commit 299e141cee)

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-04-15 15:55:53 +05:30
ruthra kumar
6cc560a579 refactor(test): set dependant value in company master
(cherry picked from commit 299e141cee)
2026-04-15 10:04:00 +00:00
ruthra kumar
47e78bd4b9 refactor(company): don't force set service expense account on save
(cherry picked from commit 927f40b296)
2026-04-15 10:03:59 +00:00
mergify[bot]
d7da5b047d fix: add portal user ownership check to supplier quotation (backport #54298) (#54300)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: add portal user ownership check to supplier quotation (#54298)
2026-04-15 06:13:11 +00:00
Frappe PR Bot
9312781dcd chore(release): Bumped to Version 16.14.0
# [16.14.0](https://github.com/frappe/erpnext/compare/v16.13.3...v16.14.0) (2026-04-14)

### Bug Fixes

* account change in warehouse (backport [#54182](https://github.com/frappe/erpnext/issues/54182)) ([#54205](https://github.com/frappe/erpnext/issues/54205)) ([b42e239](b42e23993d))
* add closing div tab ([7e5297a](7e5297a305))
* add drop ship logic in gross profit report (backport [#54220](https://github.com/frappe/erpnext/issues/54220)) ([#54277](https://github.com/frappe/erpnext/issues/54277)) ([bd6269b](bd6269b9e7))
* add permission validation when prompting company details for incomplete letterhead data ([f2450ea](f2450eaf60))
* add quotation print format in the list ([bb77018](bb77018f7b))
* banner to enable serial / batch feature ([dea2d21](dea2d21580))
* batch/serial should use parent's posting datetime for naming (backport [#54206](https://github.com/frappe/erpnext/issues/54206)) ([#54209](https://github.com/frappe/erpnext/issues/54209)) ([3bdac5c](3bdac5c30a))
* conflicting issue ([57e458c](57e458cc1e))
* conflicting issue ([29be73c](29be73c256))
* fetch correct expense account for operations in stock entry (backport [#54278](https://github.com/frappe/erpnext/issues/54278)) ([#54281](https://github.com/frappe/erpnext/issues/54281)) ([63ec36a](63ec36a6f9))
* handle multi uom conversion factor for manufacture entry (backport [#54285](https://github.com/frappe/erpnext/issues/54285)) ([#54286](https://github.com/frappe/erpnext/issues/54286)) ([d5143ed](d5143edcce))
* hardcoded precision causing decimal issues ([e361afb](e361afb6bc))
* inventory dimension patch (backport [#54141](https://github.com/frappe/erpnext/issues/54141)) ([#54146](https://github.com/frappe/erpnext/issues/54146)) ([f2b3ade](f2b3adec0f))
* inventory dimension patch (backport [#54147](https://github.com/frappe/erpnext/issues/54147)) ([#54149](https://github.com/frappe/erpnext/issues/54149)) ([943ddff](943ddff6aa))
* inventory dimensions should not be mandatory unnecesarily (backport [#54064](https://github.com/frappe/erpnext/issues/54064)) ([#54134](https://github.com/frappe/erpnext/issues/54134)) ([7b0d34e](7b0d34e979))
* last SLE not updated in the file ([60a1da0](60a1da0a1b))
* make operation mandatory when any sub operation row is added (backport [#54245](https://github.com/frappe/erpnext/issues/54245)) ([#54248](https://github.com/frappe/erpnext/issues/54248)) ([394eb93](394eb93677))
* **manufacturing:** check remaining qty to calculate operating cost (backport [#53983](https://github.com/frappe/erpnext/issues/53983)) ([#54128](https://github.com/frappe/erpnext/issues/54128)) ([856ba24](856ba24194))
* not able to submit the PO (backport [#54257](https://github.com/frappe/erpnext/issues/54257)) ([#54261](https://github.com/frappe/erpnext/issues/54261)) ([9d90fc4](9d90fc4a84))
* preserve asset movement field properties after save ([b7f1677](b7f1677eef))
* quality inspection item code fetch perm issue (backport [#54121](https://github.com/frappe/erpnext/issues/54121)) ([#54127](https://github.com/frappe/erpnext/issues/54127)) ([9d31712](9d317129f4))
* remove unneccessary function for serial no status updation (backport [#54191](https://github.com/frappe/erpnext/issues/54191)) ([#54197](https://github.com/frappe/erpnext/issues/54197)) ([4e828fd](4e828fd897))
* remove unused print format ([f5a9657](f5a9657a91))
* replace raw SQL with qb in get_against_jv to prevent SQL injection ([8f86a28](8f86a2879c))
* **sales invoice:** toggle Get Items From button based on is_return and POS view (backport [#52594](https://github.com/frappe/erpnext/issues/52594)) ([#54139](https://github.com/frappe/erpnext/issues/54139)) ([fe2161e](fe2161ea0c))
* **selling:** enable selling_settings creation through fixtures (backport [#54177](https://github.com/frappe/erpnext/issues/54177)) ([#54215](https://github.com/frappe/erpnext/issues/54215)) ([d2745f3](d2745f3ec9))
* set default posting time in RIV ([1086a72](1086a72373))
* Set remarks blank instead of No remarks in Sales/Purchase Invoices ([a71814a](a71814a483))
* **stock:** ignore delivery note on delivery trip on_cancel trigger (backport [#54120](https://github.com/frappe/erpnext/issues/54120)) ([#54123](https://github.com/frappe/erpnext/issues/54123)) ([864a7fd](864a7fdab5))
* **stock:** remove float precision to fix precision issue (backport [#54284](https://github.com/frappe/erpnext/issues/54284)) ([#54289](https://github.com/frappe/erpnext/issues/54289)) ([6e3549d](6e3549d185))
* **stock:** update bin to zero when no previous sle exists (backport [#54236](https://github.com/frappe/erpnext/issues/54236)) ([#54264](https://github.com/frappe/erpnext/issues/54264)) ([2c292f4](2c292f4770))
* **test:** Remove usage of No remark as remark in tests ([6993255](699325506f))
* timer not showing in job card (backport [#53839](https://github.com/frappe/erpnext/issues/53839)) ([#54212](https://github.com/frappe/erpnext/issues/54212)) ([7d8f59e](7d8f59eb0a))
* update return value in workstation list view indicator (backport [#54198](https://github.com/frappe/erpnext/issues/54198)) ([#54201](https://github.com/frappe/erpnext/issues/54201)) ([22774fd](22774fd810))
* update_nsm only in warehouse creation ([#54165](https://github.com/frappe/erpnext/issues/54165)) ([abb896e](abb896ecf1))
* wrong operation time calculation (backport [#53796](https://github.com/frappe/erpnext/issues/53796)) ([#54274](https://github.com/frappe/erpnext/issues/54274)) ([d0bff47](d0bff47272))

### Features

* Allowing operation level quality inspection check in BOM (backport [#53859](https://github.com/frappe/erpnext/issues/53859)) ([#54144](https://github.com/frappe/erpnext/issues/54144)) ([233dc7c](233dc7c07b))
* default print format for Quotation ([a8769bf](a8769bfb77))
2026-04-14 18:28:38 +00:00
diptanilsaha
e65596fc0f Merge pull request #54283 from frappe/version-16-hotfix 2026-04-14 23:57:09 +05:30
rohitwaghchaure
bd50a0f318 Merge pull request #54294 from frappe/mergify/bp/version-16-hotfix/pr-54279
fix: banner to enable serial / batch feature (backport #54279)
2026-04-14 23:37:38 +05:30
Rohit Waghchaure
dea2d21580 fix: banner to enable serial / batch feature
(cherry picked from commit 08e8cc8575)
2026-04-14 17:45:19 +00:00
mergify[bot]
2353bcc3fc Revert "fix: sync paid and received amount" (backport #54238) (#54293)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
fix: sync paid and received amount" (#54238)
2026-04-14 22:27:15 +05:30
mergify[bot]
6e3549d185 fix(stock): remove float precision to fix precision issue (backport #54284) (#54289)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): remove float precision to fix precision issue (#54284)
2026-04-14 11:36:24 +00:00
mergify[bot]
d5143edcce fix: handle multi uom conversion factor for manufacture entry (backport #54285) (#54286)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: handle multi uom conversion factor for manufacture entry (#54285)
2026-04-14 16:22:14 +05:30
mergify[bot]
63ec36a6f9 fix: fetch correct expense account for operations in stock entry (backport #54278) (#54281)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: fetch correct expense account for operations in stock entry (#54278)
2026-04-14 16:08:21 +05:30
mergify[bot]
bd6269b9e7 fix: add drop ship logic in gross profit report (backport #54220) (#54277)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: add drop ship logic in gross profit report (#54220)
2026-04-14 09:59:16 +00:00
mergify[bot]
d0bff47272 fix: wrong operation time calculation (backport #53796) (#54274)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: wrong operation time calculation (#53796)
2026-04-14 09:35:00 +00:00
mergify[bot]
9d90fc4a84 fix: not able to submit the PO (backport #54257) (#54261)
Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-04-13 21:44:12 +05:30
mergify[bot]
2c292f4770 fix(stock): update bin to zero when no previous sle exists (backport #54236) (#54264)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): update bin to zero when no previous sle exists (#54236)
2026-04-13 15:57:31 +00:00
mergify[bot]
394eb93677 fix: make operation mandatory when any sub operation row is added (backport #54245) (#54248)
Co-authored-by: Sudarshan <73628063+sudarsan2001@users.noreply.github.com>
fix: make operation mandatory when any sub operation row is added (#54245)
2026-04-13 21:06:01 +05:30
Khushi Rawat
fbb3ccbc28 Merge pull request #54251 from frappe/mergify/bp/version-16-hotfix/pr-54244
fix: replace raw SQL with qb in get_against_jv to prevent SQL injection (backport #54244)
2026-04-13 16:12:43 +05:30
Nishka Gosalia
708b59b519 Merge pull request #54252 from frappe/mergify/bp/version-16-hotfix/pr-54249 2026-04-13 16:10:39 +05:30
Khushi Rawat
de08a972b6 Merge pull request #53745 from frappe/mergify/bp/version-16-hotfix/pr-53588
feat: default print format for Quotation (backport #53588)
2026-04-13 16:04:20 +05:30
Khushi Rawat
512a35a0ab Merge pull request #54253 from frappe/mergify/bp/version-16-hotfix/pr-54190
fix: add permission validation when prompting company details for incomplete letterhead data (backport #54190)
2026-04-13 15:58:05 +05:30
Khushi Rawat
71a563428d Merge branch 'version-16-hotfix' into mergify/bp/version-16-hotfix/pr-53588 2026-04-13 15:41:39 +05:30
khushi8112
57e458cc1e fix: conflicting issue 2026-04-13 15:30:56 +05:30
khushi8112
f2450eaf60 fix: add permission validation when prompting company details for incomplete letterhead data
(cherry picked from commit 256a258b38)
2026-04-13 09:55:12 +00:00
nishkagosalia
847919bf4e refactor(UX): Stock ledger serial and batch number fields
(cherry picked from commit 3e2b40ad4a)
2026-04-13 09:51:46 +00:00
khushi8112
29be73c256 fix: conflicting issue 2026-04-13 15:21:39 +05:30
khushi8112
8f86a2879c fix: replace raw SQL with qb in get_against_jv to prevent SQL injection
(cherry picked from commit c133f7156d)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.py
2026-04-13 09:44:19 +00:00
ruthra kumar
eb80a3704a Merge pull request #54239 from frappe/mergify/bp/version-16-hotfix/pr-54237
refactor: boldface for group accounts in financial statements (backport #54237)
2026-04-13 12:02:19 +05:30
ruthra kumar
bfe58b2d68 refactor: boldface for group accounts in financial statements
(cherry picked from commit 545e9e069a)
2026-04-13 06:11:05 +00:00
MochaMind
39848ffb1e chore: update POT file (#54229) 2026-04-12 10:10:15 +00:00
mergify[bot]
d2745f3ec9 fix(selling): enable selling_settings creation through fixtures (backport #54177) (#54215)
Co-authored-by: mgicking-bmi <mgicking@bmi.com>
Fix(selling): enable selling_settings creation through fixtures (#54177)
2026-04-11 05:32:11 +00:00
mergify[bot]
3bdac5c30a fix: batch/serial should use parent's posting datetime for naming (backport #54206) (#54209) 2026-04-11 05:06:40 +00:00
Frappe PR Bot
a1c43ae913 chore(release): Bumped to Version 16.13.3
## [16.13.3](https://github.com/frappe/erpnext/compare/v16.13.2...v16.13.3) (2026-04-11)

### Bug Fixes

* timer not showing in job card (backport [#53839](https://github.com/frappe/erpnext/issues/53839)) (backport [#54212](https://github.com/frappe/erpnext/issues/54212)) ([#54213](https://github.com/frappe/erpnext/issues/54213)) ([93ede5b](93ede5b764))
2026-04-11 05:05:14 +00:00
mergify[bot]
93ede5b764 fix: timer not showing in job card (backport #53839) (backport #54212) (#54213)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
fix: timer not showing in job card (backport #53839) (#54212)
2026-04-11 10:33:45 +05:30
mergify[bot]
7d8f59eb0a fix: timer not showing in job card (backport #53839) (#54212)
Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-04-11 10:32:32 +05:30
mergify[bot]
66fdd061e7 Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (backport #54187) (#54208)
Co-authored-by: Sambhav Saxena <76242518+sambhavsaxena@users.noreply.github.com>
Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (#54187)
2026-04-10 23:44:42 +05:30
mergify[bot]
b42e23993d fix: account change in warehouse (backport #54182) (#54205)
Co-authored-by: nishkagosalia <nishka.gosalia@gmail.com>
2026-04-10 20:32:45 +05:30
mergify[bot]
22774fd810 fix: update return value in workstation list view indicator (backport #54198) (#54201)
Co-authored-by: Praveenkumar Dhanasekar <164200710+Praveenku-mar@users.noreply.github.com>
fix: update return value in workstation list view indicator (#54198)
2026-04-10 11:20:49 +00:00
mergify[bot]
4e828fd897 fix: remove unneccessary function for serial no status updation (backport #54191) (#54197)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: remove unneccessary function for serial no status updation (#54191)
2026-04-10 10:58:57 +00:00
Nishka Gosalia
3cf90e804d Merge pull request #54180 from frappe/revert-54171-mergify/bp/version-16-hotfix/pr-54165
fix: update_nsm only in warehouse creation (backport #54165)"
2026-04-09 18:31:39 +05:30
Khushi Rawat
1f5d0c58f2 Merge pull request #54166 from frappe/mergify/bp/version-16-hotfix/pr-54142
fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices (backport #54142)
2026-04-09 18:16:04 +05:30
Nishka Gosalia
720a79588d Revert "fix: update_nsm only in warehouse creation (backport #54165)" 2026-04-09 18:13:39 +05:30
Nishka Gosalia
bc03f2399a Merge pull request #54171 from frappe/mergify/bp/version-16-hotfix/pr-54165
fix: update_nsm only in warehouse creation (backport #54165)
2026-04-09 16:29:24 +05:30
Nishka Gosalia
abb896ecf1 fix: update_nsm only in warehouse creation (#54165)
(cherry picked from commit b0e3fa3979)
2026-04-09 10:28:32 +00:00
Frappe PR Bot
c98ded52b2 chore(release): Bumped to Version 16.13.2
## [16.13.2](https://github.com/frappe/erpnext/compare/v16.13.1...v16.13.2) (2026-04-09)

### Bug Fixes

* set default posting time in RIV ([3ce6dcc](3ce6dcc7a7))
2026-04-09 09:59:33 +00:00
rohitwaghchaure
03474c0589 Merge pull request #54164 from frappe/mergify/bp/version-16/pr-54163
fix: set default posting time in RIV (backport #54161) (backport #54163)
2026-04-09 15:27:59 +05:30
khushi8112
699325506f fix(test): Remove usage of No remark as remark in tests
(cherry picked from commit 56416d18d3)
2026-04-09 09:24:51 +00:00
khushi8112
a71814a483 fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices
(cherry picked from commit 2515bf3aff)
2026-04-09 09:24:50 +00:00
Rohit Waghchaure
3ce6dcc7a7 fix: set default posting time in RIV
(cherry picked from commit a7ece65536)
(cherry picked from commit 1086a72373)
2026-04-09 08:55:10 +00:00
rohitwaghchaure
93def4dd13 Merge pull request #54163 from frappe/mergify/bp/version-16-hotfix/pr-54161
fix: set default posting time in RIV (backport #54161)
2026-04-09 14:24:33 +05:30
Rohit Waghchaure
1086a72373 fix: set default posting time in RIV
(cherry picked from commit a7ece65536)
2026-04-09 08:27:15 +00:00
Aarol D'Souza
379a1da254 Merge pull request #54158 from frappe/mergify/bp/version-16-hotfix/pr-54129
refactor: update reset password method name (backport #54129)
2026-04-09 12:32:54 +05:30
mergify[bot]
6ec64216ce Merge branch 'version-16-hotfix' into mergify/bp/version-16-hotfix/pr-54129 2026-04-09 06:42:41 +00:00
mergify[bot]
fe2161ea0c fix(sales invoice): toggle Get Items From button based on is_return and POS view (backport #52594) (#54139)
Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
Co-authored-by: Navin-S-R <navin@aerele.in>
fix(sales invoice): toggle Get Items From button based on is_return and POS view (#52594)
2026-04-09 11:58:02 +05:30
AarDG10
21cf83b915 refactor: update reset password method name
(cherry picked from commit c4d74483e1)
2026-04-09 06:23:58 +00:00
Frappe PR Bot
62448d98de chore(release): Bumped to Version 16.13.1
## [16.13.1](https://github.com/frappe/erpnext/compare/v16.13.0...v16.13.1) (2026-04-09)

### Bug Fixes

* last SLE not updated in the file ([96446ed](96446ed78d))
2026-04-09 04:50:03 +00:00
rohitwaghchaure
3eb58b8d36 Merge pull request #54152 from frappe/mergify/bp/version-16/pr-54151
fix: last SLE not updated in the file (backport #54132) (backport #54151)
2026-04-09 10:18:32 +05:30
mergify[bot]
943ddff6aa fix: inventory dimension patch (backport #54147) (#54149)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: inventory dimension patch (#54147)
2026-04-09 09:46:08 +05:30
Rohit Waghchaure
96446ed78d fix: last SLE not updated in the file
(cherry picked from commit 38ed425ee2)
(cherry picked from commit 60a1da0a1b)
2026-04-09 03:38:10 +00:00
rohitwaghchaure
5866fc6cb4 Merge pull request #54151 from frappe/mergify/bp/version-16-hotfix/pr-54132
fix: last SLE not updated in the file (backport #54132)
2026-04-09 09:07:02 +05:30
Rohit Waghchaure
60a1da0a1b fix: last SLE not updated in the file
(cherry picked from commit 38ed425ee2)
2026-04-09 02:53:27 +00:00
mergify[bot]
f2b3adec0f fix: inventory dimension patch (backport #54141) (#54146)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: inventory dimension patch (#54141)
2026-04-09 02:09:46 +00:00
mergify[bot]
233dc7c07b feat: Allowing operation level quality inspection check in BOM (backport #53859) (#54144)
Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-09 02:02:35 +00:00
mergify[bot]
526c8d0418 refactor(lost_opportunity_report): replaced raw_sql with query builder (backport #54136) (#54140)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-08 18:30:35 +00:00
mergify[bot]
7b0d34e979 fix: inventory dimensions should not be mandatory unnecesarily (backport #54064) (#54134)
* fix: inventory dimensions should not be mandatory unnecesarily (#54064)

(cherry picked from commit 6e44b8913e)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-08 14:43:01 +00:00
mergify[bot]
856ba24194 fix(manufacturing): check remaining qty to calculate operating cost (backport #53983) (#54128)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(manufacturing): check remaining qty to calculate operating cost (#53983)
2026-04-08 12:15:28 +00:00
mergify[bot]
9d317129f4 fix: quality inspection item code fetch perm issue (backport #54121) (#54127)
Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: quality inspection item code fetch perm issue (#54121)
2026-04-08 12:14:41 +00:00
mergify[bot]
864a7fdab5 fix(stock): ignore delivery note on delivery trip on_cancel trigger (backport #54120) (#54123)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): ignore delivery note on delivery trip on_cancel trigger (#54120)
2026-04-08 17:18:35 +05:30
rohitwaghchaure
0bdb7e7894 Merge pull request #54119 from frappe/mergify/bp/version-16-hotfix/pr-54102
fix: hardcoded precision causing decimal issues (backport #54102)
2026-04-08 14:04:23 +05:30
Rohit Waghchaure
e361afb6bc fix: hardcoded precision causing decimal issues
(cherry picked from commit 90fd6f2e40)
2026-04-08 06:50:17 +00:00
Khushi Rawat
7b154c3069 Merge pull request #54117 from frappe/mergify/bp/version-16-hotfix/pr-54103
fix: preserve asset movement field properties after save (backport #54103)
2026-04-08 12:15:55 +05:30
ravibharathi656
b7f1677eef fix: preserve asset movement field properties after save
(cherry picked from commit 4a004a2a82)
2026-04-08 06:28:27 +00:00
Frappe PR Bot
1134e25bb6 chore(release): Bumped to Version 16.13.0
# [16.13.0](https://github.com/frappe/erpnext/compare/v16.12.0...v16.13.0) (2026-04-07)

### Bug Fixes

* add support to fetch items based on manufacture stock entry; fix how it's done from work order ([4232640](4232640a8b))
* add tax_id handling in Tax Withholding Entry (backport [#53598](https://github.com/frappe/erpnext/issues/53598)) ([#54081](https://github.com/frappe/erpnext/issues/54081)) ([dc58754](dc58754a60))
* auto-set source_stock_entry ([eead8d6](eead8d6d8c))
* avg stock entries for disassembly from WO ([0ceb084](0ceb084104))
* conflicts ([66ee208](66ee208cb2))
* correct warehouse preference for disassemble ([919cbd5](919cbd5c02))
* create source_stock_entry to refer to original manufacturing entry ([b91af5b](b91af5b2b9))
* custom button to disassemble manufactured stock entry with work order ([84a063a](84a063a9bf))
* dif_inward_from_outward_workspace_sidebar (backport [#54083](https://github.com/frappe/erpnext/issues/54083)) ([#54088](https://github.com/frappe/erpnext/issues/54088)) ([e6722c8](e6722c84fa))
* disassembly prompt with source stock entry field ([c9d03d0](c9d03d049c))
* divide sub-assembly cost by qty to get per-unit rate in BOM Creator (backport [#54090](https://github.com/frappe/erpnext/issues/54090)) ([#54091](https://github.com/frappe/erpnext/issues/54091)) ([454271a](454271ad68))
* do not repost GL if no change in valuation ([89e3e3c](89e3e3c59e))
* do not show inv dimension unnecessarily in stock entry (backport [#53946](https://github.com/frappe/erpnext/issues/53946)) ([#53951](https://github.com/frappe/erpnext/issues/53951)) ([573a1a0](573a1a0dcb))
* dynamic labels on invoice type change ([4705f53](4705f53d2c))
* ensure accurate rounding for item-wise tax and taxable amounts ([c4c76cc](c4c76cc1b2))
* GL entries for different exchange rate in the purchase invoice ([5719992](5719992cda))
* handle disassembly for secondary / scrap items ([d50279b](d50279b718))
* hide fields related to track Semi-Finished Goods if feature has disabled ([5a7d0d2](5a7d0d2765))
* include rejected qty in tax (purchase receipt) (backport [#53624](https://github.com/frappe/erpnext/issues/53624)) ([#53972](https://github.com/frappe/erpnext/issues/53972)) ([e230f72](e230f72e0b))
* manufacture entry with group_by support ([31ac46a](31ac46ae4c))
* **manufacturing:** handle null cur_dialog in BOM work order dialog (backport [#54011](https://github.com/frappe/erpnext/issues/54011)) ([#54015](https://github.com/frappe/erpnext/issues/54015)) ([01610b2](01610b2fa7))
* Party Field only visibile when party type selected ([f42a1e8](f42a1e8a14))
* prevent selection of group type customer group in customer master ([04cced2](04cced2fb5))
* print hide unnecessary fields ([cd98312](cd98312083))
* process loss with bom path disassembly ([0a257ea](0a257ea63d))
* **promotional_scheme:** toggle enable state between Buying and Selli… (backport [#54110](https://github.com/frappe/erpnext/issues/54110)) ([#54112](https://github.com/frappe/erpnext/issues/54112)) ([4a6fe47](4a6fe477d4))
* rejected serial no field showing even if serial / batch feature not enabled ([2c81f79](2c81f79df7))
* remove null from link_filters ([21f36f5](21f36f5c21))
* remove reference in serial/batch when document is cancelled (backport [#53979](https://github.com/frappe/erpnext/issues/53979)) ([#53989](https://github.com/frappe/erpnext/issues/53989)) ([5aaca83](5aaca83fe4))
* remove title field from purchase receipt (backport [#54051](https://github.com/frappe/erpnext/issues/54051)) ([#54065](https://github.com/frappe/erpnext/issues/54065)) ([84382db](84382db5ca))
* remove unnecessary param, and use value from self ([7bef954](7bef9542d4))
* resolve user permission error on status change by updating user … (backport [#54033](https://github.com/frappe/erpnext/issues/54033)) ([#54060](https://github.com/frappe/erpnext/issues/54060)) ([62b83ca](62b83cacce))
* screen freezes if consumed qty set in SCR ([bd67ef8](bd67ef8d26))
* set bom details on disassembly; abs batch qty ([fb1d865](fb1d865e9b))
* set serial and batch from source stock entry - on disassemble ([ff104ed](ff104edf12))
* set_query for source stock entry ([5f67ef7](5f67ef70bb))
* show current stock qty in Stock Entry PDF (backport [#53761](https://github.com/frappe/erpnext/issues/53761)) ([#54032](https://github.com/frappe/erpnext/issues/54032)) ([ab08162](ab08162f34))
* skip discount amount validation when not saving ([8941699](8941699a34))
* skip validate_stock_accounts in Journal Entry when perpetual inventory is disabled (backport [#53554](https://github.com/frappe/erpnext/issues/53554)) ([#53558](https://github.com/frappe/erpnext/issues/53558)) ([7062b71](7062b7153e))
* **stock:** update stock queue in SABE for return entries ([fc5a04d](fc5a04db2e))
* support creating disassembly (without link of WO) ([1c4b2a7](1c4b2a7148))
* sync paid and received amount (backport [#53039](https://github.com/frappe/erpnext/issues/53039)) ([#54108](https://github.com/frappe/erpnext/issues/54108)) ([df3f242](df3f242331))
* task gantt popup text not visible in light theme (backport [#53882](https://github.com/frappe/erpnext/issues/53882)) ([#54094](https://github.com/frappe/erpnext/issues/54094)) ([995a29e](995a29e3e1))
* **taxes:** improve tax calculation accuracy and update test assertions ([6ad5e89](6ad5e89607))
* **taxes:** increase rounding threshold for tax breakup calculations ([3592637](3592637b5c))
* **test:** do not use is_group enabled customer group in test ([8674aaf](8674aafc86))
* **tests:** update item code and quantity in tax detail test case ([6689b17](6689b17b88))
* transactions where update stock is 0 should not create SLEs (backport [#54035](https://github.com/frappe/erpnext/issues/54035)) ([#54077](https://github.com/frappe/erpnext/issues/54077)) ([af81ed8](af81ed874b))
* update min date based on transaction_date (backport [#53803](https://github.com/frappe/erpnext/issues/53803)) ([#54025](https://github.com/frappe/erpnext/issues/54025)) ([bc86e2c](bc86e2c1f2))
* use get_value ([e4eb88d](e4eb88d80b))
* **ux:** refresh grid to correctly persist the state of fields ([273caa3](273caa38d9))
* validate qty that can be disassembled from source stock entry. ([1237f9a](1237f9a0b1))
* validate work order consistency in stock entry ([b030eea](b030eeafb8))
* **warehouse_capacity_dashboard:** removed `escape` from template (backport [#53907](https://github.com/frappe/erpnext/issues/53907)) ([#53909](https://github.com/frappe/erpnext/issues/53909)) ([a478fb7](a478fb7131))

### Features

* co product by product support ([#52979](https://github.com/frappe/erpnext/issues/52979)) ([#53975](https://github.com/frappe/erpnext/issues/53975)) ([8db397b](8db397bdae))
* croatian_address_template (backport [#53888](https://github.com/frappe/erpnext/issues/53888)) ([#54058](https://github.com/frappe/erpnext/issues/54058)) ([ff26265](ff262655bb))
* **Payment Request:** Added a toggle for using the payment schedule (backport [#53922](https://github.com/frappe/erpnext/issues/53922)) ([#53928](https://github.com/frappe/erpnext/issues/53928)) ([5ade905](5ade905ee8))

### Performance Improvements

* optimize account balance data fetching for Chart Of Accounts (backport [#53044](https://github.com/frappe/erpnext/issues/53044)) ([#53802](https://github.com/frappe/erpnext/issues/53802)) ([093ca87](093ca8745d))
2026-04-07 17:58:52 +00:00
diptanilsaha
aaea4dfcc1 Merge pull request #54100 from frappe/version-16-hotfix 2026-04-07 23:05:35 +05:30
mergify[bot]
7b91566435 refactor: financial report template enhancements (backport #52687) (#54113)
Co-authored-by: Abdeali Chharchhodawala <99460106+Abdeali099@users.noreply.github.com>
2026-04-07 17:10:42 +00:00
mergify[bot]
4a6fe477d4 fix(promotional_scheme): toggle enable state between Buying and Selli… (backport #54110) (#54112)
Co-authored-by: Ahmed AbuKhatwa <82771130+AhmedAbokhatwa@users.noreply.github.com>
Co-authored-by: AhmedAbukhatwa <Ahmedabukhatwa1@gmail.com>
fix(promotional_scheme): toggle enable state between Buying and Selli… (#54110)
2026-04-07 21:55:18 +05:30
mergify[bot]
df3f242331 fix: sync paid and received amount (backport #53039) (#54108)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
fix: sync paid and received amount (#53039)
2026-04-07 13:06:39 +00:00
mergify[bot]
7062b7153e fix: skip validate_stock_accounts in Journal Entry when perpetual inventory is disabled (backport #53554) (#53558)
Co-authored-by: Saeed Kola <mohammedsaeedk@gmail.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-07 12:05:33 +00:00
mergify[bot]
093ca8745d perf: optimize account balance data fetching for Chart Of Accounts (backport #53044) (#53802)
Co-authored-by: Shllokkk <140623894+Shllokkk@users.noreply.github.com>
2026-04-07 16:20:17 +05:30
Smit Vora
52ac36ef02 Merge pull request #54098 from frappe/mergify/bp/version-16-hotfix/pr-53964
fix: consistently disassemble based on source  > SE / WO / BOM (backport #53964)
2026-04-07 15:20:29 +05:30
Smit Vora
9e83badbf5 chore: resolve conflicts 2026-04-07 14:57:56 +05:30
Smit Vora
7bef9542d4 fix: remove unnecessary param, and use value from self
(cherry picked from commit 98dfd64f63)
2026-04-07 08:48:19 +00:00
Smit Vora
6cebea314d test: enhance tests as per review comments
(cherry picked from commit f13d37fbf9)
2026-04-07 08:48:19 +00:00
Smit Vora
d4fde552f4 test: maintain sufficient stock for scrap item
(cherry picked from commit b892139342)
2026-04-07 08:48:19 +00:00
Smit Vora
fb1d865e9b fix: set bom details on disassembly; abs batch qty
(cherry picked from commit ab1fc22431)
2026-04-07 08:48:18 +00:00
Smit Vora
0a257ea63d fix: process loss with bom path disassembly
(cherry picked from commit 93ad48bc1b)
2026-04-07 08:48:18 +00:00
Smit Vora
b030eeafb8 fix: validate work order consistency in stock entry
(cherry picked from commit ea392b2009)
2026-04-07 08:48:18 +00:00
vorasmit
e4eb88d80b fix: use get_value
(cherry picked from commit a71e8bb116)
2026-04-07 08:48:17 +00:00
vorasmit
0ceb084104 fix: avg stock entries for disassembly from WO
(cherry picked from commit 71fd18bdf9)
2026-04-07 08:48:17 +00:00
vorasmit
31ac46ae4c fix: manufacture entry with group_by support
(cherry picked from commit 3cf1ce8360)
2026-04-07 08:48:16 +00:00
Smit Vora
901e626729 test: disassembly for scrap / secondary item
(cherry picked from commit a6d41151ff)
2026-04-07 08:48:16 +00:00
Smit Vora
d50279b718 fix: handle disassembly for secondary / scrap items
(cherry picked from commit 2be8313819)
2026-04-07 08:48:16 +00:00
Smit Vora
e1a4d9fab4 test: disassembly of items with batch and serial numbers
(cherry picked from commit 1693698fed)
2026-04-07 08:48:15 +00:00
Smit Vora
8444778f74 test: additional items in stock entry considered with disassembly
(cherry picked from commit d32977e3a9)
2026-04-07 08:48:15 +00:00
Smit Vora
4c0ebee15b test: disassemble with source stock entry reference
(cherry picked from commit 6988e2cbbc)
2026-04-07 08:48:15 +00:00
Smit Vora
195a10efb3 test: disassembly from wo
(cherry picked from commit 342a14d340)
2026-04-07 08:48:14 +00:00
Smit Vora
ff104edf12 fix: set serial and batch from source stock entry - on disassemble
(cherry picked from commit 13b019ab8e)
2026-04-07 08:48:14 +00:00
Smit Vora
919cbd5c02 fix: correct warehouse preference for disassemble
(cherry picked from commit d3d6b5c660)
2026-04-07 08:48:14 +00:00
Smit Vora
eead8d6d8c fix: auto-set source_stock_entry
(cherry picked from commit 2e4e8bcaa7)
2026-04-07 08:48:13 +00:00
Smit Vora
4232640a8b fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order
(cherry picked from commit 1ed0124ad7)
2026-04-07 08:48:13 +00:00
Smit Vora
1237f9a0b1 fix: validate qty that can be disassembled from source stock entry.
(cherry picked from commit 6394dead72)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py
2026-04-07 08:48:13 +00:00
Smit Vora
1c4b2a7148 fix: support creating disassembly (without link of WO)
(cherry picked from commit dba82720b6)
2026-04-07 08:48:13 +00:00
Smit Vora
84a063a9bf fix: custom button to disassemble manufactured stock entry with work order
(cherry picked from commit b64f86148c)
2026-04-07 08:48:12 +00:00
Smit Vora
5f67ef70bb fix: set_query for source stock entry
(cherry picked from commit b47dfacb3e)
2026-04-07 08:48:12 +00:00
Smit Vora
c9d03d049c fix: disassembly prompt with source stock entry field
(cherry picked from commit 68e97808c5)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py
2026-04-07 08:48:12 +00:00
Smit Vora
b91af5b2b9 fix: create source_stock_entry to refer to original manufacturing entry
(cherry picked from commit d4baa9a74a)
2026-04-07 08:48:11 +00:00
Khushi Rawat
ae89407840 Merge pull request #54095 from frappe/mergify/bp/version-16-hotfix/pr-53394
fix: remove null from link_filters (backport #53394)
2026-04-07 12:57:04 +05:30
ervishnucs
21f36f5c21 fix: remove null from link_filters
(cherry picked from commit a518a735f3)
2026-04-07 07:00:44 +00:00
mergify[bot]
995a29e3e1 fix: task gantt popup text not visible in light theme (backport #53882) (#54094)
Co-authored-by: Sakthivel Murugan S <129778327+ssakthivelmurugan@users.noreply.github.com>
fix: task gantt popup text not visible in light theme (#53882)
2026-04-07 06:40:45 +00:00
mergify[bot]
454271ad68 fix: divide sub-assembly cost by qty to get per-unit rate in BOM Creator (backport #54090) (#54091) 2026-04-07 05:55:55 +00:00
mergify[bot]
ff262655bb feat: croatian_address_template (backport #53888) (#54058)
Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com>
2026-04-07 10:25:44 +05:30
mergify[bot]
62b83cacce fix: resolve user permission error on status change by updating user … (backport #54033) (#54060)
Co-authored-by: Krishna Shirsath <shirsathkrishna19@gmail.com>
2026-04-07 10:25:13 +05:30
mergify[bot]
e6722c84fa fix: dif_inward_from_outward_workspace_sidebar (backport #54083) (#54088)
Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com>
fix: dif_inward_from_outward_workspace_sidebar (#54083)
2026-04-07 04:14:45 +00:00
mergify[bot]
dc58754a60 fix: add tax_id handling in Tax Withholding Entry (backport #53598) (#54081)
Co-authored-by: Lakshit Jain <ljain112@gmail.com>
fix: add tax_id handling in Tax Withholding Entry (#53598)
2026-04-06 17:18:45 +00:00
mergify[bot]
af81ed874b fix: transactions where update stock is 0 should not create SLEs (backport #54035) (#54077)
Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
fix: transactions where update stock is 0 should not create SLEs (#54035)
2026-04-06 15:17:11 +00:00
Smit Vora
14868ab98f Merge pull request #54070 from frappe/mergify/bp/version-16-hotfix/pr-53973
fix(ux): refresh grid to correctly persist the state of fields (backport #53973)
2026-04-06 19:38:55 +05:30
rohitwaghchaure
957a5e1a65 Merge pull request #54069 from frappe/mergify/bp/version-16-hotfix/pr-54050
fix: GL entries for different exchange rate in the purchase invoice (backport #54050)
2026-04-06 17:48:45 +05:30
mergify[bot]
84382db5ca fix: remove title field from purchase receipt (backport #54051) (#54065)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: remove title field from purchase receipt (#54051)
2026-04-06 12:00:04 +00:00
Smit Vora
273caa38d9 fix(ux): refresh grid to correctly persist the state of fields
(cherry picked from commit da778edf48)
2026-04-06 11:56:42 +00:00
Rohit Waghchaure
5719992cda fix: GL entries for different exchange rate in the purchase invoice
(cherry picked from commit a953709640)
2026-04-06 11:53:06 +00:00
Khushi Rawat
2de51be5ae Merge pull request #54062 from frappe/mergify/bp/version-16-hotfix/pr-54052
fix: print hide unnecessary fields (backport #54052)
2026-04-06 16:59:08 +05:30
Khushi Rawat
66ee208cb2 fix: conflicts 2026-04-06 16:19:53 +05:30
khushi8112
cd98312083 fix: print hide unnecessary fields
(cherry picked from commit 8f83616b60)

# Conflicts:
#	erpnext/selling/doctype/sales_order_item/sales_order_item.json
2026-04-06 10:43:49 +00:00
diptanilsaha
7c34cb1134 Merge pull request #54047 from frappe/mergify/bp/version-16-hotfix/pr-54042
fix: skip discount amount validation when not saving (backport #54042)
2026-04-06 13:44:31 +05:30
Sagar Vora
9bc0e3b2ce test: add test for discount amount on partial purchase receipt
Co-authored-by: ravibharathi656 <131471282+ravibharathi656@users.noreply.github.com>
(cherry picked from commit 135cb5fd67)
2026-04-06 07:31:09 +00:00
Sagar Vora
8941699a34 fix: skip discount amount validation when not saving
(cherry picked from commit 0975583388)
2026-04-06 07:31:08 +00:00
rohitwaghchaure
fe6a7ee7ff Merge pull request #54029 from frappe/mergify/bp/version-16-hotfix/pr-54004
fix: do not repost GL if no change in valuation (backport #54004)
2026-04-06 11:47:35 +05:30
mergify[bot]
ab08162f34 fix: show current stock qty in Stock Entry PDF (backport #53761) (#54032) 2026-04-06 05:42:53 +00:00
Rohit Waghchaure
89e3e3c59e fix: do not repost GL if no change in valuation
(cherry picked from commit bb53cce228)
2026-04-06 03:38:41 +00:00
rohitwaghchaure
5c3daee1bb Merge pull request #54010 from frappe/mergify/bp/version-16-hotfix/pr-53994
fix(stock): update stock queue in SABE for return entries (backport #53994)
2026-04-06 09:07:08 +05:30
rohitwaghchaure
baa714b00f Merge pull request #54016 from frappe/mergify/bp/version-16-hotfix/pr-54005
fix: screen freezes if consumed qty set in SCR (backport #54005)
2026-04-05 22:55:44 +05:30
mergify[bot]
bc86e2c1f2 fix: update min date based on transaction_date (backport #53803) (#54025)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
fix: update min date based on transaction_date (#53803)
2026-04-05 21:16:31 +05:30
MochaMind
1c0956c6e2 chore: update POT file (#54017) 2026-04-05 16:02:11 +02:00
Rohit Waghchaure
bd67ef8d26 fix: screen freezes if consumed qty set in SCR
(cherry picked from commit dd7be2b370)
2026-04-05 07:50:38 +00:00
mergify[bot]
01610b2fa7 fix(manufacturing): handle null cur_dialog in BOM work order dialog (backport #54011) (#54015) 2026-04-05 07:19:23 +00:00
kavin-114
d3f1bfc628 test(stock): add unit test to update stock queue for return
(cherry picked from commit e537896df8)
2026-04-04 21:17:30 +00:00
kavin-114
fc5a04db2e fix(stock): update stock queue in SABE for return entries
(cherry picked from commit 0af8077bcc)
2026-04-04 21:17:30 +00:00
rohitwaghchaure
fddf9f381b Merge pull request #53980 from frappe/mergify/bp/version-16-hotfix/pr-53963
fix: hide fields related to track Semi-Finished Goods if feature has disabled (backport #53963)
2026-04-02 18:03:35 +05:30
mergify[bot]
5aaca83fe4 fix: remove reference in serial/batch when document is cancelled (backport #53979) (#53989) 2026-04-02 08:11:20 +00:00
Rohit Waghchaure
5a7d0d2765 fix: hide fields related to track Semi-Finished Goods if feature has disabled
(cherry picked from commit 399faf0ced)
2026-04-01 11:08:10 +00:00
Lakshit Jain
53fc0beae5 Merge pull request #53969 from frappe/mergify/bp/version-16-hotfix/pr-53961
fix: ensure accurate rounding for item-wise tax and taxable amounts (backport #53961)
2026-04-01 14:43:15 +05:30
Mihir Kandoi
8db397bdae feat: co product by product support (#52979) (#53975) 2026-04-01 07:41:39 +00:00
mergify[bot]
e230f72e0b fix: include rejected qty in tax (purchase receipt) (backport #53624) (#53972)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: include rejected qty in tax (purchase receipt) (#53624)
2026-03-31 15:51:58 +00:00
ljain112
9386c1328a test: improve test case
(cherry picked from commit b73b161cbe)
2026-03-31 14:21:12 +00:00
ljain112
c4c76cc1b2 fix: ensure accurate rounding for item-wise tax and taxable amounts
(cherry picked from commit 9b37f2d95c)
2026-03-31 14:21:12 +00:00
Khushi Rawat
240d27274b Merge pull request #53957 from frappe/mergify/bp/version-16-hotfix/pr-53811
fix: prevent selection of group type customer group in customer master (backport #53811)
2026-03-31 18:13:29 +05:30
Nishka Gosalia
e55d0ce1f8 Merge pull request #53966 from frappe/mergify/bp/version-16-hotfix/pr-53965
fix: Party Field only visibile when party type selected (backport #53965)
2026-03-31 18:10:00 +05:30
nishkagosalia
f42a1e8a14 fix: Party Field only visibile when party type selected
(cherry picked from commit e9e510a76e)
2026-03-31 12:38:31 +00:00
khushi8112
8674aafc86 fix(test): do not use is_group enabled customer group in test
(cherry picked from commit 75fa2b2277)
2026-03-31 11:17:17 +00:00
khushi8112
04cced2fb5 fix: prevent selection of group type customer group in customer master
(cherry picked from commit 6068dc959f)
2026-03-31 11:17:17 +00:00
Khushi Rawat
16bd04dd97 Merge pull request #53943 from frappe/mergify/bp/version-16-hotfix/pr-53939
fix: dynamic labels on invoice type change (backport #53939)
2026-03-31 16:47:02 +05:30
ruthra kumar
d129e5f5bc Merge pull request #53948 from frappe/mergify/bp/version-16-hotfix/pr-53795
refactor(test): enforce ERPNextTestSuite across repo (backport #53795)
2026-03-31 16:30:56 +05:30
mergify[bot]
573a1a0dcb fix: do not show inv dimension unnecessarily in stock entry (backport #53946) (#53951)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: do not show inv dimension unnecessarily in stock entry (#53946)
2026-03-31 16:29:29 +05:30
rohitwaghchaure
7c503b33f8 Merge pull request #53954 from frappe/mergify/bp/version-16-hotfix/pr-53953
fix: rejected serial no field showing even if serial / batch feature disabled (backport #53953)
2026-03-31 16:20:56 +05:30
Rohit Waghchaure
2c81f79df7 fix: rejected serial no field showing even if serial / batch feature not enabled
(cherry picked from commit c2f419ac3d)
2026-03-31 10:41:44 +00:00
ruthra kumar
05f47bbf6e refactor(test): remove AccountsTestMixin from Sales Order
(cherry picked from commit 2aecf0103a)
2026-03-31 15:05:19 +05:30
ruthra kumar
ee61d79631 refactor(test): remove AccountsTestMixin from reactivity
(cherry picked from commit d2ee967383)
2026-03-31 15:05:17 +05:30
ruthra kumar
85b08e4706 refactor(test): remove AccountsTestMixin from distributed discount
(cherry picked from commit 0b6546ea06)
2026-03-31 09:32:44 +00:00
ruthra kumar
013aea6b7e refactor(test): move logic from AccountsTestMixin to ERPNextTestSuite
(cherry picked from commit 2b37d7514d)
2026-03-31 09:32:44 +00:00
ruthra kumar
a8d0fb5ac9 refactor(test): erpnext testsuite should be primary superclass
(cherry picked from commit f3148e052c)
2026-03-31 09:32:44 +00:00
Lakshit Jain
924983794b Merge pull request #53933 from frappe/mergify/bp/version-16-hotfix/pr-53925
fix(taxes): increase rounding threshold for tax breakup calculations (backport #53925)
2026-03-31 12:34:33 +05:30
mergify[bot]
5ade905ee8 feat(Payment Request): Added a toggle for using the payment schedule (backport #53922) (#53928)
* feat(Payment Request): Added a toggle for using the payment schedule

(cherry picked from commit 8ec15b537e)

---------

Co-authored-by: Jatin3128 <jatinsarna8@gmail.com>
Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
2026-03-31 12:26:29 +05:30
khushi8112
4705f53d2c fix: dynamic labels on invoice type change
(cherry picked from commit 820bd15e1e)
2026-03-31 06:37:19 +00:00
mergify[bot]
8be7793f89 chore: remove inter warehouse transfer settings (backport #53860) (#53941)
Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
2026-03-31 06:09:44 +00:00
ljain112
0dcacad793 chore: resolve conflicts 2026-03-31 11:05:46 +05:30
mergify[bot]
a478fb7131 fix(warehouse_capacity_dashboard): removed escape from template (backport #53907) (#53909)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(warehouse_capacity_dashboard): removed `escape` from template (#53907)
2026-03-30 23:33:22 +05:30
ljain112
5922d25210 test: update item-wise tax detail test for high conversion rates
(cherry picked from commit fc8437c499)

# Conflicts:
#	erpnext/controllers/tests/test_item_wise_tax_details.py
2026-03-30 15:17:09 +00:00
Smit Vora
6ad5e89607 fix(taxes): improve tax calculation accuracy and update test assertions
(cherry picked from commit a18196f584)
2026-03-30 15:17:09 +00:00
ljain112
6689b17b88 fix(tests): update item code and quantity in tax detail test case
(cherry picked from commit 3449ab063a)
2026-03-30 15:17:09 +00:00
ljain112
3592637b5c fix(taxes): increase rounding threshold for tax breakup calculations
(cherry picked from commit 7f87a5e5c6)
2026-03-30 15:17:08 +00:00
khushi8112
bb77018f7b fix: add quotation print format in the list
(cherry picked from commit da41057cd6)
2026-03-24 10:06:58 +00:00
khushi8112
f5a9657a91 fix: remove unused print format
(cherry picked from commit b9083411cc)
2026-03-24 10:06:58 +00:00
khushi8112
7e5297a305 fix: add closing div tab
(cherry picked from commit c99cec1071)
2026-03-24 10:06:58 +00:00
khushi8112
a8769bfb77 feat: default print format for Quotation
(cherry picked from commit 4307cd5b1c)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
#	erpnext/public/js/print.js
#	erpnext/setup/install.py
2026-03-24 10:06:57 +00:00
355 changed files with 14117 additions and 6425 deletions

View File

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

View File

@@ -6,7 +6,7 @@ import frappe
from frappe.model.document import Document
from frappe.utils.user import is_website_user
__version__ = "16.12.0"
__version__ = "16.17.0"
def get_default_company(user=None):

View File

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

View File

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

View File

@@ -52,60 +52,55 @@ frappe.treeview_settings["Account"] = {
],
root_label: "Accounts",
get_tree_nodes: "erpnext.accounts.utils.get_children",
on_get_node: function (nodes, deep = false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
on_node_render: function (node, deep) {
const render_balances = () => {
for (let account of cur_tree.account_balance_data) {
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
let accounts = [];
if (deep) {
// in case of `get_all_nodes`
accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []);
} else {
accounts = nodes;
}
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? __("Dr") : __("Cr");
const format = (value, currency) => format_currency(Math.abs(value), currency);
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
if (value) {
const get_balances = frappe.call({
method: "erpnext.accounts.utils.get_account_balances",
args: {
accounts: accounts,
company: cur_tree.args.company,
include_default_fb_balances: true,
},
});
get_balances.then((r) => {
if (!r.message || r.message.length == 0) return;
for (let account of r.message) {
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? __("Dr") : __("Cr");
const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance !== undefined) {
node.parent && node.parent.find(".balance-area").remove();
$(
'<span class="balance-area pull-right">' +
(account.balance_in_account_currency
? format(
account.balance_in_account_currency,
account.account_currency
) + " / "
: "") +
format(account.balance, account.company_currency) +
" " +
dr_or_cr +
"</span>"
).insertBefore(node.$ul);
}
}
});
if (account.balance !== undefined) {
node.parent && node.parent.find(".balance-area").remove();
$(
'<span class="balance-area pull-right">' +
(account.account_currency != account.company_currency
? format(account.balance_in_account_currency, account.account_currency) +
" / "
: "") +
format(account.balance, account.company_currency) +
" " +
dr_or_cr +
"</span>"
).insertBefore(node.$ul);
}
}
});
};
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
if (!cur_tree.account_balance_data) {
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
if (value) {
frappe.call({
method: "erpnext.accounts.utils.get_account_balances_coa",
args: {
company: cur_tree.args.company,
include_default_fb_balances: true,
},
callback: function (r) {
if (!r.message || r.message.length === 0) return;
cur_tree.account_balance_data = r.message || [];
render_balances();
},
});
}
});
} else {
render_balances();
}
},
add_tree_node: "erpnext.accounts.utils.add_ac",
menu_items: [

View File

@@ -34,6 +34,13 @@
"account_number": "0430",
"account_type": "Fixed Asset"
},
"Anlagen im Bau": {
"is_group": 1,
"Andere Anlagen, Betriebs- und Geschäftsausstattung im Bau": {
"account_number": "0498",
"account_type": "Capital Work in Progress"
}
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
}
@@ -317,13 +324,21 @@
"account_number": "3800",
"account_type": "Expenses Included In Asset Valuation"
},
"Bestandsveränderungen Roh-, Hilfs- und Betriebsstoffe sowie bezogene Waren": {
"account_number": "3960",
"account_type": "Stock Adjustment"
},
"Herstellungskosten": {
"account_number": "4996",
"account_type": "Cost of Goods Sold"
},
"Anlagenabgänge Sachanlagen (Restbuchwert bei Buchverlust)": {
"account_number": "2310",
"account_type": "Expense Account"
},
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
"account_number": "2320",
"account_type": "Stock Adjustment"
"account_type": "Expense Account"
},
"Verwaltungskosten": {
"account_number": "4997",
@@ -340,7 +355,7 @@
"is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830",
"account_type": "Accumulated Depreciation"
"account_type": "Depreciation"
},
"Abschreibungen auf Gebäude": {
"account_number": "4831",

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,6 +692,30 @@
"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,
@@ -697,7 +725,7 @@
"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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -70,8 +70,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")
@@ -99,9 +99,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
@@ -383,7 +382,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()
@@ -403,7 +403,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(
@@ -425,7 +429,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:
@@ -436,8 +445,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}'"
@@ -460,7 +471,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:
@@ -476,7 +487,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:
@@ -486,9 +497,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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,6 +61,7 @@ class JournalEntry(AccountsController):
cheque_no: DF.Data | None
clearance_date: DF.Date | None
company: DF.Link
custom_remark: DF.Check
difference: DF.Currency
due_date: DF.Date | None
finance_book: DF.Link | None
@@ -353,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)
@@ -1023,8 +1027,8 @@ class JournalEntry(AccountsController):
if self.flags.skip_remarks_creation:
return
if self.user_remark:
r.append(_("Note: {0}").format(self.user_remark))
if self.get("custom_remark"):
return
if self.cheque_no:
if self.cheque_date:
@@ -1538,31 +1542,31 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
if not frappe.db.has_column("Journal Entry", searchfield):
return []
return frappe.db.sql(
f"""
SELECT jv.name, jv.posting_date, jv.user_remark
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name
AND jv_detail.account = %(account)s
AND IFNULL(jv_detail.party, '') = %(party)s
AND (
jv_detail.reference_type IS NULL
OR jv_detail.reference_type = ''
)
AND jv.docstatus = 1
AND jv.`{searchfield}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(limit)s offset %(offset)s
""",
dict(
account=filters.get("account"),
party=cstr(filters.get("party")),
txt=f"%{txt}%",
offset=start,
limit=page_len,
),
JournalEntry = frappe.qb.DocType("Journal Entry")
JournalEntryAccount = frappe.qb.DocType("Journal Entry Account")
query = (
frappe.qb.from_(JournalEntry)
.join(JournalEntryAccount)
.on(JournalEntryAccount.parent == JournalEntry.name)
.select(JournalEntry.name, JournalEntry.posting_date, JournalEntry.remark)
.where(JournalEntryAccount.account == filters.get("account"))
.where(JournalEntryAccount.reference_type.isnull() | (JournalEntryAccount.reference_type == ""))
.where(JournalEntry.docstatus == 1)
.where(JournalEntry[searchfield].like(f"%{txt}%"))
.orderby(JournalEntry.name, order=frappe.qb.desc)
.limit(page_len)
.offset(start)
)
party = filters.get("party")
if party:
query = query.where(JournalEntryAccount.party == party)
else:
query = query.where(JournalEntryAccount.party.isnull() | (JournalEntryAccount.party == ""))
return query.run()
@frappe.whitelist()
def get_outstanding(args):

View File

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

View File

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

View File

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

View File

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

@@ -180,7 +180,7 @@ def make_customer(customer=None):
{
"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

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

View File

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

View File

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

View File

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

View File

@@ -18,9 +18,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
def test_closing_entry(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
@@ -70,9 +67,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.assertEqual(pcv_gle, expected_gle)
def test_cost_center_wise_posting(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
@@ -136,9 +130,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
)
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
@@ -190,9 +181,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.assertSequenceEqual(pcv_gle, expected_gle)
def test_gl_entries_restrictions(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
@@ -213,10 +201,6 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.assertRaises(frappe.ValidationError, jv1.submit)
def test_closing_balance_with_dimensions_and_test_reposting_entry(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
company = create_company()
cost_center1 = create_cost_center("Test Cost Center 1")
cost_center2 = create_cost_center("Test Cost Center 2")

View File

@@ -201,7 +201,6 @@ class TestPOSClosingEntry(ERPNextTestSuite):
)
from erpnext.stock.doctype.batch.batch import get_batch_qty
frappe.db.sql("delete from `tabPOS Invoice`")
item_doc = make_item(
"_Test Item With Batch FOR POS Merge Test",
properties={

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -662,7 +662,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

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

@@ -115,7 +115,12 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
}
if (doc.docstatus == 1 && doc.outstanding_amount != 0 && !doc.on_hold) {
if (
doc.docstatus == 1 &&
doc.outstanding_amount != 0 &&
!doc.on_hold &&
frappe.model.can_create("Payment Entry")
) {
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
@@ -130,7 +135,13 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
}
if (doc.docstatus == 1 && doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
if (
doc.docstatus == 1 &&
doc.outstanding_amount > 0 &&
!cint(doc.is_return) &&
!doc.on_hold &&
frappe.boot.user.in_create.includes("Payment Request")
) {
this.frm.add_custom_button(
__("Payment Request"),
function () {
@@ -443,13 +454,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
items_add(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, [
"expense_account",
"discount_account",
"cost_center",
"project",
]);
const row = frappe.get_doc(cdt, cdn);
const field_copy = ["expense_account", "discount_account", "cost_center"];
if (doc.project) {
frappe.model.set_value(cdt, cdn, "project", doc.project);
} else {
field_copy.push("project");
}
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
}
on_submit() {
@@ -558,12 +570,6 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function
};
};
cur_frm.fields_dict["items"].grid.get_field("project").get_query = function (doc, cdt, cdn) {
return {
filters: [["Project", "status", "not in", "Completed, Cancelled"]],
};
};
frappe.ui.form.on("Purchase Invoice", {
setup: function (frm) {
frm.custom_make_buttons = {

View File

@@ -27,6 +27,8 @@
"update_billed_amount_in_purchase_receipt",
"apply_tds",
"amended_from",
"section_break_hzux",
"title",
"supplier_invoice_details",
"bill_no",
"column_break_15",
@@ -180,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",
@@ -1675,6 +1678,24 @@
"fieldname": "totals_section",
"fieldtype": "Section Break",
"label": "Totals"
},
{
"collapsible": 1,
"fieldname": "automation_section",
"fieldtype": "Section Break",
"label": "Automation"
},
{
"fieldname": "section_break_hzux",
"fieldtype": "Section Break"
},
{
"allow_on_submit": 1,
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"no_copy": 1,
"print_hide": 1
}
],
"grid_page_length": 50,
@@ -1682,7 +1703,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2026-03-25 11:45:38.696888",
"modified": "2026-04-28 07:15:31.062404",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -203,6 +203,7 @@ class PurchaseInvoice(BuyingController):
taxes_and_charges_deducted: DF.Currency
tc_name: DF.Link | None
terms: DF.TextEditor | None
title: DF.Data | None
to_date: DF.Date | None
total: DF.Currency
total_advance: DF.Currency
@@ -332,9 +333,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)
@@ -983,6 +981,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:
@@ -1161,7 +1163,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]
@@ -1198,6 +1204,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,
@@ -2245,9 +2256,9 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
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

@@ -219,7 +219,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,
)
@@ -274,14 +273,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):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
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

@@ -94,7 +94,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
}
if (doc.docstatus == 1 && doc.outstanding_amount != 0) {
if (doc.docstatus == 1 && doc.outstanding_amount != 0 && frappe.model.can_create("Payment Entry")) {
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
@@ -135,13 +135,15 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
}
if (doc.outstanding_amount > 0) {
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request_with_schedule();
},
__("Create")
);
if (frappe.boot.user.in_create.includes("Payment Request")) {
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request_with_schedule();
},
__("Create")
);
}
this.frm.add_custom_button(
__("Invoice Discounting"),
this.make_invoice_discounting.bind(this),
@@ -165,13 +167,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 +256,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 +414,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 +432,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
var filters = {
docstatus: 1,
company: me.frm.doc.company,
is_return: 0,
is_return: me.frm.doc.is_return,
};
if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
return {
@@ -465,12 +554,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
}
items_add(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, [
"income_account",
"discount_account",
"cost_center",
]);
const row = frappe.get_doc(cdt, cdn);
const field_copy = ["income_account", "discount_account", "cost_center"];
if (doc.project) {
frappe.model.set_value(cdt, cdn, "project", doc.project);
} else {
field_copy.push("project");
}
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
}
set_dynamic_labels() {
@@ -610,6 +701,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 +1156,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_qllv",
"title",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -91,9 +93,9 @@
"column_break_xjag",
"base_rounding_adjustment",
"base_rounded_total",
"section_break_vacb",
"section_break_pxwz",
"total_advance",
"column_break_rdks",
"column_break_iaso",
"outstanding_amount",
"section_tax_withholding_entry",
"tax_withholding_group",
@@ -199,6 +201,7 @@
"unrealized_profit_loss_account",
"against_income_account",
"commission_section",
"column_break_rdiw",
"sales_partner",
"amount_eligible_for_commission",
"column_break10",
@@ -214,12 +217,14 @@
"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",
"column_break_rdke",
"utm_source",
"utm_medium",
"column_break_ixxw",
@@ -1147,6 +1152,7 @@
"hide_seconds": 1,
"label": "Rounding Adjustment",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1159,6 +1165,7 @@
"label": "Rounded Total",
"oldfieldname": "rounded_total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -2304,14 +2311,6 @@
"options": "fa fa-group",
"print_hide": 1
},
{
"fieldname": "section_break_vacb",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_rdks",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_ixxw",
"fieldtype": "Column Break"
@@ -2321,6 +2320,40 @@
"fieldname": "utm_analytics_section",
"fieldtype": "Section Break",
"label": "UTM Analytics"
},
{
"collapsible": 1,
"fieldname": "automation_section",
"fieldtype": "Section Break",
"label": "Automation"
},
{
"fieldname": "section_break_pxwz",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_rdke",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_rdiw",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_iaso",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_qllv",
"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 +2367,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2026-03-09 17:15:30.931929",
"modified": "2026-05-01 02:37:29.742764",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

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

View File

@@ -2025,10 +2025,6 @@ class TestSalesInvoice(ERPNextTestSuite):
)
def test_multiple_uom_in_selling(self):
frappe.db.sql(
"""delete from `tabItem Price`
where price_list='_Test Price List' and item_code='_Test Item'"""
)
item_price = frappe.new_doc("Item Price")
item_price.price_list = "_Test Price List"
item_price.item_code = "_Test Item"
@@ -2887,7 +2883,7 @@ class TestSalesInvoice(ERPNextTestSuite):
si.submit()
# Check if adjustment entry is created
self.assertTrue(
self.assertFalse(
frappe.db.exists(
"GL Entry",
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -344,7 +344,6 @@ class TaxWithholdingEntry(Document):
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
TaxWithholdingDetails,
get_tax_id_for_party,
)
@@ -641,13 +640,17 @@ class TaxWithholdingController:
.where(entry.tax_withholding_category == category.name)
.where(entry.company == self.doc.company)
.where(entry.docstatus == 1)
.where(entry.taxable_date.between(category.from_date, category.to_date))
.groupby(entry.status)
)
# NOTE: This can be a configurable option
# To check if filter by tax_id is needed
tax_id = get_tax_id_for_party(self.party_type, self.party)
query = query.where(entry.tax_id == tax_id) if tax_id else query.where(entry.party == self.party)
query = (
query.where(entry.tax_id == category.tax_id)
if category.tax_id
else query.where(entry.party == self.party)
)
return query
@@ -686,6 +689,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,
@@ -1052,6 +1056,7 @@ class TaxWithholdingController:
"party_type": self.party_type,
"party": self.party,
"company": self.doc.company,
"tax_id": category.tax_id,
}
)
return entry

View File

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

View File

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

View File

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

View File

@@ -10,9 +10,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestAccountBalance(ERPNextTestSuite):
def test_account_balance(self):
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
filters = {
"company": "_Test Company 2",
"report_date": getdate(),

View File

@@ -34,6 +34,17 @@ frappe.query_reports["Accounts Payable"] = {
},
options: "Cost Center",
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
},
{
fieldname: "party_account",
label: __("Payable Account"),

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()
@@ -117,3 +117,49 @@ class TestAccountsPayable(AccountsTestMixin, ERPNextTestSuite):
self.assertEqual(len(report[1]), 2)
self.assertEqual([pi.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])
def test_project_filter(self):
project = frappe.get_doc(
{"doctype": "Project", "project_name": "_Test AP Project", "company": self.company}
).insert()
pi = self.create_purchase_invoice(do_not_submit=True)
pi.project = project.name
pi.save().submit()
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
"project": [project.name],
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
row = report[0]
self.assertEqual(row.project, project.name)
self.assertEqual(row.invoiced, 300.0)
def test_project_on_report_output(self):
"""
Report row must carry the invoice's project.
"""
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
}
project = frappe.get_doc(
{"doctype": "Project", "project_name": "_Test AP Project Output", "company": self.company}
).insert()
pi = self.create_purchase_invoice(do_not_submit=True)
pi.project = project.name
pi.save().submit()
report = execute(filters)
self.assertEqual(len(report[1]), 1)
row = report[1][0]
self.assertEqual([pi.name, project.name, 300], [row.voucher_no, row.project, row.outstanding])

View File

@@ -53,6 +53,17 @@ frappe.query_reports["Accounts Payable Summary"] = {
},
options: "Cost Center",
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
},
{
fieldname: "party_type",
label: __("Party Type"),

View File

@@ -36,6 +36,17 @@ frappe.query_reports["Accounts Receivable"] = {
},
options: "Cost Center",
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
},
{
fieldname: "party_type",
label: __("Party Type"),

View File

@@ -196,6 +196,7 @@ class ReceivablePayableReport:
and ple.against_voucher_type in self.advance_payment_doctypes
):
self.voucher_balance[key].cost_center = ple.cost_center
self.voucher_balance[key].project = ple.project
self.get_invoices(ple)
@@ -362,6 +363,7 @@ class ReceivablePayableReport:
posting_date,
account_currency,
cost_center,
project,
sum(invoiced) `invoiced`,
sum(paid) `paid`,
sum(credit_note) `credit_note`,
@@ -390,6 +392,7 @@ class ReceivablePayableReport:
"credit_note_in_account_currency",
"outstanding_in_account_currency",
"cost_center",
"project",
]:
_d[field] = x.get(field)
@@ -931,6 +934,7 @@ class ReceivablePayableReport:
ple.against_voucher_no,
ple.party_type,
ple.cost_center,
ple.project,
ple.party,
ple.posting_date,
ple.due_date,
@@ -998,6 +1002,9 @@ class ReceivablePayableReport:
if self.filters.cost_center:
self.get_cost_center_conditions()
if self.filters.project:
self.qb_selection_filter.append(self.ple.project.isin(self.filters.project))
self.add_accounting_dimensions_filters()
def get_cost_center_conditions(self):
@@ -1237,6 +1244,7 @@ class ReceivablePayableReport:
)
self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
self.add_column(label=_("Project"), fieldname="project", fieldtype="Link", options="Project")
self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")
self.add_column(
label=_("Voucher No"),
@@ -1413,6 +1421,7 @@ class InitSQLProceduresForAR:
posting_date date,
account_currency {_varchar_type},
cost_center {_varchar_type},
project {_varchar_type},
invoiced {_currency_type},
paid {_currency_type},
credit_note {_currency_type},
@@ -1432,6 +1441,7 @@ class InitSQLProceduresForAR:
against_voucher_no {_varchar_type},
party_type {_varchar_type},
cost_center {_varchar_type},
project {_varchar_type},
party {_varchar_type},
posting_date date,
due_date date,
@@ -1447,7 +1457,7 @@ class InitSQLProceduresForAR:
begin
if not exists (select name from `{_voucher_balance_name}` where name = sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)))
then
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, 0, 0, 0, 0, 0, 0);
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, ple.project, 0, 0, 0, 0, 0, 0);
end if;
end;
"""
@@ -1489,7 +1499,7 @@ class InitSQLProceduresForAR:
end if;
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.voucher_type, ple.voucher_no, ple.party)), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0);
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.voucher_type, ple.voucher_no, ple.party)), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', '', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0);
end;
"""

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)
@@ -769,22 +774,18 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
def test_party_account_filter(self):
si1 = self.create_sales_invoice()
self.customer2 = (
frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
"type": "Individual",
"default_currency": "USD",
}
)
.insert()
.submit()
)
jane = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
"type": "Individual",
"default_currency": "USD",
}
).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
@@ -992,22 +993,18 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
self.assertEqual(expected_data, report_output)
def test_future_payments_on_foreign_currency(self):
self.customer2 = (
frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
"type": "Individual",
"default_currency": "USD",
}
)
.insert()
.submit()
)
jane = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
"type": "Individual",
"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
@@ -1199,3 +1196,52 @@ class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite):
self.assertEqual(len(report[1]), 2)
self.assertEqual([si.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])
def test_project_filter(self):
project = frappe.get_doc(
{"doctype": "Project", "project_name": "_Test AR Project", "company": self.company}
).insert()
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.project = project.name
si.save().submit()
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
"project": [project.name],
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
row = report[0]
self.assertEqual(row.project, project.name)
self.assertEqual(row.invoiced, 100.0)
def test_project_on_report_output(self):
"""
Report row must carry the invoice's project even when the payment entry
has no project set.
"""
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
}
project = frappe.get_doc(
{"doctype": "Project", "project_name": "_Test AR Project Output", "company": self.company}
).insert()
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.project = project.name
si.save().submit()
# payment has no project — report row must still show the invoice's project
self.create_payment_entry(si.name)
report = execute(filters)
self.assertEqual(len(report[1]), 1)
row = report[1][0]
self.assertEqual([si.name, project.name, 60], [row.voucher_no, row.project, row.outstanding])

View File

@@ -53,6 +53,17 @@ frappe.query_reports["Accounts Receivable Summary"] = {
},
options: "Cost Center",
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "MultiSelectList",
options: "Project",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
},
{
fieldname: "party_type",
label: __("Party Type"),

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

@@ -8,6 +8,7 @@ from frappe.utils import cint, flt
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
FinancialReportEngine,
get_xlsx_styles, #! DO NOT REMOVE - hook for styling
)
from erpnext.accounts.report.financial_statements import (
compute_growth_view_data,

View File

@@ -13,9 +13,6 @@ COMPANY_SHORT_NAME = "_TC6"
class TestBalanceSheet(ERPNextTestSuite):
def test_balance_sheet(self):
frappe.db.sql(f"delete from `tabJournal Entry` where company='{COMPANY}'")
frappe.db.sql(f"delete from `tabGL Entry` where company='{COMPANY}'")
create_account("VAT Liabilities", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY)
create_account("Advance VAT Paid", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY)
create_account("My Bank", f"Bank Accounts - {COMPANY_SHORT_NAME}", COMPANY)

View File

@@ -12,6 +12,7 @@ from pypika import Order
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
FinancialReportEngine,
get_xlsx_styles, #! DO NOT REMOVE - hook for styling
)
from erpnext.accounts.report.financial_statements import (
get_columns,

View File

@@ -3,6 +3,7 @@
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
FinancialReportEngine,
get_xlsx_styles, #! DO NOT REMOVE - hook for styling
)

View File

@@ -10,7 +10,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

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

@@ -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}):

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