Compare commits

..

752 Commits

Author SHA1 Message Date
diptanilsaha
f6c3f5c4cb chore: removed conflicts 2026-04-29 01:24:14 +05:30
diptanilsaha
1f085cde41 chore: using FrappeTestCase instead of v16's ERPNextTestSuite 2026-04-29 01:23:32 +05:30
Ravibharathi
36e146a9ed fix: filter overdue purchase order items by company (#54099)
(cherry picked from commit a8030c9713)

# Conflicts:
#	erpnext/setup/doctype/email_digest/test_email_digest.py
2026-04-28 19:29:21 +00:00
mergify[bot]
44f3f34c9e fix: add project filter to accounts payable and receivable reports (backport #54344) (#54441)
Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-28 22:39:42 +05:30
mergify[bot]
176d980764 fix: duplicate entries being shown in batch exists in future transact… (backport #54604) (#54605)
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:03 +05:30
mergify[bot]
44af175556 refactor(sms_center): replaced raw SQL queries with Query Builder (backport #54600) (#54602)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-28 15:31:58 +00:00
mergify[bot]
03f3a28f54 fix(get_stock_balance): validate inventory dimension fieldnames (backport #54587) (#54588)
* fix(`get_stock_balance`): validate inventory dimension fieldnames (#54587)

(cherry picked from commit 084c7f72f0)

# Conflicts:
#	erpnext/stock/utils.py

* chore: resolved conflicts

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-28 18:14:07 +05:30
mergify[bot]
cceedd669f fix(payment_entry): escape arguments on invoice and order fetching sql queries (backport #54582) (#54585)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(payment_entry): escape arguments on invoice and order fetching sql queries (#54582)
2026-04-28 10:44:39 +00:00
mergify[bot]
1a8dc7e332 fix: update status of quotation in patch (backport #54577) (#54579)
fix: update status of quotation in patch (#54577)

(cherry picked from commit 2088a01c19)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-28 15:13:16 +05:30
mergify[bot]
49ab25dda8 fix: negative quantity check in validate_item_qty (backport #54559) (#54571)
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:30:26 +00:00
Mihir Kandoi
e7a29abdb0 fix: unknown column error on item code in quality inspection (#54565) 2026-04-28 10:19:43 +05:30
mergify[bot]
78b2e45cb9 fix: debit credit not equal in purchase transactions for multi currency (backport #54456) (#54563)
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:14:58 +00:00
mergify[bot]
4dff436104 fix(purchase_register): filter tax rows by parenttype in invoice tax map query (backport #54272) (#54443)
Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-27 18:39:01 +05:30
Mihir Kandoi
f869e86c9c Revert "refactor: quality inspection item query (backport #54511)" (#54557)
Revert "refactor: quality inspection item query (backport #54511) (#54539)"

This reverts commit b01049814a.
2026-04-27 10:15:45 +00:00
mergify[bot]
8569ff67ff fix(stock): remove validation for transfer_qty field (backport #54542) (#54544)
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:12:24 +00:00
mergify[bot]
b01049814a refactor: quality inspection item query (backport #54511) (#54539)
* 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:51:18 +00:00
mergify[bot]
973444e20e feat: danish_bosnian_address_template (backport #54093) (#54515)
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:37 +05:30
mergify[bot]
68d213a244 fix(stock): set incoming rate as zero for outward sle (backport #54514) (#54532)
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:43 +05:30
mergify[bot]
6df39aec54 fix(PCV): set correct filters of from_date and to_date on General Ledger Report on clicking Ledger button (backport #54522) (#54523)
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:06:32 +05:30
Mihir Kandoi
1b08ac248b Revert "fix: preserve inventory dimensions when raw materials are reset (backport #54440)" (#54507)
Revert "fix: preserve inventory dimensions when raw materials are reset (back…"

This reverts commit 722dc8c3f1.
2026-04-24 08:43:59 +00:00
mergify[bot]
722dc8c3f1 fix: preserve inventory dimensions when raw materials are reset (backport #54440) (#54492)
* 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)

# Conflicts:
#	erpnext/patches.txt
#	erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
#	erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-23 17:27:07 +00:00
mergify[bot]
e0013f7618 fix(edi): restrict Code List imports to files and trusted backend URLs (backport #54137) (#54265)
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:37:39 +00:00
Smit Vora
017635ab04 Merge pull request #54451 from vorasmit/tds-reports-refactor-backport 2026-04-23 15:25:46 +05:30
Smit Vora
8f9a5e6c0c fix: use key consistently 2026-04-23 15:01:46 +05:30
mergify[bot]
9a4c693f2d fix: sales order is not valid when creating WO from MR from PP (backport #54435) (#54470)
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-22 15:05:29 +00:00
mergify[bot]
6179449036 fix: py error on stock ageing report (backport #54467) (#54468)
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:32:21 +00:00
Smit Vora
a3ad1fb163 fix: add party_type for dynamic link and add it to grouping key 2026-04-22 12:12:18 +05:30
Smit Vora
8e12bda108 refactor: better label for entity type 2026-04-22 12:11:04 +05:30
Ravibharathi
947b282e0c Merge pull request #54452 from frappe/mergify/bp/version-15-hotfix/pr-54307
fix(accounts): fetch project name from payment entry to journal entry (backport #54307)
2026-04-22 11:24:29 +05:30
sarathibalamurugan
f9ae22d85e test: add test for project name in exchange gain loss entry
(cherry picked from commit 9eeb819106)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/test_payment_entry.py
2026-04-22 11:05:18 +05:30
diptanilsaha
bd957a9bbc Revert "feat: enhance tax withholding details report with additional columns support (backport #54409)" (#54458) 2026-04-21 18:49:08 +00:00
Lakshit Jain
e22326065d feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432) 2026-04-22 00:02:19 +05:30
sarathibalamurugan
55cce2a11c fix(accounts): fetch project name from payment entry to journal entry
(cherry picked from commit d9b255b952)
2026-04-21 13:29:16 +00:00
Smit Vora
7630c01e40 refactor: use consistent report column names 2026-04-21 18:53:25 +05:30
Ravibharathi
bd4eb71205 Merge pull request #54423 from frappe/mergify/bp/version-15-hotfix/pr-54415
fix: clear conditions table when calculate_based_on is set to Fixed (backport #54415)
2026-04-20 19:44:36 +05:30
ravibharathi656
9e10ecc4cb fix: clear shipping rule conditions for fixed shipping rule
(cherry picked from commit d6bb0ae093)
2026-04-20 13:53:03 +00:00
sarathibalamurugan
35bd43775c fix: clear conditions table when calculate_based_on is set to Fixed
(cherry picked from commit d73920be12)
2026-04-20 13:53:02 +00:00
mergify[bot]
813f4644a0 fix(pos_invoice_item): fetch grant_commission from item_code (backport #54413) (#54417)
* fix(pos_invoice_item): fetch `grant_commission` from `item_code` (#54413)

(cherry picked from commit 6c51e4cd1f)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json

* chore: resolve conflicts

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-20 11:48:06 +00:00
Ravibharathi
2c1ea8d30c fix(vat audit report): fallback to item name when item code is missing (#54049)
* fix(vat audit report): fallback to item name when item code is missing

* fix: validate south africa company selection

* fix: simplify parent item lookup

* fix: handle missing item mapping

* fix: use list instead of set
2026-04-20 15:56:37 +05:30
Pandiyan P
ffa0268a57 fix: fetch item tax template from item group when creating item (#54405) 2026-04-20 11:58:16 +05:30
mergify[bot]
1ccbc9f621 fix: changed qty validation from qty field to stock_qty (backport #54352) (#54356)
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:53:44 +05:30
mergify[bot]
799f897036 fix(dashboard-trends): set default fiscal year and company before val… (backport #54339) (#54399)
* 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: resolve conflicts

---------

Co-authored-by: Ahmed AbuKhatwa <82771130+AhmedAbokhatwa@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 19:34:19 +05:30
mergify[bot]
6b7bdfdfd3 Fix : None handling in pricing rule free item quantity calculation (backport #54375) (#54395)
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 07:57:37 +00:00
rohitwaghchaure
e3374933ed Merge pull request #54359 from frappe/mergify/bp/version-15-hotfix/pr-54354
fix: negative batch report showing same batch-warehouse multiple times (backport #54354)
2026-04-17 21:26:16 +05:30
Rohit Waghchaure
3229fce9a5 fix: negative batch report showing same batch-warehouse multiple times
(cherry picked from commit 700572980d)
2026-04-17 15:41:47 +00:00
mergify[bot]
d9d8fc6912 fix: move make_dimension_in_accounting_doctypes from after_insert to on_update (backport #54172) (#54317)
* fix: move make_dimension_in_accounting_doctypes from after_insert to on_update

(cherry picked from commit ee067e6015)

# Conflicts:
#	erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py

* chore: resolve conflicts in accounting_dimension.py

---------

Co-authored-by: Shllokkk <shllokosan23@gmail.com>
2026-04-17 15:14:17 +05:30
mergify[bot]
28367ac966 fix: reset base_rounded_total when rounded_total resets (backport #54241) (#54303)
* fix: reset base_rounded_total when rounded_total resets

(cherry picked from commit f8d278b733)

# Conflicts:
#	erpnext/controllers/tests/test_taxes_and_totals.py
#	erpnext/public/js/controllers/taxes_and_totals.js

* chore: spelling mistake

(cherry picked from commit e2ac476587)

* chore: resolve conflicts

---------

Co-authored-by: ljain112 <ljain112@gmail.com>
2026-04-16 10:39:57 +05:30
NaviN
67632e81d0 Merge pull request #54308 from frappe/mergify/bp/version-15-hotfix/pr-54306
fix: non-collapsible in customer quick entry (backport #54306)
2026-04-15 17:32:16 +05:30
PKSowmiya05
9ee059465a fix: non-collapsible in customer quick entry
(cherry picked from commit 53e120269d)
2026-04-15 11:56:39 +00:00
mergify[bot]
1e4cafaa0e fix: add portal user ownership check to supplier quotation (backport #54298) (#54299)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: add portal user ownership check to supplier quotation (#54298)
2026-04-15 06:07:23 +00:00
mergify[bot]
8b3d65ae78 Revert "fix: sync paid and received amount" (backport #54238) (#54292)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
fix: sync paid and received amount" (#54238)
2026-04-14 22:27:04 +05:30
mergify[bot]
0e9b3b459a fix(stock): remove float precision to fix precision issue (backport #54284) (#54288)
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:33:23 +00:00
mergify[bot]
46a1c6fda0 fix(stock): update bin to zero when no previous sle exists (backport #54236) (#54263)
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:49:45 +00:00
mergify[bot]
cbe5ad6337 fix: make operation mandatory when any sub operation row is added (backport #54245) (#54247)
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:05 +05:30
mergify[bot]
17ce550417 Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (backport #54187) (#54207)
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:55 +05:30
mergify[bot]
430705f56c fix: account change in warehouse (backport #54182) (#54204)
Co-authored-by: nishkagosalia <nishka.gosalia@gmail.com>
2026-04-10 20:32:33 +05:30
mergify[bot]
0a3f9f0b9f fix: update return value in workstation list view indicator (backport #54198) (#54200)
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 16:50:32 +05:30
mergify[bot]
cb24d9404d fix: remove unneccessary function for serial no status updation (backport #54191) (#54196)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: remove unneccessary function for serial no status updation (#54191)
2026-04-10 10:53:04 +00:00
Nishka Gosalia
bc6780d4c7 Merge pull request #54179 from frappe/revert-54170-mergify/bp/version-15-hotfix/pr-54165
fix: update_nsm only in warehouse creation (backport #54165)"
2026-04-09 18:31:00 +05:30
Nishka Gosalia
8b16c310f4 Revert "fix: update_nsm only in warehouse creation (backport #54165)" 2026-04-09 18:12:46 +05:30
mergify[bot]
76e910e8c0 fix: sanitize genericode import inputs and secure XML parser (backport #53302) (#54174)
Co-authored-by: Shllokkk <shllokosan23@gmail.com>
2026-04-09 11:30:24 +00:00
Nishka Gosalia
c44ec7eab4 Merge pull request #54170 from frappe/mergify/bp/version-15-hotfix/pr-54165
fix: update_nsm only in warehouse creation (backport #54165)
2026-04-09 16:29:32 +05:30
Nishka Gosalia
e9c1a09af3 fix: update_nsm only in warehouse creation (#54165)
(cherry picked from commit b0e3fa3979)
2026-04-09 10:28:14 +00:00
rohitwaghchaure
8843068da9 Merge pull request #54162 from frappe/mergify/bp/version-15-hotfix/pr-54161
fix: set default posting time in RIV (backport #54161)
2026-04-09 15:28:12 +05:30
rohitwaghchaure
2df574baae chore: fix conflicts
Removed unused method reset_repost_only_accounting_ledgers and fixed the validate method to set default posting time.
2026-04-09 14:24:07 +05:30
Rohit Waghchaure
6e438e71eb fix: set default posting time in RIV
(cherry picked from commit a7ece65536)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
2026-04-09 08:26:57 +00:00
diptanilsaha
1604c21602 fix(list_opportunity_report): parameterized lost_reason (#54160) 2026-04-09 07:23:12 +00:00
Aarol D'Souza
97c4cd140b Merge pull request #54157 from frappe/mergify/bp/version-15-hotfix/pr-54129
refactor: update reset password method name (backport #54129)
2026-04-09 12:30:37 +05:30
mergify[bot]
9d64d4ac05 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-54129 2026-04-09 06:42:06 +00:00
mergify[bot]
5de4102dda fix(sales invoice): toggle Get Items From button based on is_return and POS view (backport #52594) (#54138)
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:07 +05:30
AarDG10
39a473455d refactor: update reset password method name
(cherry picked from commit c4d74483e1)
2026-04-09 06:23:37 +00:00
rohitwaghchaure
4f1203dbd0 Merge pull request #54150 from frappe/mergify/bp/version-15-hotfix/pr-54132
fix: last SLE not updated in the file (backport #54132)
2026-04-09 10:17:56 +05:30
rohitwaghchaure
c70259687a chore: fix conflicts 2026-04-09 09:06:31 +05:30
Rohit Waghchaure
8408e81335 fix: last SLE not updated in the file
(cherry picked from commit 38ed425ee2)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2026-04-09 02:53:01 +00:00
mergify[bot]
a56d6984d1 fix: inventory dimension patch (backport #54147) (#54148) 2026-04-09 02:40:40 +00:00
mergify[bot]
deb67db4a0 fix: inventory dimension patch (backport #54141) (#54145)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: inventory dimension patch (#54141)
2026-04-09 02:01:45 +00:00
mergify[bot]
a26c845332 fix: inventory dimensions should not be mandatory unnecesarily (backport #54064) (#54133)
* fix: inventory dimensions should not be mandatory unnecesarily (#54064)

(cherry picked from commit 6e44b8913e)

# Conflicts:
#	erpnext/patches.txt
#	erpnext/stock/doctype/inventory_dimension/inventory_dimension.py

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-08 14:23:20 +00:00
mergify[bot]
bcd6d99549 fix: quality inspection item code fetch perm issue (backport #54121) (#54126)
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:05:39 +00:00
rohitwaghchaure
d39d076fba Merge pull request #54118 from frappe/mergify/bp/version-15-hotfix/pr-54102
fix: hardcoded precision causing decimal issues (backport #54102)
2026-04-08 14:25:07 +05:30
rohitwaghchaure
21607f39c5 chore: fix conflicts 2026-04-08 14:05:04 +05:30
rohitwaghchaure
39a4760e07 chore: fix conflicts 2026-04-08 12:29:00 +05:30
rohitwaghchaure
d2c6a8958d chore: fix conflicts
Updated the modified date for the delivery note item.
2026-04-08 12:28:29 +05:30
Rohit Waghchaure
77545042a5 fix: hardcoded precision causing decimal issues
(cherry picked from commit 90fd6f2e40)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
#	erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
#	erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
2026-04-08 06:49:57 +00:00
Khushi Rawat
13f4ba1857 Merge pull request #54116 from frappe/mergify/bp/version-15-hotfix/pr-54103
fix: preserve asset movement field properties after save (backport #54103)
2026-04-08 12:15:45 +05:30
ravibharathi656
a87015e8e6 fix: preserve asset movement field properties after save
(cherry picked from commit 4a004a2a82)
2026-04-08 06:28:09 +00:00
mergify[bot]
5b7e6eb831 fix(promotional_scheme): toggle enable state between Buying and Selli… (backport #54110) (#54111)
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:06 +05:30
rohitwaghchaure
1fb9c5244c Merge pull request #54009 from frappe/mergify/bp/version-15-hotfix/pr-53994
fix(stock): update stock queue in SABE for return entries (backport #53994)
2026-04-07 19:29:19 +05:30
rohitwaghchaure
e68eece3da Merge pull request #53804 from frappe/mergify/bp/version-15-hotfix/pr-52152
Refactor reposting feature (backport #52152)
2026-04-07 19:28:35 +05:30
Smit Vora
b8063a07fc Merge pull request #54097 from frappe/mergify/bp/version-15-hotfix/pr-53964
fix: consistently disassemble based on source  > SE / WO / BOM (backport #53964)
2026-04-07 19:27:23 +05:30
Smit Vora
8b42fcf274 fix: ensure compatibility with v15 2026-04-07 19:09:51 +05:30
Rohit Waghchaure
0063201818 fix: do not repost GL if no change in valuation 2026-04-07 18:52:10 +05:30
Rohit Waghchaure
2f9643d44d refactor: reposting for better peformance
(cherry picked from commit 20787ef5da)
2026-04-07 18:51:17 +05:30
mergify[bot]
0505684d22 fix: sync paid and received amount (backport #53039) (#54107)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
fix: sync paid and received amount (#53039)
2026-04-07 13:06:02 +00:00
Nishka Gosalia
0b958136be Merge pull request #53996 from aerele/partial-billing-timesheet 2026-04-07 16:11:18 +05:30
Smit Vora
652bd396d4 fix: add v15 compatibility for scrap item 2026-04-07 15:43:43 +05:30
Smit Vora
904ac62830 chore: resolve conflicts 2026-04-07 14:51:35 +05:30
Smit Vora
2fee39017c Merge pull request #53974 from vorasmit/backport-50407-50856 2026-04-07 14:17:55 +05:30
Smit Vora
0b0dccd294 fix: remove unnecessary param, and use value from self
(cherry picked from commit 98dfd64f63)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-04-07 08:47:49 +00:00
Smit Vora
99df61a0d8 test: enhance tests as per review comments
(cherry picked from commit f13d37fbf9)
2026-04-07 08:47:48 +00:00
Smit Vora
7767659b87 test: maintain sufficient stock for scrap item
(cherry picked from commit b892139342)
2026-04-07 08:47:48 +00:00
Smit Vora
84d5b52483 fix: set bom details on disassembly; abs batch qty
(cherry picked from commit ab1fc22431)
2026-04-07 08:47:48 +00:00
Smit Vora
eee6d7e566 fix: process loss with bom path disassembly
(cherry picked from commit 93ad48bc1b)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2026-04-07 08:47:47 +00:00
Smit Vora
d690a0c6bd fix: validate work order consistency in stock entry
(cherry picked from commit ea392b2009)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-04-07 08:47:47 +00:00
vorasmit
8f01d12b5e fix: use get_value
(cherry picked from commit a71e8bb116)
2026-04-07 08:47:46 +00:00
vorasmit
44d40795df fix: avg stock entries for disassembly from WO
(cherry picked from commit 71fd18bdf9)
2026-04-07 08:47:46 +00:00
vorasmit
841b507502 fix: manufacture entry with group_by support
(cherry picked from commit 3cf1ce8360)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-04-07 08:47:46 +00:00
Smit Vora
20f81516cf test: disassembly for scrap / secondary item
(cherry picked from commit a6d41151ff)
2026-04-07 08:47:45 +00:00
Smit Vora
229dc23f97 fix: handle disassembly for secondary / scrap items
(cherry picked from commit 2be8313819)
2026-04-07 08:47:45 +00:00
Smit Vora
f9b1df3572 test: disassembly of items with batch and serial numbers
(cherry picked from commit 1693698fed)
2026-04-07 08:47:44 +00:00
Smit Vora
1063a56251 test: additional items in stock entry considered with disassembly
(cherry picked from commit d32977e3a9)
2026-04-07 08:47:44 +00:00
Smit Vora
75eb5ad584 test: disassemble with source stock entry reference
(cherry picked from commit 6988e2cbbc)
2026-04-07 08:47:44 +00:00
Smit Vora
43c507570b test: disassembly from wo
(cherry picked from commit 342a14d340)
2026-04-07 08:47:43 +00:00
Smit Vora
df049cd277 fix: set serial and batch from source stock entry - on disassemble
(cherry picked from commit 13b019ab8e)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-04-07 08:47:43 +00:00
Smit Vora
b8ddc2f2b9 fix: correct warehouse preference for disassemble
(cherry picked from commit d3d6b5c660)
2026-04-07 08:47:43 +00:00
Smit Vora
b87b445802 fix: auto-set source_stock_entry
(cherry picked from commit 2e4e8bcaa7)
2026-04-07 08:47:42 +00:00
Smit Vora
e9ce0a41e6 fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order
(cherry picked from commit 1ed0124ad7)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-04-07 08:47:42 +00:00
Smit Vora
583c7b9819 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:47:41 +00:00
Smit Vora
ef15c0581d fix: support creating disassembly (without link of WO)
(cherry picked from commit dba82720b6)
2026-04-07 08:47:41 +00:00
Smit Vora
835ae27b38 fix: custom button to disassemble manufactured stock entry with work order
(cherry picked from commit b64f86148c)
2026-04-07 08:47:41 +00:00
Smit Vora
849b2e6ebf fix: set_query for source stock entry
(cherry picked from commit b47dfacb3e)
2026-04-07 08:47:40 +00:00
Smit Vora
44f2e9480d fix: disassembly prompt with source stock entry field
(cherry picked from commit 68e97808c5)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.js
#	erpnext/manufacturing/doctype/work_order/work_order.py
2026-04-07 08:47:40 +00:00
Smit Vora
55ee1dcd04 fix: create source_stock_entry to refer to original manufacturing entry
(cherry picked from commit d4baa9a74a)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.json
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-04-07 08:47:40 +00:00
rohitwaghchaure
c81c1ea869 chore: fix test case 2026-04-07 13:11:44 +05:30
rohitwaghchaure
831ddcd5af Merge pull request #54068 from frappe/mergify/bp/version-15-hotfix/pr-54050
fix: GL entries for different exchange rate in the purchase invoice (backport #54050)
2026-04-07 13:10:40 +05:30
mergify[bot]
bcf59e7171 fix: transactions where update stock is 0 should not create SLEs (backport #54035) (#54076)
* fix: transactions where update stock is 0 should not create SLEs (#54035)

(cherry picked from commit 66780543bd)

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

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-07 06:31:51 +00:00
mergify[bot]
ee812687e6 feat: croatian_address_template (backport #53888) (#54057)
Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com>
2026-04-07 10:26:03 +05:30
mergify[bot]
14085de332 fix: resolve user permission error on status change by updating user … (backport #54033) (#54059)
Co-authored-by: Krishna Shirsath <shirsathkrishna19@gmail.com>
2026-04-07 10:25:32 +05:30
Khushi Rawat
22652f30db Merge pull request #53956 from frappe/mergify/bp/version-15-hotfix/pr-53811
fix: prevent selection of group type customer group in customer master (backport #53811)
2026-04-07 03:16:36 +05:30
khushi8112
7794f3033e fix: validation test for customer group 2026-04-07 02:59:32 +05:30
Poovitha Palanivelu
21805bde1f feat(timesheet): allow partial billing and handled return 2026-04-06 23:04:23 +05:30
rohitwaghchaure
93bfd62725 chore: fix conflicts
Removed redundant calculation of billed quantity and adjusted logic for billed amount based on purchase order.
2026-04-06 17:44:36 +05:30
Rohit Waghchaure
def62cf3fe fix: GL entries for different exchange rate in the purchase invoice
(cherry picked from commit a953709640)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2026-04-06 11:52:49 +00:00
khushi8112
ea3fcc214b fix(test): use non-group customer group in test setup 2026-04-06 16:09:05 +05:30
diptanilsaha
1146c9550a Merge pull request #54046 from frappe/mergify/bp/version-15-hotfix/pr-54042
fix: skip discount amount validation when not saving (backport #54042)
2026-04-06 13:44:03 +05:30
rohitwaghchaure
c5edeae97e Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-53994 2026-04-06 13:35:08 +05:30
rohitwaghchaure
f855cc89c9 chore: fix conflicts 2026-04-06 13:12:03 +05:30
Sagar Vora
1ffbc399e1 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:30:43 +00:00
Sagar Vora
13eab9f993 fix: skip discount amount validation when not saving
(cherry picked from commit 0975583388)
2026-04-06 07:30:43 +00:00
khushi8112
97684d3dae fix(test): do not use is_group enabled customer group in test
(cherry picked from commit 75fa2b2277)
2026-04-06 12:57:49 +05:30
khushi8112
7a227e048e fix: prevent selection of group type customer group in customer master
(cherry picked from commit 6068dc959f)
2026-04-06 12:57:49 +05:30
mergify[bot]
af0116cdc5 fix: show current stock qty in Stock Entry PDF (backport #53761) (#54031) 2026-04-06 05:36:09 +00:00
mergify[bot]
a71d32e668 fix: update min date based on transaction_date (backport #53803) (#54024)
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:48 +05:30
mergify[bot]
e33abeef7f fix: remove reference in serial/batch when document is cancelled (backport #53979) (#53988) 2026-04-05 15:39:20 +00:00
mergify[bot]
cb0a548a95 fix(manufacturing): handle null cur_dialog in BOM work order dialog (backport #54011) (#54014) 2026-04-05 12:48:16 +05:30
kavin-114
b57db06100 test(stock): add unit test to update stock queue for return
(cherry picked from commit e537896df8)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
2026-04-04 21:17:08 +00:00
kavin-114
05d6cf5c9a fix(stock): update stock queue in SABE for return entries
(cherry picked from commit 0af8077bcc)
2026-04-04 21:17:08 +00:00
Khushi Rawat
67183ad90c Merge pull request #53995 from khushi8112/fix-depreciation-cancel-test
fix(test): pin posting date in test_depreciation_on_cancel_invoice
2026-04-03 00:58:03 +05:30
khushi8112
7f72189665 fix(test): pin posting date in test_depreciation_on_cancel_invoice 2026-04-03 00:33:17 +05:30
Smit Vora
3c327d5225 fix(ux): refresh grid to correctly persist the state of fields 2026-04-01 08:58:00 +05:30
Rohit Waghchaure
62d58702a0 fix: not able to set operation in work order 2026-04-01 08:57:40 +05:30
Rohit Waghchaure
1d36cb55cd feat: Allow Editing of Items and Quantities in Work Order 2026-04-01 08:55:09 +05:30
mergify[bot]
3fbfad1b9b fix: include rejected qty in tax (purchase receipt) (backport #53624) (#53971)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: include rejected qty in tax (purchase receipt) (#53624)
2026-03-31 16:15:42 +00:00
Mihir Kandoi
22774fdf87 revert: botched backport (#53967)
fix(manufacturing): apply work order status filter in job card (#53776)"
fix(manufacturing): apply work order status filter in job card (backport #53766) (#53767)"
2026-03-31 13:52:02 +00:00
mergify[bot]
e159c79766 fix: do not show inv dimension unnecessarily in stock entry (backport #53946) (#53950)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: do not show inv dimension unnecessarily in stock entry (#53946)
2026-03-31 16:29:24 +05:30
mergify[bot]
94fe32f189 chore: remove inter warehouse transfer settings (backport #53860) (#53940)
* chore: remove inter warehouse transfer settings (#53860)

(cherry picked from commit 0696bd2082)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json

* chore: resolve conflicts

---------

Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-31 06:08:02 +00:00
mergify[bot]
efdb004f0b fix(warehouse_capacity_dashboard): removed escape from template (backport #53907) (#53908)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(warehouse_capacity_dashboard): removed `escape` from template (#53907)
2026-03-30 23:33:06 +05:30
Lakshit Jain
01b8ae3e11 Merge pull request #53931 from frappe/mergify/bp/version-15-hotfix/pr-53406
fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt. (backport #53406)
2026-03-30 20:39:14 +05:30
ljain112
b80e10e15e chore: resolve conflicts 2026-03-30 20:22:30 +05:30
ljain112
25852879f6 fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt.
(cherry picked from commit e68f149d3a)

# Conflicts:
#	erpnext/controllers/buying_controller.py
2026-03-30 14:24:21 +00:00
mergify[bot]
d16061f1bc fix(bank_account): added validation to fetch bank account details using get_bank_account_details (backport #53926) (#53929)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(bank_account): added validation to fetch bank account details using `get_bank_account_details` (#53926)
2026-03-30 13:42:19 +00:00
mergify[bot]
b35a6c2e73 fix(opening_invoice_creation_tool): sanitize summary content for dashboard (backport #53917) (#53923)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(opening_invoice_creation_tool): sanitize summary content for dashboard (#53917)
2026-03-30 13:40:32 +00:00
rohitwaghchaure
5884b71b0a Merge pull request #53910 from frappe/mergify/bp/version-15-hotfix/pr-53906
fix: purchase invoice missing item (backport #53906)
2026-03-30 18:39:08 +05:30
Rohit Waghchaure
bcd56abb62 fix: purchase invoice missing item
(cherry picked from commit af994c1a22)
2026-03-30 18:24:24 +05:30
mergify[bot]
db70d2e4df fix(item_dashboard): escaping warehouse, item_code, stock_uom and item_name on get_data (backport #53904) (#53912)
* fix(item_dashboard): escaping `warehouse`, `item_code`, `stock_uom` and `item_name` on `get_data` (#53904)

(cherry picked from commit fa5238ba12)

# Conflicts:
#	erpnext/stock/dashboard/item_dashboard.py

* chore: resolve conflict

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-30 09:55:57 +00:00
rohitwaghchaure
098cbcde10 Merge pull request #53895 from frappe/mergify/bp/version-15-hotfix/pr-53799
fix(stock): update company validation for expense account in lcv (backport #53799)
2026-03-30 14:45:20 +05:30
mergify[bot]
1eda22c2bd fix(warehouse_capacity_dashboard): escaping warehouse, item_code and company on get_data (backport #53894) (#53899)
* fix(warehouse_capacity_dashboard): escaping `warehouse`, `item_code` and `company` on `get_data` (#53894)

(cherry picked from commit ddeb9775ed)

# Conflicts:
#	erpnext/stock/dashboard/warehouse_capacity_dashboard.py

* chore: resolve conflicts

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-30 08:34:37 +00:00
mergify[bot]
fffd3a785c fix(stock): add warehouse filter to pick work order raw materials (backport #53748) (#53897)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): add warehouse filter to pick work order raw materials (#53748)
2026-03-30 08:02:27 +00:00
Sudharsanan11
88c16c8378 fix(test): enable perpetual inventory
(cherry picked from commit 875a2e4947)
2026-03-30 07:34:27 +00:00
Sudharsanan11
40c2b3c0f6 fix(stock): update company validation for expense account in lcv
(cherry picked from commit 913168e8b6)
2026-03-30 07:34:27 +00:00
mergify[bot]
46f751e403 fix(manufacturing): update the qty precision (backport #53874) (#53884)
* fix(manufacturing): update the qty precision (#53874)

(cherry picked from commit f3a794384a)

# Conflicts:
#	erpnext/manufacturing/doctype/production_plan/production_plan.py

* chore: resolve conflicts

---------

Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-29 16:38:51 +00:00
mergify[bot]
a21b82b238 fix: change shipment parcel dimension fields from Int to Float (backport #53867) (#53872)
* fix: change shipment parcel dimension fields from Int to Float (#53867)

(cherry picked from commit 6badf00313)

# Conflicts:
#	erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
#	erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json

* chore: resolve conflicts

* chore: resole conflicts

---------

Co-authored-by: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-29 07:06:55 +00:00
mergify[bot]
f7536f645b fix: invalid dynamic link filter for address doctype (backport #53849) (#53851) 2026-03-27 12:38:29 +00:00
mergify[bot]
ddf6eab013 fix: validate if quantity greater than 0 in item dashboard (backport #53846) (#53847)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: validate if quantity greater than 0 in item dashboard (#53846)
2026-03-27 16:03:48 +05:30
ruthra kumar
3e4c331962 Merge pull request #53829 from frappe/mergify/bp/version-15-hotfix/pr-53429
feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report (backport #53429)
2026-03-27 10:53:35 +05:30
ruthra kumar
4b1c1d33b0 Merge pull request #53827 from frappe/mergify/bp/version-15-hotfix/pr-53343
fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes (backport #53343)
2026-03-27 10:43:29 +05:30
Shllokkk
14088ee7ac feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report
(cherry picked from commit 8e5692d8a3)
2026-03-27 05:00:35 +00:00
Shllokkk
6151a496e7 fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes
(cherry picked from commit 56f597f5ad)
2026-03-27 04:57:30 +00:00
rohitwaghchaure
237915dc03 Merge pull request #53809 from frappe/mergify/bp/version-15-hotfix/pr-53216
fix(stock): handle legacy single sle recon entries (backport #53216)
2026-03-26 18:27:19 +05:30
kavin-114
d09207ab82 fix(stock): handle legacy single sle recon entries
(cherry picked from commit 7e6bbcc3fb)
2026-03-26 18:12:09 +05:30
mergify[bot]
d9cd09b24a fix: flaky currency exchange test (backport #53813) (#53816) 2026-03-26 12:38:36 +00:00
mergify[bot]
0a28fb3ae1 fix: purchase invoice for internal transfers should not require PO (backport #53791) (#53792)
* fix: purchase invoice for internal transfers should not require PO (#53791)

(cherry picked from commit 3f74733942)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-26 03:45:18 +00:00
mergify[bot]
d50c727f89 fix(contract_template): restrict create, write and delete access only to System Manager (backport #53787) (#53788)
* fix(contract_template): restrict `create`, `write` and `delete` access only to `System Manager` (#53787)

(cherry picked from commit e136bfbb61)

# Conflicts:
#	erpnext/crm/doctype/contract_template/contract_template.json

* chore: resolve conflicts

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-25 15:05:12 +00:00
diptanilsaha
40c8201302 Merge pull request #53780 from frappe/mergify/bp/version-15-hotfix/pr-53779
fix(template): escape attachment `file_url` and `file_name` and jinja syntax (backport #53779)
2026-03-25 15:20:01 +05:30
diptanilsaha
979c594e98 fix(templates): using correct syntax of include in projects.html
(cherry picked from commit bc6561cdd0)
2026-03-25 09:28:18 +00:00
diptanilsaha
7b9f2626f8 fix(templates): escape attachment file_url and file_name in order.html and projects.html
(cherry picked from commit d9760bbf4f)
2026-03-25 09:28:18 +00:00
Pandiyan P
64956ab59c fix(manufacturing): update condition for base hour rate calculation (#53777) 2026-03-25 13:49:31 +05:30
Pandiyan P
78635ebe99 fix(manufacturing): apply work order status filter in job card (#53776) 2026-03-25 13:24:07 +05:30
mergify[bot]
d6afb9b10a fix(manufacturing): apply work order status filter in job card (backport #53766) (#53767)
Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
fix(manufacturing): apply work order status filter in job card (#53766)
2026-03-25 11:21:31 +05:30
mergify[bot]
468ca2bde1 fix(manufacturing): close work order status when stock reservation is… (backport #53714) (#53720)
Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix(manufacturing): close work order status when stock reservation is… (#53714)
2026-03-23 16:28:01 +00:00
rohitwaghchaure
28aa21bf83 Merge pull request #53706 from frappe/mergify/bp/version-15-hotfix/pr-53705
fix: batch validation for subcontracting receipt (backport #53705)
2026-03-23 18:52:47 +05:30
mergify[bot]
119195c6fa fix: initialize all tax columns to resolve Key error in item_wise_sales_register and item_wise_purchase_register reports (backport #53323) (#53583)
Co-authored-by: Lakshit Jain <ljain112@gmail.com>
fix: initialize all tax columns to resolve Key error in `item_wise_sales_register` and `item_wise_purchase_register` reports (#53323)
2026-03-23 18:05:54 +05:30
Rohit Waghchaure
32c0532dec fix: batch validation for subcontracting receipt
(cherry picked from commit b8d201658a)
2026-03-23 11:32:42 +00:00
diptanilsaha
46e784d094 fix: check for submit permissions instead of write permissions when updating status (backport #53697) (#53702) 2026-03-23 16:03:50 +05:30
mergify[bot]
c0ce34e12c fix(manufacturing): update non-stock item dict (backport #53689) (#53698)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix(manufacturing): update non-stock item dict (#53689)
2026-03-23 10:26:29 +00:00
Mihir Kandoi
eaf5494502 chore: linter (#53696) 2026-03-23 10:19:41 +00:00
mergify[bot]
04d74ad6eb fix: PO should not be required for internal transfers (backport #53681) (#53683)
* fix: PO should not be required for internal transfers (#53681)

(cherry picked from commit 5154102468)

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

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-23 09:30:20 +00:00
Nishka Gosalia
2de04b8a46 Merge pull request #53693 from frappe/mergify/bp/version-15-hotfix/pr-53649 2026-03-23 14:46:49 +05:30
mergify[bot]
974755b224 fix(trends): added validation for period_based_on filter (backport #53690) (#53691)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(trends): added validation for `period_based_on` filter (#53690)
2026-03-23 14:42:03 +05:30
mergify[bot]
5e767ea595 fix: shipping rule applied twice on non stock items (backport #53655) (#53686)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: shipping rule applied twice on non stock items (#53655)
2026-03-23 14:38:32 +05:30
nishkagosalia
f5bd85b4dc chore: Adding new argument in status updater to skip qty validation
(cherry picked from commit dcd0509089)
2026-03-23 08:57:35 +00:00
rohitwaghchaure
3fcf6cfef7 Merge pull request #53674 from frappe/mergify/bp/version-15-hotfix/pr-53673
fix: stock queue for SABB (backport #53673)
2026-03-22 13:20:05 +05:30
Rohit Waghchaure
461bc1733f fix: stock queue for SABB
(cherry picked from commit 3fcf308ed8)
2026-03-22 07:30:44 +00:00
rohitwaghchaure
812ca37055 Merge pull request #53669 from frappe/mergify/bp/version-15-hotfix/pr-53638
fix: deadlock issue for SLE (backport #53638)
2026-03-21 14:41:01 +05:30
Rohit Waghchaure
540a8540d6 fix: deadlock issue for SLE
(cherry picked from commit f48b03c6ec)
2026-03-21 08:34:28 +00:00
diptanilsaha
d96590c4d9 fix(budget-variance-report): validate 'budget_against' filter (backport #53079) (#53663) 2026-03-20 15:33:36 +05:30
mergify[bot]
90e4f9026d fix: do not overwrite expense account in stock entry (backport #53658) (#53660)
* fix: do not overwrite expense account in stock entry (#53658)

(cherry picked from commit fa35fbdb8e)

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

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-20 09:41:39 +00:00
mergify[bot]
defa1d4a76 fix: validate permission before updating status (backport #53651) (#53652)
* fix: validate permission before updating status (#53651)

(cherry picked from commit 8e17c722fb)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py
#	erpnext/selling/doctype/sales_order/sales_order.py
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py

* chore: resolve conflicts

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-19 20:49:53 +05:30
Nishka Gosalia
ff11429941 Merge pull request #53647 from frappe/mergify/bp/version-15-hotfix/pr-53645
fix: Adding validation for operation time in BOM (backport #53645)
2026-03-19 20:21:20 +05:30
Nishka Gosalia
b3f0e2a00d fix: merge conflicts 2026-03-19 19:54:53 +05:30
nishkagosalia
7707a79d44 fix: Adding validation for operation time in BOM
(cherry picked from commit 7f70e62c30)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/test_job_card.py
2026-03-19 13:00:08 +00:00
diptanilsaha
f8ab56ecc9 fix(sales_invoice): using msgprint and removed condition checking for is_created_using_pos to refetch payment methods (#53636) 2026-03-19 14:18:48 +05:30
Ravibharathi
488ea7f994 Merge pull request #53628 from frappe/mergify/bp/version-15-hotfix/pr-53509
fix: set customer details on customer creation at login (backport #53509)
2026-03-19 13:59:27 +05:30
mergify[bot]
6ea3d56972 fix(stock): fix email error message (backport #53606) (#53632)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): fix email error message (#53606)
2026-03-19 07:39:30 +00:00
Navin-S-R
e2c8dc5386 chore: resolve conflict 2026-03-19 13:06:16 +05:30
mergify[bot]
9c243e8dd0 refactor: remove test file import in stock ageing report (backport #53619) (#53625)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-19 06:50:54 +00:00
Sakthivel Murugan S
4f39dfd642 fix: set customer details on customer creation at login (#53509)
(cherry picked from commit 256d267a3b)

# Conflicts:
#	erpnext/portal/utils.py
2026-03-19 06:34:50 +00:00
mergify[bot]
af86fd3cb4 fix: consider returned qty in subcontracting report (backport #53616) (#53620)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: consider returned qty in subcontracting report (#53616)
2026-03-19 11:52:44 +05:30
mergify[bot]
5a3bc27e2c fix: python error in manufacture entry if transfer against is job card (backport #53615) (#53617)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: python error in manufacture entry if transfer against is job card (#53615)
2026-03-19 05:13:36 +00:00
mergify[bot]
562f93e75c fix: ignore cost center (backport #53063) (#53613)
Co-authored-by: Sowmya <106989392+SowmyaArunachalam@users.noreply.github.com>
fix: ignore cost center (#53063)
2026-03-19 04:41:10 +00:00
mergify[bot]
e0f1e757f3 fix: check posting_date in args (backport #53303) (#53611)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
fix: check posting_date in args (#53303)
2026-03-19 04:38:20 +00:00
rohitwaghchaure
7b64f88734 Merge pull request #53600 from frappe/mergify/bp/version-15-hotfix/pr-53599
fix: incorrect sle calculation when doc has project (backport #53599)
2026-03-18 19:07:26 +05:30
Mihir Kandoi
7acd435835 fix: incorrect sle calculation when doc has project (#53599)
(cherry picked from commit 6cb6a52ded)
2026-03-18 13:20:17 +00:00
rohitwaghchaure
16fe458b92 Merge pull request #53585 from frappe/mergify/bp/version-15-hotfix/pr-53246
feat: add cost center field to the stock entry accounting dimension tab (backport #53246)
2026-03-18 16:52:27 +05:30
rohitwaghchaure
4c2dba98da chore: fix conflicts
Removed several fields related to additional transfer entries and subcontracting inward orders from the stock entry JSON.
2026-03-18 16:32:56 +05:30
sudarshan-g
e17b5dfe61 feat: add cost center field to the stock entry accounting dimension tab
(cherry picked from commit 47772f4e77)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.json
2026-03-18 06:20:08 +00:00
mergify[bot]
c09c5999dc fix(stock): add company filter while fetching batches (backport #53369) (#53580)
* fix(stock): add company filter while fetching batches (#53369)

(cherry picked from commit 31d14df37b)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py
#	erpnext/stock/doctype/pick_list/pick_list.py
#	erpnext/stock/doctype/pick_list_item/pick_list_item.json

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-18 05:04:52 +00:00
mergify[bot]
a7bf55b4bf chore: make supplier data expanded by default in PI (backport #53565) (#53578)
* chore: make supplier data expanded by default in PI (#53565)

(cherry picked from commit b433852f8a)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-18 04:59:04 +00:00
mergify[bot]
526dc68c72 chore: add documentation link in valuation method field (backport #53564) (#53570)
* chore: add documentation link in valuation method field (#53564)

(cherry picked from commit f319857939)

# Conflicts:
#	erpnext/stock/doctype/item/item.json

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-17 16:50:12 +00:00
mergify[bot]
9771ed4c57 fix(manufacturing): update working hours validation (backport #53559) (#53566)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(manufacturing): update working hours validation (#53559)
2026-03-17 22:09:00 +05:30
mergify[bot]
57815a07ac fix(stock): fix the property setter (backport #53422) (#53573)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): fix the property setter (#53422)
2026-03-17 22:07:46 +05:30
Arturo
b9c8e8d478 fix(italy): fix e-invoice ScontoMaggiorazione structure and included_in_print_rate support (#53334) 2026-03-17 15:18:14 +00:00
mergify[bot]
526ffc1176 fix: Creating new item price incase of changes in expired item price (backport #53534) (#53544)
Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
fix: Creating new item price incase of changes in expired item price (#53534)
2026-03-17 14:51:13 +00:00
mergify[bot]
239728e4d9 fix(sales_invoice): reset payment methods on pos_profile change (backport #53514) (#53560)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(sales_invoice): reset payment methods on `pos_profile` change (#53514)
2026-03-17 20:17:32 +05:30
rohitwaghchaure
48d211f8a0 Merge pull request #53554 from saeedkola/fix/validate-stock-accounts-perpetual-v15-hotfix
fix: skip validate_stock_accounts in Journal Entry when perpetual inventory is disabled
2026-03-17 18:30:34 +05:30
ruthra kumar
761caba8e8 Merge pull request #53549 from frappe/mergify/bp/version-15-hotfix/pr-53548
fix: incorrect user perms in queries (backport #53548)
2026-03-17 17:14:04 +05:30
Saeed Kola
3bc9190795 fix: skip validate_stock_accounts when perpetual inventory is disabled
When perpetual inventory is disabled, stock transactions produce no GL
entries, so blocking manual Journal Entries against stock accounts is
incorrect. Added an early return guard in validate_stock_accounts()
to skip the check when is_perpetual_inventory_enabled() returns False.
2026-03-17 17:08:29 +05:30
ruthra kumar
3720a8d5c9 Merge pull request #53528 from frappe/mergify/bp/version-15-hotfix/pr-52675
fix(banking): include paid purchase invoices in reports and bank clearance (backport #52675)
2026-03-17 17:01:33 +05:30
ruthra kumar
03f09222cf fix: use qb to prevent incorrect sql due to user permissions
(cherry picked from commit 04b967bd6d)

# Conflicts:
#	erpnext/controllers/queries.py
2026-03-17 16:45:29 +05:30
ruthra kumar
f232024fa4 chore: remove incorrect import
(cherry picked from commit fc2edfbded)

# Conflicts:
#	erpnext/controllers/queries.py
2026-03-17 11:02:32 +00:00
rohitwaghchaure
fd336e8d4b Merge pull request #53537 from frappe/mergify/bp/version-15-hotfix/pr-53500
fix: valuation rate for no Use Batch wise Valuation batches (backport #53500)
2026-03-17 15:53:29 +05:30
rohitwaghchaure
c384564314 fix: test case
Removed company parameter from get_valuation_method call.
2026-03-17 15:05:46 +05:30
Rohit Waghchaure
ca6872c768 fix: valuation rate for no Use Batch wise Valuation batches
(cherry picked from commit 4befa15198)
2026-03-17 08:42:59 +00:00
Sakthivel Murugan S
a5d1afe304 fix(minor): filter bank accounts in bank statement import (#53481) 2026-03-17 11:31:22 +05:30
Nikhil Kothari
a85aeb2f9b chore: resolve conflicts 2026-03-17 11:28:37 +05:30
rohitwaghchaure
158e290580 Merge pull request #53518 from frappe/mergify/bp/version-15-hotfix/pr-53513
fix: do not set valuation rate for invoice without update stock (backport #53513)
2026-03-17 11:23:55 +05:30
mergify[bot]
0e770c0bbd fix: add item_name to quick entry fields in Item doctype (backport #53530) (#53532)
Co-authored-by: Abdus Samad <120767334+Samad-11@users.noreply.github.com>
fix: add item_name to quick entry fields in Item doctype (#53530)
2026-03-17 05:42:21 +00:00
mergify[bot]
d262a65b00 fix: correct overlap detection in JobCard.has_overlap (backport #53473) (#53522)
Co-authored-by: Sanjesh-Raju <sanjesh@tridotstech.com>
Co-authored-by: Sanjesh <rsanjesh64@gmail.com>
Co-authored-by: Tridots Tech <info@tridotstech.com>
fix: correct overlap detection in JobCard.has_overlap (#53473)
2026-03-17 10:42:51 +05:30
Nikhil Kothari
ab9d960aa8 fix(banking): include paid purchase invoices in reports and bank clearance (#52675)
* fix(banking): include paid purchase invoices in reports and bank clearance

* fix: condition for amounts not reflected in system

* fix: set Sales Invoice to be the payment document in bank rec

* fix: add additional filter for `is_paid`

* fix: added is_paid

* fix: added invoice number in bank clearance tool

* chore: make requested changes

* fix: exclude opening JEs

* fix: bring back banking icon in desktop

(cherry picked from commit ef32622166)

# Conflicts:
#	erpnext/accounts/doctype/bank_clearance/bank_clearance.py
#	erpnext/desktop_icon/banking.json
2026-03-17 04:48:25 +00:00
mergify[bot]
eec8cf8a71 fix: change "Date" label to "Posting Date" in Sales Invoice and Purchase Invoice (backport #53503) (#53516)
* fix: change "Date" label to "Posting Date" in Sales Invoice and Purchase Invoice (#53503)

(cherry picked from commit 4cd150ba7a)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Abdus Samad <120767334+Samad-11@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-16 18:33:11 +00:00
Rohit Waghchaure
284ccd1def fix: do not set valuation rate for invoice without update stock
(cherry picked from commit bec9e48435)
2026-03-16 18:03:28 +00:00
rohitwaghchaure
3aafed0659 Merge pull request #53497 from frappe/mergify/bp/version-15-hotfix/pr-53495
fix: stock adjustment entry (backport #53495)
2026-03-16 18:07:13 +05:30
mergify[bot]
30fe711c44 fix(support-settings): disable the auto-close tickets feature if close_issue_after_days is set to 0 (backport #53499) (#53504)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(support-settings): disable the auto-close tickets feature if `close_issue_after_days` is set to 0 (#53499)
2026-03-16 12:12:04 +00:00
Rohit Waghchaure
ac6c06daf9 fix: stock adjustment entry
(cherry picked from commit af3067ee23)
2026-03-16 09:47:52 +00:00
mergify[bot]
b63b5320f2 fix(p&l_statement): disable accumulated value filter by default (backport #53488) (#53489)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(p&l_statement): disable accumulated value filter by default (#53488)
2026-03-16 12:53:12 +05:30
ruthra kumar
433dec8a6c Merge pull request #53418 from frappe/mergify/bp/version-15-hotfix/pr-53415
fix: broke cost center filter in get outstanding reference docs (backport #53415)
2026-03-16 10:09:03 +05:30
mergify[bot]
81244a84e7 chore: add docs to project URLs (backport #53467) (#53468)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2026-03-15 14:53:39 +01:00
mergify[bot]
7a7c4a03f0 fix(serial_and_batch_bundle_selector): handle CSV attachment properly (backport #53460) (#53461)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
fix(serial_and_batch_bundle_selector): handle CSV attachment properly (#53460)
2026-03-15 07:45:00 +00:00
mergify[bot]
a6cf31edad fix: sales order indicator should be based on available qty rather th… (backport #53456) (#53457) 2026-03-15 09:46:07 +05:30
ruthra kumar
d6693c9b79 Merge pull request #53425 from frappe/mergify/bp/version-15-hotfix/pr-53423
refactor: disable total row in trends report (backport #53423)
2026-03-13 18:18:26 +05:30
ruthra kumar
56ffd52335 refactor: disable total row in trends report
(cherry picked from commit 4dbc72b301)
2026-03-13 12:32:55 +00:00
ruthra kumar
53e3bfbf22 fix: broke cost center filter in get outstanding reference docs
(cherry picked from commit 7dfe36fdce)
2026-03-13 09:55:16 +00:00
mergify[bot]
db9dc86694 Revert "fix(regional): rename duplicate Customer fields in Italy setup" (backport #53409) (#53410)
* Revert "fix(regional): rename duplicate Customer fields in Italy setup" (#53409)

(cherry picked from commit bd87a7e612)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-13 07:47:07 +00:00
Khushi Rawat
1c444ef822 Merge pull request #53407 from khushi8112/show-asset-purchase-amount-currency
fix: update label on company change
2026-03-13 12:42:02 +05:30
khushi8112
f702a71126 chore: linters check 2026-03-13 12:22:45 +05:30
khushi8112
908e185cfe fix: update label on company change 2026-03-13 11:59:42 +05:30
mergify[bot]
2a70203cab fix(regional): rename duplicate Customer fields in Italy setup (backport #50921) (#53397)
* fix(regional): rename duplicate Customer fields in Italy setup (#50921)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit c6efc403cd)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

* chore: resolve conflicts

Removed obsolete patches for older versions.

---------

Co-authored-by: Solede <lorenzo.caldara@gmail.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-12 15:20:22 +00:00
Mihir Kandoi
85c4cc3e1b fix: update delivery date in line items (#53331) 2026-03-12 20:37:52 +05:30
Khushi Rawat
62280c285f Merge pull request #53377 from frappe/asset-repair-show-general-ledger
fix: use completion_date not posting date
2026-03-12 15:10:04 +05:30
mergify[bot]
5737d2afa3 fix: precision issue in production plan (backport #53370) (#53373)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: precision issue in production plan (#53370)
2026-03-12 09:32:09 +00:00
mergify[bot]
0e00ab8865 fix: do not modify rate in the child item merely for comparison (backport #53301) (#53375)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: do not modify rate in the child item merely for comparison (#53301)
2026-03-12 09:20:39 +00:00
Khushi Rawat
05d614eb04 fix: coderebbit review 2026-03-12 14:41:23 +05:30
khushi8112
6d476604df fix: use completion_date not posting date 2026-03-12 14:24:59 +05:30
Ejaaz Khan
0d527ac8ea Merge pull request #53359 from frappe/mergify/bp/version-15-hotfix/pr-53348
fix: remove redundant pos print format (backport #53348)
2026-03-12 12:53:40 +05:30
mergify[bot]
0612f1c941 fix: NoneType error when template description is to be copied to variant (backport #53358) (#53365)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
fix: NoneType error when template description is to be copied to variant (#53358)
2026-03-12 06:49:53 +00:00
mergify[bot]
dbed426725 refactor: supplier quotation comparision report button should start f… (backport #53361) (#53362)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-12 06:19:37 +00:00
Ejaaz Khan
8497d1f8cf fix: remove redundant pos print format (#53348)
(cherry picked from commit e4d79c6246)
2026-03-12 06:02:34 +00:00
mergify[bot]
034d460ae1 fix(delivery note): avoid maintaining si_detail on return delivery note (backport #52456) (#53352)
Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
fix(delivery note): avoid maintaining si_detail on return delivery note (#52456)
2026-03-12 05:40:33 +00:00
mergify[bot]
fd94cd0e7c Feat/shipment default contact (backport #53029) (#53354)
Co-authored-by: David <52141166+sdavidbastos@users.noreply.github.com>
2026-03-12 05:40:00 +00:00
mergify[bot]
db251c6e11 fix: re-calculate taxes and totals after resetting bundle item rate (backport #53342) (#53349)
Co-authored-by: V Shankar <shankarv292002@gmail.com>
fix: re-calculate taxes and totals after resetting bundle item rate (#53342)
2026-03-12 05:15:12 +00:00
Nihantra C. Patel
b037dae529 Merge pull request #53335 from frappe/mergify/bp/version-15-hotfix/pr-53327
fix: Append existing ignored doctypes in Journal Entry on_cancel instead of overwriting (backport #53327)
2026-03-11 16:29:11 +05:30
Nihantra Patel
b73d9700d0 fix: Append existing ignored doctypes in Journal Entry on_cancel instead of overwriting
(cherry picked from commit 39e10c4ab0)
2026-03-11 10:41:30 +00:00
ruthra kumar
73d347f456 Merge pull request #53328 from frappe/mergify/bp/version-15-hotfix/pr-53326
refactor: make cost center editable in payment entry deduction (backport #53326)
2026-03-11 15:08:34 +05:30
ruthra kumar
3e7d2c6f11 refactor: make cost center editable in payment entry deduction
(cherry picked from commit 078b22d985)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
2026-03-11 14:53:09 +05:30
Nishka Gosalia
4ebc23752e Merge pull request #53324 from frappe/mergify/bp/version-15-hotfix/pr-53312
fix: update item description in Production Plan Assembly Items table (backport #53312)
2026-03-11 14:35:10 +05:30
Parameshwari Palanisamy
ef6fd7dcb5 Update production_plan.py
(cherry picked from commit 39e68a9ce7)
2026-03-11 08:49:06 +00:00
creative-paramu
e3e9d7b19e fix: update item description in Production Plan Assembly Items table
(cherry picked from commit 19533551f4)
2026-03-11 08:49:06 +00:00
NaviN
a4aaf67b2b Merge pull request #52964 from ljain112/fix-tds-party
fix(tds-report): correct party type filtering and refactor
2026-03-11 11:41:44 +05:30
Jatin3128
d425e90ef7 Merge pull request #53213 from Jatin3128/fix-53173
fix: correct payment terms fetching and recalculation logic
2026-03-11 02:56:03 +05:30
Jatin3128
79b04826d9 fix: correct payment terms fetching and recalculation logic 2026-03-11 02:55:40 +05:30
NaviN
8c7100df04 Merge pull request #53279 from frappe/mergify/bp/version-15-hotfix/pr-53203
fix: update user status depends on employee status (backport #53203)
2026-03-10 22:41:20 +05:30
Navin-S-R
55a0603356 chore: resolve conflict 2026-03-10 17:58:57 +05:30
ruthra kumar
abe433cfa7 Merge pull request #53296 from frappe/mergify/bp/version-15-hotfix/pr-53071
fix(gross-profit): apply precision-based rounding to grouped totals (backport #53071)
2026-03-10 16:55:44 +05:30
Khushi Rawat
49648b5c6e Merge pull request #53258 from ljain112/backport-50804
fix: correct logic for repair cost in asset repair
2026-03-10 16:46:53 +05:30
Navin-S-R
b59dc173b8 fix(gross-profit): apply precision-based rounding to grouped totals
(cherry picked from commit 52dd7665e7)
2026-03-10 11:08:07 +00:00
rohitwaghchaure
7454db2b3e Merge pull request #53288 from frappe/mergify/bp/version-15-hotfix/pr-53283
fix: removed non existent patch (backport #53283)
2026-03-10 14:48:11 +05:30
mergify[bot]
fcfadf9dea Merge pull request #53286 from frappe/mergify/bp/version-15-hotfix/pr-53282
fix: allow user to make QI after submission not working (backport #53282)
2026-03-10 09:12:20 +00:00
rohitwaghchaure
098f6fd0d2 Merge pull request #53284 from frappe/mergify/bp/version-15-hotfix/pr-53281
fix: better validation message for Purchase Invoice with Update Stock (backport #53281)
2026-03-10 14:29:44 +05:30
Rohit Waghchaure
fd8fac7d40 fix: removed non existent patch
(cherry picked from commit c4b3080eae)
2026-03-10 08:56:16 +00:00
Rohit Waghchaure
b7fd9aea6a fix: better validation message for Purchase Invoice with Update Stock
(cherry picked from commit cfb06cf247)
2026-03-10 08:33:50 +00:00
Poovitha Palanivelu
c5796fed4a fix: update user status depends on employee status
(cherry picked from commit 194d060f13)

# Conflicts:
#	erpnext/setup/doctype/employee/employee.py
2026-03-10 07:39:56 +00:00
Mihir Kandoi
2b25059315 Merge pull request #53276 from frappe/mergify/bp/version-15-hotfix/pr-53235
fix: update item row delivery dates when header delivery date changes in sales order (backport #53235)
2026-03-10 13:09:31 +05:30
Pandiyan37
dfbb3e97a8 fix(selling): update delivery date in line items
(cherry picked from commit 77367b5517)
2026-03-10 07:23:38 +00:00
ruthra kumar
ee22347d64 Merge pull request #53243 from frappe/mergify/bp/version-15-hotfix/pr-53239
fix: validation for cancellation (backport #53239)
2026-03-10 12:23:14 +05:30
ruthra kumar
4d418d40db Merge pull request #52995 from frappe/mergify/bp/version-15-hotfix/pr-52630
fix(account): compute tax net_amount in JS controller (backport #52630)
2026-03-09 20:26:44 +05:30
ljain112
a6dd07802a fix: enforce permission check for purchase invoice and update test to use service expense account 2026-03-09 19:53:57 +05:30
ljain112
94972da845 fix: correct function syntax in TDS Computation Report 2026-03-09 19:25:38 +05:30
ljain112
c2e67599f5 fix: refactor GL entry mapping to include voucher type 2026-03-09 19:14:21 +05:30
ljain112
bcc542b1f9 fix(test): include warehouse parameter in asset repair test case 2026-03-09 19:11:02 +05:30
ljain112
ed428ceb1c fix(test): ensure warehouse is consistently referenced in asset repair tests 2026-03-09 18:54:43 +05:30
ljain112
93ebec90ef fix: enhance sorting and optimize GL entry retrieval 2026-03-09 18:50:42 +05:30
ljain112
0b1746a4c8 fix: set default repair cost to 0 if no value is returned 2026-03-09 18:34:46 +05:30
ljain112
c71557f432 fix: correct logic for repair cost in asset repair 2026-03-09 18:03:59 +05:30
Rohit Waghchaure
c142a2be9c fix: validation for cancellation
(cherry picked from commit 8de272a8a1)
2026-03-09 07:49:22 +00:00
Mihir Kandoi
3c77653508 Merge pull request #53240 from frappe/mergify/bp/version-15-hotfix/pr-53234
fix(manufacturing): show returned qty in progress bar (backport #53234)
2026-03-09 12:42:04 +05:30
Sudharsanan11
c572a019b4 feat(manufacturing): show disassembled qty in progress bar
(cherry picked from commit ae9ff767fa)
2026-03-09 07:04:32 +00:00
Sudharsanan11
260d87a80c fix(manufacturing): show returned qty in progress bar
(cherry picked from commit 8027f5aafd)
2026-03-09 07:04:31 +00:00
ruthra kumar
e0f5ae2d4c Merge pull request #53228 from frappe/mergify/bp/version-15-hotfix/pr-53227
refactor: party type and party filter for comparison report (backport #53227)
2026-03-08 05:49:51 +05:30
ruthra kumar
37e750e877 refactor: party type and party filter for comparison report
(cherry picked from commit b6f9c0844e)
2026-03-07 12:49:44 +00:00
Jatin3128
3148816451 fix: correct payment terms fetching and recalculation logic 2026-03-06 15:08:35 +05:30
rohitwaghchaure
5764f5ec80 Merge pull request #53207 from frappe/mergify/bp/version-15-hotfix/pr-53200
fix: stock balance report qty (backport #53200)
2026-03-06 13:08:11 +05:30
mergify[bot]
e9ae156323 refactor: use postprocess in mapped_doc to update items in subcontracting controller (backport #52724) (#52936)
* refactor: use postprocess in mapped_doc to update items in subcontracting controller

(cherry picked from commit 1d3d09f48c)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: ljain112 <ljain112@gmail.com>
2026-03-06 12:59:15 +05:30
rohitwaghchaure
54fdce648e chore: fix conflicts 2026-03-06 12:47:18 +05:30
Rohit Waghchaure
180e232eb0 fix: stock balance report qty
(cherry picked from commit a15e5fdc4e)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py
2026-03-06 07:15:30 +00:00
mergify[bot]
ba4a99b22c fix(help): escape query (backport #53192) (#53194)
fix(help): escape query (#53192)


(cherry picked from commit 702adda000)

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
Co-authored-by: Akhil Narang <me@akhilnarang.dev>
2026-03-05 18:40:49 +05:30
Mihir Kandoi
9100428f1b Merge pull request #53176 from frappe/resolve/version-15-hotfix/pr-53084 2026-03-05 17:36:47 +05:30
rohitwaghchaure
1950e82d1e Merge pull request #53180 from frappe/mergify/bp/version-15-hotfix/pr-52745
fix: balance qty for inv dimension (backport #52745)
2026-03-05 14:27:27 +05:30
Rohit Waghchaure
6898d70382 fix: balance qty for inv dimension
(cherry picked from commit a3eafe5b18)
2026-03-05 08:20:02 +00:00
Sudharsanan11
624d1d4759 fix(manufacturing): ignore sales order validation for subassembly item
(cherry picked from commit 6b1aac4aee)
2026-03-05 12:50:27 +05:30
Mihir Kandoi
bf6b5b7b7f Merge pull request #53164 from frappe/mergify/bp/version-15-hotfix/pr-53157
fix: disallow all actions on job card if work order is closed (backport #53157)
2026-03-04 17:09:17 +05:30
Khushi Rawat
24d9e2c5a9 Merge pull request #53162 from frappe/mergify/bp/version-15-hotfix/pr-53154
fix: skip asset sale processing for internal transfer invoices (backport #53154)
2026-03-04 17:00:56 +05:30
Mihir Kandoi
7b2e4832aa fix: disallow all actions on job card if work order is closed
(cherry picked from commit ee19c32c3a)
2026-03-04 11:21:34 +00:00
khushi8112
a7e8f31f56 fix: skip asset sale processing for internal transfer invoices
(cherry picked from commit 9cb3dad079)
2026-03-04 11:07:05 +00:00
Nishka Gosalia
d5a250a254 Merge pull request #53158 from frappe/mergify/bp/version-15-hotfix/pr-53156
fix: updating costing based on employee change in timesheet (backport #53156)
2026-03-04 16:35:06 +05:30
Nishka Gosalia
be598108b6 fix: updating costing based on employee change in timesheet
(cherry picked from commit e37d4a6f7c)
2026-03-04 10:42:50 +00:00
mergify[bot]
68bac20198 Merge pull request #53095 from frappe/mergify/bp/version-15-hotfix/pr-52838
fix: correct fields being updated on material request and purchase or… (backport #52838)
2026-03-04 07:33:02 +00:00
mergify[bot]
072ab8d5f3 feat: allowing rate modification in update item in quotation (backport #53147) (#53150)
Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
2026-03-04 07:29:46 +00:00
diptanilsaha
f9e5ac6b60 Merge branch 'version-15' into version-15-hotfix 2026-03-03 23:11:48 +05:30
Mihir Kandoi
8131af51a3 Merge pull request #53137 from frappe/mergify/bp/version-15-hotfix/pr-52784
fix(stock): validate company for receipt documents and expense accounts (backport #52784)
2026-03-03 21:46:25 +05:30
Mihir Kandoi
17dc1681b0 Merge pull request #53133 from frappe/mergify/bp/version-15-hotfix/pr-53070
fix: handle html email template separately in RFQ to avoid jinja cont… (backport #53070)
2026-03-03 21:37:21 +05:30
Kavin
62340babb8 Merge pull request #53135 from frappe/mergify/bp/version-15-hotfix/pr-53132
fix(selling): handle selling price validation for FG item  (backport #53132)
2026-03-03 21:31:12 +05:30
Kavin
6846f02cea fix: resolve conflicts 2026-03-03 21:28:34 +05:30
mergify[bot]
10b40836a9 Merge pull request #53128 from frappe/mergify/bp/version-15-hotfix/pr-53037
fix(stock): pass company to avoid document naming rule issue in QI (backport #53037)
2026-03-03 15:56:44 +00:00
Sudharsanan11
9b0d4ab4da test(stock): add test to validate company for receipts and expense accounts
(cherry picked from commit d58171987c)
2026-03-03 15:46:49 +00:00
Sudharsanan11
74e71f3868 fix: set company based expense account
(cherry picked from commit d54d0c25a2)

# Conflicts:
#	erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
2026-03-03 15:46:48 +00:00
Sudharsanan11
44620884c1 fix(stock): validate company for receipt documents and expense accounts
(cherry picked from commit 15dfc08a31)
2026-03-03 15:46:48 +00:00
Mihir Kandoi
bd76ef29cc Merge pull request #53124 from frappe/mergify/bp/version-15-hotfix/pr-53123
fix: serial no status for Disassemble entry (backport #53123)
2026-03-03 21:12:48 +05:30
kavin-114
dced21faf2 test: add unit test for FG Item selling price validation
(cherry picked from commit 723993fdf6)
2026-03-03 15:40:19 +00:00
kavin-114
d5cc51b426 fix(selling): handle selling price validation for FG item
System checks valuation rate in incoming_rate field, since SO has it in valuation_rate field of item row,
need to handle the field name dynamically based upon the doctype name in Selling Controller.

(cherry picked from commit 4335318482)
2026-03-03 15:40:18 +00:00
Mihir Kandoi
3581906033 chore: resolve conflicts 2026-03-03 21:09:03 +05:30
Pugazhendhi Velu
90169a39bb fix: handle html email template separately in RFQ to avoid jinja context error
(cherry picked from commit 49d363b174)

# Conflicts:
#	erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
2026-03-03 15:36:27 +00:00
Mihir Kandoi
86f8bd403d Merge pull request #53126 from frappe/mergify/bp/version-15-hotfix/pr-53099
fix: populate mr owner and set po owner as fallback (backport #53099)
2026-03-03 20:54:40 +05:30
kavin-114
c1f2991694 fix: populate mr owner and set po owner as fallback
(cherry picked from commit bdf4e51da3)
2026-03-03 15:09:12 +00:00
Rohit Waghchaure
8ce541bf46 fix: serial no status for Disassemble entry
(cherry picked from commit 81acefa8ad)
2026-03-03 13:24:03 +00:00
Shariq Ansari
0c0f1a6591 Merge pull request #53121 from frappe/mergify/bp/version-15-hotfix/pr-53119
fix: do not create contact if not passed from crm (backport #53119)
2026-03-03 04:00:02 -08:00
shariquerik
e2b6179b17 fix: ensure contacts are processed only if present in create_prospect_against_crm_deal
(cherry picked from commit 800810d23d)
2026-03-03 11:43:24 +00:00
Mihir Kandoi
96872d314d Merge pull request #53120 from frappe/gh53107 2026-03-03 17:12:33 +05:30
Mihir Kandoi
3c6a120c5c fix: item description html validation error 2026-03-03 16:57:02 +05:30
diptanilsaha
067a57d4c3 Merge pull request #53113 from frappe/mergify/bp/version-15-hotfix/pr-53110
fix(pricing_rule): strict validation of `transaction_type` (backport #53110)
2026-03-03 15:19:30 +05:30
diptanilsaha
814c17aafe refactor: renamed args to pricing_ctx in set_transaction_type for clarity
(cherry picked from commit 6342e78305)
2026-03-03 09:30:32 +00:00
diptanilsaha
5f82db200e fix(pricing_rule): strict validation of transaction_type
(cherry picked from commit 7ec0354a79)
2026-03-03 09:30:32 +00:00
mergify[bot]
f5f40dbcc3 fix(accounts receivable): include invoice payment terms template (backport #51940) (#53105)
Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-03 12:41:03 +05:30
rohitwaghchaure
15054a1c10 Merge pull request #53097 from frappe/mergify/bp/version-15-hotfix/pr-53093
fix: opening qty in stock balance (backport #53093)
2026-03-02 22:42:59 +05:30
rohitwaghchaure
2e26a7fa0b chore: fix linters issue 2026-03-02 21:59:50 +05:30
rohitwaghchaure
08e155cec5 Merge pull request #53091 from frappe/mergify/bp/version-15-hotfix/pr-53087
perf: add index on reference_purchase_receipt column (backport #53087)
2026-03-02 21:57:36 +05:30
rohitwaghchaure
658d219aa1 chore: fix conflicts
Refactor stock balance calculation to use dictionary for stock reconciliation voucher count and improve data handling.
2026-03-02 21:56:38 +05:30
rohitwaghchaure
6f164cb183 chore: fix conflicts 2026-03-02 21:39:36 +05:30
rohitwaghchaure
4568ed3fb4 Merge pull request #53089 from frappe/mergify/bp/version-15-hotfix/pr-53082
fix: validate warehouse of SABB for draft entry (backport #53082)
2026-03-02 21:37:47 +05:30
Rohit Waghchaure
470a9b38b1 fix: opening qty in stock balance
(cherry picked from commit d7fdab99cb)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py
2026-03-02 15:19:30 +00:00
kavin-114
0766c0ea43 perf: add index on reference_purchase_receipt column
(cherry picked from commit 8c94396ad9)

# Conflicts:
#	erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
2026-03-02 09:08:16 +00:00
rohitwaghchaure
9d73faddfe chore: fix conflicts
Removed redundant on_update method and cleaned up code.
2026-03-02 14:35:45 +05:30
Rohit Waghchaure
d5e25153f8 fix: validate warehouse of SABB for draft entry
(cherry picked from commit 9b8f685c82)

# Conflicts:
#	erpnext/controllers/stock_controller.py
2026-03-02 08:52:37 +00:00
rohitwaghchaure
94afc32a8d Merge pull request #53076 from frappe/mergify/bp/version-15-hotfix/pr-53050
fix: voucher detail no in SABB (backport #53050)
2026-03-02 12:48:03 +05:30
rohitwaghchaure
97df39a630 chore: fix conflicts 2026-03-02 12:10:43 +05:30
Rohit Waghchaure
6e5738e95c fix: voucher detail no in SABB
(cherry picked from commit c37a56ec89)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
2026-03-02 04:23:42 +00:00
Mihir Kandoi
6147541f58 Merge pull request #53068 from frappe/mergify/bp/version-15-hotfix/pr-53067
fix: use the correct precision value in stock reco (backport #53067)
2026-03-01 21:36:56 +05:30
Mihir Kandoi
6d726e4b64 fix: use the correct precision value in stock reco
(cherry picked from commit 36726b0f7b)
2026-03-01 15:52:02 +00:00
Mihir Kandoi
8fed606357 Merge pull request #53065 from frappe/mergify/bp/version-15-hotfix/pr-53062
fix: correct sle voucher_type comparison in get_ref_doctype (backport #53062)
2026-03-01 21:01:39 +05:30
Sanjesh
a587b6a57c fix: correct sle voucher_type comparison in get_ref_doctype
(cherry picked from commit cffb59ae73)
2026-03-01 15:16:29 +00:00
Mihir Kandoi
51fb7affeb Merge pull request #53060 from frappe/mergify/bp/version-15-hotfix/pr-53057
fix: allow allowed roles to bypass over billing validation (backport #53057)
2026-03-01 20:43:16 +05:30
Mihir Kandoi
6ac2f510a6 chore: resolve conflicts 2026-03-01 20:28:00 +05:30
Mihir Kandoi
2a78f47f02 Merge pull request #53058 from frappe/mergify/bp/version-15-hotfix/pr-53052
fix: use stock qty instead of qty when creating stock entry from MR (backport #53052)
2026-03-01 15:52:19 +05:30
Mihir Kandoi
8e59fe90cc fix: allow allowed roles to bypass over billing validation
(cherry picked from commit 04127019f9)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2026-03-01 10:18:05 +00:00
Mihir Kandoi
bf7b9c10dc Merge pull request #53053 from frappe/mergify/bp/version-15-hotfix/pr-53051
fix: use conversion factor when creating stock entry from pick list (backport #53051)
2026-03-01 15:25:57 +05:30
Mihir Kandoi
2984f79a69 fix: use stock qty instead of qty when creating stock entry from MR
(cherry picked from commit 30c3ff2efe)
2026-03-01 09:51:35 +00:00
Mihir Kandoi
f2e1482f63 fix: use conversion factor when creating stock entry from pick list
(cherry picked from commit 5f12b0db3f)
2026-03-01 09:40:21 +00:00
Diptanil Saha
0458930d9e Merge pull request #53019 from diptanilsaha/tb_total 2026-02-27 16:52:46 +05:30
Frappe PR Bot
b2a8af5ba6 chore(release): Bumped to Version 15.99.1
## [15.99.1](https://github.com/frappe/erpnext/compare/v15.99.0...v15.99.1) (2026-02-27)

### Bug Fixes

* old stock reco entries causing issue in the stock balance report ([bba0a6d](bba0a6d950))
2026-02-27 10:50:36 +00:00
rohitwaghchaure
45eed5fd81 Merge pull request #53025 from frappe/mergify/bp/version-15/pr-53020
fix: old stock reco entries causing issue in the stock balance report (backport #53013) (backport #53020)
2026-02-27 16:18:40 +05:30
rohitwaghchaure
435c852b54 chore: fix conflicts
Added a method to prepare stock reconciliation voucher-wise count and updated logic for handling stock reconciliation entries.

(cherry picked from commit cc6fbde463)
2026-02-27 10:32:36 +00:00
Rohit Waghchaure
bba0a6d950 fix: old stock reco entries causing issue in the stock balance report
(cherry picked from commit 0874cbc268)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py
(cherry picked from commit b712467049)
2026-02-27 10:32:36 +00:00
rohitwaghchaure
51d5885454 Merge pull request #53020 from frappe/mergify/bp/version-15-hotfix/pr-53013
fix: old stock reco entries causing issue in the stock balance report (backport #53013)
2026-02-27 16:02:12 +05:30
rohitwaghchaure
cc6fbde463 chore: fix conflicts
Added a method to prepare stock reconciliation voucher-wise count and updated logic for handling stock reconciliation entries.
2026-02-27 15:46:52 +05:30
Rohit Waghchaure
b712467049 fix: old stock reco entries causing issue in the stock balance report
(cherry picked from commit 0874cbc268)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py
2026-02-27 09:26:37 +00:00
diptanilsaha
eabaef2f0b fix(trial-balance): totals with filter show_group_accounts enabled 2026-02-27 14:42:53 +05:30
Mihir Kandoi
f90e984833 Merge pull request #53017 from frappe/mergify/bp/version-15-hotfix/pr-53012 2026-02-27 13:58:02 +05:30
Thomas antony
f6a05ec85e feat: UOM query filter for opportunity items
Add UOM query filter based on item code in opportunity form.

(cherry picked from commit b33f06701c)
2026-02-27 08:23:31 +00:00
Diptanil Saha
5ba38bcf32 Merge pull request #53001 from frappe/mergify/bp/version-15-hotfix/pr-38468
fix: Variant Items, List View Enabled to Variant Status Change (backport #38468)
2026-02-26 16:50:55 +05:30
diptanilsaha
39f8d35414 chore: resolve conflict 2026-02-26 16:44:17 +05:30
Parameshwari Palanisamy
25b169059d fix: Variant Items, List View Enabled to Variant Status Change (#38468)
* feat: Purchase Register Report Supplier Group filter Adding

* fix: Variant Items, List View Enabled to Variant Status Change

* Update purchase_register.js

* Update purchase_register.py

(cherry picked from commit 9ea963bfe9)

# Conflicts:
#	erpnext/stock/doctype/item/item_list.js
2026-02-26 11:08:55 +00:00
David Arnold
717c5b25eb fix: client-side taxes calculation (#44510)
closes: #44328
2026-02-26 13:55:17 +05:30
ruthra kumar
e5282a48ae chore: resolve conflicts 2026-02-26 13:52:23 +05:30
Luis Mendoza
516ad9021b fix(accounts): round and convert net_amount to company currency in JS tax controller
(cherry picked from commit b10b205394)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2026-02-26 08:17:52 +00:00
Luis Mendoza
86c628521e style: prettier formatting
(cherry picked from commit 485166b668)
2026-02-26 08:17:51 +00:00
Luis Mendoza
6ad84d66cc fix(accounts): compute tax net_amount in JS controller
(cherry picked from commit 153ad99f85)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2026-02-26 08:17:51 +00:00
ruthra kumar
a2a6460c6c Merge pull request #52992 from frappe/mergify/bp/version-15-hotfix/pr-52188
fix(payment entry): round unallocated amount (backport #52188)
2026-02-26 13:24:06 +05:30
ravibharathi656
76a19076bf fix(payment entry): round unallocated amount
(cherry picked from commit b0d6751777)
2026-02-26 07:38:07 +00:00
ruthra kumar
fdaa0b8b58 Merge pull request #52974 from frappe/mergify/bp/version-15-hotfix/pr-52973
fix: ensure cache is cleared on fiscal year update and trash (backport #52973)
2026-02-26 10:00:29 +05:30
ljain112
7278166b2c fix: ensure cache is cleared on fiscal year update and trash
(cherry picked from commit 39b0e3522a)
2026-02-25 14:35:48 +00:00
ljain112
e5eb5406da fix(tds-report): correct party type filtering and refactor 2026-02-25 16:30:30 +05:30
mergify[bot]
9657c6431d Merge pull request #52932 from frappe/mergify/bp/version-15-hotfix/pr-52824
refactor: separate construction of chart related data from `get_columns()` (backport #52824)
2026-02-25 15:11:46 +05:30
Frappe PR Bot
7ad770a83a chore(release): Bumped to Version 15.99.0
# [15.99.0](https://github.com/frappe/erpnext/compare/v15.98.1...v15.99.0) (2026-02-25)

### Bug Fixes

* **`fiscal_year_company`:** made `company` field mandatory ([2fffc94](2fffc9448b))
* **`fiscal_year`:** `Fiscal Year` auto-generation and notification ([397f39e](397f39e271))
* `update_stock` behaviour on selling invoices ([82bcb62](82bcb62b21))
* Add handling for Sales Invoice Item quantity field ([41c7890](41c7890a6d))
* add purchase invoice as well ([2fc3e30](2fc3e30f9f))
* avoid duplicate taxes and charges rows in payment entry (backport [#52178](https://github.com/frappe/erpnext/issues/52178)) ([#52318](https://github.com/frappe/erpnext/issues/52318)) ([946c355](946c3554b1))
* better permissions on make payment request ([ce7101f](ce7101f555))
* bug with comparison regarding `None` values and empty string ([852c200](852c200ee0))
* check gl account of an associated bank account in bank transaction ([6b286ae](6b286ae03d))
* enfore permission on make_payment_request ([4602919](460291990a))
* get employee email with priority if preferred is not set ([943e2c0](943e2c00bc))
* ignore permissions instead of saving parent ([bce77b6](bce77b6117))
* inconsistent label name between parent and child ([1bf608f](1bf608f835))
* **manufacturing:** remove delete query of job card & batch and serial no  ([#52840](https://github.com/frappe/erpnext/issues/52840)) ([e30b2f1](e30b2f1d04))
* **manufacturing:** set pick list purpose while creating it from work order ([33d48c5](33d48c5575))
* **manufacturing:** update status for work order before calculating planned qty ([b3bcfd5](b3bcfd5a64))
* permission issue for quotation item during update item ([5a3c027](5a3c027432))
* prevent precision errors in  discount distribution with inclusive tax ([61ac180](61ac18069b))
* **Purchase Receipt:** copy project from first row when adding items ([fd48fb4](fd48fb49b9))
* remove supplier invoice date/posting date validation ([dcf4ac6](dcf4ac66bb))
* reservation based on field should be read only in SRE ([c3626d6](c3626d67ca))
* restore missing  `has_permission` import ([0ba965a](0ba965aae6))
* **sales-order:** update quotation status while cancelling sales order ([#52822](https://github.com/frappe/erpnext/issues/52822)) ([2420122](2420122f0e))
* **sales-order:** update quotation status while cancelling sales order (backport [#52822](https://github.com/frappe/erpnext/issues/52822)) ([#52918](https://github.com/frappe/erpnext/issues/52918)) ([3ae5de7](3ae5de7b11))
* sensible insufficient stock message in pick list ([3bafa36](3bafa360b2))
* setup fails to set abbr to departments ([c432506](c432506912))
* skip empty dimension values in exchange gain loss ([09ba980](09ba9808de))
* typo ([3893900](38939005ca))
* unable to submit subcontracting order if created from material request ([0422117](0422117003))
* update items fetches wrong item code ([97a4a5f](97a4a5f1cc))
* **work_order:** update returned qty ([bb1a655](bb1a655efb))

### Features

* **Journal Entry Account:** add Bank Transaction as Reference Type (backport [#52760](https://github.com/frappe/erpnext/issues/52760)) ([#52815](https://github.com/frappe/erpnext/issues/52815)) ([7032197](7032197f97))
* retrieve employee basic contact information ([4b2ac62](4b2ac626c5))
* retrieve employee contact details ([caa03ef](caa03efbe1))
* update item button addition for quotation (backport [#50976](https://github.com/frappe/erpnext/issues/50976)) ([#52810](https://github.com/frappe/erpnext/issues/52810)) ([800e384](800e38453b))
* update item button addition for quotation (backport [#50976](https://github.com/frappe/erpnext/issues/50976)) ([#52810](https://github.com/frappe/erpnext/issues/52810)) ([e2a1a7a](e2a1a7a36d))

### Reverts

* Revert "feat: update item button addition for quotation (backport [#50976](https://github.com/frappe/erpnext/issues/50976)) ([#5](https://github.com/frappe/erpnext/issues/5)…" ([656b1bc](656b1bcede))
2026-02-25 06:29:35 +00:00
ruthra kumar
5396b141dd Merge pull request #52926 from frappe/version-15-hotfix
chore: release v15
2026-02-25 11:58:05 +05:30
Mihir Kandoi
4ffb161ec8 Merge pull request #52943 from frappe/mergify/bp/version-15-hotfix/pr-52942 2026-02-25 10:30:10 +05:30
Mihir Kandoi
b37fc6676e chore: clearer description for internal transfer at arms length
(cherry picked from commit bd9e5e97d7)
2026-02-25 04:43:07 +00:00
ruthra kumar
965fcf8b71 Merge pull request #52928 from frappe/mergify/bp/version-15-hotfix/pr-52029
fix: prevent precision errors in  discount distribution with inclusive tax (backport #52029)
2026-02-24 18:39:37 +05:30
ljain112
61ac18069b fix: prevent precision errors in discount distribution with inclusive tax
(cherry picked from commit 2068299766)
2026-02-24 11:28:44 +00:00
ruthra kumar
3ae5de7b11 fix(sales-order): update quotation status while cancelling sales order (backport #52822) (#52918)
fix(sales-order): update quotation status while cancelling sales order (#52822)

* fix(sales-order): update quotation status while cancelling sales order

* test: validate quotation status

* chore: remove submit

(cherry picked from commit d638f3e033)

Co-authored-by: Sowmya <106989392+SowmyaArunachalam@users.noreply.github.com>
2026-02-24 12:08:25 +05:30
Sowmya
2420122f0e fix(sales-order): update quotation status while cancelling sales order (#52822)
* fix(sales-order): update quotation status while cancelling sales order

* test: validate quotation status

* chore: remove submit

(cherry picked from commit d638f3e033)
2026-02-24 06:18:23 +00:00
ruthra kumar
c5826f4132 Merge pull request #52907 from frappe/mergify/bp/version-15-hotfix/pr-52896
fix: skip empty dimension values in exchange gain loss (backport #52896)
2026-02-23 21:22:02 +05:30
ravibharathi656
09ba9808de fix: skip empty dimension values in exchange gain loss
(cherry picked from commit 7df9d951c6)
2026-02-23 15:36:29 +00:00
Imesha Sudasingha
71248ff40b Merge pull request #52544 from one-highflyer/fix/improve-reserved-serial-no-error-message
fix(stock): improve error message when serial no is reserved via SRE
2026-02-23 15:12:08 +00:00
Diptanil Saha
bfe0443530 Merge pull request #52899 from frappe/mergify/bp/version-15-hotfix/pr-50301 2026-02-23 20:38:49 +05:30
mergify[bot]
eda4462e5f Merge pull request #52722 from frappe/mergify/bp/version-15-hotfix/pr-52720
fix: wrong display_depends_on condition for item group and brand chil… (backport #52720)
2026-02-23 15:04:41 +00:00
diptanilsaha
0ba965aae6 fix: restore missing has_permission import 2026-02-23 20:07:27 +05:30
mergify[bot]
07eb5c714a Merge pull request #52897 from frappe/mergify/bp/version-15-hotfix/pr-52878
fix: standalone sales invoice return should not fallback to item mast… (backport #52878)
2026-02-23 13:54:53 +00:00
Mihir Kandoi
245bc7d2fb Merge pull request #52886 from frappe/mergify/bp/version-15-hotfix/pr-52840
fix(manufacturing): remove delete query of job card & batch and serial no  (backport #52840)
2026-02-23 19:16:45 +05:30
Abdeali Chharchhoda
773e56808a refactor: method to get employee contact without permission check
(cherry picked from commit 58cdb9503b)
2026-02-23 13:33:39 +00:00
Abdeali Chharchhoda
cb17dbd616 refactor: use common method to get employee contacts
(cherry picked from commit ec1eb6d222)
2026-02-23 13:33:39 +00:00
Abdeali Chharchhoda
943e2c00bc fix: get employee email with priority if preferred is not set
(cherry picked from commit 7b89c12470)
2026-02-23 13:33:39 +00:00
Abdeali Chharchhoda
0866c03e20 refactor: add validation for missing employee parameter
(cherry picked from commit b8e06b9636)
2026-02-23 13:33:38 +00:00
Abdeali Chharchhoda
4078e252c2 refactor: fetch employee contact details in realtime
(cherry picked from commit 2ea6508fa5)
2026-02-23 13:33:38 +00:00
Abdeali Chharchhoda
caa03efbe1 feat: retrieve employee contact details
(cherry picked from commit a41297d841)
2026-02-23 13:33:37 +00:00
Abdeali Chharchhoda
4b2ac626c5 feat: retrieve employee basic contact information
(cherry picked from commit 4ad1474e32)
2026-02-23 13:33:37 +00:00
Abdeali Chharchhoda
ae733cd7ad chore: Removing unused import
(cherry picked from commit 87c59f471c)
2026-02-23 13:33:37 +00:00
ruthra kumar
cec539629b Merge pull request #52894 from frappe/mergify/bp/version-15-hotfix/pr-52812
fix: bank account mismatch error on reverse transaction reconciliation (backport #52812)
2026-02-23 18:31:05 +05:30
ervishnucs
6b286ae03d fix: check gl account of an associated bank account in bank transaction
(cherry picked from commit 8fe0bf4ba3)
2026-02-23 12:36:25 +00:00
Sudharsanan Ashok
e30b2f1d04 fix(manufacturing): remove delete query of job card & batch and serial no (#52840)
* fix(manufacturing): remove delete query of batch and serial no

* fix(manufacturing): remove delete query of job card

* fix: remove delete function call for work order

(cherry picked from commit 8b2a971019)
2026-02-23 11:29:18 +00:00
Mihir Kandoi
d59d30c50a Merge pull request #52882 from frappe/mergify/bp/version-15-hotfix/pr-52880
fix(work_order): update returned qty on work order (backport #52880)
2026-02-23 15:48:35 +05:30
mergify[bot]
946c3554b1 fix: avoid duplicate taxes and charges rows in payment entry (backport #52178) (#52318)
Co-authored-by: Dharanidharan S <dharanidharans1328@gmail.com>
fix: avoid duplicate taxes and charges rows in payment entry (#52178)
2026-02-23 10:15:00 +00:00
Pandiyan37
bb1a655efb fix(work_order): update returned qty
(cherry picked from commit b7f45e6963)
2026-02-23 10:03:53 +00:00
mergify[bot]
dada7c4aa8 Merge pull request #52873 from frappe/mergify/bp/version-15-hotfix/pr-52871
fix: use stock qty instead of qty when updating transferred qty in WO (backport #52871)
2026-02-23 09:43:11 +00:00
Mihir Kandoi
dabf2c7027 Merge pull request #52862 from frappe/mergify/bp/version-15-hotfix/pr-52803
fix(manufacturing): update closed status for current work order before calculating planned qty (backport #52803)
2026-02-23 07:46:07 +05:30
Sudharsanan11
76760c2ee3 test(manufacturing): add test to validate the planned qty
(cherry picked from commit cfbdfcf515)
2026-02-23 02:01:23 +00:00
Sudharsanan11
b3bcfd5a64 fix(manufacturing): update status for work order before calculating planned qty
(cherry picked from commit 4d40c84a31)
2026-02-23 02:01:22 +00:00
Diptanil Saha
377435fa7d Merge pull request #52854 from diptanilsaha/us_si_pi 2026-02-22 19:48:02 +05:30
diptanilsaha
82bcb62b21 fix: update_stock behaviour on selling invoices 2026-02-22 04:20:31 +05:30
mergify[bot]
7032197f97 feat(Journal Entry Account): add Bank Transaction as Reference Type (backport #52760) (#52815)
* feat: add Bank Transaction as Reference Type to Journal Entry Account (#52760)

* feat: add Bank Transaction as Reference Type to Journal Entry Account

* fix: take care of existing property setters

* fix: cancelling Bank Transactions should still be possible

* fix: handle blank options in patch

* fix: hide Reference Due Date for Bank Transaction

(cherry picked from commit 387fb1b202)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2026-02-21 15:43:26 +01:00
Diptanil Saha
ef801d6bb4 Merge pull request #52849 from frappe/mergify/bp/version-15-hotfix/pr-52842
refactor: `Fiscal Year` cleanup (backport #52842)
2026-02-21 17:05:13 +05:30
diptanilsaha
bb8e5adadc chore: resolve conflicts 2026-02-21 16:36:58 +05:30
diptanilsaha
397f39e271 fix(fiscal_year): Fiscal Year auto-generation and notification
(cherry picked from commit 4c76786ce4)

# Conflicts:
#	erpnext/accounts/doctype/fiscal_year/fiscal_year.py
#	erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html
#	erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
2026-02-21 07:41:21 +00:00
diptanilsaha
2fffc9448b fix(fiscal_year_company): made company field mandatory
(cherry picked from commit 94fb7e11b4)

# Conflicts:
#	erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json
2026-02-21 07:41:21 +00:00
diptanilsaha
5a1c61f4d9 refactor: Fiscal Year DocType cleanup
(cherry picked from commit 74ac28fc70)

# Conflicts:
#	erpnext/accounts/doctype/fiscal_year/fiscal_year.py
2026-02-21 07:41:20 +00:00
Mihir Kandoi
266658cda1 Merge pull request #52846 from frappe/mergify/bp/version-15-hotfix/pr-52845 2026-02-21 12:40:11 +05:30
Mihir Kandoi
dcf4ac66bb fix: remove supplier invoice date/posting date validation
(cherry picked from commit 7cff0ba626)
2026-02-21 06:55:08 +00:00
Mihir Kandoi
813f0fa8c9 Merge pull request #52836 from frappe/mergify/bp/version-15-hotfix/pr-52835
fix: inconsistent label name between parent and child (backport #52835)
2026-02-20 17:17:55 +05:30
Mihir Kandoi
58d9694173 chore: resolve conflicts 2026-02-20 17:01:17 +05:30
Mihir Kandoi
1bf608f835 fix: inconsistent label name between parent and child
(cherry picked from commit d6e1ca0f10)

# Conflicts:
#	erpnext/selling/doctype/sales_order_item/sales_order_item.json
2026-02-20 11:28:35 +00:00
Mihir Kandoi
5eb4baccd6 Merge pull request #52826 from frappe/mergify/bp/version-15-hotfix/pr-52821
fix: sensible insufficient stock message in pick list (backport #52821)
2026-02-20 14:39:00 +05:30
Mihir Kandoi
ae0acd9ba1 Merge pull request #52828 from frappe/mergify/bp/version-15-hotfix/pr-52825
fix: update items fetches wrong item code (backport #52825)
2026-02-20 14:26:37 +05:30
Mihir Kandoi
97a4a5f1cc fix: update items fetches wrong item code
(cherry picked from commit ba96d37c11)
2026-02-20 08:54:14 +00:00
Mihir Kandoi
3bafa360b2 fix: sensible insufficient stock message in pick list
(cherry picked from commit 1352dc79bb)
2026-02-20 08:53:36 +00:00
Mihir Kandoi
de571c9266 Merge pull request #52817 from mihir-kandoi/gh52069
feat: update item button addition for quotation (backport #50976)
2026-02-19 23:30:52 +05:30
Mihir Kandoi
e7e67902e9 Merge pull request #52818 from frappe/mergify/bp/version-15-hotfix/pr-52811
fix: permission issue for quotation item during update item (backport #52811)
2026-02-19 23:19:42 +05:30
mergify[bot]
800e38453b feat: update item button addition for quotation (backport #50976) (#52810)
* feat: update item button addition for quotation (#50976)

* feat: update item button addition for quotation

* feat: update item button addition for supplier quotation

* fix: test case

---------

Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit f4c0611cc5)

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-02-19 23:11:32 +05:30
Mihir Kandoi
38939005ca fix: typo
(cherry picked from commit 732c98b72f)
2026-02-19 17:28:31 +00:00
Mihir Kandoi
bce77b6117 fix: ignore permissions instead of saving parent
(cherry picked from commit 6342e9a3e2)
2026-02-19 17:28:31 +00:00
Nishka Gosalia
5a3c027432 fix: permission issue for quotation item during update item
(cherry picked from commit 58b8af0fa8)
2026-02-19 17:28:30 +00:00
Mihir Kandoi
ee7602e752 Merge pull request #52813 from frappe/revert-52810-mergify/bp/version-15-hotfix/pr-50976
revert: "feat: update item button addition for quotation"
2026-02-19 20:39:43 +05:30
Mihir Kandoi
656b1bcede Revert "feat: update item button addition for quotation (backport #50976) (#5…"
This reverts commit e2a1a7a36d.
2026-02-19 20:22:25 +05:30
mergify[bot]
e2a1a7a36d feat: update item button addition for quotation (backport #50976) (#52810)
* feat: update item button addition for quotation (#50976)

* feat: update item button addition for quotation

* feat: update item button addition for supplier quotation

* fix: test case

---------

Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit f4c0611cc5)

# Conflicts:
#	erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
#	erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
#	erpnext/controllers/accounts_controller.py
#	erpnext/selling/doctype/quotation/test_quotation.py

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-02-19 14:41:01 +00:00
Mihir Kandoi
283b118773 Merge pull request #52805 from frappe/mergify/bp/version-15-hotfix/pr-52804
fix(Purchase Receipt): copy project from first row when adding items (backport #52804)
2026-02-19 14:03:34 +05:30
Marc Ramser
fd48fb49b9 fix(Purchase Receipt): copy project from first row when adding items
Adds `items_add` method to copy expense_account, cost_center and project from first row to newly added items, matching Purchase Invoice behavior.

(cherry picked from commit 21423676c9)
2026-02-19 08:31:59 +00:00
Mihir Kandoi
a57a9d7548 Merge pull request #52795 from frappe/mergify/bp/version-15-hotfix/pr-52794
fix: reservation based on field should be read only in SRE (backport #52794)
2026-02-19 12:56:57 +05:30
Mihir Kandoi
49dca6016c Merge pull request #52797 from frappe/mergify/bp/version-15-hotfix/pr-52792
fix: unable to submit subcontracting order if created from material r… (backport #52792)
2026-02-19 12:56:45 +05:30
Mihir Kandoi
0422117003 fix: unable to submit subcontracting order if created from material request
(cherry picked from commit 37323480dd)
2026-02-19 05:39:34 +00:00
Mihir Kandoi
6d32089d9e chore: resolve conflicts 2026-02-19 10:51:40 +05:30
Mihir Kandoi
c3626d67ca fix: reservation based on field should be read only in SRE
(cherry picked from commit 21452b4c6e)

# Conflicts:
#	erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
2026-02-19 05:10:25 +00:00
Frappe PR Bot
2888b68334 chore(release): Bumped to Version 15.98.1
## [15.98.1](https://github.com/frappe/erpnext/compare/v15.98.0...v15.98.1) (2026-02-19)

### Bug Fixes

* better permissions on make payment request ([f06e20d](f06e20d993))
2026-02-19 05:00:53 +00:00
ruthra kumar
f7f10a5930 Merge pull request #52789 from frappe/mergify/bp/version-15/pr-52763
fix: better permissions on make payment request (backport #52763)
2026-02-19 10:27:15 +05:30
Mihir Kandoi
3fcda3a414 Merge pull request #52787 from frappe/mergify/bp/version-15-hotfix/pr-52490
fix: Add handling for Sales Invoice Item quantity field (backport #52490)
2026-02-19 10:15:30 +05:30
ruthra kumar
f06e20d993 fix: better permissions on make payment request
(cherry picked from commit f36962fc58)
2026-02-19 04:31:56 +00:00
Mihir Kandoi
2fc3e30f9f fix: add purchase invoice as well
(cherry picked from commit 1fc2eddf6f)
2026-02-19 04:12:47 +00:00
Thomas antony
41c7890a6d fix: Add handling for Sales Invoice Item quantity field
Add handling for Sales Invoice Item quantity field

(cherry picked from commit edfcaee99b)
2026-02-19 04:12:46 +00:00
mergify[bot]
d9f1b0be77 Merge pull request #52785 from frappe/mergify/bp/version-15-hotfix/pr-52712
fix: addresses portal (backport #52712)
2026-02-19 04:08:56 +00:00
Mihir Kandoi
fd7f6fd7f3 Merge pull request #52774 from frappe/mergify/bp/version-15-hotfix/pr-52628
fix(manufacturing): set pick list purpose while creating it from work order (backport #52628)
2026-02-18 15:30:45 +05:30
Sudharsanan11
33d48c5575 fix(manufacturing): set pick list purpose while creating it from work order
(cherry picked from commit 23ccc2a8c5)
2026-02-18 09:45:36 +00:00
Sagar Vora
f7332258e7 Merge pull request #52773 from frappe/revert-52366-mergify/bp/version-15-hotfix/pr-52279
revert: "fix(profit and loss statement): exclude non period columns"
2026-02-18 08:34:13 +00:00
Sagar Vora
d782c52b76 Revert "fix(profit and loss statement): exclude non period columns (backport #52279)" 2026-02-18 13:52:50 +05:30
ruthra kumar
be4fe38998 Merge pull request #52767 from frappe/mergify/bp/version-15-hotfix/pr-52763
fix: better permissions on make payment request (backport #52763)
2026-02-18 12:44:32 +05:30
ruthra kumar
ce7101f555 fix: better permissions on make payment request
(cherry picked from commit f36962fc58)
2026-02-18 06:58:27 +00:00
ruthra kumar
56dd37c9e6 Merge pull request #52765 from frappe/mergify/bp/version-15-hotfix/pr-52419
fix: enfore permission on make_payment_request (backport #52419)
2026-02-18 12:23:29 +05:30
ruthra kumar
460291990a fix: enfore permission on make_payment_request
(cherry picked from commit b755ca12ca)
2026-02-18 06:32:29 +00:00
Mihir Kandoi
80976ae466 Merge pull request #52753 from frappe/mergify/bp/version-15-hotfix/pr-52750
fix: setup fails to set abbr to departments (backport #52750)
2026-02-17 21:47:32 +05:30
Mihir Kandoi
9a28d4ef5a Merge pull request #52751 from frappe/mergify/bp/version-15-hotfix/pr-52743
bug: fix comparison regarding `None` values (backport #52743)
2026-02-17 21:37:25 +05:30
Mihir Kandoi
c432506912 fix: setup fails to set abbr to departments
(cherry picked from commit debe868950)
2026-02-17 15:57:32 +00:00
Markus Lobedann
852c200ee0 fix: bug with comparison regarding None values and empty string
In their default state, the fields can be `None`. When a user enters something and deletes it afterwards, the fields contain an empty string.

This fixes the comparison.

(cherry picked from commit 3fd5a0f100)
2026-02-17 15:51:48 +00:00
Frappe PR Bot
a66854d16d chore(release): Bumped to Version 15.98.0
# [15.98.0](https://github.com/frappe/erpnext/compare/v15.97.0...v15.98.0) (2026-02-17)

### Bug Fixes

* **accounts-controller:** handle empty items list ([13239a9](13239a9dee))
* **accounts:** correct base grand total and rounded total mismatch ([#51739](https://github.com/frappe/erpnext/issues/51739)) ([8bdbb24](8bdbb24d73))
* add base_tax_withholding_net_total to tax withholding report ([ed42d54](ed42d54989))
* allow rename for market segment doctype ([0a41987](0a4198718b))
* allow sequence id edit in BOM if routing is not set ([c425944](c425944bdf))
* better validation for negative batch ([85d18fa](85d18fa7a4))
* cancel SABB if SLE cancelled from LCV ([f2a77d1](f2a77d178d))
* consider sle for negative stock validation ([ca79f64](ca79f6478a))
* do not allow plant floor company and warehouse to be updated ([d6333c1](d6333c1562))
* **manufacturing:** add sales order fields in subassembly child table ([0576752](0576752d3b))
* **manufacturing:** set sales order references in subassembly child table ([53e18a9](53e18a9beb))
* Payment Terms auto-fetched in Sales Invoice even when automatically_fetch_payment_terms is disabled ([78a3701](78a3701f4c))
* **pos_invoice:** add correct depends on condition (backport [#52689](https://github.com/frappe/erpnext/issues/52689)) ([#52693](https://github.com/frappe/erpnext/issues/52693)) ([4fe9689](4fe968961a))
* **postgres:** validate against period closing using MAX(period_end_date) ([#51554](https://github.com/frappe/erpnext/issues/51554)) ([9ec3031](9ec30319e4))
* production plan status ([97a6610](97a6610c0c))
* recalculate tax withholding during Purchase Order child update ([273029d](273029d0f0))
* set base_tax_withholding_net_total for jv in tds report ([68099a9](68099a9b5c))
* standalone credit/debit notes should not fetch any serial or batch by default ([79c3bc9](79c3bc9bcd))
* total weight does not update when updating items ([e12871b](e12871b408))

### Features

* Negative Batch report ([6313636](631363632b))
* show formatted currency symbol on ledger preview ([383648f](383648fb59))
2026-02-17 14:12:38 +00:00
ruthra kumar
72bb3e26cd Merge pull request #52730 from frappe/version-15-hotfix
chore: release v15
2026-02-17 19:38:01 +05:30
rohitwaghchaure
128c2bf8b9 Merge pull request #52739 from frappe/mergify/bp/version-15-hotfix/pr-52729
feat: Negative Batch report (backport #52729)
2026-02-17 17:17:42 +05:30
Mihir Kandoi
7aa46af0c3 Merge pull request #52735 from frappe/mergify/bp/version-15-hotfix/pr-52733
fix: allow sequence ID edit in BOM if routing is not set (backport #52733)
2026-02-17 16:39:01 +05:30
Rohit Waghchaure
631363632b feat: Negative Batch report
(cherry picked from commit 34edbed00b)
2026-02-17 11:04:17 +00:00
Mihir Kandoi
3e3c489178 Merge pull request #52737 from frappe/mergify/bp/version-15-hotfix/pr-52677
fix: standalone credit/debit notes should not fetch any serial or bat… (backport #52677)
2026-02-17 16:25:58 +05:30
Mihir Kandoi
79c3bc9bcd fix: standalone credit/debit notes should not fetch any serial or batch by default
(cherry picked from commit 2017edca88)
2026-02-17 10:40:08 +00:00
Mihir Kandoi
c6682f130c chore: resolve conflicts 2026-02-17 16:07:49 +05:30
Mihir Kandoi
c425944bdf fix: allow sequence id edit in BOM if routing is not set
(cherry picked from commit 08529964b4)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_operation/bom_operation.json
2026-02-17 10:35:58 +00:00
Mihir Kandoi
0935b181bf Merge pull request #52721 from aerele/backport-52626
fix(manufacturing): add sales order fields in subassembly child table (backport #52626)
2026-02-17 13:42:26 +05:30
ruthra kumar
bc50b94a87 Merge pull request #52595 from ljain112/fix-tds-report-v15
fix: add base_tax_withholding_net_total to tax withholding report
2026-02-17 13:39:48 +05:30
Mihir Kandoi
02f16715e0 Merge pull request #52717 from frappe/mergify/bp/version-15-hotfix/pr-52716
fix: do not allow plant floor company and warehouse to be updated (backport #52716)
2026-02-17 12:42:30 +05:30
ruthra kumar
f0ab9b6e76 Merge pull request #52151 from aerele/fix-po-tax-withholding
fix(purchase order): re-calculate tax withholding during update items
2026-02-17 12:19:06 +05:30
Pandiyan37
d2dc0a4c9a test(manufacturing): add test to validate the sales order references for sub assembly items 2026-02-17 12:15:33 +05:30
Mihir Kandoi
e8b46d9815 chore: resolve conflicts 2026-02-17 12:13:33 +05:30
Mihir Kandoi
d6333c1562 fix: do not allow plant floor company and warehouse to be updated
(cherry picked from commit fd72132743)

# Conflicts:
#	erpnext/manufacturing/doctype/plant_floor/plant_floor.json
2026-02-17 06:42:15 +00:00
Pandiyan37
53e18a9beb fix(manufacturing): set sales order references in subassembly child table 2026-02-17 12:11:05 +05:30
Pandiyan37
0576752d3b fix(manufacturing): add sales order fields in subassembly child table 2026-02-17 12:10:08 +05:30
Mihir Kandoi
e3a2b310d8 Merge pull request #52714 from frappe/mergify/bp/version-15-hotfix/pr-52713
fix: production plan status (backport #52713)
2026-02-17 11:40:07 +05:30
Mihir Kandoi
97a6610c0c fix: production plan status
(cherry picked from commit b3e6b304e4)
2026-02-17 05:53:54 +00:00
ili.ad
9ec30319e4 fix(postgres): validate against period closing using MAX(period_end_date) (#51554)
* fix(postgres): validate against period closing using MAX(period_end_date)

* refactor: remove non-existent field

---------

Co-authored-by: Matt Howard <github.severity519@passmail.net>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-02-17 05:02:53 +00:00
rohitwaghchaure
e511503597 Merge pull request #52700 from rohitwaghchaure/fixed-negative-stock-validation-sle-v15
fix: consider sle for negative stock validation
2026-02-16 23:56:18 +05:30
Rohit Waghchaure
ca79f6478a fix: consider sle for negative stock validation 2026-02-16 23:29:59 +05:30
rohitwaghchaure
9ece276e76 Merge pull request #52695 from frappe/mergify/bp/version-15-hotfix/pr-52691
fix: cancel SABB if SLE cancelled from LCV (backport #52691)
2026-02-16 21:52:48 +05:30
mergify[bot]
4fe968961a fix(pos_invoice): add correct depends on condition (backport #52689) (#52693)
* fix(pos_invoice): add correct depends on condition (#52689)

* fix(pos_invoice): add correct depends on condition

* fix: show field in sales order

* refactor: eval condition

(cherry picked from commit 219cf6bc57)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json

* chore: resolve conflict

---------

Co-authored-by: Soham Kulkarni <77533095+sokumon@users.noreply.github.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2026-02-16 16:01:23 +00:00
Rohit Waghchaure
f2a77d178d fix: cancel SABB if SLE cancelled from LCV
(cherry picked from commit f23a49a25e)
2026-02-16 15:42:04 +00:00
rohitwaghchaure
dbbba7262b Merge pull request #52683 from frappe/mergify/bp/version-15-hotfix/pr-52681
fix: better validation for negative batch (backport #52681)
2026-02-16 16:03:59 +05:30
Rohit Waghchaure
85d18fa7a4 fix: better validation for negative batch
(cherry picked from commit a8636e4f59)
2026-02-16 09:46:46 +00:00
Mihir Kandoi
11a6c5b394 Merge pull request #52671 from frappe/mergify/bp/version-15-hotfix/pr-52670 2026-02-15 14:30:17 +05:30
Mihir Kandoi
e12871b408 fix: total weight does not update when updating items
(cherry picked from commit 63323a2611)
2026-02-15 08:45:04 +00:00
ruthra kumar
a75c2cb4a0 Merge pull request #52645 from frappe/mergify/bp/version-15-hotfix/pr-52644
refactor: use query builder for profitability analysis (backport #52644)
2026-02-12 14:39:35 +05:30
ruthra kumar
c18ed0862e refactor: use query builder for profitability analysis
(cherry picked from commit 5e34325604)
2026-02-12 08:54:48 +00:00
ruthra kumar
8bddefb18b Merge pull request #52641 from frappe/mergify/bp/version-15-hotfix/pr-52640
refactor: use query builder for sales person commission summary (backport #52640)
2026-02-12 12:52:37 +05:30
ruthra kumar
55448b7437 Merge pull request #50911 from Dharanidharan2813/fix/list-index-bp-50860
fix: Payment Terms auto-fetched in Sales Invoice Without enabling the (automatically_fetch_payment_terms) in Account settings  (backport #50149)
2026-02-12 12:51:08 +05:30
ruthra kumar
ac02af476a refactor: use query builder for sales person commission summary
(cherry picked from commit 7105e3fb69)
2026-02-12 07:07:25 +00:00
ruthra kumar
0a1b532f69 Merge pull request #52637 from frappe/mergify/bp/version-15-hotfix/pr-52619
feat: show formatted currency symbol on ledger preview (backport #52619)
2026-02-12 11:46:37 +05:30
Navin-S-R
383648fb59 feat: show formatted currency symbol on ledger preview
(cherry picked from commit 5c8cb1e7ec)
2026-02-12 05:58:43 +00:00
Dharanidharan S
13239a9dee fix(accounts-controller): handle empty items list 2026-02-12 11:18:44 +05:30
diptanilsaha
2c13b2cc22 test: fixed test_make_sales_invoice_with_terms 2026-02-12 11:18:44 +05:30
Diptanil Saha
4da44e2c3f chore: resolve linter issue 2026-02-12 11:18:44 +05:30
Diptanil Saha
a503460bd5 chore: resolve conflict 2026-02-12 11:18:44 +05:30
dharanidharan2813
78a3701f4c fix: Payment Terms auto-fetched in Sales Invoice even when automatically_fetch_payment_terms is disabled
(cherry picked from commit cf1d892d60)

# Conflicts:
#	erpnext/selling/doctype/sales_order/test_sales_order.py
2026-02-12 11:18:44 +05:30
ruthra kumar
eedb93b2d6 Merge pull request #52100 from frappe/mergify/bp/version-15-hotfix/pr-51739
fix(accounts): correct base grand total and rounded total mismatch (backport #51739)
2026-02-12 11:13:27 +05:30
Diptanil Saha
ea3042bc74 Merge pull request #52614 from frappe/mergify/bp/version-15-hotfix/pr-51155
fix: allow rename for market segment doctype (backport #51155)
2026-02-11 14:56:34 +05:30
diptanilsaha
b977366dcd chore: resolve conflict 2026-02-11 14:41:01 +05:30
diptanilsaha
0a4198718b fix: allow rename for market segment doctype
(cherry picked from commit f3142c4af6)

# Conflicts:
#	erpnext/crm/doctype/market_segment/market_segment.json
2026-02-11 08:59:24 +00:00
Frappe PR Bot
b340d7c6bb chore(release): Bumped to Version 15.97.0
# [15.97.0](https://github.com/frappe/erpnext/compare/v15.96.1...v15.97.0) (2026-02-11)

### Bug Fixes

* Added a missing option to the currency field (backport [#52528](https://github.com/frappe/erpnext/issues/52528)) ([#52586](https://github.com/frappe/erpnext/issues/52586)) ([a6f5b88](a6f5b88f9b))
* **buying:** add supplier group link filters in field level ([436cb8d](436cb8dbfc))
* email campaign timeout issue (backport [#51994](https://github.com/frappe/erpnext/issues/51994)) ([#52555](https://github.com/frappe/erpnext/issues/52555)) ([6c9681b](6c9681ba4c))
* enabling skip delivery option for order type maintenance ([a8f05ca](a8f05cadea))
* **gross profit report:** translate column Sales Invoice ([4e910d8](4e910d8a69))
* **gross-profit:** handle item group filters ([7cd9de2](7cd9de211f))
* **gross-profit:** handle returns outside sale period ([303dac2](303dac262c))
* handle gross profit and percentage for return invoices ([bde19ab](bde19ab010))
* **manufacturing:** fix chart period keys ([99f3a7e](99f3a7e4cf))
* **manufacturing:** handle None value for actual_end_date ([f965b35](f965b352c8))
* **map_current_doc:** prevent mutation of query args in get_query (backport [#52202](https://github.com/frappe/erpnext/issues/52202)) ([#52583](https://github.com/frappe/erpnext/issues/52583)) ([9519773](9519773c5c))
* merge taxes in purchase receipt when get items from multiple purchase invoices ([#51422](https://github.com/frappe/erpnext/issues/51422)) ([68338ab](68338abe07))
* **quotation:** ignore zero ordered_qty ([ad92c02](ad92c021f7))
* rate comparison in stock reco ([cacca81](cacca812ed))
* remove incorrect validation from email digest throwing spurious error (backport [#51827](https://github.com/frappe/erpnext/issues/51827)) ([#52582](https://github.com/frappe/erpnext/issues/52582)) ([b034f3d](b034f3d3db))
* resolve conflicts ([36e2cf4](36e2cf49f3))
* return None instead of 0 if valuation rate is falsy ([195f020](195f020636))
* stock balance report issue ([bda7220](bda7220b70))
* **stock:** add is group filter for warehouse fields ([5b7ee0a](5b7ee0af66))
* **stock:** ignore pos reserved batches for stock levels ([635a421](635a421807))
* **stock:** inward stock for pick list test record ([5a42ff0](5a42ff0c3c))
* **stock:** set source warehouse for issue type ([19dca36](19dca36dec))
* **stock:** update target field attribute ([9cfd704](9cfd704eef))
* validate asset movement transaction date (backport [#52340](https://github.com/frappe/erpnext/issues/52340)) ([#52560](https://github.com/frappe/erpnext/issues/52560)) ([eea8cb5](eea8cb5885))

### Features

* allow negative stock for the batch item ([4c094c3](4c094c3d86))
2026-02-11 04:58:18 +00:00
ruthra kumar
0bd3c3b566 Merge pull request #52598 from frappe/version-15-hotfix
chore: release v15
2026-02-11 10:26:50 +05:30
Dharanidharan S
8bdbb24d73 fix(accounts): correct base grand total and rounded total mismatch (#51739)
(cherry picked from commit d82c92a237)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2026-02-10 19:05:23 +05:30
ruthra kumar
442e46c80f Merge pull request #52603 from frappe/mergify/bp/version-15-hotfix/pr-52017
fix(gross-profit): handle returns outside the given sale period (backport #52017)
2026-02-10 18:24:27 +05:30
ruthra kumar
83a72b8b30 Merge pull request #52413 from frappe/mergify/bp/version-15-hotfix/pr-51745
fix(gross profit report): translate column Sales Invoice (backport #51745)
2026-02-10 18:23:04 +05:30
ruthra kumar
a50836ab07 Merge pull request #52553 from frappe/mergify/bp/version-15-hotfix/pr-52501
fix(quotation): ignore zero ordered_qty (backport #52501)
2026-02-10 18:22:08 +05:30
Kavin
e9c4762309 Merge pull request #52541 from frappe/mergify/bp/version-15-hotfix/pr-52516
fix(stock): ignore pos reserved batches for stock levels (backport #52516)
2026-02-10 18:21:21 +05:30
Navin-S-R
7cd9de211f fix(gross-profit): handle item group filters
(cherry picked from commit 047b278791)
2026-02-10 12:41:08 +00:00
Navin-S-R
da37fea583 test: fix test assertions to use index-based totals
(cherry picked from commit fdfa7bc963)
2026-02-10 12:41:08 +00:00
Navin-S-R
a912b78bb8 test: validate sales person wise gross profit
(cherry picked from commit 3ab978ab46)
2026-02-10 12:41:08 +00:00
Navin-S-R
8ba5ef683f test: validate return invoice profit and profit percentage
(cherry picked from commit 4da3d43013)
2026-02-10 12:41:08 +00:00
Navin-S-R
bde19ab010 fix: handle gross profit and percentage for return invoices
(cherry picked from commit 51709f032f)
2026-02-10 12:41:07 +00:00
Navin-S-R
303dac262c fix(gross-profit): handle returns outside sale period
(cherry picked from commit 67d8223f73)
2026-02-10 12:41:07 +00:00
Kavin
3e4bd3040a Merge pull request #52591 from aerele/backport-52527
fix(stock): correct warehouse mapping for material issue (backport #52527)
2026-02-10 15:33:53 +05:30
Kavin
ae490804f9 chore: fix failing pre-commit checks
- Remove empty line with spaces
2026-02-10 15:02:23 +05:30
ljain112
e57f3fe727 test: update expected values for tax withholding calculations in tests 2026-02-10 14:40:53 +05:30
ljain112
68099a9b5c fix: set base_tax_withholding_net_total for jv in tds report 2026-02-10 13:53:31 +05:30
Kavin
36e2cf49f3 fix: resolve conflicts
- Remove POS Settings configuration for version 15 backport.
2026-02-10 13:35:52 +05:30
ljain112
ed42d54989 fix: add base_tax_withholding_net_total to tax withholding report 2026-02-10 13:30:12 +05:30
ljain112
b740846b68 refactor: update labels for tax withholding reports columns to improve clarity 2026-02-10 13:08:35 +05:30
Pandiyan37
5a42ff0c3c fix(stock): inward stock for pick list test record 2026-02-10 12:14:54 +05:30
Pandiyan37
37ca45ea49 test(stock): add test to check from warehouse for issue type 2026-02-10 12:11:59 +05:30
Pandiyan37
19dca36dec fix(stock): set source warehouse for issue type 2026-02-10 12:10:34 +05:30
mergify[bot]
a6f5b88f9b fix: Added a missing option to the currency field (backport #52528) (#52586)
fix: Added a missing option to the currency field (#52528)

(cherry picked from commit da07f84e44)

Co-authored-by: El-Shafei H. <el.shafei.developer@gmail.com>
2026-02-09 20:59:58 +00:00
mergify[bot]
6c9681ba4c fix: email campaign timeout issue (backport #51994) (#52555)
fix: email campaign timeout issue (#51994)

* fix: email campaign timeout issue

* refactor: email campaign backend logic

* refactor: use sendmail instead of manually batching

(cherry picked from commit 22123dd955)

Co-authored-by: Pratik Badhe <badhepd@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2026-02-10 02:10:05 +05:30
mergify[bot]
9519773c5c fix(map_current_doc): prevent mutation of query args in get_query (backport #52202) (#52583)
fix(map_current_doc): prevent mutation of query args in get_query (#52202)

(cherry picked from commit 23a73c9cdb)

Co-authored-by: V Shankar <shankarv292002@gmail.com>
2026-02-10 01:26:16 +05:30
mergify[bot]
eea8cb5885 fix: validate asset movement transaction date (backport #52340) (#52560)
* fix: validate asset movement transaction date (#52340)

* fix: validate asset transaction date

* fix: validate asset transaction date

* fix: add translation in validate_transaction_date

* test: test_movement_transaction_date

* fix: to ensure test reliability

(cherry picked from commit e98b68c38f)

# Conflicts:
#	erpnext/assets/doctype/asset_movement/test_asset_movement.py

* chore: fix conflicts

Removed unused imports and cleaned up code.

---------

Co-authored-by: Poojashree T R <159940572+22-poojashree@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2026-02-10 01:02:23 +05:30
Trusted Computer
b034f3d3db fix: remove incorrect validation from email digest throwing spurious error (backport #51827) (#52582) 2026-02-09 23:45:56 +05:30
rohitwaghchaure
cf851cfa56 Merge pull request #52557 from frappe/mergify/bp/version-15-hotfix/pr-52550
feat: allow negative stock for the batch item (backport #52550)
2026-02-09 20:07:43 +05:30
rohitwaghchaure
7f69556c45 chore: fix conflicts 2026-02-09 16:31:33 +05:30
Rohit Waghchaure
4c094c3d86 feat: allow negative stock for the batch item
(cherry picked from commit 376ab0e346)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json
2026-02-09 10:50:24 +00:00
ravibharathi656
ad92c021f7 fix(quotation): ignore zero ordered_qty
(cherry picked from commit 32ea37035e)
2026-02-09 10:38:44 +00:00
Sudharsanan11
aedab5c210 test(stock): add test to ignore pos reserved batches for stock levels
(cherry picked from commit 47ac67f7a2)
2026-02-09 06:28:42 +00:00
Sudharsanan11
635a421807 fix(stock): ignore pos reserved batches for stock levels
(cherry picked from commit 277ba9cb79)
2026-02-09 06:28:42 +00:00
Mihir Kandoi
816cbdea0d Merge pull request #52523 from frappe/mergify/bp/version-15-hotfix/pr-52497
fix: add is_group filter for supplier_group and warehouse fields (backport #52497)
2026-02-07 22:02:24 +05:30
Mihir Kandoi
66a4823640 chore: resolve conflicts 2026-02-07 21:45:35 +05:30
Sudharsanan11
5b7ee0af66 fix(stock): add is group filter for warehouse fields
(cherry picked from commit a9829f5f7b)
2026-02-07 16:11:16 +00:00
Sudharsanan11
436cb8dbfc fix(buying): add supplier group link filters in field level
(cherry picked from commit cfdc554a19)

# Conflicts:
#	erpnext/buying/doctype/supplier/supplier.json
2026-02-07 16:11:16 +00:00
Mihir Kandoi
ad2b8d2455 Merge pull request #52440 from frappe/mergify/bp/version-15-hotfix/pr-52416
fix(stock): update target field attribute (backport #52416)
2026-02-06 12:53:38 +05:30
Mihir Kandoi
2a1a7fd1f6 Merge pull request #52487 from frappe/mergify/bp/version-15-hotfix/pr-52219
fix: enabling skip delivery option for order type maintenance (backport #52219)
2026-02-06 12:46:22 +05:30
Pandiyan37
d0a8639a2d test(stock): testcase for different inventory dimension
(cherry picked from commit 21d0ee8db1)
2026-02-06 12:28:22 +05:30
Pandiyan37
9cfd704eef fix(stock): update target field attribute
(cherry picked from commit 7e08154217)
2026-02-06 12:28:22 +05:30
mergify[bot]
29b35494da Merge pull request #52483 from frappe/mergify/bp/version-15-hotfix/pr-52475
fix: do not show update stock flag unneccessarily (backport #52475)
2026-02-06 06:46:54 +00:00
Mihir Kandoi
292f17b1b0 chore: resolve conflicts 2026-02-06 12:12:59 +05:30
Mihir Kandoi
740dd878e9 chore: resolve conflicts 2026-02-06 12:12:24 +05:30
Nishka Gosalia
a8f05cadea fix: enabling skip delivery option for order type maintenance
(cherry picked from commit 1a22e3cb61)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.json
#	erpnext/selling/doctype/sales_order/test_sales_order.py
2026-02-06 06:39:47 +00:00
Frappe PR Bot
9c2b4f611d chore(release): Bumped to Version 15.96.1
## [15.96.1](https://github.com/frappe/erpnext/compare/v15.96.0...v15.96.1) (2026-02-05)

### Bug Fixes

* stock balance report issue ([3d525ad](3d525addbe))
2026-02-05 10:53:47 +00:00
rohitwaghchaure
350282f0cc Merge pull request #52463 from frappe/mergify/bp/version-15/pr-52462
fix: stock balance report issue (backport #52462)
2026-02-05 16:20:43 +05:30
Rohit Waghchaure
3d525addbe fix: stock balance report issue
(cherry picked from commit bda7220b70)
2026-02-05 10:20:21 +00:00
rohitwaghchaure
3cf10fafdf Merge pull request #52462 from rohitwaghchaure/fixed-stock-reco-balance-value-v15
fix: stock balance report issue
2026-02-05 15:49:33 +05:30
Rohit Waghchaure
bda7220b70 fix: stock balance report issue 2026-02-05 15:00:40 +05:30
ruthra kumar
c62c30f7a3 Merge pull request #52425 from frappe/mergify/bp/version-15-hotfix/pr-51990
refactor: use https over http while saving website link (backport #51990)
2026-02-05 11:13:42 +05:30
ruthra kumar
d91cf01970 refactor: patch partner_website for old data
(cherry picked from commit 8db29b0a81)

# Conflicts:
#	erpnext/patches.txt
2026-02-05 10:58:29 +05:30
Mihir Kandoi
da0776a38c Merge pull request #52429 from frappe/mergify/bp/version-15-hotfix/pr-52427 2026-02-04 20:18:15 +05:30
archielister
9efdcf208a fix for obtaining bom_no
(cherry picked from commit e4df0a393a)
2026-02-04 14:33:05 +00:00
ruthra kumar
fb525fec80 refactor: scrub http and use https in sales partner
(cherry picked from commit 8cf31548f2)
2026-02-04 12:32:22 +00:00
Mihir Kandoi
f31bb6ad4a Merge pull request #52420 from frappe/mergify/bp/version-15-hotfix/pr-51773
fix(manufacturing): refactor production analytics report (backport #51773)
2026-02-04 17:24:13 +05:30
Sudharsanan11
99f3a7e4cf fix(manufacturing): fix chart period keys
(cherry picked from commit 27091e5168)
2026-02-04 11:28:05 +00:00
Sudharsanan11
f965b352c8 fix(manufacturing): handle None value for actual_end_date
(cherry picked from commit 16f09141da)
2026-02-04 11:28:05 +00:00
elshafei-developer
4e910d8a69 fix(gross profit report): translate column Sales Invoice
(cherry picked from commit 3e39d13172)
2026-02-04 09:18:13 +00:00
Mihir Kandoi
d93ba985e4 Merge pull request #52407 from frappe/mergify/bp/version-15-hotfix/pr-52383
fix: rate comparison in stock reco (backport #52383)
2026-02-04 12:32:56 +05:30
Mihir Kandoi
195f020636 fix: return None instead of 0 if valuation rate is falsy
(cherry picked from commit e8d1e9d946)
2026-02-04 06:48:17 +00:00
Mihir Kandoi
cacca812ed fix: rate comparison in stock reco
(cherry picked from commit f1b4fe12a2)
2026-02-04 06:48:16 +00:00
ruthra kumar
4347efdf2c Merge pull request #52387 from frappe/mergify/bp/version-15-hotfix/pr-51422
fix: merge taxes in purchase receipt when get items from multiple purchase invoices (backport #51422)
2026-02-04 09:52:23 +05:30
NaviN
68338abe07 fix: merge taxes in purchase receipt when get items from multiple purchase invoices (#51422)
* fix: merge taxes in purchase receipt when get items from multiple purchase invoices

* fix: make merge tax configurable

* chore: follow standard merge taxes method

* chore: follow standard merge taxes method

(cherry picked from commit 6fde0a6261)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2026-02-04 07:19:28 +05:30
Frappe PR Bot
6ba8725940 chore(release): Bumped to Version 15.96.0
# [15.96.0](https://github.com/frappe/erpnext/compare/v15.95.2...v15.96.0) (2026-02-03)

### Bug Fixes

* add docstatus condition to get_sales_invoice_item function ([#51517](https://github.com/frappe/erpnext/issues/51517)) ([afc4c85](afc4c856f8))
* add missing param ([a61ad15](a61ad15998))
* add precision to rejected batch no qty calculation ([d5570f8](d5570f83d2))
* backport Switzerland VAT rates update to version-15 ([#52244](https://github.com/frappe/erpnext/issues/52244)) ([f5481dc](f5481dc7d5))
* **bank_account:** `is_company_account` related validations (backport [#51887](https://github.com/frappe/erpnext/issues/51887)) ([#51921](https://github.com/frappe/erpnext/issues/51921)) ([7226066](7226066772))
* **barcode:** failing request when item has both batch and serial ([19e0d75](19e0d75c22))
* correct exchange gain loss in ppr ([e42f8ff](e42f8ffd5d))
* duplicate account number (Indonesia COA) (backport [#52080](https://github.com/frappe/erpnext/issues/52080)) ([#52316](https://github.com/frappe/erpnext/issues/52316)) ([ac1f29d](ac1f29d5cb))
* hide close button on WO if WO is completed ([bd96868](bd96868736))
* imports ([528a482](528a482240))
* include credit notes in project gross margin calculation ([d9d48da](d9d48da505))
* journal auditing voucher print date to use posting_date ([6413ce4](6413ce467f))
* **mode of payment:** use valid syntax (backport [#51542](https://github.com/frappe/erpnext/issues/51542)) ([#52134](https://github.com/frappe/erpnext/issues/52134)) ([22869b6](22869b6f9d))
* negative stock for purchase return ([c30d76a](c30d76ae68))
* populate contact fields when creating quotation from customer ([78f8922](78f8922a9c))
* production plan not considering planning datetime when creating WO ([7b6c7c3](7b6c7c3e27))
* **profit and loss statement:** exclude non period columns ([32c5861](32c5861919))
* remove unneccessary check ([6a68155](6a681557a9))
* reset incoming rate in selling controller if there are changes in item ([c6937c8](c6937c8375))
* revert to old orm ([7e01ae9](7e01ae9e4a))
* **RFQ:** render email templates for preview and sending ([07c5622](07c56221a5))
* **stock:** add stock recon opening stock condition ([0cbb7f8](0cbb7f8714))
* **stock:** fetch batch wise valuation rate in get_items ([f1ba825](f1ba825818))
* **stock:** ignore packing slip while cancelling the sales invoice ([e6083a5](e6083a57de))
* **stock:** include subcontracting order qty while calculating the bin qty ([ba17fdd](ba17fdd072))
* **stock:** remove is_return condition on pos batch qty calculation ([a638dec](a638dece6b))
* **stock:** set incoming_rate with lcv rate for internal purchase ([41c592a](41c592a1a8))
* **subcontracting:** include item bom in supplied items grouping key ([3b12d60](3b12d60877))
* test cases ([2c74491](2c74491eb6))
* validate over ordering of quotation ([0e60750](0e60750bd8))
* validation when more than one FG items in repack stock entry ([fec3a8b](fec3a8b511))
* zero valuation rate if returning from different warehouse ([28929df](28929df0e8))

### Features

* **delivery-note:** add status indicator when document is partially billed ([e5e3b8a](e5e3b8a6ae))
* filter to display trial balance report without group account (backport [#48486](https://github.com/frappe/erpnext/issues/48486)) ([#52146](https://github.com/frappe/erpnext/issues/52146)) ([f48b4cd](f48b4cda50))
2026-02-03 17:18:56 +00:00
rohitwaghchaure
d0f96c48cf Merge pull request #52348 from frappe/version-15-hotfix
chore: release v15
2026-02-03 22:47:29 +05:30
ruthra kumar
12be1dca7d Merge pull request #52378 from frappe/mergify/bp/version-15-hotfix/pr-51651
fix: correct exchange gain loss in ppr (backport #51651)
2026-02-03 20:59:56 +05:30
Mihir Kandoi
2d2bbe5fc2 Merge pull request #52384 from frappe/mergify/bp/version-15-hotfix/pr-52259
fix(stock): include subcontracting order qty while calculating the bin qty (backport #52259)
2026-02-03 20:48:53 +05:30
Mihir Kandoi
a0da47d7f4 Merge pull request #52381 from frappe/mergify/bp/version-15-hotfix/pr-52374
fix(stock): fetch batch wise valuation rate in get_items (backport #52374)
2026-02-03 20:32:15 +05:30
Mihir Kandoi
c0116bcde5 chore: resolve conflicts 2026-02-03 20:30:40 +05:30
Sudharsanan11
ba17fdd072 fix(stock): include subcontracting order qty while calculating the bin qty
(cherry picked from commit de8f8ef9f4)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
2026-02-03 14:55:08 +00:00
kavin-114
f1ba825818 fix(stock): fetch batch wise valuation rate in get_items
(cherry picked from commit c5df570262)
2026-02-03 14:47:36 +00:00
ravibharathi656
e42f8ffd5d fix: correct exchange gain loss in ppr
(cherry picked from commit 02e96039ac)
2026-02-03 14:35:42 +00:00
rohitwaghchaure
c9955ddb35 Merge pull request #52369 from rohitwaghchaure/fixed-zero-incoming-rate-58744
fix: zero valuation rate if returning from different warehouse
2026-02-03 19:10:37 +05:30
Rohit Waghchaure
28929df0e8 fix: zero valuation rate if returning from different warehouse 2026-02-03 18:54:36 +05:30
ruthra kumar
6027c25c48 Merge pull request #52366 from frappe/mergify/bp/version-15-hotfix/pr-52279
fix(profit and loss statement): exclude non period columns (backport #52279)
2026-02-03 17:47:03 +05:30
ravibharathi656
32c5861919 fix(profit and loss statement): exclude non period columns
(cherry picked from commit 6180e5eb53)
2026-02-03 11:59:45 +00:00
ruthra kumar
e7297f2fc0 Merge pull request #52364 from frappe/mergify/bp/version-15-hotfix/pr-52160
fix(stock): remove is_return condition on pos batch qty calculation (backport #52160)
2026-02-03 17:20:04 +05:30
ruthra kumar
d14d09c286 Merge pull request #52361 from frappe/mergify/bp/version-15-hotfix/pr-51997
Add partially billed status indicator (backport #51997)
2026-02-03 17:19:21 +05:30
kavin-114
5b5d0f56de test: add unit test case for pos reserved with return qty
(cherry picked from commit 12ec997027)
2026-02-03 11:11:33 +00:00
kavin-114
a638dece6b fix(stock): remove is_return condition on pos batch qty calculation
(cherry picked from commit 2c19c1fd06)
2026-02-03 11:11:32 +00:00
rohitwaghchaure
d479930bef Merge pull request #52335 from aerele/v15/pr-52281
fix(stock): add stock recon opening stock condition
2026-02-03 16:40:22 +05:30
rohitwaghchaure
edd2814fc4 Merge branch 'version-15' into version-15-hotfix 2026-02-03 16:36:52 +05:30
Dharanidharan2813
e5e3b8a6ae feat(delivery-note): add status indicator when document is partially billed
(cherry picked from commit 7767000ccf)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.json
2026-02-03 16:30:49 +05:30
rohitwaghchaure
a1b1011555 Merge pull request #52343 from rohitwaghchaure/fixed-negative-stock-error-for-purchase-return-v15
fix: negative stock for purchase return
2026-02-03 16:16:08 +05:30
Rohit Waghchaure
c30d76ae68 fix: negative stock for purchase return 2026-02-03 15:56:53 +05:30
ruthra kumar
2c9c5dcc85 Merge pull request #52336 from frappe/mergify/bp/version-15-hotfix/pr-52280
fix(stock): ignore packing slip while cancelling the sales invoice (backport #52280)
2026-02-03 13:59:09 +05:30
Sudharsanan11
e6083a57de fix(stock): ignore packing slip while cancelling the sales invoice
(cherry picked from commit c58887b44a)
2026-02-03 08:24:45 +00:00
kavin-114
0cbb7f8714 fix(stock): add stock recon opening stock condition 2026-02-03 13:41:21 +05:30
ruthra kumar
fa01eefd89 Merge pull request #52329 from frappe/mergify/bp/version-15-hotfix/pr-51655
fix: include credit notes in project gross margin calculation (backport #51655)
2026-02-03 12:19:41 +05:30
ravibharathi656
d9d48da505 fix: include credit notes in project gross margin calculation
(cherry picked from commit a378fee8e0)
2026-02-03 06:07:34 +00:00
Mihir Kandoi
e1c71e0b8f Merge pull request #52310 from frappe/mergify/bp/version-15-hotfix/pr-52246
fix: validation considers moving average by default instead of set va… (backport #52246)
2026-02-03 09:44:50 +05:30
Mihir Kandoi
a61ad15998 fix: add missing param 2026-02-03 09:29:46 +05:30
Mihir Kandoi
c114f8445b Merge pull request #52323 from frappe/mergify/bp/version-15-hotfix/pr-52184
fix(subcontracting): include item bom in supplied items grouping key (backport #52184)
2026-02-03 09:27:55 +05:30
Mihir Kandoi
c6127575f5 chore: resolve conflicts 2026-02-03 09:12:14 +05:30
Mihir Kandoi
12a2e98751 chore: resolve conflicts 2026-02-03 09:10:41 +05:30
Sudharsanan11
1d7ba16caf test(subcontracting): add test for consumed_qty calculation with similar finished goods
(cherry picked from commit 4d9412181c)
2026-02-03 03:39:49 +00:00
Sudharsanan11
3b12d60877 fix(subcontracting): include item bom in supplied items grouping key
(cherry picked from commit 0d372a62a1)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py
2026-02-03 03:39:49 +00:00
mergify[bot]
ac1f29d5cb fix: duplicate account number (Indonesia COA) (backport #52080) (#52316)
Co-authored-by: Apriliansyah Idris <apriliansyahidris@gmail.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
fix: duplicate account number (Indonesia COA) (#52080)
2026-02-02 19:37:52 +00:00
Solede
f5481dc7d5 fix: backport Switzerland VAT rates update to version-15 (#52244)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 00:58:42 +05:30
Mihir Kandoi
5f60c0e85e Merge pull request #52246 from mihir-kandoi/st58765
(cherry picked from commit 135a433018)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
2026-02-02 15:15:17 +00:00
Mihir Kandoi
7b059a9221 Merge pull request #52307 from frappe/mergify/bp/version-15-hotfix/pr-52304
fix: populate contact fields when creating quotation from customer (backport #52304)
2026-02-02 20:32:16 +05:30
Mihir Kandoi
78f8922a9c fix: populate contact fields when creating quotation from customer
(cherry picked from commit 75b2c2c83d)
2026-02-02 14:46:21 +00:00
Mihir Kandoi
09c56abeb0 Merge pull request #52301 from frappe/mergify/bp/version-15-hotfix/pr-52286
fix: reset incoming rate in selling controller if there are changes i… (backport #52286)
2026-02-02 20:03:13 +05:30
Mihir Kandoi
c6937c8375 fix: reset incoming rate in selling controller if there are changes in item
(cherry picked from commit 2d6b43fd54)
2026-02-02 14:17:43 +00:00
rohitwaghchaure
99ad34d686 Merge pull request #52239 from frappe/mergify/bp/version-15-hotfix/pr-52232
fix: validation when more than one FG items in repack stock entry (backport #52232)
2026-02-02 14:10:48 +05:30
ruthra kumar
09ab2653d1 Merge pull request #52283 from frappe/mergify/bp/version-15-hotfix/pr-52200
fix(accounts): correct date in Journal Auditing Voucher print format (backport #52200)
2026-02-02 12:54:47 +05:30
Tamal Majumdar
6413ce467f fix: journal auditing voucher print date to use posting_date
(cherry picked from commit 43e2495df8)
2026-02-02 07:21:21 +00:00
Mihir Kandoi
a5156f696c Merge pull request #52275 from frappe/mergify/bp/version-15-hotfix/pr-52274 2026-02-02 11:06:19 +05:30
Mihir Kandoi
528a482240 fix: imports 2026-02-02 10:51:27 +05:30
Mihir Kandoi
d9d4b9b117 test: over ordering of quotation items
(cherry picked from commit 53e58f6678)
2026-02-02 10:51:27 +05:30
Mihir Kandoi
a4ad4e8279 Merge pull request #52277 from frappe/mihir-kandoi-patch-1 2026-02-02 10:49:30 +05:30
Mihir Kandoi
d516110572 chore: fix py error on v15 2026-02-02 10:34:23 +05:30
Rohit Waghchaure
6d14cb0c6b chore: fix conflicts 2026-02-01 16:30:59 +05:30
Mihir Kandoi
d30dacce80 Merge pull request #52229 from frappe/mergify/bp/version-15-hotfix/pr-52222 2026-02-01 13:46:46 +05:30
Mihir Kandoi
7e01ae9e4a fix: revert to old orm 2026-02-01 10:26:14 +05:30
Mihir Kandoi
42f94f1aba chore: remove incorrect import 2026-01-31 20:36:46 +05:30
Mihir Kandoi
6a681557a9 fix: remove unneccessary check 2026-01-31 20:27:02 +05:30
Mihir Kandoi
7ab59aa094 chore: resolve conflicts 2026-01-31 20:25:00 +05:30
Mihir Kandoi
c3b92075f0 chore: resolve conflicts
Removed old patch entries and updated the list.
2026-01-31 20:20:36 +05:30
Mihir Kandoi
4017936bef chore: resolve conflicts 2026-01-31 20:19:07 +05:30
Mihir Kandoi
6cecae288c chore: resolve conflicts 2026-01-31 20:18:22 +05:30
Rohit Waghchaure
fec3a8b511 fix: validation when more than one FG items in repack stock entry
(cherry picked from commit 6423ce2fa7)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-01-31 07:16:29 +00:00
Mihir Kandoi
2c74491eb6 fix: test cases
(cherry picked from commit 36f1e3572c)
2026-01-30 14:00:29 +00:00
Mihir Kandoi
0e60750bd8 fix: validate over ordering of quotation
(cherry picked from commit 4cc306d2d8)

# Conflicts:
#	erpnext/controllers/status_updater.py
#	erpnext/patches.txt
#	erpnext/selling/doctype/quotation/quotation.py
#	erpnext/selling/doctype/quotation_item/quotation_item.json
2026-01-30 14:00:28 +00:00
Mihir Kandoi
4dfc5671b0 Merge pull request #52217 from frappe/mergify/bp/version-15-hotfix/pr-52209
fix: add precision to rejected batch no qty calculation (backport #52209)
2026-01-30 12:21:00 +05:30
Mihir Kandoi
003eb02e24 Merge pull request #52214 from frappe/mergify/bp/version-15-hotfix/pr-52213
fix: hide close button on WO if WO is completed (backport #52213)
2026-01-30 12:06:47 +05:30
Mihir Kandoi
d5570f83d2 fix: add precision to rejected batch no qty calculation
(cherry picked from commit 838d245215)
2026-01-30 06:35:43 +00:00
Mihir Kandoi
bd96868736 fix: hide close button on WO if WO is completed
(cherry picked from commit 6e17ccf499)
2026-01-30 06:29:12 +00:00
Mihir Kandoi
85f7196eb5 Merge pull request #52211 from frappe/mergify/bp/version-15-hotfix/pr-52210
fix(barcode): failing request when item has both batch and serial (backport #52210)
2026-01-30 11:52:42 +05:30
Mihir Kandoi
19e0d75c22 fix(barcode): failing request when item has both batch and serial
(cherry picked from commit 89f6f0f46f)
2026-01-30 06:17:25 +00:00
Frappe PR Bot
621558a30c chore(release): Bumped to Version 15.95.2
## [15.95.2](https://github.com/frappe/erpnext/compare/v15.95.1...v15.95.2) (2026-01-29)

### Bug Fixes

* **stock:** set incoming_rate with lcv rate for internal purchase ([6ea4f1a](6ea4f1a03d))
2026-01-29 12:50:23 +00:00
rohitwaghchaure
547fbec55f Merge pull request #52176 from frappe/mergify/bp/version-15/pr-52140
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007) (backport #52140)
2026-01-29 18:17:43 +05:30
rohitwaghchaure
d9bd42965a Merge pull request #52195 from frappe/mergify/bp/version-15/pr-52191
Add Landed Cost Voucher Amount in Internal Purchase Receipt (backport #52158) (backport #52191)
2026-01-29 18:17:32 +05:30
kavin-114
419df361a7 test: add unit test to check internal purchase with lcv
(cherry picked from commit dd4fd89ef8)
(cherry picked from commit 3ccd1b4a6c)
2026-01-29 12:26:35 +00:00
kavin-114
6ea4f1a03d fix(stock): set incoming_rate with lcv rate for internal purchase
(cherry picked from commit f0dccc3cd7)
(cherry picked from commit 41c592a1a8)
2026-01-29 12:26:34 +00:00
rohitwaghchaure
3570ab8868 Merge pull request #52191 from frappe/mergify/bp/version-15-hotfix/pr-52158
Add Landed Cost Voucher Amount in Internal Purchase Receipt (backport #52158)
2026-01-29 17:56:11 +05:30
kavin-114
3ccd1b4a6c test: add unit test to check internal purchase with lcv
(cherry picked from commit dd4fd89ef8)
2026-01-29 12:02:34 +00:00
kavin-114
41c592a1a8 fix(stock): set incoming_rate with lcv rate for internal purchase
(cherry picked from commit f0dccc3cd7)
2026-01-29 12:02:34 +00:00
mergify[bot]
65ed4e5cf6 Merge pull request #52140 from frappe/mergify/bp/version-15-hotfix/pr-52007
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007)
(cherry picked from commit ad8c8cb0e8)
2026-01-29 09:01:32 +00:00
mergify[bot]
ad8c8cb0e8 Merge pull request #52140 from frappe/mergify/bp/version-15-hotfix/pr-52007
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007)
2026-01-29 14:30:22 +05:30
Mihir Kandoi
7417432ddb Merge pull request #52167 from frappe/mergify/bp/version-15-hotfix/pr-52166
fix: production plan not considering planning datetime when creating WO (backport #52166)
2026-01-29 11:05:39 +05:30
Mihir Kandoi
7b6c7c3e27 fix: production plan not considering planning datetime when creating WO
(cherry picked from commit 4e19c7e8bd)
2026-01-29 05:20:41 +00:00
Aarol D'Souza
df857f8177 Merge pull request #52163 from frappe/mergify/bp/version-15-hotfix/pr-52092
fix(RFQ): render email templates for preview and sending (backport #52092)
2026-01-29 09:22:32 +05:30
AarDG10
43fc1ae4bf ci: minor text correction
(cherry picked from commit 37cdae2f34)
2026-01-29 03:37:44 +00:00
AarDG10
07c56221a5 fix(RFQ): render email templates for preview and sending
(cherry picked from commit 525b3960e1)
2026-01-29 03:37:44 +00:00
Navin-S-R
273029d0f0 fix: recalculate tax withholding during Purchase Order child update 2026-01-28 18:51:49 +05:30
mergify[bot]
f48b4cda50 feat: filter to display trial balance report without group account (backport #48486) (#52146)
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2026-01-28 11:40:18 +00:00
mergify[bot]
7226066772 fix(bank_account): is_company_account related validations (backport #51887) (#51921)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-01-28 10:22:09 +00:00
aymenit2008
afc4c856f8 fix: add docstatus condition to get_sales_invoice_item function (#51517) 2026-01-28 15:43:33 +05:30
mergify[bot]
22869b6f9d fix(mode of payment): use valid syntax (backport #51542) (#52134)
Co-authored-by: ervishnucs <ervishnucs369@gmail.com>
2026-01-28 15:10:31 +05:30
Frappe PR Bot
b268de4609 chore(release): Bumped to Version 15.95.1
## [15.95.1](https://github.com/frappe/erpnext/compare/v15.95.0...v15.95.1) (2026-01-28)

### Bug Fixes

* allow creation of DN in SI for items not having DN reference ([184fa88](184fa889c3))
* **asset capitalization:** update asset values using db_set ([74bf61e](74bf61e0c1))
* autofill warehouse for packed items ([0a87fa5](0a87fa5348))
* Bin reserved qty for production for extra material transfer ([b5d8477](b5d8477354))
* check the payment ledger entry has the dimension ([#51823](https://github.com/frappe/erpnext/issues/51823)) ([468ec80](468ec805f1))
* Ensure paid_amount is always numeric before calling allocate_amount_to_references (backport [#50935](https://github.com/frappe/erpnext/issues/50935)) ([#52035](https://github.com/frappe/erpnext/issues/52035)) ([9fce694](9fce694936))
* handle parent level project change ([7146c03](7146c0385c))
* handle undefined bank_transaction_mapping in quick entry ([d4195d3](d4195d31bf))
* job cards should not be deleted on close of WO ([8d06ee3](8d06ee3966))
* **journal-entry:** prevent submit failure due to double background queuing (backport [#52083](https://github.com/frappe/erpnext/issues/52083)) ([#52086](https://github.com/frappe/erpnext/issues/52086)) ([72a9b58](72a9b58b14))
* negative stock for purchae return ([f9fd0ff](f9fd0ffbae))
* **payment entry:** update currency symbol (backport [#51956](https://github.com/frappe/erpnext/issues/51956)) ([#52093](https://github.com/frappe/erpnext/issues/52093)) ([934b549](934b5494f0))
* **project:** add missing counter to project update naming series ([f61305a](f61305aa45))
* rejected qty in PR doesn't consider conversion factor ([83352b5](83352b5a34))
* **sales order:** set project at item level from parent ([a09b73e](a09b73e65d))
* **shipment:** user contact validation to use full name ([90dc22a](90dc22a57d))
* show message if image is removed from item description ([0c89cd5](0c89cd5524))
* **stock:** use purchase UOM in Supplier Quotation items ([dadd4b1](dadd4b1f95))
* strip whitespace in customer_name ([853faca](853facad96))
* swedish_address_template ([5e61922](5e6192249e))
* UOM of item not fetching in BOM ([14de520](14de520ebb))
* update country_wise_tax.json for Algerian Taxes (backport [#51878](https://github.com/frappe/erpnext/issues/51878)) ([#52037](https://github.com/frappe/erpnext/issues/52037)) ([d89ac99](d89ac99e76))
* validation to check at-least one raw material for manufacture entry ([650f874](650f874fbd))
2026-01-28 04:14:22 +00:00
ruthra kumar
44b726c2e3 Merge pull request #52104 from frappe/version-15-hotfix
chore: release v15
2026-01-28 09:43:01 +05:30
Mihir Kandoi
0c395725b7 Merge pull request #52123 from frappe/mergify/bp/version-15-hotfix/pr-51961
fix(sales order): set project at item level from parent (backport #51961)
2026-01-27 21:55:31 +05:30
SowmyaArunachalam
7146c0385c fix: handle parent level project change
(cherry picked from commit 543b6e51c0)
2026-01-27 16:24:06 +00:00
SowmyaArunachalam
e12564daa6 chore: use frappe.model.set_value
(cherry picked from commit 3b27f49d79)
2026-01-27 16:24:06 +00:00
SowmyaArunachalam
a09b73e65d fix(sales order): set project at item level from parent
(cherry picked from commit 9e51701e2a)
2026-01-27 16:24:05 +00:00
Mihir Kandoi
654a55260d Merge pull request #52121 from frappe/mergify/bp/version-15-hotfix/pr-52084
fix(shipment): user contact validation to use full name (backport #52084)
2026-01-27 21:28:34 +05:30
harrishragavan
90dc22a57d fix(shipment): user contact validation to use full name
(cherry picked from commit 3c6eb9a531)
2026-01-27 15:57:05 +00:00
Khushi Rawat
e826e03f9a Merge pull request #52073 from aerele/update-asset-purchase-amt
fix(asset capitalization): update asset values using db_set
2026-01-27 17:06:17 +05:30
ruthra kumar
de4e62e308 Merge pull request #52107 from frappe/mergify/bp/version-15-hotfix/pr-51823
fix: check the payment ledger entry has the dimension (backport #51823)
2026-01-27 16:27:32 +05:30
Vishnu Priya Baskaran
468ec805f1 fix: check the payment ledger entry has the dimension (#51823)
* fix: check the payment ledger entry has the dimension

* fix: add project in payment ledger entry

(cherry picked from commit efa3973b77)
2026-01-27 10:26:52 +00:00
Mihir Kandoi
cd8c6eac7c Merge pull request #52096 from frappe/mergify/bp/version-15-hotfix/pr-52088
fix: show message if image is removed from item description (backport #52088)
2026-01-27 14:56:03 +05:30
Mihir Kandoi
90d6bb34dc chore: resolve conflicts 2026-01-27 14:38:19 +05:30
Mihir Kandoi
1545904693 Merge pull request #52099 from aerele/support/fix--58134 2026-01-27 12:50:11 +05:30
Pandiyan37
dadd4b1f95 fix(stock): use purchase UOM in Supplier Quotation items 2026-01-27 12:28:07 +05:30
Mihir Kandoi
0c89cd5524 fix: show message if image is removed from item description
(cherry picked from commit b49c679a50)

# Conflicts:
#	erpnext/stock/doctype/item/item.py
2026-01-27 06:50:16 +00:00
mergify[bot]
934b5494f0 fix(payment entry): update currency symbol (backport #51956) (#52093)
Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
fix(payment entry): update currency symbol (#51956)
2026-01-27 06:32:59 +00:00
mergify[bot]
72a9b58b14 fix(journal-entry): prevent submit failure due to double background queuing (backport #52083) (#52086)
Co-authored-by: V Shankar <shankarv292002@gmail.com>
fix(journal-entry): prevent submit failure due to double background queuing (#52083)
2026-01-27 05:52:23 +00:00
Navin-S-R
5cfd8d1930 refactor: avoid multiple db_set 2026-01-26 23:06:37 +05:30
Navin-S-R
74bf61e0c1 fix(asset capitalization): update asset values using db_set 2026-01-26 21:17:06 +05:30
Mihir Kandoi
c4b135e1a2 Merge pull request #52065 from frappe/mergify/bp/version-15-hotfix/pr-52064
fix: strip whitespace in customer_name (backport #52064)
2026-01-26 15:30:50 +05:30
Shankarv19bcr
853facad96 fix: strip whitespace in customer_name
(cherry picked from commit e5ba0e6401)
2026-01-26 09:46:51 +00:00
ruthra kumar
636e1ac1f1 Merge pull request #52039 from frappe/mergify/bp/version-15-hotfix/pr-51670
fix: handle undefined bank_transaction_mapping in quick entry (backport #51670)
2026-01-25 13:11:45 +05:30
ruthra kumar
df996b8fd3 Merge pull request #52054 from frappe/mergify/bp/version-15-hotfix/pr-52050
fix: swedish_address_template (backport #52050)
2026-01-25 13:09:18 +05:30
mahsem
5e6192249e fix: swedish_address_template
(cherry picked from commit 334e8ada30)
2026-01-25 05:22:25 +00:00
rohitwaghchaure
398e8d00ec Merge pull request #52052 from frappe/mergify/bp/version-15-hotfix/pr-52043
fix: UOM of item not fetching in BOM (backport #52043)
2026-01-25 10:50:52 +05:30
rohitwaghchaure
6be30bbd71 Merge pull request #51904 from frappe/mergify/bp/version-15-hotfix/pr-51900
fix: validation to check at-least one raw material for manufacture entry (backport #51900)
2026-01-25 10:45:52 +05:30
Rohit Waghchaure
14de520ebb fix: UOM of item not fetching in BOM
(cherry picked from commit ba8eadda52)
2026-01-25 05:14:50 +00:00
rohitwaghchaure
770d0e7f7f Merge pull request #52030 from frappe/mergify/bp/version-15-hotfix/pr-52024
fix: Bin reserved qty for production for extra material transfer (backport #52024)
2026-01-25 10:43:48 +05:30
rohitwaghchaure
c351d6b1c0 chore: fix conflicts
Removed old implementation of make_serialized_item function and updated its definition.
2026-01-24 13:51:54 +05:30
rohitwaghchaure
a4b099e481 chore: fix conflicts
Removed subcontracting order validation methods from stock entry.
2026-01-24 13:50:33 +05:30
rohitwaghchaure
624ec19305 chore: fix conflicts
Remove test for reserved serial and batch items and clean up related code.
2026-01-24 13:42:04 +05:30
Abdeali Chharchhoda
e1c3125efa refactor: use console.error for error logging in Plaid integration
(cherry picked from commit 9322095786)
2026-01-24 07:07:32 +00:00
Abdeali Chharchhoda
d4195d31bf fix: handle undefined bank_transaction_mapping in quick entry
(cherry picked from commit 8a1b8259bd)
2026-01-24 07:07:32 +00:00
Abdeali Chharchhoda
f349be0a00 refactor: remove redundant onload function for bank mapping table
(cherry picked from commit 7c7ba0154a)
2026-01-24 07:07:31 +00:00
mergify[bot]
d89ac99e76 fix: update country_wise_tax.json for Algerian Taxes (backport #51878) (#52037)
fix: update country_wise_tax.json for Algerian Taxes (#51878)

* Algeria chart of accounts

Algeria chart of accounts

* Update Algeria Chart Of Account

* Algeria chart of account

* Algeria Chart of Account

Algeria Chart of Account

* Modify Algeria tax entries in country_wise_tax.json

Updated tax rates and account names for Algeria.

* Rename account for Algeria tax from VAT to TVA

Rename account for Algeria tax from VAT to TVA

(cherry picked from commit e810cd8440)

Co-authored-by: HALFWARE <contact@half-ware.com>
2026-01-24 06:48:04 +00:00
mergify[bot]
9fce694936 fix: Ensure paid_amount is always numeric before calling allocate_amount_to_references (backport #50935) (#52035)
fix: Ensure paid_amount is always numeric before calling allocate_amount_to_references (#50935)

fix: ensure paid_amount is not null in allocate_party_amount_against_ref_docs
(cherry picked from commit 50b3396064)

Co-authored-by: El-Shafei H. <el.shafei.developer@gmail.com>
2026-01-24 12:03:51 +05:30
Rohit Waghchaure
b5d8477354 fix: Bin reserved qty for production for extra material transfer
(cherry picked from commit f5378b6573)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2026-01-23 15:45:30 +00:00
rohitwaghchaure
0e3d276348 Merge pull request #52014 from frappe/mergify/bp/version-15-hotfix/pr-52006
fix: negative stock for purchase return (backport #52006)
2026-01-23 13:23:00 +05:30
rohitwaghchaure
3489b65f1a chore: fix conflicts 2026-01-23 12:27:37 +05:30
rohitwaghchaure
c8a52ec43c chore: fix conflicts
Removed deprecated method for batch-wise total available quantity and adjusted stock value calculations.
2026-01-23 11:51:02 +05:30
Rohit Waghchaure
f9fd0ffbae fix: negative stock for purchae return
(cherry picked from commit d68a04ad16)

# Conflicts:
#	erpnext/stock/serial_batch_bundle.py
2026-01-23 06:03:47 +00:00
rohitwaghchaure
69dc9e81d5 Merge pull request #52004 from frappe/mergify/bp/version-15-hotfix/pr-51989
fix: autofill warehouse for packed items (backport #51989)
2026-01-22 23:56:40 +05:30
Sudharsanan11
0a87fa5348 fix: autofill warehouse for packed items
(cherry picked from commit 3f8a0a4833)
2026-01-22 17:28:03 +00:00
Mihir Kandoi
81e7e96cb6 Merge pull request #51977 from frappe/mergify/bp/version-15-hotfix/pr-51967
fix(project): add missing counter to project update naming series (backport #51967)
2026-01-22 11:45:58 +05:30
mergify[bot]
f7770c3225 Merge pull request #51979 from frappe/mergify/bp/version-15-hotfix/pr-51966
fix(customer): add customer group filters (backport #51966)
2026-01-22 05:16:45 +00:00
ravibharathi656
f61305aa45 fix(project): add missing counter to project update naming series
(cherry picked from commit 49e64f4e1c)
2026-01-22 04:52:56 +00:00
Mihir Kandoi
113a6e079a Merge pull request #51971 from frappe/mergify/bp/version-15-hotfix/pr-51968 2026-01-22 09:04:48 +05:30
mergify[bot]
c35426b9f9 Merge pull request #51969 from frappe/mergify/bp/version-15-hotfix/pr-51964
fix: create DN btn should not be shown if it cannot be created (backport #51964)
2026-01-21 17:27:37 +00:00
Mihir Kandoi
83352b5a34 fix: rejected qty in PR doesn't consider conversion factor
(cherry picked from commit 343ee9695b)
2026-01-21 17:20:45 +00:00
Mihir Kandoi
e54bb0da69 Merge pull request #51959 from frappe/mergify/bp/version-15-hotfix/pr-51947
fix: job cards should not be deleted on close of WO (backport #51947)
2026-01-21 16:02:01 +05:30
Mihir Kandoi
8d06ee3966 fix: job cards should not be deleted on close of WO
(cherry picked from commit c919b1de38)
2026-01-21 10:17:00 +00:00
Mihir Kandoi
6b4101d202 Merge pull request #51925 from frappe/mergify/bp/version-15-hotfix/pr-51909
fix: allow creation of DN in SI for items not having DN reference (backport #51909)
2026-01-21 15:41:21 +05:30
Mihir Kandoi
386567a6ea chore: resolve conflicts 2026-01-21 15:27:12 +05:30
Mihir Kandoi
d3440cf545 chore: resolve conflicts 2026-01-21 15:24:14 +05:30
mergify[bot]
11544818f1 Merge pull request #51950 from frappe/mergify/bp/version-15-hotfix/pr-51948
fix: warehouse permissions in MR incorrectly ignored (backport #51948)
2026-01-21 08:51:41 +00:00
Mihir Kandoi
184fa889c3 fix: allow creation of DN in SI for items not having DN reference
(cherry picked from commit b691de0147)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2026-01-20 12:14:43 +00:00
Rohit Waghchaure
650f874fbd fix: validation to check at-least one raw material for manufacture entry
(cherry picked from commit f003b3c378)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py
2026-01-20 08:25:57 +00:00
330 changed files with 10771 additions and 3263 deletions

View File

@@ -60,7 +60,7 @@ body:
description: Share exact version number of Frappe and ERPNext you are using.
placeholder: |
Frappe version -
ERPNext Verion -
ERPNext version -
validations:
required: true

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.95.0"
__version__ = "15.99.1"
def get_default_company(user=None):

View File

@@ -33,6 +33,17 @@
},
"account_number": "1151.000"
},
"Pajak Dibayar di Muka": {
"PPN Masukan": {
"account_number": "1152.001",
"account_type": "Tax"
},
"PPh 23 Dibayar di Muka": {
"account_number": "1152.002",
"account_type": "Tax"
},
"account_number": "1152.000"
},
"account_number": "1150.000"
},
"Kas": {

View File

@@ -82,13 +82,15 @@ 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.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
)
frappe.flags.accounting_dimensions = None
frappe.flags.accounting_dimensions_details = None
def on_trash(self):
if frappe.flags.in_test:
@@ -103,10 +105,6 @@ class AccountingDimension(Document):
if not self.fieldname:
self.fieldname = scrub(self.label)
def on_update(self):
frappe.flags.accounting_dimensions = None
frappe.flags.accounting_dimensions_details = None
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:

View File

@@ -216,7 +216,7 @@
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order"
"label": "Automatically Fetch Payment Terms from Order/Quotation"
},
{
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
@@ -307,7 +307,7 @@
},
{
"default": "0",
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
@@ -671,7 +671,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-12-26 19:46:55.093717",
"modified": "2026-03-06 14:49:11.467716",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -701,4 +701,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -48,6 +48,7 @@
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"options": "currency",
"read_only": 1
},
{

View File

@@ -3,9 +3,6 @@
frappe.provide("erpnext.integrations");
frappe.ui.form.on("Bank", {
onload: function (frm) {
add_fields_to_mapping_table(frm);
},
refresh: function (frm) {
add_fields_to_mapping_table(frm);
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
@@ -37,11 +34,11 @@ let add_fields_to_mapping_table = function (frm) {
});
});
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
"bank_transaction_field",
"options",
options
);
const grid = frm.fields_dict.bank_transaction_mapping?.grid;
if (grid) {
grid.update_docfield_property("bank_transaction_field", "options", options);
}
};
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
@@ -116,7 +113,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
)
);
console.log(error);
console.error(error);
}
plaid_success(token, response) {

View File

@@ -42,8 +42,4 @@ frappe.ui.form.on("Bank Account", {
});
}
},
is_company_account: function (frm) {
frm.set_df_property("account", "reqd", frm.doc.is_company_account);
},
});

View File

@@ -52,6 +52,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company Account",
"mandatory_depends_on": "is_company_account",
"options": "Account"
},
{
@@ -98,6 +99,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"mandatory_depends_on": "is_company_account",
"options": "Company"
},
{
@@ -252,7 +254,7 @@
"link_fieldname": "default_bank_account"
}
],
"modified": "2025-08-29 12:32:01.081687",
"modified": "2026-01-20 00:46:16.633364",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",

View File

@@ -52,31 +52,35 @@ class BankAccount(Document):
delete_contact_and_address("Bank Account", self.name)
def validate(self):
self.validate_company()
self.validate_account()
self.validate_is_company_account()
self.update_default_bank_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_is_company_account(self):
if self.is_company_account:
if not self.company:
frappe.throw(_("Company is mandatory for company account"))
def validate_company(self):
if self.is_company_account and not self.company:
frappe.throw(_("Company is manadatory for company account"))
if not self.account:
frappe.throw(_("Company Account is mandatory"))
self.validate_account()
@deprecated
def validate_iban(self):
"""Kept for backward compatibility, will be removed in v16."""
validate_iban(self.iban, throw=True)
def validate_account(self):
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def update_default_bank_account(self):
if self.is_default and not self.disabled:
frappe.db.set_value(
@@ -127,6 +131,7 @@ def get_default_company_bank_account(company, party_type, party):
@frappe.whitelist()
def get_bank_account_details(bank_account):
frappe.has_permission("Bank Account", doc=bank_account, ptype="read", throw=True)
return frappe.get_cached_value(
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
)

View File

@@ -5,7 +5,9 @@
import frappe
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.query_builder import Case
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
from pypika import Order
@@ -136,65 +138,162 @@ def get_payment_entries_for_bank_clearance(
):
entries = []
condition = ""
pe_condition = ""
journal_entry = frappe.qb.DocType("Journal Entry")
journal_entry_account = frappe.qb.DocType("Journal Entry Account")
journal_entry_query = (
frappe.qb.from_(journal_entry_account)
.inner_join(journal_entry)
.on(journal_entry_account.parent == journal_entry.name)
.select(
ConstantColumn("Journal Entry").as_("payment_document"),
journal_entry.name.as_("payment_entry"),
journal_entry.cheque_no.as_("cheque_number"),
journal_entry.cheque_date,
Sum(journal_entry_account.debit_in_account_currency).as_("debit"),
Sum(journal_entry_account.credit_in_account_currency).as_("credit"),
journal_entry.posting_date,
journal_entry_account.against_account,
journal_entry.clearance_date,
journal_entry_account.account_currency,
)
.where(
(journal_entry_account.account == account)
& (journal_entry.docstatus == 1)
& (journal_entry.posting_date >= from_date)
& (journal_entry.posting_date <= to_date)
& (journal_entry.is_opening == "No")
)
)
if not include_reconciled_entries:
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
pe_condition = "and (pe.clearance_date IS NULL or pe.clearance_date='0000-00-00')"
journal_entry_query = journal_entry_query.where(
(journal_entry.clearance_date.isnull()) | (journal_entry.clearance_date == "0000-00-00")
)
journal_entries = frappe.db.sql(
f"""
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit,
t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1
and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""",
{"account": account, "from": from_date, "to": to_date},
as_dict=1,
journal_entries = (
journal_entry_query.groupby(journal_entry_account.account, journal_entry.name)
.orderby(journal_entry.posting_date)
.orderby(journal_entry.name, order=Order.desc)
).run(as_dict=True)
pe = frappe.qb.DocType("Payment Entry")
company = frappe.qb.DocType("Company")
payment_entry_query = (
frappe.qb.from_(pe)
.join(company)
.on(pe.company == company.name)
.select(
ConstantColumn("Payment Entry").as_("payment_document"),
pe.name.as_("payment_entry"),
pe.reference_no.as_("cheque_number"),
pe.reference_date.as_("cheque_date"),
(
Case()
.when(
pe.paid_from == account,
(
pe.paid_amount
+ (
Case()
.when(
(pe.payment_type == "Pay")
& (company.default_currency == pe.paid_from_account_currency),
pe.base_total_taxes_and_charges,
)
.else_(pe.total_taxes_and_charges)
)
),
)
.else_(0)
).as_("credit"),
(
Case()
.when(pe.paid_from == account, 0)
.else_(
pe.received_amount
+ (
Case()
.when(
company.default_currency == pe.paid_to_account_currency,
pe.base_total_taxes_and_charges,
)
.else_(pe.total_taxes_and_charges)
)
)
).as_("debit"),
pe.posting_date,
Coalesce(pe.party, Case().when(pe.paid_from == account, pe.paid_to).else_(pe.paid_from)).as_(
"against_account"
),
pe.clearance_date,
(
Case()
.when(pe.paid_to == account, pe.paid_to_account_currency)
.else_(pe.paid_from_account_currency)
).as_("account_currency"),
)
.where(
((pe.paid_from == account) | (pe.paid_to == account))
& (pe.docstatus == 1)
& (pe.posting_date >= from_date)
& (pe.posting_date <= to_date)
)
)
payment_entries = frappe.db.sql(
f"""
select
"Payment Entry" as payment_document, pe.name as payment_entry,
pe.reference_no as cheque_number, pe.reference_date as cheque_date,
if(pe.paid_from=%(account)s, pe.paid_amount + if(pe.payment_type = 'Pay' and c.default_currency = pe.paid_from_account_currency, pe.base_total_taxes_and_charges, pe.total_taxes_and_charges) , 0) as credit,
if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit,
pe.posting_date, ifnull(pe.party,if(pe.paid_from=%(account)s,pe.paid_to,pe.paid_from)) as against_account, pe.clearance_date,
if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency
from `tabPayment Entry` as pe
join `tabCompany` c on c.name = pe.company
where
(pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1
and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s
{pe_condition}
order by
pe.posting_date ASC, pe.name DESC
""",
{
"account": account,
"from": from_date,
"to": to_date,
},
as_dict=1,
if not include_reconciled_entries:
payment_entry_query = payment_entry_query.where(
(pe.clearance_date.isnull()) | (pe.clearance_date == "0000-00-00")
)
payment_entries = (payment_entry_query.orderby(pe.posting_date).orderby(pe.name, order=Order.desc)).run(
as_dict=True
)
pos_sales_invoices, pos_purchase_invoices = [], []
acc = frappe.qb.DocType("Account")
pi = frappe.qb.DocType("Purchase Invoice")
paid_purchase_invoices_query = (
frappe.qb.from_(pi)
.inner_join(acc)
.on(pi.cash_bank_account == acc.name)
.select(
ConstantColumn("Purchase Invoice").as_("payment_document"),
pi.name.as_("payment_entry"),
pi.paid_amount.as_("credit"),
pi.posting_date,
pi.supplier.as_("against_account"),
pi.bill_no.as_("cheque_number"),
pi.clearance_date,
acc.account_currency,
ConstantColumn(0).as_("debit"),
)
.where(
(pi.docstatus == 1)
& (pi.is_paid == 1)
& (pi.cash_bank_account == account)
& (pi.posting_date >= from_date)
& (pi.posting_date <= to_date)
)
)
if not include_reconciled_entries:
paid_purchase_invoices_query = paid_purchase_invoices_query.where(
(pi.clearance_date.isnull()) | (pi.clearance_date == "0000-00-00")
)
paid_purchase_invoices = (
paid_purchase_invoices_query.orderby(pi.posting_date).orderby(pi.name, order=Order.desc)
).run(as_dict=True)
pos_sales_invoices = []
if include_pos_transactions:
si_payment = frappe.qb.DocType("Sales Invoice Payment")
si = frappe.qb.DocType("Sales Invoice")
acc = frappe.qb.DocType("Account")
pos_sales_invoices = (
pos_sales_invoices_query = (
frappe.qb.from_(si_payment)
.inner_join(si)
.on(si_payment.parent == si.name)
@@ -217,38 +316,22 @@ def get_payment_entries_for_bank_clearance(
& (si.posting_date >= from_date)
& (si.posting_date <= to_date)
)
.orderby(si.posting_date)
.orderby(si.name, order=Order.desc)
).run(as_dict=True)
)
pi = frappe.qb.DocType("Purchase Invoice")
if not include_reconciled_entries:
pos_sales_invoices_query = pos_sales_invoices_query.where(
(si_payment.clearance_date.isnull()) | (si_payment.clearance_date == "0000-00-00")
)
pos_purchase_invoices = (
frappe.qb.from_(pi)
.inner_join(acc)
.on(pi.cash_bank_account == acc.name)
.select(
ConstantColumn("Purchase Invoice").as_("payment_document"),
pi.name.as_("payment_entry"),
pi.paid_amount.as_("credit"),
pi.posting_date,
pi.supplier.as_("against_account"),
pi.clearance_date,
acc.account_currency,
ConstantColumn(0).as_("debit"),
)
.where(
(pi.docstatus == 1)
& (pi.cash_bank_account == account)
& (pi.posting_date >= from_date)
& (pi.posting_date <= to_date)
)
.orderby(pi.posting_date)
.orderby(pi.name, order=Order.desc)
pos_sales_invoices = (
pos_sales_invoices_query.orderby(si.posting_date).orderby(si.name, order=Order.desc)
).run(as_dict=True)
entries = (
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
list(payment_entries)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(paid_purchase_invoices)
)
return entries

View File

@@ -2,6 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on("Bank Statement Import", {
onload(frm) {
frm.set_query("bank_account", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
},
setup(frm) {
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false;

View File

@@ -136,6 +136,8 @@ class BankTransaction(Document):
self.set_status()
def on_cancel(self):
self.ignore_linked_doctypes = ["GL Entry"]
for payment_entry in self.payment_entries:
self.delink_payment_entry(payment_entry)
@@ -370,11 +372,12 @@ def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries
("unallocated_amount", "bank_account"),
as_dict=True,
)
bt_bank_account = frappe.db.get_value("Bank Account", bt.bank_account, "account")
if bt.bank_account != gl_bank_account:
if bt_bank_account != gl_bank_account:
frappe.throw(
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
bt.bank_account, payment_entry.payment_entry, gl_bank_account
bt_bank_account, payment_entry.payment_entry, gl_bank_account
)
)

View File

@@ -398,7 +398,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",
}
@@ -429,7 +429,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

@@ -4,7 +4,7 @@
import frappe
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe import _, cint
from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
@@ -33,23 +33,11 @@ class FiscalYear(Document):
self.validate_dates()
self.validate_overlap()
if not self.is_new():
year_start_end_dates = frappe.db.sql(
"""select year_start_date, year_end_date
from `tabFiscal Year` where name=%s""",
(self.name),
)
def on_update(self):
frappe.cache().delete_key("fiscal_years")
if year_start_end_dates:
if (
getdate(self.year_start_date) != year_start_end_dates[0][0]
or getdate(self.year_end_date) != year_start_end_dates[0][1]
):
frappe.throw(
_(
"Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."
)
)
def on_trash(self):
frappe.cache().delete_key("fiscal_years")
def validate_dates(self):
self.validate_from_to_dates("year_start_date", "year_end_date")
@@ -66,28 +54,20 @@ class FiscalYear(Document):
frappe.exceptions.InvalidDates,
)
def on_update(self):
check_duplicate_fiscal_year(self)
frappe.cache().delete_value("fiscal_years")
def on_trash(self):
frappe.cache().delete_value("fiscal_years")
def validate_overlap(self):
existing_fiscal_years = frappe.db.sql(
"""select name from `tabFiscal Year`
where (
(%(year_start_date)s between year_start_date and year_end_date)
or (%(year_end_date)s between year_start_date and year_end_date)
or (year_start_date between %(year_start_date)s and %(year_end_date)s)
or (year_end_date between %(year_start_date)s and %(year_end_date)s)
) and name!=%(name)s""",
{
"year_start_date": self.year_start_date,
"year_end_date": self.year_end_date,
"name": self.name or "No Name",
},
as_dict=True,
fy = frappe.qb.DocType("Fiscal Year")
name = self.name or self.year
existing_fiscal_years = (
frappe.qb.from_(fy)
.select(fy.name)
.where(
(fy.year_start_date <= self.year_end_date)
& (fy.year_end_date >= self.year_start_date)
& (fy.name != name)
)
.run(as_dict=True)
)
if existing_fiscal_years:
@@ -110,37 +90,30 @@ class FiscalYear(Document):
frappe.throw(
_(
"Year start date or end date is overlapping with {0}. To avoid please set company"
).format(existing.name),
).format(frappe.get_desk_link("Fiscal Year", existing.name, open_in_new_tab=True)),
frappe.NameError,
)
@frappe.whitelist()
def check_duplicate_fiscal_year(doc):
year_start_end_dates = frappe.db.sql(
"""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""",
(doc.name),
)
for fiscal_year, ysd, yed in year_start_end_dates:
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
not frappe.flags.in_test
):
frappe.throw(
_(
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
).format(fiscal_year)
)
@frappe.whitelist()
def auto_create_fiscal_year():
for d in frappe.db.sql(
"""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""
):
fy = frappe.qb.DocType("Fiscal Year")
# Skipped auto-creating Short Year, as it has very rare use case.
# Reference: https://www.irs.gov/businesses/small-businesses-self-employed/tax-years (US)
follow_up_date = add_days(getdate(), days=3)
fiscal_year = (
frappe.qb.from_(fy)
.select(fy.name)
.where((fy.year_end_date == follow_up_date) & (fy.is_short_year == 0))
.run()
)
for d in fiscal_year:
try:
current_fy = frappe.get_doc("Fiscal Year", d[0])
new_fy = frappe.copy_doc(current_fy, ignore_no_copy=False)
new_fy = frappe.new_doc("Fiscal Year")
new_fy.disabled = cint(current_fy.disabled)
new_fy.year_start_date = add_days(current_fy.year_end_date, 1)
new_fy.year_end_date = add_years(current_fy.year_end_date, 1)
@@ -148,6 +121,10 @@ def auto_create_fiscal_year():
start_year = cstr(new_fy.year_start_date.year)
end_year = cstr(new_fy.year_end_date.year)
new_fy.year = start_year if start_year == end_year else (start_year + "-" + end_year)
for row in current_fy.companies:
new_fy.append("companies", {"company": row.company})
new_fy.auto_created = 1
new_fy.insert(ignore_permissions=True)

View File

@@ -15,13 +15,14 @@
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company"
"options": "Company",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-28 18:01:53.495929",
"modified": "2026-02-20 23:02:26.193606",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year Company",
@@ -30,4 +31,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -14,7 +14,7 @@ class FiscalYearCompany(Document):
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link | None
company: DF.Link
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data

View File

@@ -172,7 +172,7 @@ class JournalEntry(AccountsController):
validate_docs_for_deferred_accounting([self.name], [])
def submit(self):
if len(self.accounts) > 100:
if len(self.accounts) > 100 and not self.meta.queue_in_background:
queue_submission(self, "_submit")
else:
return self._submit()
@@ -214,6 +214,8 @@ class JournalEntry(AccountsController):
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller
super().on_cancel()
from_doc_events = getattr(self, "ignore_linked_doctypes", ())
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
@@ -226,6 +228,10 @@ class JournalEntry(AccountsController):
"Unreconcile Payment Entries",
"Advance Payment Ledger Entry",
)
if from_doc_events and from_doc_events != self.ignore_linked_doctypes:
self.ignore_linked_doctypes = self.ignore_linked_doctypes + from_doc_events
self.make_gl_entries(1)
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
@@ -263,6 +269,9 @@ class JournalEntry(AccountsController):
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
def validate_stock_accounts(self):
if not erpnext.is_perpetual_inventory_enabled(self.company):
return
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
for account in stock_accounts:
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(

View File

@@ -185,7 +185,7 @@
"fieldtype": "Select",
"label": "Reference Type",
"no_copy": 1,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry\nBank Transaction",
"search_index": 1
},
{
@@ -198,7 +198,7 @@
"search_index": 1
},
{
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance', 'Bank Transaction'])",
"fieldname": "reference_due_date",
"fieldtype": "Date",
"label": "Reference Due Date",
@@ -295,7 +295,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-11-27 12:23:33.157655",
"modified": "2026-02-19 17:01:22.642454",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -55,6 +55,7 @@ class JournalEntryAccount(Document):
"Fees",
"Full and Final Statement",
"Payment Entry",
"Bank Transaction",
]
user_remark: DF.SmallText | None
# end: auto-generated types

View File

@@ -7,7 +7,7 @@ frappe.ui.form.on("Mode of Payment", {
let d = locals[cdt][cdn];
return {
filters: [
["Account", "account_type", "in", "Bank, Cash, Receivable"],
["Account", "account_type", "in", ["Bank", "Cash", "Receivable"]],
["Account", "is_group", "=", 0],
["Account", "company", "=", d.company],
],

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _, scrub
from frappe.model.document import Document
from frappe.utils import flt, nowdate
from frappe.utils import escape_html, flt, nowdate
from frappe.utils.background_jobs import enqueue, is_job_enqueued
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -84,6 +84,11 @@ class OpeningInvoiceCreationTool(Document):
)
prepare_invoice_summary(doctype, invoices)
invoices_summary_companies = list(invoices_summary.keys())
for company in invoices_summary_companies:
invoices_summary[escape_html(company)] = invoices_summary.pop(company)
return invoices_summary, max_count
def validate_company(self):

View File

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

@@ -400,6 +400,16 @@ frappe.ui.form.on("Payment Entry", {
);
frm.refresh_fields();
const party_currency =
frm.doc.payment_type === "Receive" ? "paid_from_account_currency" : "paid_to_account_currency";
var reference_grid = frm.fields_dict["references"].grid;
["total_amount", "outstanding_amount", "allocated_amount"].forEach((fieldname) => {
reference_grid.update_docfield_property(fieldname, "options", party_currency);
});
reference_grid.refresh();
},
show_general_ledger: function (frm) {
@@ -506,12 +516,16 @@ frappe.ui.form.on("Payment Entry", {
frm.set_value("contact_email", "");
frm.set_value("contact_person", "");
}
if (frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
if (!frm.doc.posting_date) {
frappe.msgprint(__("Please select Posting Date before selecting Party"));
frm.set_value("party", "");
return;
}
erpnext.utils.get_employee_contact_details(frm);
frm.set_party_account_based_on_party = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
@@ -1119,7 +1133,7 @@ frappe.ui.form.on("Payment Entry", {
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
await frm.call("allocate_amount_to_references", {
paid_amount: paid_amount,
paid_amount: flt(paid_amount),
paid_amount_change: paid_amount_change,
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
});
@@ -1455,16 +1469,15 @@ frappe.ui.form.on("Payment Entry", {
callback: function (r) {
if (!r.exc && r.message) {
// set taxes table
if (r.message) {
for (let tax of r.message) {
if (tax.charge_type === "On Net Total") {
tax.charge_type = "On Paid Amount";
}
frm.add_child("taxes", tax);
let taxes = r.message;
taxes.forEach((tax) => {
if (tax.charge_type === "On Net Total") {
tax.charge_type = "On Paid Amount";
}
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
});
frm.set_value("taxes", taxes);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
},
});

View File

@@ -1092,20 +1092,32 @@ class PaymentEntry(AccountsController):
self.base_paid_amount + deductions_to_consider
):
self.unallocated_amount = (
self.base_paid_amount
+ deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
) / self.source_exchange_rate
flt(
(
self.base_paid_amount
+ deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
),
self.precision("unallocated_amount"),
)
/ self.source_exchange_rate
)
elif self.payment_type == "Pay" and self.base_total_allocated_amount < (
self.base_received_amount - deductions_to_consider
):
self.unallocated_amount = (
self.base_received_amount
- deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
) / self.target_exchange_rate
flt(
(
self.base_received_amount
- deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
),
self.precision("unallocated_amount"),
)
/ self.target_exchange_rate
)
def set_exchange_gain_loss(self):
exchange_gain_loss = flt(
@@ -2294,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 = {
@@ -2319,17 +2329,15 @@ def get_outstanding_reference_documents(args, validate=False):
for fieldname, date_fields in date_fields_dict.items():
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 {frappe.db.escape(args.get(date_fields[0]))} and {frappe.db.escape(args.get(date_fields[1]))}"
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} >= {frappe.db.escape(args.get(date_fields[0]))}"
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} <= {frappe.db.escape(args.get(date_fields[1]))}"
posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
if args.get("company"):
@@ -2544,17 +2552,12 @@ def get_orders_to_be_billed(
if not voucher_type:
return []
# Add cost center condition
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
# dynamic dimension filters
active_dimensions = get_dimensions()[0]
condition = ""
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

@@ -1937,6 +1937,37 @@ class TestPaymentEntry(FrappeTestCase):
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, pe.doctype, pe.name)
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, "Journal Entry", jv[0])
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")
@@ -2043,6 +2074,7 @@ def create_customer(name="_Test Customer 2 USD", currency="USD"):
customer.customer_name = name
customer.default_currency = currency
customer.type = "Individual"
customer.customer_group = "Individual"
customer.save()
customer = customer.name
return customer

View File

@@ -22,6 +22,7 @@
"reqd": 1
},
{
"allow_on_submit": 1,
"fieldname": "cost_center",
"fieldtype": "Link",
"in_list_view": 1,
@@ -59,7 +60,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-11-05 16:07:47.307971",
"modified": "2026-03-11 14:26:11.312950",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Deduction",

View File

@@ -132,6 +132,12 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "due_date",

View File

@@ -38,6 +38,7 @@ class PaymentLedgerEntry(Document):
amount_in_account_currency: DF.Currency
company: DF.Link | None
cost_center: DF.Link | None
project: DF.Link | None
delinked: DF.Check
due_date: DF.Date | None
finance_book: DF.Link | None

View File

@@ -80,6 +80,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.type = "Individual"
customer.customer_group = "Individual"
customer.save()
self.customer = customer.name

View File

@@ -746,7 +746,7 @@ class PaymentReconciliation(Document):
ple = qb.DocType("Payment Ledger Entry")
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension):
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):

View File

@@ -2546,6 +2546,7 @@ def make_customer(customer_name, currency=None):
customer = frappe.new_doc("Customer")
customer.customer_name = customer_name
customer.type = "Individual"
customer.customer_group = "Individual"
if currency:
customer.default_currency = currency

View File

@@ -536,7 +536,7 @@ class PaymentRequest(Document):
row_number += TO_SKIP_NEW_ROW
@frappe.whitelist(allow_guest=True)
@frappe.whitelist()
def make_payment_request(**args):
"""Make payment request"""
@@ -548,6 +548,9 @@ def make_payment_request(**args):
if args.dn and not isinstance(args.dn, str):
frappe.throw(_("Invalid parameter. 'dn' should be of type str"))
frappe.has_permission("Payment Request", "create", throw=True)
frappe.has_permission(args.dt, "read", args.dn, throw=True)
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
if not args.get("company"):
args.company = ref_doc.company
@@ -822,7 +825,7 @@ def get_print_format_list(ref_doctype):
return {"print_format": print_format_list}
@frappe.whitelist(allow_guest=True)
@frappe.whitelist()
def resend_payment_email(docname):
return frappe.get_doc("Payment Request", docname).send_email()

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

@@ -60,7 +60,6 @@
"sec_warehouse",
"set_warehouse",
"items_section",
"update_stock",
"scan_barcode",
"last_scanned_warehouse",
"items",
@@ -574,7 +573,6 @@
"label": "Warehouse"
},
{
"depends_on": "update_stock",
"fieldname": "set_warehouse",
"fieldtype": "Link",
"label": "Source Warehouse",
@@ -588,15 +586,6 @@
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
{
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
"oldfieldname": "update_stock",
"oldfieldtype": "Check",
"print_hide": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
@@ -1582,7 +1571,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2025-08-04 22:22:31.471752",
"modified": "2026-02-22 04:18:50.691218",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
@@ -1627,6 +1616,7 @@
"role": "All"
}
],
"row_format": "Dynamic",
"search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "modified",
@@ -1635,4 +1625,4 @@
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -183,7 +183,6 @@ class POSInvoice(SalesInvoice):
total_taxes_and_charges: DF.Currency
update_billed_amount_in_delivery_note: DF.Check
update_billed_amount_in_sales_order: DF.Check
update_stock: DF.Check
write_off_account: DF.Link | None
write_off_amount: DF.Currency
write_off_cost_center: DF.Link | None
@@ -652,7 +651,6 @@ class POSInvoice(SalesInvoice):
"tax_category",
"ignore_pricing_rule",
"company_address",
"update_stock",
):
if not for_validate:
self.set(fieldname, profile.get(fieldname))

View File

@@ -838,6 +838,53 @@ class TestPOSInvoice(unittest.TestCase):
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
self.assertEqual(batch.qty, 5)
def test_pos_batch_reservation_with_return_qty(self):
"""
Test POS Invoice reserved qty for batch without bundle with return invoices.
"""
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
get_auto_batch_nos,
)
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_batch_item_with_batch,
)
create_batch_item_with_batch("_Batch Item Reserve Return", "TestBatch-RR 01")
se = make_stock_entry(
target="_Test Warehouse - _TC",
item_code="_Batch Item Reserve Return",
qty=30,
basic_rate=100,
)
se.reload()
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
# POS Invoice for the batch without bundle
pos_inv = create_pos_invoice(item="_Batch Item Reserve Return", rate=300, qty=15, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "amount": 4500},
)
pos_inv.items[0].batch_no = batch_no
pos_inv.save()
pos_inv.submit()
# POS Invoice return
pos_return = make_sales_return(pos_inv.name)
pos_return.insert()
pos_return.submit()
batches = get_auto_batch_nos(
frappe._dict({"item_code": "_Batch Item Reserve Return", "warehouse": "_Test Warehouse - _TC"})
)
for batch in batches:
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
self.assertEqual(batch.qty, 30)
def test_pos_batch_item_qty_validation(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
BatchNegativeStockError,
@@ -1054,7 +1101,6 @@ def create_pos_invoice(**args):
pos_inv = frappe.new_doc("POS Invoice")
pos_inv.update(args)
pos_inv.update_stock = 1
pos_inv.is_pos = 1
pos_inv.pos_profile = args.pos_profile or pos_profile.name

View File

@@ -812,6 +812,7 @@
},
{
"default": "0",
"fetch_from": "item_code.grant_commission",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
@@ -858,7 +859,7 @@
],
"istable": 1,
"links": [],
"modified": "2024-05-07 15:56:54.343317",
"modified": "2026-04-20 16:16:12.322024",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",

View File

@@ -146,6 +146,7 @@ class POSInvoiceMergeLog(Document):
sales_invoice.is_consolidated = 1
sales_invoice.set_posting_time = 1
sales_invoice.update_stock = 1
if not sales_invoice.posting_date:
sales_invoice.posting_date = getdate(self.posting_date)
@@ -174,6 +175,7 @@ class POSInvoiceMergeLog(Document):
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.update_stock = 1
credit_note.posting_date = getdate(self.posting_date)
credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
@@ -697,6 +699,7 @@ def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
& (SalesInvoice.is_return == 0)
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
& (SalesInvoice.docstatus == 1)
)
)

View File

@@ -25,7 +25,6 @@
"validate_stock_on_save",
"print_receipt_on_order_complete",
"column_break_16",
"update_stock",
"ignore_pricing_rule",
"allow_rate_change",
"allow_discount_change",
@@ -297,7 +296,6 @@
"options": "Print Format"
},
{
"depends_on": "update_stock",
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
@@ -312,14 +310,6 @@
"fieldtype": "Check",
"label": "Ignore Pricing Rule"
},
{
"default": "1",
"fieldname": "update_stock",
"fieldtype": "Check",
"hidden": 1,
"label": "Update Stock",
"read_only": 1
},
{
"default": "0",
"fieldname": "hide_unavailable_items",
@@ -432,7 +422,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2025-04-14 15:58:20.497426",
"modified": "2026-02-22 04:17:03.308876",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@@ -61,7 +61,6 @@ class POSProfile(Document):
tax_category: DF.Link | None
taxes_and_charges: DF.Link | None
tc_name: DF.Link | None
update_stock: DF.Check
validate_stock_on_save: DF.Check
warehouse: DF.Link
write_off_account: DF.Link

View File

@@ -121,7 +121,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Apply On",
"options": "\nItem Code\nItem Group\nBrand\nTransaction",
"options": "Item Code\nItem Group\nBrand\nTransaction",
"reqd": 1
},
{
@@ -657,7 +657,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2025-08-20 11:40:07.096854",
"modified": "2026-02-17 12:24:07.553505",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
@@ -719,4 +719,4 @@
"sort_order": "DESC",
"states": [],
"title_field": "title"
}
}

View File

@@ -45,7 +45,7 @@ class PricingRule(Document):
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
apply_discount_on_rate: DF.Check
apply_multiple_pricing_rules: DF.Check
apply_on: DF.Literal["", "Item Code", "Item Group", "Brand", "Transaction"]
apply_on: DF.Literal["Item Code", "Item Group", "Brand", "Transaction"]
apply_recursion_over: DF.Float
apply_rule_on_other: DF.Literal["", "Item Code", "Item Group", "Brand"]
brands: DF.Table[PricingRuleBrand]
@@ -346,8 +346,7 @@ def apply_pricing_rule(args, doc=None):
args = frappe._dict(args)
if not args.transaction_type:
set_transaction_type(args)
set_transaction_type(args)
# list of dictionaries
out = []
@@ -683,23 +682,23 @@ def remove_pricing_rules(item_list):
return out
def set_transaction_type(args):
if args.transaction_type:
def set_transaction_type(pricing_ctx: frappe._dict) -> None:
if pricing_ctx.transaction_type in ["buying", "selling"]:
return
if args.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
args.transaction_type = "selling"
elif args.doctype in (
if pricing_ctx.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
pricing_ctx.transaction_type = "selling"
elif pricing_ctx.doctype in (
"Material Request",
"Supplier Quotation",
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice",
):
args.transaction_type = "buying"
elif args.customer:
args.transaction_type = "selling"
pricing_ctx.transaction_type = "buying"
elif pricing_ctx.customer:
pricing_ctx.transaction_type = "selling"
else:
args.transaction_type = "buying"
pricing_ctx.transaction_type = "buying"
@frappe.whitelist()

View File

@@ -243,8 +243,10 @@ def get_other_conditions(conditions, values, args):
if group_condition:
conditions += " and " + group_condition
date = args.get("transaction_date") or frappe.get_value(
args.get("doctype"), args.get("name"), "posting_date", ignore=True
date = (
args.get("transaction_date")
or args.get("posting_date")
or frappe.get_value(args.get("doctype"), args.get("name"), "posting_date", ignore=True)
)
if date:
conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01')
@@ -656,7 +658,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

@@ -20,7 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:parent.apply_on == 'Item Code'",
"depends_on": "eval:parent.apply_on == 'Brand'",
"fieldname": "brand",
"fieldtype": "Link",
"hidden": 0,
@@ -91,7 +91,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-03-24 14:48:59.649168",
"modified": "2026-02-17 12:17:13.073587",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule Brand",
@@ -107,4 +107,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}
}

View File

@@ -20,7 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:parent.apply_on == 'Item Code'",
"depends_on": "eval:parent.apply_on == 'Item Group'",
"fieldname": "item_group",
"fieldtype": "Link",
"hidden": 0,
@@ -91,7 +91,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-03-24 14:48:59.649168",
"modified": "2026-02-17 12:16:57.778471",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule Item Group",
@@ -107,4 +107,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}
}

View File

@@ -412,8 +412,9 @@ def reconcile(doc: None | str = None) -> None:
for x in allocations:
pr.append("allocation", x)
skip_ref_details_update_for_pe = check_multi_currency(pr)
# reconcile
pr.reconcile_allocations(skip_ref_details_update_for_pe=True)
pr.reconcile_allocations(skip_ref_details_update_for_pe=skip_ref_details_update_for_pe)
# If Payment Entry, update details only for newly linked references
# This is for performance
@@ -503,6 +504,37 @@ def reconcile(doc: None | str = None) -> None:
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
def check_multi_currency(pr_doc):
GL = frappe.qb.DocType("GL Entry")
Account = frappe.qb.DocType("Account")
def get_account_currency(voucher_type, voucher_no):
currency = (
frappe.qb.from_(GL)
.join(Account)
.on(GL.account == Account.name)
.select(Account.account_currency)
.where(
(GL.voucher_type == voucher_type)
& (GL.voucher_no == voucher_no)
& (Account.account_type.isin(["Payable", "Receivable"]))
)
.limit(1)
).run(as_dict=True)
return currency[0].account_currency if currency else None
for allocation in pr_doc.allocation:
reference_currency = get_account_currency(allocation.reference_type, allocation.reference_name)
invoice_currency = get_account_currency(allocation.invoice_type, allocation.invoice_number)
if reference_currency != invoice_currency:
return True
return False
@frappe.whitelist()
def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None

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

@@ -312,7 +312,7 @@
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date",
"label": "Posting Date",
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
"print_hide": 1,
@@ -382,7 +382,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "bill_no",
"collapsible_depends_on": "posting_date",
"fieldname": "supplier_invoice_details",
"fieldtype": "Section Break",
"label": "Supplier Invoice"
@@ -603,6 +603,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.pr_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
@@ -1659,7 +1660,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-08-04 19:19:11.380664",
"modified": "2026-03-17 20:44:00.221219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -38,7 +38,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
@@ -612,12 +612,13 @@ class PurchaseInvoice(BuyingController):
frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account)
def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes":
if frappe.get_value(
if (
frappe.db.get_single_value("Buying Settings", "po_required") == "Yes"
and not self.is_internal_transfer()
and not frappe.db.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
):
return
)
):
for d in self.get("items"):
if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
@@ -728,9 +729,10 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"):
if item.purchase_receipt:
frappe.throw(
_("Stock cannot be updated against Purchase Receipt {0}").format(
item.purchase_receipt
)
_(
"Stock cannot be updated for Purchase Invoice {0} because a Purchase Receipt {1} has already been created for this transaction. Please disable the 'Update Stock' checkbox in the Purchase Invoice and save the invoice."
).format(self.name, item.purchase_receipt),
title=_("Stock Update Not Allowed"),
)
def validate_for_repost(self):
@@ -976,6 +978,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:
@@ -1144,7 +1150,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]
@@ -1181,6 +1191,7 @@ class PurchaseInvoice(BuyingController):
item=item,
)
)
if (
self.auto_accounting_for_stock
and self.is_opening == "No"
@@ -1729,10 +1740,6 @@ class PurchaseInvoice(BuyingController):
project_doc.db_update()
def validate_supplier_invoice(self):
if self.bill_date:
if getdate(self.bill_date) > getdate(self.posting_date):
frappe.throw(_("Supplier Invoice Date cannot be greater than Posting Date"))
if self.bill_no:
if cint(frappe.db.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
fiscal_year = get_fiscal_year(self.posting_date, company=self.company, as_dict=True)
@@ -2083,6 +2090,19 @@ def make_purchase_receipt(source_name, target_doc=None, args=None):
if isinstance(args, str):
args = json.loads(args)
def post_parent_process(source_parent, target_parent):
remove_items_with_zero_qty(target_parent)
set_missing_values(source_parent, target_parent)
def remove_items_with_zero_qty(target_parent):
target_parent.items = [row for row in target_parent.get("items") if row.get("qty") != 0]
def set_missing_values(source_parent, target_parent):
target_parent.run_method("set_missing_values")
if args and args.get("merge_taxes"):
merge_taxes(source_parent, target_parent)
target_parent.run_method("calculate_taxes_and_totals")
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.received_qty)
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
@@ -2122,7 +2142,11 @@ def make_purchase_receipt(source_name, target_doc=None, args=None):
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
"reset_value": not (args and args.get("merge_taxes")),
"ignore": args.get("merge_taxes") if args else 0,
},
},
target_doc,
)

View File

@@ -356,6 +356,12 @@ class TestPurchaseInvoice(FrappeTestCase, 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",
@@ -376,12 +382,17 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
amount = frappe.db.get_value(
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
)
discrepancy_caused_by_exchange_rate_diff = abs(
pi.items[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,

View File

@@ -731,7 +731,6 @@
"label": "Valuation Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"precision": "6",
"print_hide": 1,
"read_only": 1
},
@@ -984,7 +983,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-10-14 13:01:54.441511",
"modified": "2026-04-07 15:41:45.687554",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -43,6 +43,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
"Unreconcile Payment Entries",
"Serial and Batch Bundle",
"Bank Transaction",
"Packing Slip",
];
if (!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
@@ -117,12 +118,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
return item.delivery_note ? true : false;
});
if (!from_delivery_note && !is_delivered_by_supplier) {
cur_frm.add_custom_button(
__("Delivery"),
cur_frm.cscript["Make Delivery Note"],
__("Create")
if (!is_delivered_by_supplier) {
const should_create_delivery_note = doc.items.some(
(item) =>
item.qty - item.delivered_qty > 0 &&
!item.dn_detail &&
!item.delivered_by_supplier
);
if (should_create_delivery_note) {
this.frm.add_custom_button(
__("Delivery Note"),
this.frm.cscript["Make Delivery Note"],
__("Create")
);
}
}
}
@@ -157,13 +166,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
);
}
}
// Show buttons only when pos view is active
if (cint(doc.docstatus == 0) && cur_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) {
@@ -249,6 +252,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;
this.$sales_order_btn = this.frm.add_custom_button(
@@ -313,6 +403,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",
@@ -325,7 +421,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 {
@@ -585,6 +681,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
this.calculate_taxes_and_totals();
}
apply_tds(frm) {
this.frm.clear_table("tax_withholding_entries");
}
is_return() {
this.toggle_get_items();
}
};
// for backward compatibility: combine new and previous states
@@ -1030,71 +1134,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

@@ -373,7 +373,7 @@
"fieldtype": "Date",
"hide_days": 1,
"hide_seconds": 1,
"label": "Date",
"label": "Posting Date",
"no_copy": 1,
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
@@ -701,6 +701,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.dn_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"hide_days": 1,
@@ -776,8 +777,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
"depends_on": "eval:!doc.is_return",
"collapsible_depends_on": "eval:doc.total_billing_amount > 0 || doc.total_billing_hours > 0",
"fieldname": "time_sheet_list",
"fieldtype": "Section Break",
"hide_border": 1,
@@ -791,7 +791,6 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Time Sheets",
"no_copy": 1,
"options": "Sales Invoice Timesheet",
"print_hide": 1
},
@@ -2111,7 +2110,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:(!doc.is_return && doc.total_billing_amount > 0)",
"depends_on": "eval:doc.total_billing_amount > 0 || doc.total_billing_hours > 0",
"fieldname": "section_break_104",
"fieldtype": "Section Break"
},
@@ -2199,7 +2198,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-09-09 14:48:59.472826",
"modified": "2026-04-06 22:30:28.513139",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -323,10 +323,22 @@ class SalesInvoice(SellingController):
)
self.set_against_income_account()
self.validate_time_sheets_are_submitted()
if self.is_return and not self.return_against and self.timesheets:
frappe.throw(_("Direct return is not allowed for Timesheet."))
if not self.is_return:
self.validate_time_sheets_are_submitted()
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
if self.is_return:
self.timesheets = []
if self.is_return and self.return_against:
for row in self.timesheets:
if row.billing_hours:
row.billing_hours = -abs(row.billing_hours)
if row.billing_amount:
row.billing_amount = -abs(row.billing_amount)
self.update_packing_list()
self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project()
@@ -494,7 +506,7 @@ class SalesInvoice(SellingController):
if not cint(self.is_pos) == 1 and not self.is_return:
self.update_against_document_in_jv()
self.update_time_sheet(self.name)
self.update_time_sheet(None if (self.is_return and self.return_against) else self.name)
if frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction":
update_company_current_month_sales(self.company)
@@ -550,7 +562,7 @@ class SalesInvoice(SellingController):
self.check_if_consolidated_invoice()
super().before_cancel()
self.update_time_sheet(None)
self.update_time_sheet(self.return_against if (self.is_return and self.return_against) else None)
def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)
@@ -735,8 +747,20 @@ class SalesInvoice(SellingController):
for data in timesheet.time_logs:
if (
(self.project and args.timesheet_detail == data.name)
or (not self.project and not data.sales_invoice)
or (not sales_invoice and data.sales_invoice == self.name)
or (not self.project and not data.sales_invoice and args.timesheet_detail == data.name)
or (
not sales_invoice
and data.sales_invoice == self.name
and args.timesheet_detail == data.name
)
or (
self.is_return
and self.return_against
and data.sales_invoice
and data.sales_invoice == self.return_against
and not sales_invoice
and args.timesheet_detail == data.name
)
):
data.sales_invoice = sales_invoice
@@ -776,11 +800,25 @@ class SalesInvoice(SellingController):
payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
def validate_time_sheets_are_submitted(self):
# Note: This validation is skipped for return invoices
# to allow returns to reference already-billed timesheet details
for data in self.timesheets:
# Handle invoice duplication
if data.time_sheet and data.timesheet_detail:
if sales_invoice := frappe.db.get_value(
"Timesheet Detail", data.timesheet_detail, "sales_invoice"
):
frappe.throw(
_("Row {0}: Sales Invoice {1} is already created for {2}").format(
data.idx, frappe.bold(sales_invoice), frappe.bold(data.time_sheet)
)
)
if data.time_sheet:
status = frappe.db.get_value("Timesheet", data.time_sheet, "status")
if status not in ["Submitted", "Payslip"]:
frappe.throw(_("Timesheet {0} is already completed or cancelled").format(data.time_sheet))
if status not in ["Submitted", "Payslip", "Partially Billed"]:
frappe.throw(
_("Timesheet {0} cannot be invoiced in its current state").format(data.time_sheet)
)
def set_pos_fields(self, for_validate=False):
"""Set retail related fields from POS Profiles"""
@@ -804,11 +842,9 @@ class SalesInvoice(SellingController):
if self.pos_profile:
pos = frappe.get_doc("POS Profile", self.pos_profile)
if not self.get("payments") and not for_validate:
update_multi_mode_option(self, pos)
if pos:
if not for_validate:
update_multi_mode_option(self, pos)
self.tax_category = pos.get("tax_category")
if not for_validate and not self.customer:
@@ -854,9 +890,6 @@ class SalesInvoice(SellingController):
if selling_price_list:
self.set("selling_price_list", selling_price_list)
if not for_validate:
self.update_stock = cint(pos.get("update_stock"))
# set pos values in items
for item in self.get("items"):
if item.get("item_code"):
@@ -1097,7 +1130,9 @@ class SalesInvoice(SellingController):
d.projected_qty = bin and flt(bin[0]["projected_qty"]) or 0
def update_packing_list(self):
if cint(self.update_stock) == 1:
if self.doctype == "POS Invoice" or (
self.doctype == "Sales Invoice" and cint(self.update_stock) == 1
):
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self)
@@ -1115,7 +1150,12 @@ class SalesInvoice(SellingController):
timesheet.billing_amount = ts_doc.total_billable_amount
def update_timesheet_billing_for_project(self):
if not self.timesheets and self.project and self.is_auto_fetch_timesheet_enabled():
if (
not self.is_return
and not self.timesheets
and self.project
and self.is_auto_fetch_timesheet_enabled()
):
self.add_timesheet_data()
else:
self.calculate_billing_amount_for_timesheet()
@@ -1200,6 +1240,9 @@ class SalesInvoice(SellingController):
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def process_asset_depreciation(self):
if self.is_internal_transfer():
return
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
self.depreciate_asset_on_sale()
else:
@@ -2211,7 +2254,9 @@ def make_delivery_note(source_name, target_doc=None):
"cost_center": "cost_center",
},
"postprocess": update_item,
"condition": lambda doc: doc.delivered_by_supplier != 1,
"condition": lambda doc: doc.delivered_by_supplier != 1
and not doc.dn_detail
and doc.qty - doc.delivered_qty > 0,
},
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {
@@ -2514,7 +2559,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"doctype": target_doctype,
"postprocess": update_details,
"set_target_warehouse": "set_from_warehouse",
"field_no_map": ["taxes_and_charges", "set_warehouse", "shipping_address"],
"field_no_map": ["taxes_and_charges", "set_warehouse", "shipping_address", "cost_center"],
},
doctype + " Item": item_field_map,
},
@@ -2743,6 +2788,8 @@ def update_multi_mode_option(doc, pos_profile):
payment.account = payment_mode.default_account
payment.type = payment_mode.type
mop_refetched = bool(doc.payments)
doc.set("payments", [])
invalid_modes = []
mode_of_payments = [d.mode_of_payment for d in pos_profile.get("payments")]
@@ -2764,6 +2811,11 @@ def update_multi_mode_option(doc, pos_profile):
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
if mop_refetched:
frappe.msgprint(
_("Payment methods refreshed. Please review before proceeding."), indicator="orange", alert=True
)
def get_all_mode_of_payments(doc):
return frappe.db.sql(

View File

@@ -2917,7 +2917,7 @@ class TestSalesInvoice(FrappeTestCase):
si.submit()
# Check if adjustment entry is created
self.assertTrue(
self.assertFalse(
frappe.db.exists(
"GL Entry",
{
@@ -3230,7 +3230,7 @@ class TestSalesInvoice(FrappeTestCase):
calculate_depreciation=1,
submit=1,
)
post_depreciation_entries()
post_depreciation_entries(date="2025-04-01")
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=10000, posting_date=getdate("2025-05-01")
@@ -4775,6 +4775,93 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(q[0][0], 1)
@change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True})
def test_zero_valuation_for_standalone_credit_note_with_expired_batch(self):
item_code = "_Test Item for Expiry Batch Zero Valuation"
make_item_for_si(
item_code,
{
"is_stock_item": 1,
"has_batch_no": 1,
"has_expiry_date": 1,
"shelf_life_in_days": 2,
"create_new_batch": 1,
"batch_number_series": "TBATCH-EBZV.####",
},
)
se = make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=100,
)
# fetch batch no from bundle
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
si = create_sales_invoice(
posting_date=add_days(nowdate(), 3),
item=item_code,
qty=-10,
rate=100,
is_return=1,
update_stock=1,
use_serial_batch_fields=1,
do_not_save=1,
do_not_submit=1,
)
si.items[0].batch_no = batch_no
si.save()
si.submit()
si.reload()
# check zero incoming rate in voucher
self.assertEqual(si.items[0].incoming_rate, 0.0)
# chekc zero incoming rate in stock ledger
stock_ledger_entry = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"item_code": item_code,
"warehouse": "_Test Warehouse - _TC",
},
["incoming_rate", "valuation_rate"],
as_dict=True,
)
self.assertEqual(stock_ledger_entry.incoming_rate, 0.0)
def test_inter_company_transaction_cost_center(self):
si = create_sales_invoice(
company="Wind Power LLC",
customer="_Test Internal Customer",
debit_to="Debtors - WP",
warehouse="Stores - WP",
income_account="Sales - WP",
expense_account="Cost of Goods Sold - WP",
parent_cost_center="Main - WP",
cost_center="Main - WP",
currency="USD",
do_not_save=1,
)
si.selling_price_list = "_Test Price List Rest of the World"
si.submit()
cost_center = frappe.db.get_value("Company", "_Test Company 1", "cost_center")
frappe.db.set_value("Company", "_Test Company 1", "cost_center", None)
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
self.assertEqual(target_doc.cost_center, None)
self.assertEqual(target_doc.items[0].cost_center, None)
frappe.db.set_value("Company", "_Test Company 1", "cost_center", cost_center)
def make_item_for_si(item_code, properties=None):
from erpnext.stock.doctype.item.test_item import make_item

View File

@@ -840,6 +840,7 @@
"fieldtype": "Currency",
"label": "Incoming Rate (Costing)",
"no_copy": 1,
"non_negative": 1,
"options": "Company:company:default_currency",
"print_hide": 1
},
@@ -983,7 +984,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-03-12 16:33:55.503777",
"modified": "2026-02-23 14:37:14.853941",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
@@ -993,4 +994,4 @@
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -26,7 +26,7 @@
},
{
"default": "0",
"depends_on": "eval:parent.doctype == 'Sales Invoice'",
"depends_on": "eval: [\"POS Invoice\", \"Sales Invoice\"].includes(parent.doctype)",
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -85,7 +85,7 @@
],
"istable": 1,
"links": [],
"modified": "2024-01-23 16:20:06.436979",
"modified": "2026-02-16 20:46:34.592604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
@@ -95,4 +95,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -52,7 +52,6 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Timesheet Detail",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
@@ -117,7 +116,7 @@
],
"istable": 1,
"links": [],
"modified": "2021-10-02 03:48:44.979777",
"modified": "2026-04-06 22:30:28.513139",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Timesheet",

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"):
@@ -152,7 +157,9 @@ class ShippingRule(Document):
frappe.throw(_("Shipping rule only applicable for Buying"))
shipping_charge["doctype"] = "Purchase Taxes and Charges"
shipping_charge["category"] = "Valuation and Total"
shipping_charge["category"] = (
"Valuation and Total" if doc.get_stock_items() or doc.get_asset_items() else "Total"
)
shipping_charge["add_deduct_tax"] = "Add"
existing_shipping_charge = doc.get("taxes", filters=shipping_charge)

View File

@@ -629,18 +629,21 @@ def create_parties():
customer.customer_name = "_Test Subscription Customer"
customer.default_currency = "USD"
customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"})
customer.customer_group = "Individual"
customer.insert()
if not frappe.db.exists("Customer", "_Test Subscription Customer Multi Currency"):
customer = frappe.new_doc("Customer")
customer.customer_name = "Test Subscription Customer Multi Currency"
customer.default_currency = "USD"
customer.customer_group = "Individual"
customer.insert()
if not frappe.db.exists("Customer", "_Test Subscription Customer John Doe"):
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test Subscription Customer John Doe"
customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable - _TC"})
customer.customer_group = "Individual"
customer.insert()

View File

@@ -804,12 +804,19 @@ def validate_against_pcv(is_opening, posting_date, company):
title=_("Invalid Opening Entry"),
)
last_pcv_date = frappe.db.get_value(
"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(period_end_date)"
)
# Local import so you don't have to touch file-level imports
from frappe.query_builder.functions import Max
pcv = frappe.qb.DocType("Period Closing Voucher")
last_pcv_date = (
frappe.qb.from_(pcv)
.select(Max(pcv.period_end_date))
.where((pcv.docstatus == 1) & (pcv.company == company))
).run(pluck=True)[0]
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
message = _("Books have been closed till the period ending on {0}").format(formatdate(last_pcv_date))
message = _("Books have been closed till the period ending on {0}.").format(formatdate(last_pcv_date))
message += "</br >"
message += _("You cannot create/amend any accounting entries till this date.")
frappe.throw(message, title=_("Period Closed"))

View File

@@ -0,0 +1,43 @@
<h4>{{ _("New Fiscal Year - {0}").format(doc.name) }}</h4>
<p>{{ _("A new fiscal year has been automatically created.") }}</p>
<p>{{ _("Fiscal Year Details") }}</p>
<table style="margin-bottom: 1rem; width: 70%">
<tr>
<td style="font-weight:bold; width: 40%">{{ _("Year Name") }}</td>
<td>{{ doc.name }}</td>
</tr>
<tr>
<td style="font-weight:bold; width: 40%">{{ _("Start Date") }}</td>
<td>{{ frappe.format_value(doc.year_start_date) }}</td>
</tr>
<tr>
<td style="font-weight:bold; width: 40%">{{ _("End Date") }}</td>
<td>{{ frappe.format_value(doc.year_end_date) }}</td>
</tr>
{% if doc.companies|length > 0 %}
<tr>
<td style="vertical-align: top; font-weight: bold; width: 40%" rowspan="{{ doc.companies|length }}">
{% if doc.companies|length < 2 %}
{{ _("Company") }}
{% else %}
{{ _("Companies") }}
{% endif %}
</td>
<td>{{ doc.companies[0].company }}</td>
</tr>
{% for idx in range(1, doc.companies|length) %}
<tr>
<td>{{ doc.companies[idx].company }}</td>
</tr>
{% endfor %}
{% endif %}
</table>
{% if doc.disabled %}
<p>{{ _("The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status.") }}</p>
{% endif %}
<p>{{ _("Please review the {0} configuration and complete any required financial setup activities.").format(frappe.utils.get_link_to_form("Fiscal Year", doc.name, frappe.bold("Fiscal Year"))) }}</p>

View File

@@ -1,7 +1,7 @@
{
"attach_print": 0,
"channel": "Email",
"condition": "doc.auto_created",
"condition": "doc.auto_created == 1",
"creation": "2018-04-25 14:19:05.440361",
"days_in_advance": 0,
"docstatus": 0,
@@ -11,19 +11,22 @@
"event": "New",
"idx": 0,
"is_standard": 1,
"message": "<h3>{{_(\"Fiscal Year\")}}</h3>\n\n<p>{{ _(\"New fiscal year created :- \") }} {{ doc.name }}</p>",
"modified": "2018-04-25 14:30:38.588534",
"message": "<h4>{{ _(\"New Fiscal Year - {0}\").format(doc.name) }}</h4>\n\n<p>{{ _(\"A new fiscal year has been automatically created.\") }}</p>\n\n<p>{{ _(\"Fiscal Year Details\") }}</p>\n\n<table style=\"margin-bottom: 1rem; width: 70%\">\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"Year Name\") }}</td>\n <td>{{ doc.name }}</td>\n </tr>\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"Start Date\") }}</td>\n <td>{{ frappe.format_value(doc.year_start_date) }}</td>\n </tr>\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"End Date\") }}</td>\n <td>{{ frappe.format_value(doc.year_end_date) }}</td>\n </tr>\n {% if doc.companies|length > 0 %}\n <tr>\n <td style=\"vertical-align: top; font-weight: bold; width: 40%\" rowspan=\"{{ doc.companies|length }}\">\n {% if doc.companies|length < 2 %}\n {{ _(\"Company\") }}\n {% else %}\n {{ _(\"Companies\") }}\n {% endif %}\n </td>\n <td>{{ doc.companies[0].company }}</td>\n </tr>\n {% for idx in range(1, doc.companies|length) %}\n <tr>\n <td>{{ doc.companies[idx].company }}</td>\n </tr>\n {% endfor %}\n {% endif %}\n</table>\n\n{% if doc.disabled %}\n<p>{{ _(\"The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status.\") }}</p>\n{% endif %}\n\n<p>{{ _(\"Please review the {0} configuration and complete any required financial setup activities.\").format(frappe.utils.get_link_to_form(\"Fiscal Year\", doc.name, frappe.bold(\"Fiscal Year\"))) }}</p>",
"message_type": "HTML",
"modified": "2026-02-21 15:59:07.775679",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Notification for new fiscal year",
"owner": "Administrator",
"recipients": [
{
"email_by_role": "Accounts User"
"receiver_by_role": "Accounts Manager"
},
{
"email_by_role": "Accounts Manager"
"receiver_by_role": "Accounts User"
}
],
"subject": "Notification for new fiscal year {{ doc.name }}"
"send_system_notification": 0,
"send_to_all_assignees": 0,
"subject": "{{ _(\"New Fiscal Year {0} - Review Required\").format(doc.name) }}"
}

View File

@@ -7,18 +7,16 @@ from frappe import _, msgprint, qb, scrub
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Abs, Count, Date, Sum
from frappe.query_builder.functions import Abs, Date, Sum
from frappe.utils import (
add_days,
add_months,
add_years,
cint,
cstr,
date_diff,
flt,
formatdate,
get_last_day,
get_timestamp,
getdate,
nowdate,
)
@@ -302,19 +300,9 @@ def complete_contact_details(party_details):
contact_details = frappe._dict()
if party_details.party_type == "Employee":
contact_details = frappe.db.get_value(
"Employee",
party_details.party,
[
"employee_name as contact_display",
"prefered_email as contact_email",
"cell_number as contact_mobile",
"designation as contact_designation",
"department as contact_department",
],
as_dict=True,
)
from erpnext.setup.doctype.employee.employee import _get_contact_details as get_employee_contact
contact_details = get_employee_contact(party_details.party)
contact_details.update({"contact_person": None, "contact_phone": None})
elif party_details.contact_person:
contact_details = frappe.db.get_value(

View File

@@ -17,7 +17,7 @@
</div>
<div class="col-xs-6">
<table>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.posting_date) }}</td></tr>
</table>
</div>
</div>

View File

@@ -1,23 +0,0 @@
{
"align_labels_right": 0,
"creation": "2016-05-05 17:16:18.564460",
"custom_format": 1,
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Qty Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ qty_total }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
"idx": 0,
"line_breaks": 0,
"modified": "2019-09-05 17:20:30.726659",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Point of Sale",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "JS",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

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

@@ -1,6 +1,6 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
from frappe.utils import add_days, today
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.report.accounts_payable.accounts_payable import execute
@@ -57,3 +57,112 @@ class TestAccountsPayable(AccountsTestMixin, FrappeTestCase):
if not do_not_submit:
pi = pi.submit()
return pi
def test_payment_terms_template_filters(self):
from erpnext.controllers.accounts_controller import get_payment_terms
payment_term1 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
).insert()
payment_term2 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
).insert()
template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "_Test 50-50",
"terms": [
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term1.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 15,
},
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term2.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 30,
},
],
}
)
template.insert()
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
"based_on_payment_terms": 1,
"payment_terms_template": template.name,
"ageing_based_on": "Posting Date",
}
pi = self.create_purchase_invoice(do_not_submit=True)
pi.payment_terms_template = template.name
schedule = get_payment_terms(template.name)
pi.set("payment_schedule", [])
for row in schedule:
row["due_date"] = add_days(pi.posting_date, row.get("credit_days", 0))
pi.append("payment_schedule", row)
pi.save()
pi.submit()
report = execute(filters)
row = report[1][0]
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

@@ -194,6 +194,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)
@@ -360,6 +361,7 @@ class ReceivablePayableReport:
posting_date,
account_currency,
cost_center,
project,
sum(invoiced) `invoiced`,
sum(paid) `paid`,
sum(credit_note) `credit_note`,
@@ -388,6 +390,7 @@ class ReceivablePayableReport:
"credit_note_in_account_currency",
"outstanding_in_account_currency",
"cost_center",
"project",
]:
_d[field] = x.get(field)
@@ -925,6 +928,7 @@ class ReceivablePayableReport:
ple.against_voucher_no,
ple.party_type,
ple.cost_center,
ple.project,
ple.party,
ple.posting_date,
ple.due_date,
@@ -992,6 +996,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):
@@ -1029,9 +1036,8 @@ class ReceivablePayableReport:
self,
):
self.customer = qb.DocType("Customer")
if self.filters.get("customer_group"):
groups = get_customer_group_with_children(self.filters.customer_group)
groups = get_party_group_with_children("Customer", self.filters.customer_group)
customers = (
qb.from_(self.customer)
.select(self.customer.name)
@@ -1043,14 +1049,18 @@ class ReceivablePayableReport:
self.get_hierarchical_filters("Territory", "territory")
if self.filters.get("payment_terms_template"):
self.qb_selection_filter.append(
self.ple.party.isin(
qb.from_(self.customer)
.select(self.customer.name)
.where(self.customer.payment_terms == self.filters.get("payment_terms_template"))
)
customer_ptt = self.ple.party.isin(
qb.from_(self.customer)
.select(self.customer.name)
.where(self.customer.payment_terms == self.filters.get("payment_terms_template"))
)
si_ptt = self.add_payment_term_template_filters("Sales Invoice")
sales_ptt = self.ple.against_voucher_no.isin(si_ptt)
self.qb_selection_filter.append(Criterion.any([customer_ptt, sales_ptt]))
if self.filters.get("sales_partner"):
self.qb_selection_filter.append(
self.ple.party.isin(
@@ -1075,14 +1085,53 @@ class ReceivablePayableReport:
)
if self.filters.get("payment_terms_template"):
self.qb_selection_filter.append(
self.ple.party.isin(
qb.from_(supplier)
.select(supplier.name)
.where(supplier.payment_terms == self.filters.get("supplier_group"))
)
supplier_ptt = self.ple.party.isin(
qb.from_(supplier)
.select(supplier.name)
.where(supplier.payment_terms == self.filters.get("payment_terms_template"))
)
pi_ptt = self.add_payment_term_template_filters("Purchase Invoice")
purchase_ptt = self.ple.against_voucher_no.isin(pi_ptt)
self.qb_selection_filter.append(Criterion.any([supplier_ptt, purchase_ptt]))
def add_payment_term_template_filters(self, dtype):
voucher_type = qb.DocType(dtype)
ptt = (
qb.from_(voucher_type)
.select(voucher_type.name)
.where(voucher_type.payment_terms_template == self.filters.get("payment_terms_template"))
.where(voucher_type.company == self.filters.company)
)
if dtype == "Purchase Invoice":
party = "Supplier"
party_group_type = "supplier_group"
acc_type = "credit_to"
else:
party = "Customer"
party_group_type = "customer_group"
acc_type = "debit_to"
if self.filters.get(party_group_type):
party_groups = get_party_group_with_children(party, self.filters.get(party_group_type))
ptt = ptt.where((voucher_type[party_group_type]).isin(party_groups))
if self.filters.party:
ptt = ptt.where((voucher_type[party.lower()]).isin(self.filters.party))
if self.filters.cost_center:
cost_centers = get_cost_centers_with_children(self.filters.cost_center)
ptt = ptt.where(voucher_type.cost_center.isin(cost_centers))
if self.filters.party_account:
ptt = ptt.where(voucher_type[acc_type] == self.filters.party_account)
return ptt
def get_hierarchical_filters(self, doctype, key):
lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
@@ -1189,6 +1238,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"),
@@ -1320,20 +1370,26 @@ class ReceivablePayableReport:
self.err_journals = [x[0] for x in results] if results else []
def get_customer_group_with_children(customer_groups):
if not isinstance(customer_groups, list):
customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d]
def get_party_group_with_children(party, party_groups):
if party not in ("Customer", "Supplier"):
return []
all_customer_groups = []
for d in customer_groups:
if frappe.db.exists("Customer Group", d):
lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"])
children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_customer_groups += [c.name for c in children]
group_dtype = f"{party} Group"
if not isinstance(party_groups, list):
party_groups = [d.strip() for d in party_groups.strip().split(",") if d]
all_party_groups = []
for d in party_groups:
if frappe.db.exists(group_dtype, d):
lft, rgt = frappe.db.get_value(group_dtype, d, ["lft", "rgt"])
children = frappe.get_all(
group_dtype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, pluck="name"
)
all_party_groups += children
else:
frappe.throw(_("Customer Group: {0} does not exist").format(d))
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
return list(set(all_customer_groups))
return list(set(all_party_groups))
class InitSQLProceduresForAR:
@@ -1355,6 +1411,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},
@@ -1374,6 +1431,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,
@@ -1402,7 +1460,7 @@ class InitSQLProceduresForAR:
begin
if not exists (select name from `{_voucher_balance_name}` where name = `{genkey_function_name}`(ple, false))
then
insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, false), 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 (`{genkey_function_name}`(ple, false), 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;
"""
@@ -1444,7 +1502,7 @@ class InitSQLProceduresForAR:
end if;
insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, true), 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 (`{genkey_function_name}`(ple, true), 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

@@ -779,6 +779,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
"customer_name": "Jane Doe",
"type": "Individual",
"default_currency": "USD",
"customer_group": "Individual",
}
)
.insert()
@@ -1002,6 +1003,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
"customer_name": "Jane Doe",
"type": "Individual",
"default_currency": "USD",
"customer_group": "Individual",
}
)
.insert()
@@ -1139,3 +1141,115 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(report[1]), 1)
row = report[1][0]
self.assertEqual(expected_data_after_payment, [row.voucher_no, row.cost_center, row.outstanding])
def test_payment_terms_template_filters(self):
from erpnext.controllers.accounts_controller import get_payment_terms
payment_term1 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
).insert()
payment_term2 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
).insert()
template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "_Test 50-50",
"terms": [
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term1.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 15,
},
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term2.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 30,
},
],
}
)
template.insert()
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
"based_on_payment_terms": 1,
"payment_terms_template": template.name,
"ageing_based_on": "Posting Date",
}
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.payment_terms_template = template.name
schedule = get_payment_terms(template.name)
si.set("payment_schedule", [])
for row in schedule:
row["due_date"] = add_days(si.posting_date, row.get("credit_days", 0))
si.append("payment_schedule", row)
si.save()
si.submit()
report = execute(filters)
row = report[1][0]
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

@@ -96,7 +96,7 @@ def execute(filters=None):
filters.periodicity, period_list, filters.accumulated_values, company=filters.company
)
chart = get_chart_data(filters, columns, asset, liability, equity, currency)
chart = get_chart_data(filters, period_list, asset, liability, equity, currency)
report_summary, primitive_summary = get_report_summary(
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
@@ -225,18 +225,19 @@ def get_report_summary(
], (net_asset - net_liability + net_equity)
def get_chart_data(filters, columns, asset, liability, equity, currency):
labels = [d.get("label") for d in columns[2:]]
def get_chart_data(filters, chart_columns, asset, liability, equity, currency):
labels = [col.get("label") for col in chart_columns]
asset_data, liability_data, equity_data = [], [], []
for p in columns[2:]:
for col in chart_columns:
key = col.get("key") or col.get("fieldname")
if asset:
asset_data.append(asset[-2].get(p.get("fieldname")))
asset_data.append(asset[-2].get(key))
if liability:
liability_data.append(liability[-2].get(p.get("fieldname")))
liability_data.append(liability[-2].get(key))
if equity:
equity_data.append(equity[-2].get(p.get("fieldname")))
equity_data.append(equity[-2].get(key))
datasets = []
if asset_data:

View File

@@ -4,7 +4,10 @@
import frappe
from frappe import _
from frappe.utils import getdate, nowdate
from frappe.query_builder import Case
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import getdate
from pypika import Order
def execute(filters=None):
@@ -48,17 +51,6 @@ def get_columns():
return columns
def get_conditions(filters):
conditions = ""
if filters.get("from_date"):
conditions += " and posting_date>=%(from_date)s"
if filters.get("to_date"):
conditions += " and posting_date<=%(to_date)s"
return conditions
def get_entries(filters):
entries = []
@@ -73,41 +65,90 @@ def get_entries(filters):
return sorted(
entries,
key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()),
key=lambda k: getdate(k[2]),
)
def get_entries_for_bank_clearance_summary(filters):
entries = []
conditions = get_conditions(filters)
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
journal_entries = frappe.db.sql(
f"""SELECT
"Journal Entry", jv.name, jv.posting_date, jv.cheque_no,
jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit
FROM
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
WHERE
jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {conditions}
order by posting_date DESC, jv.name DESC""",
filters,
as_list=1,
)
journal_entries = (
frappe.qb.from_(jea)
.inner_join(je)
.on(jea.parent == je.name)
.select(
ConstantColumn("Journal Entry").as_("payment_document"),
je.name.as_("payment_entry"),
je.posting_date,
je.cheque_no,
je.clearance_date,
jea.against_account,
jea.debit_in_account_currency - jea.credit_in_account_currency,
)
.where(
(jea.account == filters.account)
& (je.docstatus == 1)
& (je.posting_date >= filters.from_date)
& (je.posting_date <= filters.to_date)
& ((je.is_opening == "No") | (je.is_opening.isnull()))
)
.orderby(je.posting_date, order=Order.desc)
.orderby(je.name, order=Order.desc)
).run(as_list=True)
payment_entries = frappe.db.sql(
f"""SELECT
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount)
FROM
`tabPayment Entry`
WHERE
docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {conditions}
order by posting_date DESC, name DESC""",
filters,
as_list=1,
)
pe = frappe.qb.DocType("Payment Entry")
payment_entries = (
frappe.qb.from_(pe)
.select(
ConstantColumn("Payment Entry").as_("payment_document"),
pe.name.as_("payment_entry"),
pe.posting_date,
pe.reference_no.as_("cheque_no"),
pe.clearance_date,
pe.party.as_("against_account"),
Case()
.when(
(pe.paid_from == filters.account),
((pe.paid_amount * -1) - pe.total_taxes_and_charges),
)
.else_(pe.received_amount),
)
.where((pe.paid_from == filters.account) | (pe.paid_to == filters.account))
.where(
(pe.docstatus == 1)
& (pe.posting_date >= filters.from_date)
& (pe.posting_date <= filters.to_date)
)
.orderby(pe.posting_date, order=Order.desc)
.orderby(pe.name, order=Order.desc)
).run(as_list=True)
entries = journal_entries + payment_entries
pi = frappe.qb.DocType("Purchase Invoice")
purchase_invoices = (
frappe.qb.from_(pi)
.select(
ConstantColumn("Purchase Invoice").as_("payment_document"),
pi.name.as_("payment_entry"),
pi.posting_date,
pi.bill_no.as_("cheque_no"),
pi.clearance_date,
pi.supplier.as_("against_account"),
(pi.paid_amount * -1).as_("amount"),
)
.where(
(pi.docstatus == 1)
& (pi.is_paid == 1)
& (pi.cash_bank_account == filters.account)
& (pi.posting_date >= filters.from_date)
& (pi.posting_date <= filters.to_date)
)
.orderby(pi.posting_date, order=Order.desc)
.orderby(pi.name, order=Order.desc)
).run(as_list=True)
entries = journal_entries + payment_entries + purchase_invoices
return entries

View File

@@ -4,7 +4,11 @@
import frappe
from frappe import _
from frappe.query_builder import Case
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import flt, getdate
from pypika import Order
from erpnext.accounts.utils import get_balance_on
@@ -123,73 +127,143 @@ def get_entries_for_bank_reconciliation_statement(filters):
payment_entries = get_payment_entries(filters)
purchase_invoices = get_purchase_invoices(filters)
pos_entries = []
if filters.include_pos_transactions:
pos_entries = get_pos_entries(filters)
return list(journal_entries) + list(payment_entries) + list(pos_entries)
return list(journal_entries) + list(payment_entries) + list(pos_entries) + list(purchase_invoices)
def get_journal_entries(filters):
return frappe.db.sql(
"""
select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account,
jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency
from
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
where jvd.parent = jv.name and jv.docstatus=1
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No'
and jv.company = %(company)s """,
filters,
as_dict=1,
)
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
return (
frappe.qb.from_(jea)
.join(je)
.on(jea.parent == je.name)
.select(
ConstantColumn("Journal Entry").as_("payment_document"),
je.name.as_("payment_entry"),
je.posting_date,
jea.debit_in_account_currency.as_("debit"),
jea.credit_in_account_currency.as_("credit"),
jea.against_account,
je.cheque_no.as_("reference_no"),
je.cheque_date.as_("ref_date"),
je.clearance_date,
jea.account_currency,
)
.where(
(je.docstatus == 1)
& (jea.account == filters.account)
& (je.posting_date <= filters.report_date)
& (je.clearance_date.isnull() | (je.clearance_date > filters.report_date))
& (je.company == filters.company)
& ((je.is_opening.isnull()) | (je.is_opening == "No"))
)
.orderby(je.posting_date)
.orderby(je.name, order=Order.desc)
).run(as_dict=True)
def get_payment_entries(filters):
return frappe.db.sql(
"""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
if(paid_to=%(account)s, received_amount_after_tax, 0) as debit,
if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date <= %(report_date)s
and ifnull(clearance_date, '4000-01-01') > %(report_date)s
and company = %(company)s
""",
filters,
as_dict=1,
)
pe = frappe.qb.DocType("Payment Entry")
return (
frappe.qb.from_(pe)
.select(
ConstantColumn("Payment Entry").as_("payment_document"),
pe.name.as_("payment_entry"),
pe.reference_no.as_("reference_no"),
pe.reference_date.as_("ref_date"),
Case().when(pe.paid_to == filters.account, pe.received_amount_after_tax).else_(0).as_("debit"),
Case().when(pe.paid_from == filters.account, pe.paid_amount_after_tax).else_(0).as_("credit"),
pe.posting_date,
Coalesce(
pe.party, Case().when(pe.paid_from == filters.account, pe.paid_to).else_(pe.paid_from)
).as_("against_account"),
pe.clearance_date,
(
Case()
.when(pe.paid_to == filters.account, pe.paid_to_account_currency)
.else_(pe.paid_from_account_currency)
).as_("account_currency"),
)
.where(
(pe.docstatus == 1)
& ((pe.paid_from == filters.account) | (pe.paid_to == filters.account))
& (pe.posting_date <= filters.report_date)
& (pe.clearance_date.isnull() | (pe.clearance_date > filters.report_date))
& (pe.company == filters.company)
)
.orderby(pe.posting_date)
.orderby(pe.name, order=Order.desc)
).run(as_dict=True)
def get_purchase_invoices(filters):
pi = frappe.qb.DocType("Purchase Invoice")
acc = frappe.qb.DocType("Account")
return (
frappe.qb.from_(pi)
.inner_join(acc)
.on(pi.cash_bank_account == acc.name)
.select(
ConstantColumn("Purchase Invoice").as_("payment_document"),
pi.name.as_("payment_entry"),
pi.bill_no.as_("reference_no"),
pi.posting_date.as_("ref_date"),
Case().when(pi.paid_amount < 0, pi.paid_amount * -1).else_(0).as_("debit"),
Case().when(pi.paid_amount > 0, pi.paid_amount).else_(0).as_("credit"),
pi.posting_date,
pi.supplier.as_("against_account"),
pi.clearance_date,
acc.account_currency,
)
.where(
(pi.docstatus == 1)
& (pi.is_paid == 1)
& (pi.cash_bank_account == filters.account)
& (pi.posting_date <= filters.report_date)
& (pi.clearance_date.isnull() | (pi.clearance_date > filters.report_date))
& (pi.company == filters.company)
)
.orderby(pi.posting_date)
.orderby(pi.name, order=Order.desc)
).run(as_dict=True)
def get_pos_entries(filters):
return frappe.db.sql(
"""
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.debit_to as against_account, sip.clearance_date,
account.account_currency, 0 as credit
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
where
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
and account.name = sip.account and si.posting_date <= %(report_date)s and
ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s
and si.company = %(company)s
order by
si.posting_date ASC, si.name DESC
""",
filters,
as_dict=1,
)
si = frappe.qb.DocType("Sales Invoice")
si_payment = frappe.qb.DocType("Sales Invoice Payment")
acc = frappe.qb.DocType("Account")
return (
frappe.qb.from_(si_payment)
.join(si)
.on(si_payment.parent == si.name)
.join(acc)
.on(si_payment.account == acc.name)
.select(
ConstantColumn("Sales Invoice").as_("payment_document"),
si.name.as_("payment_entry"),
si_payment.amount.as_("debit"),
si.posting_date,
si.debit_to.as_("against_account"),
si_payment.clearance_date,
acc.account_currency,
ConstantColumn(0).as_("credit"),
)
.where(
(si_payment.account == filters.account)
& (si.docstatus == 1)
& (si.posting_date <= filters.report_date)
& (si_payment.clearance_date.isnull() | (si_payment.clearance_date > filters.report_date))
& (si.company == filters.company)
)
.orderby(si.posting_date)
.orderby(si_payment.name, order=Order.desc)
).run(as_dict=True)
def get_amounts_not_reflected_in_system(filters):
@@ -205,30 +279,66 @@ def get_amounts_not_reflected_in_system(filters):
def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters):
je_amount = frappe.db.sql(
"""
select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency)
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No' """,
filters,
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
je_amount = (
frappe.qb.from_(jea)
.inner_join(je)
.on(jea.parent == je.name)
.select(
Sum(jea.debit_in_account_currency - jea.credit_in_account_currency).as_("amount"),
)
.where(
(je.docstatus == 1)
& (jea.account == filters.account)
& (je.posting_date > filters.report_date)
& (je.clearance_date <= filters.report_date)
& (je.company == filters.company)
& ((je.is_opening.isnull()) | (je.is_opening == "No"))
)
.run(as_dict=True)
)
je_amount = flt(je_amount[0].amount) if je_amount else 0.0
je_amount = flt(je_amount[0][0]) if je_amount else 0.0
pe_amount = frappe.db.sql(
"""
select sum(if(paid_from=%(account)s, paid_amount, received_amount))
from `tabPayment Entry`
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date > %(report_date)s and clearance_date <= %(report_date)s""",
filters,
pe = frappe.qb.DocType("Payment Entry")
pe_amount = (
frappe.qb.from_(pe)
.select(
Sum(Case().when(pe.paid_from == filters.account, pe.paid_amount).else_(pe.received_amount)).as_(
"amount"
),
)
.where(
((pe.paid_from == filters.account) | (pe.paid_to == filters.account))
& (pe.docstatus == 1)
& (pe.posting_date > filters.report_date)
& (pe.clearance_date <= filters.report_date)
& (pe.company == filters.company)
)
.run(as_dict=True)
)
pe_amount = flt(pe_amount[0].amount) if pe_amount else 0.0
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
pi = frappe.qb.DocType("Purchase Invoice")
pi_amount = (
frappe.qb.from_(pi)
.select(
Sum(pi.paid_amount).as_("amount"),
)
.where(
(pi.docstatus == 1)
& (pi.is_paid == 1)
& (pi.cash_bank_account == filters.account)
& (pi.posting_date > filters.report_date)
& (pi.clearance_date <= filters.report_date)
& (pi.company == filters.company)
)
).run(as_dict=True)
return je_amount + pe_amount
pi_amount = flt(pi_amount[0].amount) if pi_amount else 0.0
return je_amount + pe_amount + pi_amount
def get_balance_row(label, amount, account_currency):

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.utils import flt, formatdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges
@@ -15,6 +16,8 @@ def execute(filters=None):
if not filters:
filters = {}
validate_filters(filters)
columns = get_columns(filters)
if filters.get("budget_against_filter"):
dimensions = filters.get("budget_against_filter")
@@ -35,6 +38,21 @@ def execute(filters=None):
return columns, data, None, chart
def validate_filters(filters):
validate_budget_dimensions(filters)
def validate_budget_dimensions(filters):
dimensions = [d.get("document_type") for d in get_dimensions(with_cost_center_and_project=True)[0]]
if filters.get("budget_against") and filters.get("budget_against") not in dimensions:
frappe.throw(
title=_("Invalid Accounting Dimension"),
msg=_("{0} is not a valid Accounting Dimension.").format(
frappe.bold(filters.get("budget_against"))
),
)
def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
for account, monthwise_data in dimension_items.items():
row = [dimension, account]

View File

@@ -139,7 +139,7 @@ def execute(filters=None):
True,
)
chart = get_chart_data(columns, data, company_currency)
chart = get_chart_data(period_list, data, company_currency)
report_summary = get_report_summary(summary_data, company_currency)
@@ -411,13 +411,12 @@ def get_report_summary(summary_data, currency):
return report_summary
def get_chart_data(columns, data, currency):
labels = [d.get("label") for d in columns[2:]]
print(data)
def get_chart_data(period_list, data, currency):
labels = [period.get("label") for period in period_list]
datasets = [
{
"name": section.get("section").replace("'", ""),
"values": [section.get(d.get("fieldname")) for d in columns[2:]],
"values": [section.get(period.get("key")) for period in period_list],
}
for section in data
if section.get("parent_section") is None and section.get("currency")

View File

@@ -48,22 +48,25 @@ def execute(filters=None):
return columns, data, message, chart
fiscal_year = get_fiscal_year_data(filters.get("from_fiscal_year"), filters.get("to_fiscal_year"))
companies_column, companies = get_companies(filters)
columns = get_columns(companies_column, filters)
company_list, companies = get_companies(filters)
company_columns = get_company_columns(company_list, filters)
columns = get_columns(company_columns)
if filters.get("report") == "Balance Sheet":
data, message, chart, report_summary = get_balance_sheet_data(
fiscal_year, companies, columns, filters
fiscal_year, companies, company_columns, filters
)
elif filters.get("report") == "Profit and Loss Statement":
data, message, chart, report_summary = get_profit_loss_data(fiscal_year, companies, columns, filters)
data, message, chart, report_summary = get_profit_loss_data(
fiscal_year, companies, company_columns, filters
)
else:
data, report_summary = get_cash_flow_data(fiscal_year, companies, filters)
return columns, data, message, chart, report_summary
def get_balance_sheet_data(fiscal_year, companies, columns, filters):
def get_balance_sheet_data(fiscal_year, companies, company_columns, filters):
asset = get_data(companies, "Asset", "Debit", fiscal_year, filters=filters)
liability = get_data(companies, "Liability", "Credit", fiscal_year, filters=filters)
@@ -116,7 +119,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
True,
)
chart = get_chart_data(filters, columns, asset, liability, equity, company_currency)
chart = get_chart_data(filters, company_columns, asset, liability, equity, company_currency)
return data, message, chart, report_summary
@@ -164,7 +167,7 @@ def get_root_account_name(root_type, company):
return root_account[0][0]
def get_profit_loss_data(fiscal_year, companies, columns, filters):
def get_profit_loss_data(fiscal_year, companies, company_columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters)
@@ -174,7 +177,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
if net_profit_loss:
data.append(net_profit_loss)
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss, company_currency)
chart = get_pl_chart_data(filters, company_columns, income, expense, net_profit_loss, company_currency)
report_summary, primitive_summary = get_pl_summary(
companies, "", income, expense, net_profit_loss, company_currency, filters, True
@@ -279,7 +282,30 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters):
return data
def get_columns(companies, filters):
def get_company_columns(companies, filters):
company_columns = []
for company in companies:
apply_currency_formatter = 1 if not filters.presentation_currency else 0
currency = filters.presentation_currency
if not currency:
currency = erpnext.get_company_currency(company)
company_columns.append(
{
"fieldname": company,
"label": f"{company} ({currency})",
"fieldtype": "Currency",
"options": "currency",
"width": 150,
"apply_currency_formatter": apply_currency_formatter,
"company_name": company,
}
)
return company_columns
def get_columns(company_columns):
columns = [
{
"fieldname": "account",
@@ -297,23 +323,7 @@ def get_columns(companies, filters):
},
]
for company in companies:
apply_currency_formatter = 1 if not filters.presentation_currency else 0
currency = filters.presentation_currency
if not currency:
currency = erpnext.get_company_currency(company)
columns.append(
{
"fieldname": company,
"label": f"{company} ({currency})",
"fieldtype": "Currency",
"options": "currency",
"width": 150,
"apply_currency_formatter": apply_currency_formatter,
"company_name": company,
}
)
columns.extend(company_columns)
return columns

View File

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

View File

@@ -37,6 +37,20 @@ function get_filters() {
});
},
},
{
fieldname: "party_type",
label: __("Party Type"),
fieldtype: "Link",
options: "Party Type",
width: 100,
},
{
fieldname: "party",
label: __("Party"),
fieldtype: "Dynamic Link",
options: "party_type",
width: 100,
},
{
fieldname: "voucher_no",
label: __("Voucher No"),

View File

@@ -68,6 +68,12 @@ class General_Payment_Ledger_Comparison:
if self.filters.period_end_date:
filter_criterion.append(gle.posting_date.lte(self.filters.period_end_date))
if self.filters.party_type:
filter_criterion.append(gle.party_type.eq(self.filters.party_type))
if self.filters.party:
filter_criterion.append(gle.party.eq(self.filters.party))
if acc_type == "receivable":
outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding")
else:
@@ -111,6 +117,12 @@ class General_Payment_Ledger_Comparison:
if self.filters.period_end_date:
filter_criterion.append(ple.posting_date.lte(self.filters.period_end_date))
if self.filters.party_type:
filter_criterion.append(ple.party_type.eq(self.filters.party_type))
if self.filters.party:
filter_criterion.append(ple.party.eq(self.filters.party))
self.account_types[acc_type].ple = (
qb.from_(ple)
.select(

View File

@@ -5,15 +5,16 @@ from collections import OrderedDict
import frappe
from frappe import _, qb, scrub
from frappe.query_builder import Order
from frappe.query_builder import Case, Order
from frappe.query_builder.functions import Coalesce
from frappe.utils import cint, flt, formatdate
from pypika.terms import ExistsCriterion
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
from erpnext.controllers.queries import get_match_cond
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
from erpnext.stock.utils import get_incoming_rate
@@ -176,7 +177,9 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
column_names = get_column_names()
# to display item as Item Code: Item Name
columns[0] = "Sales Invoice:Link/Item:300"
columns[0]["fieldname"] = "sales_invoice"
columns[0]["options"] = "Item"
columns[0]["width"] = 300
# removing Item Code and Item Name columns
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
@@ -203,7 +206,11 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
total_gross_profit = flt(
total_base_amount + abs(total_buying_amount)
if total_buying_amount < 0
else total_base_amount - total_buying_amount,
)
data.append(
frappe._dict(
{
@@ -215,7 +222,7 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_%": flt(
(total_gross_profit / total_base_amount) * 100.0,
(total_gross_profit / abs(total_base_amount)) * 100.0,
cint(frappe.db.get_default("currency_precision")) or 3,
)
if total_base_amount
@@ -248,9 +255,13 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
total_gross_profit = flt(
total_base_amount + abs(total_buying_amount)
if total_buying_amount < 0
else total_base_amount - total_buying_amount,
)
currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
gross_profit_percent = (total_gross_profit / total_base_amount * 100.0) if total_base_amount else 0
gross_profit_percent = (total_gross_profit / abs(total_base_amount) * 100.0) if total_base_amount else 0
total_row = {
group_columns[0]: "Total",
@@ -581,10 +592,15 @@ class GrossProfitGenerator:
base_amount += row.base_amount
# calculate gross profit
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
row.gross_profit = flt(
row.base_amount + abs(row.buying_amount)
if row.buying_amount < 0
else row.base_amount - row.buying_amount,
self.currency_precision,
)
if row.base_amount:
row.gross_profit_percent = flt(
(row.gross_profit / row.base_amount) * 100.0,
(row.gross_profit / abs(row.base_amount)) * 100.0,
self.currency_precision,
)
else:
@@ -633,7 +649,7 @@ class GrossProfitGenerator:
new_row = row
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion)
else:
new_row.qty += flt(row.qty)
new_row.qty = flt((new_row.qty + row.qty), self.float_precision)
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion, True)
new_row = self.set_average_rate(new_row)
@@ -643,11 +659,17 @@ class GrossProfitGenerator:
if i == 0:
new_row = row
else:
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
new_row.qty = flt((new_row.qty + row.qty), self.float_precision)
new_row.buying_amount = flt(
(new_row.buying_amount + row.buying_amount), self.currency_precision
)
new_row.base_amount = flt(
(new_row.base_amount + row.base_amount), self.currency_precision
)
if self.filters.get("group_by") == "Sales Person":
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
new_row.allocated_amount = flt(
(new_row.allocated_amount + row.allocated_amount), self.currency_precision
)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
@@ -673,9 +695,14 @@ class GrossProfitGenerator:
return new_row
def set_average_gross_profit(self, new_row):
new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount, self.currency_precision)
new_row.gross_profit = flt(
new_row.base_amount + abs(new_row.buying_amount)
if new_row.buying_amount < 0
else new_row.base_amount - new_row.buying_amount,
self.currency_precision,
)
new_row.gross_profit_percent = (
flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision)
flt(((new_row.gross_profit / abs(new_row.base_amount)) * 100.0), self.currency_precision)
if new_row.base_amount
else 0
)
@@ -851,129 +878,173 @@ class GrossProfitGenerator:
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
def load_invoice_items(self):
conditions = ""
if self.filters.company:
conditions += " and `tabSales Invoice`.company = %(company)s"
if self.filters.from_date:
conditions += " and posting_date >= %(from_date)s"
if self.filters.to_date:
conditions += " and posting_date <= %(to_date)s"
self.si_list = []
SalesInvoice = frappe.qb.DocType("Sales Invoice")
base_query = self.prepare_invoice_query()
if self.filters.include_returned_invoices:
conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
invoice_query = base_query.where(
(SalesInvoice.is_return == 0)
| ((SalesInvoice.is_return == 1) & SalesInvoice.return_against.isnull())
)
else:
conditions += " and is_return = 0"
invoice_query = base_query.where(SalesInvoice.is_return == 0)
if self.filters.item_group:
conditions += f" and {get_item_group_condition(self.filters.item_group)}"
self.si_list += invoice_query.run(as_dict=True)
self.prepare_vouchers_to_ignore()
if self.filters.sales_person:
conditions += """
and exists(select 1
from `tabSales Team` st
where st.parent = `tabSales Invoice`.name
and st.sales_person = %(sales_person)s)
"""
ret_invoice_query = base_query.where(
(SalesInvoice.is_return == 1) & SalesInvoice.return_against.isnotnull()
)
if self.vouchers_to_ignore:
ret_invoice_query = ret_invoice_query.where(
SalesInvoice.return_against.notin(self.vouchers_to_ignore)
)
self.si_list += ret_invoice_query.run(as_dict=True)
def prepare_invoice_query(self):
SalesInvoice = frappe.qb.DocType("Sales Invoice")
SalesInvoiceItem = frappe.qb.DocType("Sales Invoice Item")
Item = frappe.qb.DocType("Item")
SalesTeam = frappe.qb.DocType("Sales Team")
PaymentSchedule = frappe.qb.DocType("Payment Schedule")
query = (
frappe.qb.from_(SalesInvoice)
.join(SalesInvoiceItem)
.on(SalesInvoiceItem.parent == SalesInvoice.name)
.join(Item)
.on(Item.name == SalesInvoiceItem.item_code)
.where((SalesInvoice.docstatus == 1) & (SalesInvoice.is_opening != "Yes"))
)
query = self.apply_common_filters(query, SalesInvoice, SalesInvoiceItem, SalesTeam, Item)
query = query.select(
SalesInvoiceItem.parenttype,
SalesInvoiceItem.parent,
SalesInvoice.posting_date,
SalesInvoice.posting_time,
SalesInvoice.project,
SalesInvoice.update_stock,
SalesInvoice.customer,
SalesInvoice.customer_group,
SalesInvoice.customer_name,
SalesInvoice.territory,
SalesInvoiceItem.item_code,
SalesInvoice.base_net_total.as_("invoice_base_net_total"),
SalesInvoiceItem.item_name,
SalesInvoiceItem.description,
SalesInvoiceItem.warehouse,
SalesInvoiceItem.item_group,
SalesInvoiceItem.brand,
SalesInvoiceItem.so_detail,
SalesInvoiceItem.sales_order,
SalesInvoiceItem.dn_detail,
SalesInvoiceItem.delivery_note,
SalesInvoiceItem.stock_qty.as_("qty"),
SalesInvoiceItem.base_net_rate,
SalesInvoiceItem.base_net_amount,
SalesInvoiceItem.name.as_("item_row"),
SalesInvoice.is_return,
SalesInvoiceItem.cost_center,
SalesInvoiceItem.serial_and_batch_bundle,
)
if self.filters.group_by == "Sales Person":
sales_person_cols = """, sales.sales_person,
sales.allocated_percentage * `tabSales Invoice Item`.base_net_amount / 100 as allocated_amount,
sales.incentives
"""
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
else:
sales_person_cols = ""
sales_team_table = ""
query = query.select(
SalesTeam.sales_person,
(SalesTeam.allocated_percentage * SalesInvoiceItem.base_net_amount / 100).as_(
"allocated_amount"
),
SalesTeam.incentives,
)
query = query.left_join(SalesTeam).on(SalesTeam.parent == SalesInvoice.name)
if self.filters.group_by == "Payment Term":
payment_term_cols = """,if(`tabSales Invoice`.is_return = 1,
'{}',
coalesce(schedule.payment_term, '{}')) as payment_term,
schedule.invoice_portion,
schedule.payment_amount """.format(_("Sales Return"), _("No Terms"))
payment_term_table = """ left join `tabPayment Schedule` schedule on schedule.parent = `tabSales Invoice`.name and
`tabSales Invoice`.is_return = 0 """
else:
payment_term_cols = ""
payment_term_table = ""
query = query.select(
Case()
.when(SalesInvoice.is_return == 1, _("Sales Return"))
.else_(Coalesce(PaymentSchedule.payment_term, _("No Terms")))
.as_("payment_term"),
PaymentSchedule.invoice_portion,
PaymentSchedule.payment_amount,
)
if self.filters.get("sales_invoice"):
conditions += " and `tabSales Invoice`.name = %(sales_invoice)s"
query = query.left_join(PaymentSchedule).on(
(PaymentSchedule.parent == SalesInvoice.name) & (SalesInvoice.is_return == 0)
)
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
query = query.orderby(SalesInvoice.posting_date, order=Order.desc).orderby(
SalesInvoice.posting_time, order=Order.desc
)
if self.filters.get("cost_center"):
return query
def apply_common_filters(self, query, SalesInvoice, SalesInvoiceItem, SalesTeam, Item):
if self.filters.company:
query = query.where(SalesInvoice.company == self.filters.company)
if self.filters.from_date:
query = query.where(SalesInvoice.posting_date >= self.filters.from_date)
if self.filters.to_date:
query = query.where(SalesInvoice.posting_date <= self.filters.to_date)
if self.filters.item_group:
query = query.where(get_item_group_condition(self.filters.item_group, Item))
if self.filters.sales_person:
query = query.where(
ExistsCriterion(
frappe.qb.from_(SalesTeam)
.select(1)
.where(
(SalesTeam.parent == SalesInvoice.name)
& (SalesTeam.sales_person == self.filters.sales_person)
)
)
)
if self.filters.sales_invoice:
query = query.where(SalesInvoice.name == self.filters.sales_invoice)
if self.filters.item_code:
query = query.where(SalesInvoiceItem.item_code == self.filters.item_code)
if self.filters.cost_center:
self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center"))
self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center)
conditions += " and `tabSales Invoice Item`.cost_center in %(cost_center)s"
query = query.where(SalesInvoiceItem.cost_center.isin(self.filters.cost_center))
if self.filters.get("project"):
if self.filters.project:
self.filters.project = frappe.parse_json(self.filters.get("project"))
conditions += " and `tabSales Invoice Item`.project in %(project)s"
query = query.where(SalesInvoiceItem.project.isin(self.filters.project))
accounting_dimensions = get_accounting_dimensions(as_list=False)
if accounting_dimensions:
for dimension in accounting_dimensions:
if self.filters.get(dimension.fieldname):
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
self.filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, self.filters.get(dimension.fieldname)
)
conditions += (
f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
)
else:
conditions += (
f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
)
for dim in get_accounting_dimensions(as_list=False) or []:
if self.filters.get(dim.fieldname):
if frappe.get_cached_value("DocType", dim.document_type, "is_tree"):
self.filters[dim.fieldname] = get_dimension_with_children(
dim.document_type, self.filters.get(dim.fieldname)
)
query = query.where(SalesInvoiceItem[dim.fieldname].isin(self.filters[dim.fieldname]))
if self.filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
if self.filters.warehouse:
lft, rgt = frappe.db.get_value("Warehouse", self.filters.warehouse, ["lft", "rgt"])
WH = frappe.qb.DocType("Warehouse")
query = query.where(
SalesInvoiceItem.warehouse.isin(
frappe.qb.from_(WH).select(WH.name).where((WH.lft >= lft) & (WH.rgt <= rgt))
)
)
if warehouse_details:
conditions += f" and `tabSales Invoice Item`.warehouse in (select name from `tabWarehouse` wh where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
self.si_list = frappe.db.sql(
"""
select
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.customer_name,
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.serial_and_batch_bundle
{sales_person_cols}
{payment_term_cols}
from
`tabSales Invoice` inner join `tabSales Invoice Item`
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
join `tabItem` item on item.name = `tabSales Invoice Item`.item_code
{sales_team_table}
{payment_term_table}
where
`tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond}
order by
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""".format(
conditions=conditions,
sales_person_cols=sales_person_cols,
sales_team_table=sales_team_table,
payment_term_cols=payment_term_cols,
payment_term_table=payment_term_table,
match_cond=get_match_cond("Sales Invoice"),
),
self.filters,
as_dict=1,
)
return query
def prepare_vouchers_to_ignore(self):
self.vouchers_to_ignore = tuple(row["parent"] for row in self.si_list)
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})

View File

@@ -82,6 +82,7 @@ class TestGrossProfit(FrappeTestCase):
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.type = "Individual"
customer.customer_group = "Individual"
customer.save()
self.customer = customer.name
@@ -439,6 +440,7 @@ class TestGrossProfit(FrappeTestCase):
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
sinv.is_return = 1
sinv.items[0].allow_zero_valuation_rate = 1
sinv = sinv.save().submit()
filters = frappe._dict(
@@ -465,7 +467,7 @@ class TestGrossProfit(FrappeTestCase):
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
"gross_profit_%": -100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
@@ -642,21 +644,24 @@ class TestGrossProfit(FrappeTestCase):
def test_profit_for_later_period_return(self):
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
sales_inv_date = month_start_date
return_inv_date = add_days(month_end_date, 1)
# create sales invoice on month start date
sinv = self.create_sales_invoice(qty=1, rate=100, do_not_save=True, do_not_submit=True)
sinv.set_posting_time = 1
sinv.posting_date = month_start_date
sinv.posting_date = sales_inv_date
sinv.save().submit()
# create credit note on next month start date
cr_note = make_sales_return(sinv.name)
cr_note.set_posting_time = 1
cr_note.posting_date = add_days(month_end_date, 1)
cr_note.posting_date = return_inv_date
cr_note.save().submit()
# apply filters for invoiced period
filters = frappe._dict(
company=self.company, from_date=month_start_date, to_date=month_end_date, group_by="Invoice"
company=self.company, from_date=month_start_date, to_date=month_start_date, group_by="Invoice"
)
_, data = execute(filters=filters)
@@ -668,7 +673,7 @@ class TestGrossProfit(FrappeTestCase):
self.assertEqual(total.get("gross_profit_%"), 100.0)
# extend filters upto returned period
filters.update(to_date=add_days(month_end_date, 1))
filters.update({"to_date": return_inv_date})
_, data = execute(filters=filters)
total = data[-1]
@@ -677,3 +682,63 @@ class TestGrossProfit(FrappeTestCase):
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 0.0)
self.assertEqual(total.get("gross_profit_%"), 0.0)
# apply filters only on returned period
filters.update({"from_date": return_inv_date, "to_date": return_inv_date})
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, -100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, -100.0)
self.assertEqual(total.get("gross_profit_%"), -100.0)
def test_sales_person_wise_gross_profit(self):
sales_person = make_sales_person("_Test Sales Person")
posting_date = get_first_day(nowdate())
qty = 10
rate = 100
sinv = self.create_sales_invoice(qty=qty, rate=rate, do_not_save=True, do_not_submit=True)
sinv.set_posting_time = 1
sinv.posting_date = posting_date
sinv.append(
"sales_team",
{
"sales_person": sales_person.name,
"allocated_percentage": 100,
"allocated_amount": 1000.0,
"commission_rate": 5,
"incentives": 5,
},
)
sinv.save().submit()
filters = frappe._dict(
company=self.company, from_date=posting_date, to_date=posting_date, group_by="Sales Person"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total[5], 1000.0)
self.assertEqual(total[6], 0.0)
self.assertEqual(total[7], 1000.0)
self.assertEqual(total[8], 100.0)
def make_sales_person(sales_person_name="_Test Sales Person"):
if not frappe.db.exists("Sales Person", {"sales_person_name": sales_person_name}):
sales_person_doc = frappe.get_doc(
{
"doctype": "Sales Person",
"is_group": 0,
"parent_sales_person": "Sales Team",
"sales_person_name": sales_person_name,
}
).insert(ignore_permissions=True)
else:
sales_person_doc = frappe.get_doc("Sales Person", {"sales_person_name": sales_person_name})
return sales_person_doc

View File

@@ -31,6 +31,7 @@ def _execute(filters=None, additional_table_columns=None):
item_list = get_items(filters, additional_table_columns)
aii_account_map = get_aii_accounts()
default_taxes = {}
if item_list:
itemised_tax, tax_columns = get_tax_accounts(
item_list,
@@ -39,6 +40,9 @@ def _execute(filters=None, additional_table_columns=None):
doctype="Purchase Invoice",
tax_doctype="Purchase Taxes and Charges",
)
for tax in tax_columns:
default_taxes[f"{tax}_rate"] = 0
default_taxes[f"{tax}_amount"] = 0
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@@ -85,6 +89,8 @@ def _execute(filters=None, additional_table_columns=None):
}
total_tax = 0
row.update(default_taxes.copy())
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(

View File

@@ -29,8 +29,12 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
item_list = get_items(filters, additional_table_columns, additional_conditions)
default_taxes = {}
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
for tax in tax_columns:
default_taxes[f"{tax}_rate"] = 0
default_taxes[f"{tax}_amount"] = 0
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
so_dn_map = get_delivery_notes_against_sales_order(item_list)
@@ -88,6 +92,8 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
total_tax = 0
total_other_charges = 0
row.update(default_taxes.copy())
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(

View File

@@ -17,9 +17,11 @@ class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_sales_invoice(self, do_not_submit=False):
def create_sales_invoice(self, item=None, taxes=None, do_not_submit=False):
si = create_sales_invoice(
item=self.item,
item=item or self.item,
item_name=item or self.item,
description=item or self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
@@ -30,6 +32,19 @@ class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
price_list_rate=100,
do_not_save=1,
)
for tax in taxes or []:
si.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": tax["account_head"],
"cost_center": self.cost_center,
"description": tax["description"],
"rate": tax["rate"],
},
)
si = si.save()
if not do_not_submit:
si = si.submit()
@@ -63,3 +78,50 @@ class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
report_output = {k: v for k, v in report[1][0].items() if k in expected_result}
self.assertDictEqual(report_output, expected_result)
def test_grouped_report_handles_different_tax_descriptions(self):
self.create_item(item_name="_Test Item Tax Description A")
first_item = self.item
self.create_item(item_name="_Test Item Tax Description B")
second_item = self.item
first_tax_description = "Tax Description A"
second_tax_description = "Tax Description B"
first_tax_amount_field = f"{frappe.scrub(first_tax_description)}_amount"
second_tax_amount_field = f"{frappe.scrub(second_tax_description)}_amount"
self.create_sales_invoice(
item=first_item,
taxes=[
{
"account_head": "_Test Account VAT - _TC",
"description": first_tax_description,
"rate": 5,
}
],
)
self.create_sales_invoice(
item=second_item,
taxes=[
{
"account_head": "_Test Account Service Tax - _TC",
"description": second_tax_description,
"rate": 2,
}
],
)
filters = frappe._dict(
{
"from_date": today(),
"to_date": today(),
"company": self.company,
"group_by": "Customer",
}
)
_, data, _, _, _, _ = execute(filters)
grand_total_row = next(row for row in data if row.get("bold") and row.get("item_code") == "Total")
self.assertEqual(grand_total_row[first_tax_amount_field], 5.0)
self.assertEqual(grand_total_row[second_tax_amount_field], 2.0)

View File

@@ -22,7 +22,7 @@ frappe.query_reports["Profit and Loss Statement"]["filters"].push(
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
default: 0,
},
{
fieldname: "include_default_book_entries",

View File

@@ -62,7 +62,7 @@ def execute(filters=None):
currency = filters.presentation_currency or frappe.get_cached_value(
"Company", filters.company, "default_currency"
)
chart = get_chart_data(filters, columns, income, expense, net_profit_loss, currency)
chart = get_chart_data(filters, period_list, income, expense, net_profit_loss, currency)
report_summary, primitive_summary = get_report_summary(
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
@@ -158,18 +158,20 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co
return net_profit_loss
def get_chart_data(filters, columns, income, expense, net_profit_loss, currency):
labels = [d.get("label") for d in columns[2:]]
def get_chart_data(filters, chart_columns, income, expense, net_profit_loss, currency):
labels = [col.get("label") for col in chart_columns]
income_data, expense_data, net_profit = [], [], []
for p in columns[2:]:
for col in chart_columns:
key = col.get("key") or col.get("fieldname")
if income:
income_data.append(income[-2].get(p.get("fieldname")))
income_data.append(income[-2].get(key))
if expense:
expense_data.append(expense[-2].get(p.get("fieldname")))
expense_data.append(expense[-2].get(key))
if net_profit_loss:
net_profit.append(net_profit_loss.get(p.get("fieldname")))
net_profit.append(net_profit_loss.get(key))
datasets = []
if income_data:

View File

@@ -3,7 +3,8 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.query_builder import Criterion
from frappe.utils import cstr, flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
@@ -33,11 +34,19 @@ def execute(filters=None):
def get_accounts_data(based_on, company):
if based_on == "Cost Center":
return frappe.db.sql(
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
from `tabCost Center` where company=%s order by name""",
company,
as_dict=True,
cc = qb.DocType("Cost Center")
return (
qb.from_(cc)
.select(
cc.name,
cc.parent_cost_center.as_("parent_account"),
cc.cost_center_name.as_("account_name"),
cc.lft,
cc.rgt,
)
.where(cc.company.eq(company))
.orderby(cc.name)
.run(as_dict=True)
)
elif based_on == "Project":
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
@@ -206,27 +215,38 @@ def set_gl_entries_by_account(
company, from_date, to_date, based_on, gl_entries_by_account, ignore_closing_entries=False
):
"""Returns a dict like { "account": [gl entries], ... }"""
additional_conditions = []
gl = qb.DocType("GL Entry")
acc = qb.DocType("Account")
conditions = []
conditions.append(gl.company.eq(company))
conditions.append(gl[based_on].notnull())
conditions.append(gl.is_cancelled.eq(0))
if from_date and to_date:
conditions.append(gl.posting_date.between(from_date, to_date))
elif from_date and not to_date:
conditions.append(gl.posting_date.gte(from_date))
elif not from_date and to_date:
conditions.append(gl.posting_date.lte(to_date))
if ignore_closing_entries:
additional_conditions.append("and voucher_type !='Period Closing Voucher'")
conditions.append(gl.voucher_type.ne("Period Closing Voucher"))
if from_date:
additional_conditions.append("and posting_date >= %(from_date)s")
gl_entries = frappe.db.sql(
"""select posting_date, {based_on} as based_on, debit, credit,
is_opening, (select root_type from `tabAccount` where name = account) as type
from `tabGL Entry` where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and {based_on} is not null
and is_cancelled = 0
order by {based_on}, posting_date""".format(
additional_conditions="\n".join(additional_conditions), based_on=based_on
),
{"company": company, "from_date": from_date, "to_date": to_date},
as_dict=True,
root_subquery = qb.from_(acc).select(acc.root_type).where(acc.name.eq(gl.account))
gl_entries = (
qb.from_(gl)
.select(
gl.posting_date,
gl[based_on].as_("based_on"),
gl.debit,
gl.credit,
gl.is_opening,
root_subquery.as_("type"),
)
.where(Criterion.all(conditions))
.orderby(gl[based_on], gl.posting_date)
.run(as_dict=True)
)
for entry in gl_entries:

View File

@@ -7,10 +7,10 @@
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"idx": 4,
"is_standard": "Yes",
"letterhead": null,
"modified": "2025-11-05 11:55:49.950442",
"letter_head": null,
"modified": "2026-03-13 17:35:39.703838",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Trends",

View File

@@ -501,7 +501,7 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts, inc
else sum(base_tax_amount_after_discount_amount) * -1 end as tax_amount
from `tabPurchase Taxes and Charges`
where parent in (%s) and category in ('Total', 'Valuation and Total')
and base_tax_amount_after_discount_amount != 0
and base_tax_amount_after_discount_amount != 0 and parenttype='Purchase Invoice'
group by parent, account_head, add_deduct_tax
"""
% ", ".join(["%s"] * len(invoice_list)),

View File

@@ -6,6 +6,7 @@ from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months, today
from erpnext.accounts.report.purchase_register.purchase_register import execute
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
class TestPurchaseRegister(FrappeTestCase):
@@ -26,6 +27,52 @@ class TestPurchaseRegister(FrappeTestCase):
self.assertEqual(first_row.total_tax, 100)
self.assertEqual(first_row.grand_total, 1100)
def test_purchase_register_ignores_tax_rows_from_other_doctype(self):
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
filters = frappe._dict(company="_Test Company 6", from_date=add_months(today(), -1), to_date=today())
pi = make_purchase_invoice()
# Real workflow setup: create a Purchase Receipt tax row in the same shared child table.
pr = make_purchase_receipt(
company="_Test Company 6",
supplier="_Test Supplier",
item="_Test Item",
warehouse="_Test Warehouse - _TC6",
cost_center="_Test Cost Center - _TC6",
do_not_save=1,
do_not_submit=1,
qty=1,
rate=1000,
)
pr.append(
"taxes",
{
"account_head": "GST - _TC6",
"cost_center": "_Test Cost Center - _TC6",
"add_deduct_tax": "Add",
"category": "Valuation and Total",
"charge_type": "Actual",
"description": "PR Tax",
"tax_amount": 100.0,
"rate": 100,
},
)
pr.insert()
pr.submit()
# Mimic custom naming collision across doctypes (same parent value in shared child table).
frappe.rename_doc("Purchase Receipt", pr.name, pi.name, force=True)
report_results = execute(filters)
first_row = frappe._dict(report_results[1][0])
self.assertEqual(first_row.voucher_no, pi.name)
self.assertEqual(first_row.total_tax, 100)
self.assertEqual(first_row.grand_total, 1100)
def test_purchase_register_ledger_view(self):
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")

View File

@@ -9,8 +9,8 @@
"filters": [],
"idx": 3,
"is_standard": "Yes",
"letterhead": null,
"modified": "2025-11-05 11:55:50.070651",
"letter_head": null,
"modified": "2026-03-13 17:36:13.725601",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Trends",

View File

@@ -5,6 +5,7 @@ from frappe.utils import getdate, today
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.sales_register.sales_register import execute
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
@@ -75,6 +76,43 @@ class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
report_output = {k: v for k, v in res[0].items() if k in expected_result}
self.assertDictEqual(report_output, expected_result)
def test_sales_register_ignores_tax_rows_from_other_doctype(self):
si = self.create_sales_invoice(rate=98)
# Real workflow setup: create a Sales Order with taxes in the shared child table.
so = make_sales_order(
item=self.item,
company=self.company,
customer=self.customer,
rate=77,
do_not_save=1,
do_not_submit=1,
)
so.append(
"taxes",
{
"charge_type": "Actual",
"account_head": self.income_account,
"description": "SO Tax",
"tax_amount": 55.0,
},
)
so.insert()
so.submit()
# Mimic custom naming collision across doctypes (same parent value in shared child table).
frappe.rename_doc("Sales Order", so.name, si.name, force=True)
filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company})
report = execute(filters)
res = [x for x in report[1] if x.get("voucher_no") == si.name]
self.assertEqual(len(res), 1)
result = frappe._dict(res[0])
self.assertEqual(result.net_total, 98.0)
self.assertEqual(result.tax_total, 0)
self.assertEqual(result.grand_total, 98.0)
def test_journal_with_cost_center_filter(self):
je1 = frappe.get_doc(
{

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.utils import flt, getdate
from pypika import Tuple
from erpnext.accounts.utils import get_currency_precision
@@ -19,18 +20,14 @@ def execute(filters=None):
validate_filters(filters)
(
tds_docs,
tds_accounts,
tax_category_map,
journal_entry_party_map,
net_total_map,
) = get_tds_docs(filters)
columns = get_columns(filters)
res = get_result(
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
)
res = get_result(filters, tds_accounts, tax_category_map, net_total_map)
return columns, res
@@ -41,27 +38,23 @@ def validate_filters(filters):
frappe.throw(_("From Date must be before To Date"))
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map):
party_map = get_party_pan_map(filters.get("party_type"))
def get_result(filters, tds_accounts, tax_category_map, net_total_map):
party_names = {v.party for v in net_total_map.values() if v.party}
party_map = get_party_pan_map(filters.get("party_type"), party_names)
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs)
gle_map = get_gle_map(net_total_map)
precision = get_currency_precision()
out = []
entries = {}
for name, details in gle_map.items():
for (voucher_type, name), details in gle_map.items():
for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
tax_amount, total_amount, grand_total, base_total, base_tax_withholding_net_total = 0, 0, 0, 0, 0
tax_withholding_category, rate = None, None
bill_no, bill_date = "", ""
party = entry.party or entry.against
posting_date = entry.posting_date
voucher_type = entry.voucher_type
if voucher_type == "Journal Entry":
party_list = journal_entry_party_map.get(name)
if party_list:
party = party_list[0]
values = net_total_map.get((voucher_type, name))
party = values.party if values else (entry.party or entry.against)
if entry.account in tds_accounts.keys():
tax_amount += entry.credit - entry.debit
@@ -76,29 +69,35 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
rate = get_tax_withholding_rates(tax_rate_map.get(tax_withholding_category, []), posting_date)
values = net_total_map.get((voucher_type, name))
if values:
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calculate total amount from rate and tax_amount
base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0])
base_total = min(
flt(tax_amount / (rate / 100), precision=precision),
values.base_tax_withholding_net_total,
)
total_amount = grand_total = base_total
base_tax_withholding_net_total = total_amount
else:
if tax_amount and rate:
# back calculate total amount from rate and tax_amount
total_amount = flt((tax_amount * 100) / rate, precision=precision)
else:
total_amount = values[0]
total_amount = values.base_tax_withholding_net_total
grand_total = values[1]
base_total = values[2]
grand_total = values.grand_total
base_total = values.base_total
base_tax_withholding_net_total = total_amount
if voucher_type == "Purchase Invoice":
bill_no = values[3]
bill_date = values[4]
base_tax_withholding_net_total = values.base_tax_withholding_net_total
bill_no = values.bill_no
bill_date = values.bill_date
else:
total_amount += entry.credit
base_tax_withholding_net_total = total_amount
if tax_amount:
if party_map.get(party, {}).get("party_type") == "Supplier":
@@ -120,11 +119,12 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
row.update(
{
"section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type),
"tax_withholding_category": tax_withholding_category or "",
"party_entity_type": party_map.get(party, {}).get(party_type),
"rate": rate,
"total_amount": total_amount,
"grand_total": grand_total,
"base_tax_withholding_net_total": base_tax_withholding_net_total,
"base_total": base_total,
"tax_amount": tax_amount,
"transaction_date": posting_date,
@@ -141,14 +141,17 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
else:
entries[key] = row
out = list(entries.values())
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
out.sort(key=lambda x: (x["tax_withholding_category"], x["transaction_date"], x["ref_no"]))
return out
def get_party_pan_map(party_type):
def get_party_pan_map(party_type, party_names):
party_map = frappe._dict()
if not party_names:
return party_map
fields = ["name", "tax_withholding_category"]
if party_type == "Supplier":
fields += ["supplier_type", "supplier_name"]
@@ -158,7 +161,7 @@ def get_party_pan_map(party_type):
if frappe.db.has_column(party_type, "pan"):
fields.append("pan")
party_details = frappe.db.get_all(party_type, fields=fields)
party_details = frappe.db.get_all(party_type, filters={"name": ("in", list(party_names))}, fields=fields)
for party in party_details:
party.party_type = party_type
@@ -167,22 +170,33 @@ def get_party_pan_map(party_type):
return party_map
def get_gle_map(documents):
# create gle_map of the form
# {"purchase_invoice": list of dict of all gle created for this invoice}
def get_gle_map(net_total_map):
if not net_total_map:
return {}
gle = frappe.qb.DocType("GL Entry")
voucher_pairs = list(net_total_map.keys())
rows = (
frappe.qb.from_(gle)
.select(
gle.credit,
gle.debit,
gle.account,
gle.voucher_no,
gle.posting_date,
gle.voucher_type,
gle.against,
gle.party,
gle.party_type,
)
.where(gle.is_cancelled == 0)
.where(Tuple(gle.voucher_type, gle.voucher_no).isin(voucher_pairs))
).run(as_dict=True)
gle_map = {}
gle = frappe.db.get_all(
"GL Entry",
{"voucher_no": ["in", documents], "is_cancelled": 0},
["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
)
for d in gle:
if d.voucher_no not in gle_map:
gle_map[d.voucher_no] = [d]
else:
gle_map[d.voucher_no].append(d)
for d in rows:
gle_map.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gle_map
@@ -191,9 +205,9 @@ def get_columns(filters):
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
columns = [
{
"label": _("Section Code"),
"label": _("Tax Withholding Category"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"width": 90,
},
@@ -222,7 +236,12 @@ def get_columns(filters):
columns.extend(
[
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
{
"label": _(f"{filters.get('party_type', 'Party')} Type"),
"fieldname": "party_entity_type",
"fieldtype": "Data",
"width": 100,
},
]
)
if filters.party_type == "Supplier":
@@ -252,14 +271,14 @@ def get_columns(filters):
"width": 60,
},
{
"label": _("Total Amount"),
"fieldname": "total_amount",
"label": _("Tax Withholding Net Total"),
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Float",
"width": 120,
"width": 150,
},
{
"label": _("Base Total"),
"fieldname": "base_total",
"label": _("Taxable Amount"),
"fieldname": "total_amount",
"fieldtype": "Float",
"width": 120,
},
@@ -270,10 +289,16 @@ def get_columns(filters):
"width": 120,
},
{
"label": _("Grand Total"),
"label": _("Grand Total (Company Currency)"),
"fieldname": "base_total",
"fieldtype": "Float",
"width": 150,
},
{
"label": _("Grand Total (Transaction Currency)"),
"fieldname": "grand_total",
"fieldtype": "Float",
"width": 120,
"width": 170,
},
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
{
@@ -296,14 +321,9 @@ def get_columns(filters):
def get_tds_docs(filters):
tds_documents = []
purchase_invoices = []
sales_invoices = []
payment_entries = []
journal_entries = []
vouchers = frappe._dict()
tax_category_map = frappe._dict()
net_total_map = frappe._dict()
journal_entry_party_map = frappe._dict()
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
_tds_accounts = frappe.get_all(
@@ -322,35 +342,14 @@ def get_tds_docs(filters):
tds_docs = get_tds_docs_query(filters, bank_accounts, list(tds_accounts.keys())).run(as_dict=True)
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
purchase_invoices.append(d.voucher_no)
if d.voucher_type == "Sales Invoice":
sales_invoices.append(d.voucher_no)
elif d.voucher_type == "Payment Entry":
payment_entries.append(d.voucher_no)
elif d.voucher_type == "Journal Entry":
journal_entries.append(d.voucher_no)
vouchers.setdefault(d.voucher_type, set()).add(d.voucher_no)
tds_documents.append(d.voucher_no)
if purchase_invoices:
get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, net_total_map)
if sales_invoices:
get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, net_total_map)
if payment_entries:
get_doc_info(payment_entries, "Payment Entry", tax_category_map, net_total_map)
if journal_entries:
journal_entry_party_map = get_journal_entry_party_map(journal_entries)
get_doc_info(journal_entries, "Journal Entry", tax_category_map, net_total_map)
for voucher_type, docs in vouchers.items():
get_doc_info(docs, voucher_type, tax_category_map, net_total_map, filters)
return (
tds_documents,
tds_accounts,
tax_category_map,
journal_entry_party_map,
net_total_map,
)
@@ -361,11 +360,16 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
_("No {0} Accounts found for this company.").format(frappe.bold(_("Tax Withholding"))),
title=_("Accounts Missing Error"),
)
invoice_voucher = "Purchase Invoice" if filters.get("party_type") == "Supplier" else "Sales Invoice"
voucher_types = {"Payment Entry", "Journal Entry", invoice_voucher}
gle = frappe.qb.DocType("GL Entry")
query = (
frappe.qb.from_(gle)
.select("voucher_no", "voucher_type", "against", "party")
.where(gle.is_cancelled == 0)
.where(gle.voucher_type.isin(voucher_types))
)
if filters.get("from_date"):
@@ -391,25 +395,27 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
return query
def get_journal_entry_party_map(journal_entries):
def get_journal_entry_party_map(journal_entries, party_type):
journal_entry_party_map = {}
for d in frappe.db.get_all(
"Journal Entry Account",
{
"parent": ("in", journal_entries),
"party_type": ("in", ("Supplier", "Customer")),
"party_type": party_type,
"party": ("is", "set"),
},
["parent", "party"],
):
if d.parent not in journal_entry_party_map:
journal_entry_party_map[d.parent] = []
journal_entry_party_map[d.parent].append(d.party)
journal_entry_party_map.setdefault(d.parent, []).append(d.party)
return journal_entry_party_map
def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None, filters=None):
journal_entry_party_map = {}
party_type = filters.get("party_type") if filters else None
party = filters.get("party") if filters else None
common_fields = ["name"]
fields_dict = {
"Purchase Invoice": [
@@ -419,37 +425,81 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
"base_total",
"bill_no",
"bill_date",
"supplier",
],
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
"Sales Invoice": ["base_net_total", "grand_total", "base_total", "customer"],
"Payment Entry": [
"tax_withholding_category",
"paid_amount",
"paid_amount_after_tax",
"base_paid_amount",
"party",
"party_type",
],
"Journal Entry": ["tax_withholding_category", "total_debit"],
}
party_field = {
"Purchase Invoice": "supplier",
"Sales Invoice": "customer",
"Payment Entry": "party",
}
entries = frappe.get_all(
doctype, filters={"name": ("in", vouchers)}, fields=common_fields + fields_dict[doctype]
)
doc_filters = {"name": ("in", vouchers)}
if party and party_field.get(doctype):
doc_filters[party_field[doctype]] = party
if doctype == "Payment Entry":
doc_filters["party_type"] = party_type
entries = frappe.get_all(doctype, filters=doc_filters, fields=common_fields + fields_dict[doctype])
if doctype == "Journal Entry":
journal_entry_party_map = get_journal_entry_party_map(vouchers, party_type=party_type)
for entry in entries:
tax_category_map[(doctype, entry.name)] = entry.tax_withholding_category
value = frappe._dict(
party=None,
party_type=party_type,
base_tax_withholding_net_total=0,
grand_total=0,
base_total=0,
bill_no="",
bill_date="",
)
if doctype == "Purchase Invoice":
value = [
entry.base_tax_withholding_net_total,
entry.grand_total,
entry.base_total,
entry.bill_no,
entry.bill_date,
]
value.party = entry.supplier
value.party_type = "Supplier"
value.base_tax_withholding_net_total = entry.base_tax_withholding_net_total
value.grand_total = entry.grand_total
value.base_total = entry.base_total
value.bill_no = entry.bill_no
value.bill_date = entry.bill_date
elif doctype == "Sales Invoice":
value = [entry.base_net_total, entry.grand_total, entry.base_total]
value.party = entry.customer
value.party_type = "Customer"
value.base_tax_withholding_net_total = entry.base_net_total
value.grand_total = entry.grand_total
value.base_total = entry.base_total
elif doctype == "Payment Entry":
value = [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]
value.party = entry.party
value.party_type = entry.party_type
value.base_tax_withholding_net_total = entry.paid_amount
value.grand_total = entry.paid_amount_after_tax
value.base_total = entry.base_paid_amount
else:
value = [entry.total_debit] * 3
party_list = journal_entry_party_map.get(entry.name, [])
if party and party in party_list:
value.party = party
elif party_list:
value.party = sorted(party_list)[0]
value.party_type = party_type
value.base_tax_withholding_net_total = entry.total_debit
value.grand_total = entry.total_debit
value.base_total = entry.total_debit
net_total_map[(doctype, entry.name)] = value

View File

@@ -35,9 +35,9 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
result = execute(filters)[1]
expected_values = [
# Check for JV totals using back calculation logic
[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
[si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
[jv.name, "TCS", 0.075, -10000.0, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 706.67, 2550.0, 0.53, 2550.53],
[si.name, "TCS", 0.075, 693.33, 1000.0, 0.52, 1000.52],
]
self.check_expected_values(result, expected_values)
@@ -55,8 +55,8 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
frappe._dict(company="_Test Company", party_type="Supplier", from_date=today(), to_date=today())
)[1]
expected_values = [
[inv_1.name, "TDS - 1", 10, 5000, 500, 5500],
[inv_2.name, "TDS - 2", 20, 5000, 1000, 6000],
[inv_1.name, "TDS - 1", 10, 5000, 5000, 500, 5500],
[inv_2.name, "TDS - 2", 20, 5000, 5000, 1000, 6000],
]
self.check_expected_values(result, expected_values)
@@ -107,8 +107,8 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
)[1]
expected_values = [
[inv_1.name, "TDS - 3", 10.0, 5000, 500, 4500],
[inv_2.name, "TDS - 3", 20.0, 5000, 1000, 4000],
[inv_1.name, "TDS - 3", 10.0, 5000, 5000, 500, 4500],
[inv_2.name, "TDS - 3", 20.0, 5000, 5000, 1000, 4000],
]
self.check_expected_values(result, expected_values)
@@ -118,8 +118,9 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
voucher_expected_values = expected_values[i]
voucher_actual_values = (
voucher.ref_no,
voucher.section_code,
voucher.tax_withholding_category,
voucher.rate,
voucher.base_tax_withholding_net_total,
voucher.base_total,
voucher.tax_amount,
voucher.grand_total,

View File

@@ -20,16 +20,12 @@ def execute(filters=None):
columns = get_columns(filters)
(
tds_docs,
tds_accounts,
tax_category_map,
journal_entry_party_map,
invoice_total_map,
net_total_map,
) = get_tds_docs(filters)
res = get_result(
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
)
res = get_result(filters, tds_accounts, tax_category_map, net_total_map)
final_result = group_by_party_and_category(res, filters)
return columns, final_result
@@ -52,28 +48,25 @@ def group_by_party_and_category(data, filters):
party_category_wise_map = {}
for row in data:
key = (row.get("party_type"), row.get("party"), row.get("tax_withholding_category"))
party_category_wise_map.setdefault(
(row.get("party"), row.get("section_code")),
key,
{
"pan": row.get("pan"),
"tax_id": row.get("tax_id"),
"party": row.get("party"),
"party_type": row.get("party_type"),
"party_name": row.get("party_name"),
"section_code": row.get("section_code"),
"entity_type": row.get("entity_type"),
"tax_withholding_category": row.get("tax_withholding_category"),
"party_entity_type": row.get("party_entity_type"),
"rate": row.get("rate"),
"total_amount": 0.0,
"tax_amount": 0.0,
},
)
party_category_wise_map.get((row.get("party"), row.get("section_code")))["total_amount"] += row.get(
"total_amount", 0.0
)
party_category_wise_map.get((row.get("party"), row.get("section_code")))["tax_amount"] += row.get(
"tax_amount", 0.0
)
party_category_wise_map.get(key)["total_amount"] += row.get("total_amount", 0.0)
party_category_wise_map.get(key)["tax_amount"] += row.get("tax_amount", 0.0)
final_result = get_final_result(party_category_wise_map)
@@ -114,13 +107,18 @@ def get_columns(filters):
columns.extend(
[
{
"label": _("Section Code"),
"label": _("Tax Withholding Category"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"width": 180,
},
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
{
"label": _(f"{filters.get('party_type', 'Party')} Type"),
"fieldname": "party_entity_type",
"fieldtype": "Data",
"width": 180,
},
{
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
"fieldname": "rate",
@@ -128,7 +126,7 @@ def get_columns(filters):
"width": 120,
},
{
"label": _("Total Amount"),
"label": _("Total Taxable Amount"),
"fieldname": "total_amount",
"fieldtype": "Float",
"width": 120,

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