Compare commits

..

57 Commits

Author SHA1 Message Date
Frappe PR Bot
df8b7f9648 chore(release): Bumped to Version 15.121.2
## [15.121.2](https://github.com/frappe/erpnext/compare/v15.121.1...v15.121.2) (2026-09-09)

### Bug Fixes

* **stock:** preserve rates for unsaved mapped rows (backport [#58852](https://github.com/frappe/erpnext/issues/58852)) (backport [#58898](https://github.com/frappe/erpnext/issues/58898)) ([#58916](https://github.com/frappe/erpnext/issues/58916)) ([0deed27](0deed27cfe))
2026-09-09 09:24:59 +00:00
mergify[bot]
0deed27cfe fix(stock): preserve rates for unsaved mapped rows (backport #58852) (backport #58898) (#58916)
fix(stock): preserve rates for unsaved mapped rows (backport #58852) (#58898)

* fix: keep each row's own rate under maintain same rate

* test: cover maintain-same-rate rate lock for batch items

(cherry picked from commit 32d8c359ed)

Co-authored-by: Shllokkk <140623894+Shllokkk@users.noreply.github.com>
2026-09-09 14:53:20 +05:30
Frappe PR Bot
fdc48277a8 chore(release): Bumped to Version 15.121.1
## [15.121.1](https://github.com/frappe/erpnext/compare/v15.121.0...v15.121.1) (2026-09-08)

### Bug Fixes

* **accounts:** reject same-account internal transfers ([#58529](https://github.com/frappe/erpnext/issues/58529)) ([05a1127](05a1127587))
* **accounts:** round journal entry totals to field precision (backport [#58629](https://github.com/frappe/erpnext/issues/58629)) ([#58874](https://github.com/frappe/erpnext/issues/58874)) ([96357b4](96357b4fdc))
* **accounts:** support drop-shipped items in Gross Profit report ([#58686](https://github.com/frappe/erpnext/issues/58686)) ([6c6ee7c](6c6ee7c524))
* add reconciliation after submit logic for bank transactions ([#57330](https://github.com/frappe/erpnext/issues/57330)) ([2ee5f6b](2ee5f6b1c4))
* **batch:** show Expired status only after expiry date has passed ([#58736](https://github.com/frappe/erpnext/issues/58736)) ([cd93007](cd93007780))
* check material request price list permission ([#58740](https://github.com/frappe/erpnext/issues/58740)) ([a348d41](a348d413d0))
* check write permission in whitelisted document methods (backport [#58689](https://github.com/frappe/erpnext/issues/58689)) ([#58702](https://github.com/frappe/erpnext/issues/58702)) ([baa30e7](baa30e703d))
* **crm:** check read permission on lead in add_lead_to_prospect ([4dd56ec](4dd56ec4e6))
* **crm:** validate contact email before saving an email campaign (backport [#58667](https://github.com/frappe/erpnext/issues/58667)) ([#58671](https://github.com/frappe/erpnext/issues/58671)) ([1ea597d](1ea597d6b5))
* fetch accounts by company (backport [#58523](https://github.com/frappe/erpnext/issues/58523)) ([#58878](https://github.com/frappe/erpnext/issues/58878)) ([2c2a3e9](2c2a3e9f10))
* filter cancelled BOMs in BOM Stock Analysis ([#58647](https://github.com/frappe/erpnext/issues/58647)) ([fff2616](fff261680c))
* **gross-profit:** include item_name in export ([#58631](https://github.com/frappe/erpnext/issues/58631)) ([c752bb3](c752bb34a8))
* include payment deductions in sales/purchase register ledger bal… (backport [#58437](https://github.com/frappe/erpnext/issues/58437)) ([#58679](https://github.com/frappe/erpnext/issues/58679)) ([24e7ff5](24e7ff55de))
* persist rendered terms on transaction save ([#58858](https://github.com/frappe/erpnext/issues/58858)) ([24cac30](24cac30aa9))
* **pos:** use company-currency change amount when netting pos gl entries (backport [#58599](https://github.com/frappe/erpnext/issues/58599)) ([#58677](https://github.com/frappe/erpnext/issues/58677)) ([c7d865e](c7d865ef3b))
* resolve conflicts ([95b2030](95b203079a))
* **selling:** fetch orders within billing allowance (backport [#58751](https://github.com/frappe/erpnext/issues/58751)) ([#58822](https://github.com/frappe/erpnext/issues/58822)) ([a64b78d](a64b78d283))
* **setup:** strict permissions for transaction deletion record (backport [#58687](https://github.com/frappe/erpnext/issues/58687)) ([#58722](https://github.com/frappe/erpnext/issues/58722)) ([934c7d8](934c7d833c))
* show cost center for unmatched payments in receivables (backport [#58453](https://github.com/frappe/erpnext/issues/58453)) ([#58880](https://github.com/frappe/erpnext/issues/58880)) ([69f38a0](69f38a04c8))
* skip zero quantity items in production plan material requests ([#58642](https://github.com/frappe/erpnext/issues/58642)) ([3217a69](3217a69fc2))
* **stock:** recalculate delivery note billing after return ([#58869](https://github.com/frappe/erpnext/issues/58869)) ([b409ded](b409ded2f6))

### Reverts

* **manufacturing:** remove material coverage changes on version 15 ([#58715](https://github.com/frappe/erpnext/issues/58715)) ([7cbf8b8](7cbf8b8660))
2026-09-08 13:48:11 +00:00
Diptanil Saha
8635652c90 Merge pull request #58867 from frappe/version-15-hotfix
chore: release v15
2026-09-08 19:16:20 +05:30
mergify[bot]
c7d865ef3b fix(pos): use company-currency change amount when netting pos gl entries (backport #58599) (#58677)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-09-08 13:23:45 +00:00
mergify[bot]
2c2a3e9f10 fix: fetch accounts by company (backport #58523) (#58878)
Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
2026-09-08 18:18:36 +05:30
mergify[bot]
96357b4fdc fix(accounts): round journal entry totals to field precision (backport #58629) (#58874)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-09-08 12:47:54 +00:00
Sudharsanan Ashok
13367a5c54 Merge pull request #58872 from frappe/mergify/bp/version-15-hotfix/pr-58869
fix(stock): recalculate delivery note billing after return (backport #58869)
2026-09-08 18:10:08 +05:30
Sudharsanan Ashok
28918c8338 Merge pull request #58876 from frappe/mergify/bp/version-15-hotfix/pr-58529
fix(accounts): reject same-account internal transfers (backport #58529)
2026-09-08 18:09:11 +05:30
Sudharsanan Ashok
341089ce35 Merge pull request #58863 from frappe/mergify/bp/version-15-hotfix/pr-58631
fix(gross-profit): include item_name in export (backport #58631)
2026-09-08 18:05:08 +05:30
Sudharsanan Ashok
a8c6cec5a3 Merge pull request #58861 from frappe/mergify/bp/version-15-hotfix/pr-58858
fix: persist rendered terms on transaction save (backport #58858)
2026-09-08 18:04:24 +05:30
Sudharsanan Ashok
89b29eee94 Merge pull request #58762 from frappe/mergify/bp/version-15-hotfix/pr-57330
fix: add reconciliation after submit logic for bank transactions (backport #57330)
2026-09-08 18:02:38 +05:30
Pandiyan P
b409ded2f6 fix(stock): recalculate delivery note billing after return (#58869)
(cherry picked from commit f864333afa)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/services/billing_status.py
2026-09-08 17:38:48 +05:30
mergify[bot]
69f38a04c8 fix: show cost center for unmatched payments in receivables (backport #58453) (#58880)
Co-authored-by: Krishna Pramod Shirsath <91021227+krishna-254@users.noreply.github.com>
2026-09-08 17:33:31 +05:30
Pandiyan P
05a1127587 fix(accounts): reject same-account internal transfers (#58529)
(cherry picked from commit 36a4dfe797)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/test_payment_entry.py
2026-09-08 16:51:53 +05:30
Sudharsanan Ashok
940fda3327 Merge pull request #58859 from frappe/mergify/bp/version-15-hotfix/pr-58841
fix: skip zero quantity items in production plan material requests (backport #58841)
2026-09-08 15:59:18 +05:30
Sudharsanan11
95b203079a fix: resolve conflicts 2026-09-08 15:21:30 +05:30
Vishnu Priya Baskaran
c752bb34a8 fix(gross-profit): include item_name in export (#58631)
(cherry picked from commit 467f54162f)
2026-09-08 09:13:58 +00:00
Pandiyan P
24cac30aa9 fix: persist rendered terms on transaction save (#58858)
(cherry picked from commit cadc0ca86d)
2026-09-08 07:58:53 +00:00
Mihir Kandoi
a64b78d283 fix(selling): fetch orders within billing allowance (backport #58751) (#58822) 2026-09-08 09:16:56 +05:30
Mihir Kandoi
4a3a2cbdc4 test(stock): use existing warehouse fixtures in Pick List tests (v15) (#58840) 2026-09-07 15:22:12 +00:00
Vishnu Priya Baskaran
2ee5f6b1c4 fix: add reconciliation after submit logic for bank transactions (#57330)
Co-authored-by: Poovetha <poovethapalanivelu@gmail.com>
(cherry picked from commit c3319d74cf)
2026-09-04 11:35:56 +00:00
Frappe PR Bot
1a0bf0bf6c chore(release): Bumped to Version 15.121.0
# [15.121.0](https://github.com/frappe/erpnext/compare/v15.120.0...v15.121.0) (2026-09-02)

### Bug Fixes

* **accounts:** set pos profile on invoices respecting user permissions (backport [#58508](https://github.com/frappe/erpnext/issues/58508)) ([#58518](https://github.com/frappe/erpnext/issues/58518)) ([1187fb8](1187fb8e01))
* **accounts:** validate frozen accounts in period closing voucher ([#58477](https://github.com/frappe/erpnext/issues/58477)) ([045d708](045d7086d1))
* add missing perm checks on various whitelisted methods (backport [#58652](https://github.com/frappe/erpnext/issues/58652)) ([#58653](https://github.com/frappe/erpnext/issues/58653)) ([547d82d](547d82da56))
* add type hints to make_request_for_quotation ([567730e](567730e38e))
* **bank_statement_import:** add missing permission check on multiple whitelisted methods (backport [#58221](https://github.com/frappe/erpnext/issues/58221)) ([#58223](https://github.com/frappe/erpnext/issues/58223)) ([fa903f4](fa903f40fd))
* clarify duplicate internal party messages ([#58469](https://github.com/frappe/erpnext/issues/58469)) ([17208e7](17208e7434))
* **company:** throw if linked to demo_company field ([de260bd](de260bd43c))
* **crm:** add missing permission validation on `get_contract_template` (backport [#58621](https://github.com/frappe/erpnext/issues/58621)) ([#58622](https://github.com/frappe/erpnext/issues/58622)) ([d1b615b](d1b615b851))
* **crm:** check write permission in edit_note ([2b2a005](2b2a005682))
* do not map the same row twice in "Get Items From" (backport [#58617](https://github.com/frappe/erpnext/issues/58617)) ([#58618](https://github.com/frappe/erpnext/issues/58618)) ([28175a5](28175a5c9d))
* filter fully ordered items when creating RFQ from Material Request ([08ec311](08ec31174c))
* keep Currency and Price List section open for foreign currency (backport [#58074](https://github.com/frappe/erpnext/issues/58074)) ([#58112](https://github.com/frappe/erpnext/issues/58112)) ([f5ce20f](f5ce20f122))
* keep source rate on re-fetch when maintain same rate is enabled (backport [#57479](https://github.com/frappe/erpnext/issues/57479)) ([#58332](https://github.com/frappe/erpnext/issues/58332)) ([228ab2d](228ab2d97e)), closes [frappe/erpnext#57436](https://github.com/frappe/erpnext/issues/57436)
* **manufacturing:** preserve job card qty in mr transfer (backport [#58482](https://github.com/frappe/erpnext/issues/58482)) ([#58498](https://github.com/frappe/erpnext/issues/58498)) ([099a5a7](099a5a71d9))
* **manufacturing:** use packed row delivery date ([#58569](https://github.com/frappe/erpnext/issues/58569)) ([9c19b02](9c19b024db))
* mark item tax templates as not applicable (backport [#54673](https://github.com/frappe/erpnext/issues/54673)) (backport [#54677](https://github.com/frappe/erpnext/issues/54677)) ([#58462](https://github.com/frappe/erpnext/issues/58462)) ([d62f6e7](d62f6e715f))
* persist redistributed additional costs during stock entry repost (backport [#58433](https://github.com/frappe/erpnext/issues/58433)) ([#58532](https://github.com/frappe/erpnext/issues/58532)) ([c4e7cfa](c4e7cfa6a0))
* **projects:** check read permission on source in create_duplicate_project ([af8c010](af8c010aa8))
* **projects:** ignore cancelled invoices in timesheet portal (backport [#58501](https://github.com/frappe/erpnext/issues/58501)) ([#58503](https://github.com/frappe/erpnext/issues/58503)) ([5a51895](5a518951e4))
* reset hardcoded letter head on Incorrect Serial and Batch Bundle report ([ccdaa92](ccdaa92de8))
* resolve analytics backport conflicts ([605918d](605918d3a2))
* resolve conflicts ([c6717fb](c6717fb758))
* resolve stock entry backport conflicts ([e126c45](e126c4597b))
* round party balances to currency precision in Trial Balance for Party ([b98d193](b98d193b77))
* **selling:** check quotation write permission before marking lost ([#58493](https://github.com/frappe/erpnext/issues/58493)) ([33ebc7d](33ebc7d7e3))
* **selling:** check sales order permission before work order creation ([#58492](https://github.com/frappe/erpnext/issues/58492)) ([0609b06](0609b06564))
* **sms_settings:** add patch to pre-fill roles into SMS Settings Roles Table ([0cef90d](0cef90d079))
* **stock:** keep pick list links when refetching stock entry items ([22377fa](22377fa86d))
* **stock:** load available serial no report (backport [#58558](https://github.com/frappe/erpnext/issues/58558)) ([#58561](https://github.com/frappe/erpnext/issues/58561)) ([2d46bb9](2d46bb962c))
* **stock:** preserve exchange gain loss journals in lcv ([#58505](https://github.com/frappe/erpnext/issues/58505)) ([c6fedf4](c6fedf4aca))
* **stock:** reset bin when a repost finds no stock ledger entries  ([#58434](https://github.com/frappe/erpnext/issues/58434)) ([b00062d](b00062df86)), closes [#58362](https://github.com/frappe/erpnext/issues/58362)
* **stock:** validate serial inventory dimensions ([#58394](https://github.com/frappe/erpnext/issues/58394)) ([bc071d7](bc071d7793))
* sum item and its alternate transferred qty on work order required items ([5851bb3](5851bb3b6e))
* **taxes:** skip tax addition for invoice created from opening invoice tool ([7090c6f](7090c6fdab))
* **timesheet:** scoping whitelisted methods output to projects and timesheets that are acccessible to users (backport [#58267](https://github.com/frappe/erpnext/issues/58267)) ([#58576](https://github.com/frappe/erpnext/issues/58576)) ([c656497](c656497aac))
* translate doctype in map msg (backport [#58515](https://github.com/frappe/erpnext/issues/58515)) ([#58516](https://github.com/frappe/erpnext/issues/58516)) ([d87a4b2](d87a4b2541))
* translate label in party validation (backport [#58624](https://github.com/frappe/erpnext/issues/58624)) ([#58625](https://github.com/frappe/erpnext/issues/58625)) ([307ffcd](307ffcd5c3))
* validate items against source Sales Order in Material Request ([32d44c6](32d44c6e91))
* validate POS Settings invoice and search fields on the server (backport [#58611](https://github.com/frappe/erpnext/issues/58611)) ([#58612](https://github.com/frappe/erpnext/issues/58612)) ([c5bc5f9](c5bc5f9c31))
* Work Order picks wrong Delivery Date when Sales Order has the same item in multiple rows ([#58527](https://github.com/frappe/erpnext/issues/58527)) ([7454735](7454735595))

### Features

* **analytics:** filter sales and purchase analytics by entity ([#58402](https://github.com/frappe/erpnext/issues/58402)) ([0dddd00](0dddd00073))
2026-09-02 07:16:53 +00:00
Diptanil Saha
57266ab474 Merge pull request #58646 from frappe/version-15-hotfix
chore: release v15
2026-09-02 12:45:04 +05:30
Frappe PR Bot
945e825bee chore(release): Bumped to Version 15.120.0
# [15.120.0](https://github.com/frappe/erpnext/compare/v15.119.3...v15.120.0) (2026-08-25)

### Bug Fixes

* **accounts:** disallow reversing a reverse journal entry ([15041a6](15041a62dd))
* **accounts:** supplier group filter not applied on accounts payable report ([743f7d8](743f7d8713))
* aggregate child warehouses in Stock Qty vs Serial No Count report (backport [#58134](https://github.com/frappe/erpnext/issues/58134)) ([#58365](https://github.com/frappe/erpnext/issues/58365)) ([61238e7](61238e7c4c))
* allow custom remark on reversal journal entry ([#58308](https://github.com/frappe/erpnext/issues/58308)) ([b578fb5](b578fb52d5))
* asset scrap flow related changes (backport [#55126](https://github.com/frappe/erpnext/issues/55126)) ([#58144](https://github.com/frappe/erpnext/issues/58144)) ([167cc1e](167cc1e5b9))
* **assets:** split FIFO/LIFO rate across grouped stock item rows ([c2c4e5e](c2c4e5ee8b))
* attributeerror on budget against a non profit and loss account ([2095411](2095411a28))
* **bank_statement_import:** add missing permission check on `get_import_status` (backport [#58217](https://github.com/frappe/erpnext/issues/58217)) ([#58218](https://github.com/frappe/erpnext/issues/58218)) ([1464a34](1464a34fc6))
* block disabled/frozen party on Opportunity and Request for Quotation (backport [#57983](https://github.com/frappe/erpnext/issues/57983)) ([#58034](https://github.com/frappe/erpnext/issues/58034)) ([88a36a8](88a36a800a))
* **buying:** allow purchase returns against a closed purchase order ([#58140](https://github.com/frappe/erpnext/issues/58140)) ([81c5393](81c53931b5))
* correct Item Group doctype name in item tax template dashboard  (backport [#58192](https://github.com/frappe/erpnext/issues/58192)) ([#58212](https://github.com/frappe/erpnext/issues/58212)) ([46d883d](46d883d00d))
* **crm_settings:** create custom fields for Frappe CRM on enabling synchronization ([eb85ca6](eb85ca68f6))
* don't set work order status to In Process only due to skip material transfer ([#58246](https://github.com/frappe/erpnext/issues/58246)) ([73b7ec3](73b7ec32b6))
* drop removed Restaurant doctype from sales tax template dashboard  (backport [#58191](https://github.com/frappe/erpnext/issues/58191)) ([#58210](https://github.com/frappe/erpnext/issues/58210)) ([32a5b23](32a5b23b3c))
* **email_digest:** added permission check for `get_msg_html` (backport [#58197](https://github.com/frappe/erpnext/issues/58197)) ([#58199](https://github.com/frappe/erpnext/issues/58199)) ([c6211eb](c6211eb075))
* escape interpolated values in portal, print and desk templates (backport [#58273](https://github.com/frappe/erpnext/issues/58273)) ([#58277](https://github.com/frappe/erpnext/issues/58277)) ([37f2770](37f2770809))
* escape interpolated values in text positions across portal and desk templates (backport [#58286](https://github.com/frappe/erpnext/issues/58286)) ([#58287](https://github.com/frappe/erpnext/issues/58287)) ([1210c61](1210c6187d))
* escape on status image for workstations in production status (backport [#58279](https://github.com/frappe/erpnext/issues/58279)) ([#58280](https://github.com/frappe/erpnext/issues/58280)) ([d047caf](d047caf4aa))
* filter available batch report by company (backport [#57995](https://github.com/frappe/erpnext/issues/57995)) ([#58076](https://github.com/frappe/erpnext/issues/58076)) ([8c8a4b6](8c8a4b6f20))
* fix conflicts ([f8e614f](f8e614f0c7))
* get items from sales order in sales invoice (backport [#58163](https://github.com/frappe/erpnext/issues/58163)) ([#58187](https://github.com/frappe/erpnext/issues/58187)) ([89d3701](89d3701e3b))
* hide rfq status in supplier portal ([#58368](https://github.com/frappe/erpnext/issues/58368)) ([2dbd224](2dbd224643))
* hide supplier name in rfq portal (backport [#58373](https://github.com/frappe/erpnext/issues/58373)) ([#58375](https://github.com/frappe/erpnext/issues/58375)) ([91fc999](91fc999575))
* ignore historical negative batch stock in outward validation (backport [#58148](https://github.com/frappe/erpnext/issues/58148)) ([#58150](https://github.com/frappe/erpnext/issues/58150)) ([9cc9aa0](9cc9aa0fa5))
* include time logs ending at midnight in timesheet billing summary (backport [#58355](https://github.com/frappe/erpnext/issues/58355)) ([#58356](https://github.com/frappe/erpnext/issues/58356)) ([3d4245b](3d4245b9b4))
* **italy:** handle none price_list_rate in e-invoice xml generation (backport [#58242](https://github.com/frappe/erpnext/issues/58242)) ([#58369](https://github.com/frappe/erpnext/issues/58369)) ([cb8ae93](cb8ae93fa3))
* **manufacturing:** derive material transfers from actual coverage ([#58114](https://github.com/frappe/erpnext/issues/58114)) ([a1ecea1](a1ecea1794))
* **manufacturing:** fall back to item group defaults for work order w… ([#58236](https://github.com/frappe/erpnext/issues/58236)) ([48bd313](48bd3139f3))
* mirror rounding adjustment on distributed_discount_amount (backport [#58047](https://github.com/frappe/erpnext/issues/58047)) ([#58054](https://github.com/frappe/erpnext/issues/58054)) ([b03e098](b03e098684))
* new docs should refetch incoming rates (backport [#58097](https://github.com/frappe/erpnext/issues/58097)) ([#58294](https://github.com/frappe/erpnext/issues/58294)) ([fd82c7d](fd82c7d691))
* **party_ledger_summary:** added missing filters for `cost_center` and `projects` ([#58411](https://github.com/frappe/erpnext/issues/58411)) ([d4815cb](d4815cb231))
* patch to delete the `crm_deal` custom fields ([ff1a191](ff1a1914a1))
* prevent duplicate supplier quotations from portal ([e1246ae](e1246ae95d))
* remove ignore_permissions from get_party_details signature ([#55491](https://github.com/frappe/erpnext/issues/55491)) ([4cd39aa](4cd39aa147))
* render missing terms before printing ([#58367](https://github.com/frappe/erpnext/issues/58367)) ([b33475e](b33475e7cf))
* respect zero currency precision ([#58395](https://github.com/frappe/erpnext/issues/58395)) ([6a84621](6a8462116b))
* **stock:** confirm before changing item qty from the batch selector (backport [#58123](https://github.com/frappe/erpnext/issues/58123)) ([#58124](https://github.com/frappe/erpnext/issues/58124)) ([954a5ec](954a5ec006))
* **stock:** fetch item stock UOM in stock reconciliation (backport [#58284](https://github.com/frappe/erpnext/issues/58284)) ([#58290](https://github.com/frappe/erpnext/issues/58290)) ([7883f59](7883f595d7))
* **stock:** honour pick serial / batch based on in the batch selector (backport [#58176](https://github.com/frappe/erpnext/issues/58176)) ([#58181](https://github.com/frappe/erpnext/issues/58181)) ([6d06b43](6d06b43434))
* update stock variance account logic which defaults to default expense (backport [#57656](https://github.com/frappe/erpnext/issues/57656)) ([#57675](https://github.com/frappe/erpnext/issues/57675)) ([c132b99](c132b99b4e))
* use user data fields hook (backport [#58274](https://github.com/frappe/erpnext/issues/58274)) ([#58282](https://github.com/frappe/erpnext/issues/58282)) ([74c3eea](74c3eeaa44))
* validation for task end date check ([8dc9919](8dc9919691))

### Features

* **accounts:** opt-in 'Consider Accounting Dimension' filter on General Ledger Report (backport [#58156](https://github.com/frappe/erpnext/issues/58156)) ([#58157](https://github.com/frappe/erpnext/issues/58157)) ([e704e58](e704e589ca))
* add status filter to Supplier Quotation Comparison report ([9e6a166](9e6a16658d))
* Belgian Charts of Accounts (commercial + non-profit, FR + NL) (backport [#54679](https://github.com/frappe/erpnext/issues/54679)) ([#58185](https://github.com/frappe/erpnext/issues/58185)) ([4c9a76e](4c9a76ef9f))
* sync serial no status from stock ledger in Stock Qty vs Serial No Count report (version-15-hotfix) ([#57865](https://github.com/frappe/erpnext/issues/57865)) ([faaaa07](faaaa0776d))
* taxable-base resolver hook for custom charge types ([#56175](https://github.com/frappe/erpnext/issues/56175)) ([8c9e941](8c9e941614))
* validate purchase receipt exchange rate parity on purchase invoice (backport [#58177](https://github.com/frappe/erpnext/issues/58177)) ([#58189](https://github.com/frappe/erpnext/issues/58189)) ([a5f4d3a](a5f4d3abeb))
2026-08-25 16:59:18 +00:00
Diptanil Saha
5d60bd9f54 Merge pull request #58396 from frappe/version-15-hotfix
chore: release v15
2026-08-25 22:27:36 +05:30
Frappe PR Bot
0526834071 chore(release): Bumped to Version 15.119.3
## [15.119.3](https://github.com/frappe/erpnext/compare/v15.119.2...v15.119.3) (2026-08-18)

### Bug Fixes

* get items from sales order in sales invoice (backport [#58163](https://github.com/frappe/erpnext/issues/58163)) (backport [#58187](https://github.com/frappe/erpnext/issues/58187)) ([#58259](https://github.com/frappe/erpnext/issues/58259)) ([88f32f1](88f32f1d1b))
2026-08-18 07:37:05 +00:00
mergify[bot]
88f32f1d1b fix: get items from sales order in sales invoice (backport #58163) (backport #58187) (#58259)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-08-18 07:35:00 +00:00
Frappe PR Bot
d707cb1e0e chore(release): Bumped to Version 15.119.2
## [15.119.2](https://github.com/frappe/erpnext/compare/v15.119.1...v15.119.2) (2026-08-14)

### Bug Fixes

* ignore historical negative batch stock in outward validation (backport [#58148](https://github.com/frappe/erpnext/issues/58148)) (backport [#58150](https://github.com/frappe/erpnext/issues/58150)) ([#58152](https://github.com/frappe/erpnext/issues/58152)) ([ac5b4d1](ac5b4d185b))
2026-08-14 06:20:14 +00:00
mergify[bot]
ac5b4d185b fix: ignore historical negative batch stock in outward validation (backport #58148) (backport #58150) (#58152)
fix: ignore historical negative batch stock in outward validation (backport #58148) (#58150)

fix: ignore historical negative batch stock in outward validation (#58148)

(cherry picked from commit 9239d1c2a3)


(cherry picked from commit 9cc9aa0fa5)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2026-08-14 11:48:34 +05:30
Frappe PR Bot
be176617cc chore(release): Bumped to Version 15.119.1
## [15.119.1](https://github.com/frappe/erpnext/compare/v15.119.0...v15.119.1) (2026-08-11)

### Bug Fixes

* **accounts receivable:** made territory field multi select (backport [#57322](https://github.com/frappe/erpnext/issues/57322)) ([#57808](https://github.com/frappe/erpnext/issues/57808)) ([69c00b3](69c00b3362))
* allow selecting a warehouse for new items in the update items dialog ([#57876](https://github.com/frappe/erpnext/issues/57876)) ([76f485b](76f485ba43))
* clear deferred revenue/expense fields on uncheck (backport [#57140](https://github.com/frappe/erpnext/issues/57140)) ([ce898a9](ce898a9695))
* **coa_importer:** added server-side validations for importing chart of accounts (backport [#58065](https://github.com/frappe/erpnext/issues/58065)) ([#58067](https://github.com/frappe/erpnext/issues/58067)) ([d0ef967](d0ef967890))
* **coa_importer:** allow importing COA through `import_coa` only for `Accounts Manager` (backport [#56132](https://github.com/frappe/erpnext/issues/56132)) ([#58064](https://github.com/frappe/erpnext/issues/58064)) ([00d8dc7](00d8dc73b9))
* convert hours to minutes in workstation complete_job ([5e753ec](5e753ec6e4))
* escape `customer_details` on lead creation from appointment (backport [#57947](https://github.com/frappe/erpnext/issues/57947)) ([#57948](https://github.com/frappe/erpnext/issues/57948)) ([2ca71f1](2ca71f16c4))
* field validation and perm checks on `get_stock_reservation_entries_for_voucher` (backport [#57968](https://github.com/frappe/erpnext/issues/57968)) ([#57986](https://github.com/frappe/erpnext/issues/57986)) ([a0d1561](a0d156120b))
* get_valuation_method takes no company argument on version-15 ([c45ea35](c45ea3545d))
* guard reconciliation table deletes when tables are missing ([140e06d](140e06dfa4))
* incorrect entry detection in Stock Ledger Invariant Check ([#57886](https://github.com/frappe/erpnext/issues/57886)) ([529ff25](529ff251fb))
* keep asset repair downtime in sync with entered dates ([0e9c08d](0e9c08d494))
* **manufacturing:** avoid child BOM cache lookups ([5c2123f](5c2123f6ed))
* **manufacturing:** correct nested BOM quantities ([a310346](a3103469e2))
* **manufacturing:** keep item code searchable when a barcode matches the same text ([0310db2](0310db22ec))
* optimize product bundle item search ([52a1b12](52a1b124b2))
* preserve custom title on new JV (backport [#57987](https://github.com/frappe/erpnext/issues/57987)) ([#57988](https://github.com/frappe/erpnext/issues/57988)) ([713af31](713af31eda))
* purchase return of batchwise valuation batch valued at original receipt rate instead of batch avg rate (version-15-hotfix) ([#57837](https://github.com/frappe/erpnext/issues/57837)) ([e2ded11](e2ded11e09))
* re-check future sle before queuing repost on submit ([#57664](https://github.com/frappe/erpnext/issues/57664)) ([#57960](https://github.com/frappe/erpnext/issues/57960)) ([d510978](d510978a44))
* reflect in-invoice receivable settlements in Sales Register ledger view ([91afc04](91afc04d9f))
* repost read stale sibling SLE rate for moving average returns ([5738cfc](5738cfce79))
* require material transfer before job card time logs ([2e5ae18](2e5ae188d6))
* resolve version-15 backport conflicts ([b25a2f2](b25a2f2cdf))
* **selling:** bill re-delivered sales order quantities ([47c6274](47c6274b13))
* **selling:** reset stale item details on item change (backport [#58051](https://github.com/frappe/erpnext/issues/58051)) ([#58052](https://github.com/frappe/erpnext/issues/58052)) ([01372cf](01372cf295))
* set `restrict_globals=True` in `frappe.render_template` (backport [#57899](https://github.com/frappe/erpnext/issues/57899)) ([#57901](https://github.com/frappe/erpnext/issues/57901)) ([af7953a](af7953a933))
* **setup:** fetch driver address by supplier link ([2a4a9ff](2a4a9fff3d))
* skip incoming rate calc when serial no qty is zero (backport [#57427](https://github.com/frappe/erpnext/issues/57427)) ([#57956](https://github.com/frappe/erpnext/issues/57956)) ([f9a09f0](f9a09f0ac4))
* **stock:** grant account access in blanket order role test ([5e78603](5e7860362e))
* **stock:** handle multi-item opening balance in Stock Ledger report (backport [#57591](https://github.com/frappe/erpnext/issues/57591)) ([#57795](https://github.com/frappe/erpnext/issues/57795)) ([ebdc0cd](ebdc0cdf7f))
* **stock:** scope over deliver/receive role check to delivery and receipt overflow ([328c72d](328c72da18))
* **stock:** validate new warehouse inventory account after naming ([5e3d094](5e3d0947c8))
* **stock:** validate over delivery/receipt allowance in stock settings ([7be8a99](7be8a99a23))
* **stock:** validate warehouse accounts when used ([d620720](d620720445))
* **subscription:** don't reactivate a cancelled subscription (backport [#57774](https://github.com/frappe/erpnext/issues/57774)) ([#57780](https://github.com/frappe/erpnext/issues/57780)) ([edc8daa](edc8daae21)), closes [#57761](https://github.com/frappe/erpnext/issues/57761) [#57761](https://github.com/frappe/erpnext/issues/57761)
* sync open reference forms after Quality Inspection updates them ([c1d198d](c1d198d205))
* tolerate floating-point drift in sales team allocated percentage ([262fbc5](262fbc52d8))
* use stock settings for warehouse defaults ([d4ed207](d4ed2074d5))
* validate webform for project ([680ce70](680ce70c35))
* zero-rate repost fallback could still read sibling SLE ([8131af9](8131af9405))
2026-08-11 23:19:46 +00:00
Diptanil Saha
942a1ddb2c Merge pull request #58025 from frappe/version-15-hotfix
chore: release v15
2026-08-12 04:47:53 +05:30
Frappe PR Bot
866688931b chore(release): Bumped to Version 15.119.0
# [15.119.0](https://github.com/frappe/erpnext/compare/v15.118.3...v15.119.0) (2026-08-04)

### Bug Fixes

* **accounts:** fetch deferred invoice docs on non-empty `sales_docs` or `purchase_docs` in repost accounting ledger (backport [#57753](https://github.com/frappe/erpnext/issues/57753)) ([#57756](https://github.com/frappe/erpnext/issues/57756)) ([ee4e296](ee4e296ce6))
* **accounts:** skip party dashboard without invoice permission ([9d417da](9d417da3d8))
* **accounts:** update AU standard chart of accounts (backport [#57145](https://github.com/frappe/erpnext/issues/57145)) ([#57607](https://github.com/frappe/erpnext/issues/57607)) ([15c3817](15c381701d))
* **assets:** add permission checks on whitelisted methods on `asset_capitalization` ([338fff2](338fff20db))
* do not fetch a random inventory account when multiple inventory accounts exist (backport [#57626](https://github.com/frappe/erpnext/issues/57626)) ([#57631](https://github.com/frappe/erpnext/issues/57631)) ([972a990](972a990b01))
* escape data in multiple templates (backport [#57742](https://github.com/frappe/erpnext/issues/57742)) ([#57769](https://github.com/frappe/erpnext/issues/57769)) ([928f984](928f984198))
* filter Accounts Receivable by invoice sales partner (backport [#57628](https://github.com/frappe/erpnext/issues/57628)) ([#57646](https://github.com/frappe/erpnext/issues/57646)) ([ebf5a46](ebf5a462b3))
* guard against None row in get_stock_balance_for (backport [#57567](https://github.com/frappe/erpnext/issues/57567)) ([72f293f](72f293f131))
* Ignore permission while deleting user permission ([faa7c46](faa7c466b1))
* incorrect batch-wise valuation rate for entries with same posting datetime ([#57794](https://github.com/frappe/erpnext/issues/57794)) ([431dc2e](431dc2e5f1))
* **item_variant:** added permission checks on `enqueue_multiple_variant_creation` ([9cd5997](9cd5997500))
* let Purchase Receipt cancel defer to Frappe's linked-document check (backport [#57592](https://github.com/frappe/erpnext/issues/57592)) ([#57602](https://github.com/frappe/erpnext/issues/57602)) ([7cecff9](7cecff9fa4))
* **opportunity:** add validation for positive item quantities ([f47346a](f47346aa90))
* **payment reconciliation:** correct supplier gain/loss posting ([78cd25d](78cd25de04))
* **payment_request:** added permission checks on `resend_payment_email` ([c38c9d5](c38c9d5d9b))
* **plant_floor:** add missing perm check on `get_stock_summary` (backport [#57667](https://github.com/frappe/erpnext/issues/57667)) ([#57669](https://github.com/frappe/erpnext/issues/57669)) ([42d5378](42d53783bb))
* preserve UOM conversion factor precision in transactions ([4373e29](4373e295de))
* prevent duplicate shipping charges without cost center ([a6dff3f](a6dff3fc47))
* **projects:** add project filter ([54390bd](54390bdeb9))
* **projects:** include on hold status in project filters and reports ([478426b](478426b436))
* **purchase:** reject purchase returns where every item has zero quantity ([8676add](8676add875))
* **quotation:** carry forward communications from opportunity at after_insert (backport [#57639](https://github.com/frappe/erpnext/issues/57639)) ([#57642](https://github.com/frappe/erpnext/issues/57642)) ([cf42c52](cf42c52530))
* resolve backport conflicts for version-15 ([1602639](1602639a80))
* resolve version 15 backport conflict ([00df865](00df8652e3))
* resolve version-15 backport conflict ([70da05e](70da05edb7))
* respect quantity precision in material transfer validation ([a5ed3a5](a5ed3a5945))
* **sales:** reject sales returns where every item has zero quantity ([48beb2e](48beb2ee23))
* seed standard Item Groups under the existing tree root ([8483350](848335086c)), closes [#57581](https://github.com/frappe/erpnext/issues/57581)
* set transaction currency on payment entry gl entries ([#57613](https://github.com/frappe/erpnext/issues/57613)) ([35f523e](35f523e2dd))
* source manually created asset value from valuation rate ([455d6d4](455d6d4ac1))
* **stock:** pick list serial batch posting date ([#57015](https://github.com/frappe/erpnext/issues/57015)) ([a51750d](a51750db56)), closes [#56951](https://github.com/frappe/erpnext/issues/56951)
* **stock:** validate only the variant attributes that changed ([2993747](2993747636))
* **stock:** value batched packed-item returns from the original bundle  (backport [#57327](https://github.com/frappe/erpnext/issues/57327)) ([#57510](https://github.com/frappe/erpnext/issues/57510)) ([94d63eb](94d63ebb49))
* use payment entry posting date for received amount exchange rate (backport [#57660](https://github.com/frappe/erpnext/issues/57660)) ([#57662](https://github.com/frappe/erpnext/issues/57662)) ([b826b7c](b826b7c3e6))
* validate account frozen date ([310b9d4](310b9d4e65))

### Features

* auto-fill subscription accounting dimensions from plan with item fallback (backport [#57615](https://github.com/frappe/erpnext/issues/57615)) ([#57621](https://github.com/frappe/erpnext/issues/57621)) ([bb36a4f](bb36a4fd08))
* make Shipping Rule Cost Center optional with company default fallback (backport [#57355](https://github.com/frappe/erpnext/issues/57355)) ([#57402](https://github.com/frappe/erpnext/issues/57402)) ([b2918b8](b2918b8bb3))
* status based bar colors in Work Order gantt view (backport [#57634](https://github.com/frappe/erpnext/issues/57634)) ([#57635](https://github.com/frappe/erpnext/issues/57635)) ([68c24f3](68c24f3767))
2026-08-04 20:31:08 +00:00
Diptanil Saha
f0d1887e6e Merge pull request #57782 from frappe/version-15-hotfix
chore: release v15
2026-08-05 01:59:03 +05:30
Frappe PR Bot
7098602dcc chore(release): Bumped to Version 15.118.3
## [15.118.3](https://github.com/frappe/erpnext/compare/v15.118.2...v15.118.3) (2026-07-30)

### Bug Fixes

* **italy:** skip e-invoicing for opening invoices ([4f07e25](4f07e2503b))
* **stock:** keep manufactured item rate at zero when inputs are free  (backport [#57334](https://github.com/frappe/erpnext/issues/57334)) ([#57512](https://github.com/frappe/erpnext/issues/57512)) ([ade3f79](ade3f791a4))
2026-07-30 05:19:18 +00:00
Diptanil Saha
32b56ac505 Merge pull request #57611 from frappe/version-15-hotfix
chore: release v15
2026-07-30 10:47:30 +05:30
mergify[bot]
9f1bdba9a7 refactor(accounts): repost accounting ledger (backport #56442) (#57598)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2026-07-30 10:15:40 +05:30
Frappe PR Bot
c630226846 chore(release): Bumped to Version 15.118.2
## [15.118.2](https://github.com/frappe/erpnext/compare/v15.118.1...v15.118.2) (2026-07-28)

### Bug Fixes

* add is_active filter ([bb1320f](bb1320f8df))
* add permission check for `get_item_details` (backport [#57515](https://github.com/frappe/erpnext/issues/57515)) ([#57550](https://github.com/frappe/erpnext/issues/57550)) ([41effcf](41effcf754))
* check if item is variant when creating WO from MR ([745513d](745513d0c2))
* **crm:** align Opportunity status checks with Quotation statuses (backport [#57489](https://github.com/frappe/erpnext/issues/57489)) ([#57490](https://github.com/frappe/erpnext/issues/57490)) ([53d9d1c](53d9d1c50d))
* **crm:** clarify the reason why an opportunity cannot be declared as lost (backport [#57495](https://github.com/frappe/erpnext/issues/57495)) ([#57497](https://github.com/frappe/erpnext/issues/57497)) ([98a0fd8](98a0fd814e))
* detect the currency column by fieldtype in trends total row ([8cceb6a](8cceb6af10))
* enable the 'Include Zero Stock Items' filter by default to show zero-stock items in the Stock Balance report ([#57458](https://github.com/frappe/erpnext/issues/57458)) ([4e8f5de](4e8f5de5cb))
* guard against missing is_your_company_address custom field on address ([6fa522d](6fa522d031))
* Incorrect creation time at the time cancelling an entry causing an issue especially same posting datetime  (backport [#57380](https://github.com/frappe/erpnext/issues/57380)) ([#57396](https://github.com/frappe/erpnext/issues/57396)) ([ae0cd16](ae0cd164f3))
* **manufacturing:** fall back to UOM Conversion Factor in Production Plan (backport [#57553](https://github.com/frappe/erpnext/issues/57553)) ([#57555](https://github.com/frappe/erpnext/issues/57555)) ([12b4c13](12b4c134ca))
* **manufacturing:** update cost of BOMs created via BOM Creator ([25e5b10](25e5b107be))
* map pick list customer to delivery note when no sales order ([0dc5894](0dc5894499)), closes [#57412](https://github.com/frappe/erpnext/issues/57412)
* migrate stored AR/AP ageing filter to renamed field ([e2319c3](e2319c3ffe))
* pool batch slot values on every run, not only when negative ([f38b3b4](f38b3b422d))
* **ppcv:** replace incorrect usage of `frappe.in_test` with `frappe.flags.in_test` in version-15 ([#57579](https://github.com/frappe/erpnext/issues/57579)) ([abc53b0](abc53b0d39))
* **quotation:** carry forward communications from opportunity (backport [#57507](https://github.com/frappe/erpnext/issues/57507)) ([#57508](https://github.com/frappe/erpnext/issues/57508)) ([ceb6778](ceb677844f))
* rebalance batch slot values at the pooled rate when driven negative ([4808202](48082020e8))
* recalculate operating cost on hour rate change in routing ([4fe91bd](4fe91bd8b8))
* rename misleading filter labels in AR/AP reports ([60b4e60](60b4e6053d))
* resolve backport conflict in stock ageing test imports ([a2ace9d](a2ace9d394))
* respect child warehouse account override in Stock and Account Value Comparison ([#57552](https://github.com/frappe/erpnext/issues/57552)) ([20b6dd3](20b6dd3d0f))
* respect selected BOM when creating work order for variant item ([#57359](https://github.com/frappe/erpnext/issues/57359)) ([c5235af](c5235af6bf))
* respect user permissions in party dashboard company list ([348555b](348555b127)), closes [frappe/erpnext#57428](https://github.com/frappe/erpnext/issues/57428)
* restore Save button on reverse journal entry ([d937385](d9373850ad))
* seed cancelled voucher replay from before its posting datetime ([1ff297e](1ff297e9da))
* **stock:** narrow legacy serial ledger lookup by item (backport [#57499](https://github.com/frappe/erpnext/issues/57499)) ([#57505](https://github.com/frappe/erpnext/issues/57505)) ([c70cf8e](c70cf8e554))
* stop storing "{supplier_name}" / "{customer_name}" as the document title ([d96999d](d96999de7f))
* **subcontracting:** release raw-material reservation when closing a subcontracting order ([d970627](d9706271ff))
* **tnc:** `get_terms_and_conditions` render_template with `safe_exec` (backport [#56944](https://github.com/frappe/erpnext/issues/56944)) (backport [#56977](https://github.com/frappe/erpnext/issues/56977)) ([#57106](https://github.com/frappe/erpnext/issues/57106)) ([1d60ab4](1d60ab449c))
* typeerror in get_batches_by_oldest for mixed batch expiry ([56bd024](56bd024f39))
* update operating cost when propagating workstation hour rate to routing ([#57504](https://github.com/frappe/erpnext/issues/57504)) ([8f68b7e](8f68b7ed20))
* use company currency instead of global default in report (backport [#56561](https://github.com/frappe/erpnext/issues/56561)) ([0ad0d77](0ad0d7733b))
2026-07-28 23:31:43 +00:00
Diptanil Saha
d5b49cd66e Merge pull request #57549 from frappe/version-15-hotfix
chore: release v15
2026-07-29 04:59:58 +05:30
Frappe PR Bot
b9c9b76f5b chore(release): Bumped to Version 15.118.1
## [15.118.1](https://github.com/frappe/erpnext/compare/v15.118.0...v15.118.1) (2026-07-23)

### Bug Fixes

* seed cancelled voucher replay from before its posting datetime ([3d41984](3d4198494b))
2026-07-23 08:44:24 +00:00
Mihir Kandoi
da0e3b5882 Merge pull request #57395 from frappe/mergify/bp/version-15/pr-57347
fix: seed cancelled voucher replay from before its posting datetime (v15) (backport #57347)
2026-07-23 14:12:42 +05:30
Mihir Kandoi
3d4198494b fix: seed cancelled voucher replay from before its posting datetime
On cancel, update_entries_after replays every live SLE at the voucher's
posting datetime, but get_previous_sle_of_current_voucher seeded the
replay with the reversal SLE's creation, which resolves to the bucket's
own closing row. The bucket's net qty got double-counted into every
same-datetime row, so later submissions passed negative stock validation
against inflated balances, and the queued repost then rewrote correct
values with allow_negative_stock forced on, silently creating negative
stock. Backports the missing guard from eca71dce54.

(cherry picked from commit 1ff297e9da)
2026-07-23 08:22:04 +00:00
Frappe PR Bot
e6b4799b1a chore(release): Bumped to Version 15.118.0
# [15.118.0](https://github.com/frappe/erpnext/compare/v15.117.0...v15.118.0) (2026-07-22)

### Bug Fixes

* add fetch from in production plan material request child table ([6ffd759](6ffd759968))
* added missing validations for `Dunning Type` (backport [#57224](https://github.com/frappe/erpnext/issues/57224)) ([#57226](https://github.com/frappe/erpnext/issues/57226)) ([fd01171](fd01171df5))
* apply default accounting dimensions reliably on new documents ([88abe11](88abe119c3))
* apply user permissions via build_match_conditions ([202f522](202f52271c))
* block changing Stock account type when stock ledger entries exist (backport [#57283](https://github.com/frappe/erpnext/issues/57283)) ([#57284](https://github.com/frappe/erpnext/issues/57284)) ([2cd531d](2cd531d099))
* consider min order qty in the purchase/transfer flow of production plan (backport [#57204](https://github.com/frappe/erpnext/issues/57204)) ([#57209](https://github.com/frappe/erpnext/issues/57209)) ([77ec644](77ec6447c3))
* correct typo in allow_negative_stock parameter ([f24e09c](f24e09cc97))
* dont overwrite rate with 0 if not found ([74e9718](74e9718871))
* pass ctx keys get_price_list_rate_for reads, skip rate update on insert ([302cbbe](302cbbe5d8))
* **payments:** ensure `payments` app installed on the site in `payment_app_import_guard` (backport [#57342](https://github.com/frappe/erpnext/issues/57342)) ([#57343](https://github.com/frappe/erpnext/issues/57343)) ([3efddfd](3efddfd270))
* permission issue (backport [#57112](https://github.com/frappe/erpnext/issues/57112)) ([#57142](https://github.com/frappe/erpnext/issues/57142)) ([6b23b00](6b23b007a4))
* project % complete field allowing modification when manual method (backport [#57274](https://github.com/frappe/erpnext/issues/57274)) ([#57275](https://github.com/frappe/erpnext/issues/57275)) ([21e5620](21e5620e92))
* **project:** improved access control for project users (backport [#56675](https://github.com/frappe/erpnext/issues/56675)) ([#57180](https://github.com/frappe/erpnext/issues/57180)) ([ff6c8bb](ff6c8bbb44))
* read serial and batch flags from Item in Stock Balance's SLE query ([e7f0461](e7f0461b57))
* rescale batch FIFO slot values on stock reconciliation ([72b3210](72b3210cbb))
* rescale stock ageing FIFO slot values on stock reconciliation ([d51f907](d51f9076b5))
* resolve float precision before streaming stock ledger entries ([b9ff5be](b9ff5be43e))
* resolve merge conflicts ([81e865f](81e865f6c7))
* restrict jinja globals in process statement of accounts templates (backport [#56458](https://github.com/frappe/erpnext/issues/56458)) ([#57231](https://github.com/frappe/erpnext/issues/57231)) ([88443e4](88443e4a97))
* revalue batch reco slots only when the entry covers the full batch ([2673029](2673029bd4))
* set correct currency in supplier quotation net rate field ([30ab2db](30ab2dba6e))
* strip account number when building account name in COA importer ([314dd16](314dd16aa3))
* sync process loss percentage when fg qty changes ([f0e24e2](f0e24e2f53))
* use account currency in Bank Reconciliation Statement print ([d6f797d](d6f797d077))
* use system float precision for batch qty comparison ([1679bde](1679bdecdc))
* validate buying price list on material request and update item rates on change ([96dc408](96dc408484))

### Features

* add on hold status to project ([5e2e154](5e2e15436d))
* **stock:** automatically link portal users to their associated contact profiles for customers and suppliers ([4f9ea98](4f9ea989c4))
* **stock:** expose all Bin qty fields in Stock Summary and Stock Projected Qty ([90009a4](90009a4687))
2026-07-22 00:17:40 +00:00
Diptanil Saha
49eb2366cd Merge pull request #57323 from frappe/version-15-hotfix
chore: release v15
2026-07-22 05:45:46 +05:30
Frappe PR Bot
fb2a4e5f98 chore(release): Bumped to Version 15.117.0
# [15.117.0](https://github.com/frappe/erpnext/compare/v15.116.0...v15.117.0) (2026-07-15)

### Bug Fixes

* allow barcode scan to add and increment items in pick list ([bb5be10](bb5be10372))
* **asset:** allow asset repair creation for fully depreciated assets (backport [#55276](https://github.com/frappe/erpnext/issues/55276)) ([e4d2a96](e4d2a960d2))
* **journal-entry:** fetch outstanding on foreign currency ([ace51fc](ace51fccfa))
* **manufacturing:** preserve job card transferred quantity ([fc4ac7a](fc4ac7a441))
* **stock:** fix sqlparse token limit in get_bundle_wise_serial_nos ([cc45743](cc457433fe))
* **stock:** show qty (company) and qty (warehouse) in sales transactions ([8086542](8086542885))

### Features

* faster (synced) financial statements using duckdb (backport [#56304](https://github.com/frappe/erpnext/issues/56304)) ([#57093](https://github.com/frappe/erpnext/issues/57093)) ([a6a887f](a6a887f833))
2026-07-15 02:28:29 +00:00
ruthra kumar
0ce7917648 Merge pull request #57160 from frappe/version-15-hotfix
chore: release v15
2026-07-15 06:25:15 +05:30
Frappe PR Bot
41038979ec chore(release): Bumped to Version 15.116.0
# [15.116.0](https://github.com/frappe/erpnext/compare/v15.115.0...v15.116.0) (2026-07-13)

### Bug Fixes

* **`frappe_crm_api`:** handle failure for `after_app_install` and `after_app_uninstall` ([983ec56](983ec5660c))
* **accounts:** clear clearance date when amending reconciled voucher (backport [#55947](https://github.com/frappe/erpnext/issues/55947)) ([5f14f2c](5f14f2ccd6)), closes [#55972](https://github.com/frappe/erpnext/issues/55972)
* **accounts:** retain invoice table on opening invoice creation error (backport [#56353](https://github.com/frappe/erpnext/issues/56353)) ([#57057](https://github.com/frappe/erpnext/issues/57057)) ([cfc546c](cfc546cf3f))
* added permission checks on various whitelisted functions (backport [#56745](https://github.com/frappe/erpnext/issues/56745)) ([#56945](https://github.com/frappe/erpnext/issues/56945)) ([55e0d10](55e0d106c9))
* address review comments ([3c47adb](3c47adb021))
* block serialized to non-serialized item change when SABB exists (backport [#56773](https://github.com/frappe/erpnext/issues/56773)) ([#56774](https://github.com/frappe/erpnext/issues/56774)) ([44a7013](44a7013ab4))
* **company:** ignore user permissions for link fields having link to `Account` and `Cost Center` (backport [#56748](https://github.com/frappe/erpnext/issues/56748)) ([#56794](https://github.com/frappe/erpnext/issues/56794)) ([5666fd1](5666fd1334))
* **crm_settings:** skip allowed users check when frappe crm is installed locally ([95b3bd4](95b3bd4e1d))
* disallow editing on reversal journals ([ac2e69f](ac2e69fbfd))
* display outstanding amount using company default currency (backport [#56785](https://github.com/frappe/erpnext/issues/56785)) ([#57008](https://github.com/frappe/erpnext/issues/57008)) ([8688f1f](8688f1f51a))
* don't attempt to create SABB for non-serialized / non-batch items (backport [#56627](https://github.com/frappe/erpnext/issues/56627)) ([#56802](https://github.com/frappe/erpnext/issues/56802)) ([b0446b6](b0446b6a09))
* don't treat batch slot at FIFO queue head as qty slot ([a9330e8](a9330e8900))
* fetch asset category expense account on purchase receipt ([b7e0331](b7e0331d56))
* fetch payment entry reference amounts from invoice (backport [#56928](https://github.com/frappe/erpnext/issues/56928)) ([#57041](https://github.com/frappe/erpnext/issues/57041)) ([0f6b2b6](0f6b2b68fa))
* FIFO queue checks and incorrect entries filter in stock ledger reports ([19fc8b8](19fc8b8e6f)), closes [#43619](https://github.com/frappe/erpnext/issues/43619)
* for purchases do voucher based reposting (backport [#56601](https://github.com/frappe/erpnext/issues/56601)) ([#56607](https://github.com/frappe/erpnext/issues/56607)) ([6b022a5](6b022a5a7e))
* **inactive_customers:** add allowlist for doctype filter and migrate to qb ([29dd6e6](29dd6e6681))
* incorrect import path for FrappeTestCase ([cecfcf8](cecfcf82e0))
* **journal entry:** validate opening entry against pcv on save ([2ed7c84](2ed7c84d0a))
* make represents company field in purchase invoice ignore user permissions ([b7f2769](b7f276989d))
* make trend report based-on and group-by column labels translatable ([d2d5fae](d2d5fae033))
* **manufacturing:** update work order status on partial pick-list transfer (backport [#56800](https://github.com/frappe/erpnext/issues/56800)) ([afa7288](afa72884bb)), closes [56836/#57001](https://github.com/frappe/erpnext/issues/57001)
* map stock_qty in apply_price_list_on_item (backport [#56869](https://github.com/frappe/erpnext/issues/56869)) ([#57051](https://github.com/frappe/erpnext/issues/57051)) ([3e2a734](3e2a734414))
* partial delivery note against pick list ([#56985](https://github.com/frappe/erpnext/issues/56985)) ([53af4d5](53af4d53ef))
* **patch:** moved create_company_custom_fields from `pre_model_sync` to `post_model_sync` ([#56962](https://github.com/frappe/erpnext/issues/56962)) ([d0f2940](d0f29405d0))
* precision issue causing reconciliation error ([#54043](https://github.com/frappe/erpnext/issues/54043)) ([d62831d](d62831d885))
* prevent repeatable read related concurrency errors ([1ee8904](1ee8904a94))
* race condition and repeatable read in process pcv ([ae43f47](ae43f47c39))
* remove company default on cost center in stock entry detail ([b170913](b170913880))
* remove incorrect Payable account_type from Customer Deposits in Philippines CoA (backport [#57018](https://github.com/frappe/erpnext/issues/57018)) ([#57060](https://github.com/frappe/erpnext/issues/57060)) ([bd62699](bd6269918f))
* rename variant item_code/item_name when attribute abbreviation changes ([99bdef6](99bdef6baf))
* replay immutable SLE qty for serial/batch bundle valuation (backport [#56814](https://github.com/frappe/erpnext/issues/56814)) ([#56834](https://github.com/frappe/erpnext/issues/56834)) ([1cf94f5](1cf94f5501))
* resolve conflicts ([da623eb](da623eb124))
* **selling:** update sales order per billed on credit note submission ([63e85c6](63e85c6551))
* set customer group in inactive customers report test setup ([ede2a68](ede2a68afd))
* show only template items in Variant Of filter ([fde7cd5](fde7cd5345))
* **stock:** backfill transferred qty for existing pick lists ([cd61243](cd612434a4))
* **stock:** correct stock ageing value for moving average and lifo items (backport [#56693](https://github.com/frappe/erpnext/issues/56693)) ([#57016](https://github.com/frappe/erpnext/issues/57016)) ([9d654b7](9d654b7890))
* **stock:** pick list serial batch posting date ([#56957](https://github.com/frappe/erpnext/issues/56957)) ([#57014](https://github.com/frappe/erpnext/issues/57014)) ([4091188](4091188908)), closes [#56951](https://github.com/frappe/erpnext/issues/56951)
* **stock:** set stock received but not billed account for purchase ([#55149](https://github.com/frappe/erpnext/issues/55149)) ([38b7042](38b7042481))
* update BOM operations when routing is changed ([de0c75b](de0c75bcba))
* update events order by date asc (backport [#56963](https://github.com/frappe/erpnext/issues/56963)) ([#57055](https://github.com/frappe/erpnext/issues/57055)) ([1cb4307](1cb4307578))
* update modified timestamp in item.json ([902a3c5](902a3c5c55))
* use change_settings decorator matching version-15-hotfix test utils ([5cedc50](5cedc50c55))
* use correct import ([db4136b](db4136b258))
* use live source warehouse valuation for internal transfer purchse receipts ([#56812](https://github.com/frappe/erpnext/issues/56812)) ([ece1c98](ece1c9872f))
* validate planned end date is not before planned start date in work order ([de6a157](de6a157773))
* validate reverse GL entries on current date under immutable ledger (backport [#56709](https://github.com/frappe/erpnext/issues/56709)) ([#56751](https://github.com/frappe/erpnext/issues/56751)) ([71a2d6e](71a2d6e43d))
* validate template and its variant in the same Pricing Rule ([3df52c7](3df52c7fc6))

### Features

* **crm_settings:** auto-update crm sync settings on frappe crm install and uninstall ([0a5daee](0a5daeed57))
* **opening invoice creation tool:** add project to opening invoice child row (backport [#54662](https://github.com/frappe/erpnext/issues/54662)) ([#56400](https://github.com/frappe/erpnext/issues/56400)) ([808109a](808109abc0))
* **sla:** filter service level agreement link by document type (backport [#56954](https://github.com/frappe/erpnext/issues/56954)) ([#56955](https://github.com/frappe/erpnext/issues/56955)) ([5166950](51669501ab))
* **stock:** support partial transfer from pick list ([02168b7](02168b7b3d))

### Performance Improvements

* avoid per-row Warehouse doc fetches in auto reorder job ([c10b86d](c10b86d538))
* batch bin lookups in delivery note stock update ([989a6b5](989a6b507d))
2026-07-13 07:37:49 +00:00
Diptanil Saha
88b6779062 Merge pull request #56930 from frappe/version-15-hotfix
chore: release v15
2026-07-13 13:04:19 +05:30
Frappe PR Bot
b5f784612d chore(release): Bumped to Version 15.115.0
# [15.115.0](https://github.com/frappe/erpnext/compare/v15.114.0...v15.115.0) (2026-07-01)

### Bug Fixes

* add permission checks in whitelisted functions (backport [#53103](https://github.com/frappe/erpnext/issues/53103)) ([#56668](https://github.com/frappe/erpnext/issues/56668)) ([3455877](345587754f))
* allow rename for Quality Inspection Parameter ([9e6edea](9e6edea818))
* carry item-level project to Purchase Receipt GL entries (backport [#56568](https://github.com/frappe/erpnext/issues/56568)) ([#56618](https://github.com/frappe/erpnext/issues/56618)) ([83e8d1e](83e8d1eb2f))
* **company:** using `String.prototype.bold` method instead of `frappe.utils.bold` on company deletion prompt ([#56441](https://github.com/frappe/erpnext/issues/56441)) ([414b366](414b3665c1))
* **controllers:** fix supplier-RFQ portal list query (wrong column + Postgres DISTINCT) ([9125ab6](9125ab6c77))
* **crm:** using `get_list` instead of `get_all` in `get_opportunities` (backport [#56463](https://github.com/frappe/erpnext/issues/56463)) ([#56465](https://github.com/frappe/erpnext/issues/56465)) ([28e4bca](28e4bca4f2))
* do not allow closing the accounting period for future dates (backport [#56551](https://github.com/frappe/erpnext/issues/56551)) ([#56576](https://github.com/frappe/erpnext/issues/56576)) ([54fcd09](54fcd09995))
* extract stock ageing data helper in stock balance (backport [#55774](https://github.com/frappe/erpnext/issues/55774)) ([8abc278](8abc27863a)), closes [#55776](https://github.com/frappe/erpnext/issues/55776)
* gross profit calculation with rate adjustment entries ([17733a5](17733a5641))
* handle missing serial and batch bundle in print format ([9adfab1](9adfab19dc))
* ignored posting time 00:00:00 in RIV (backport [#56571](https://github.com/frappe/erpnext/issues/56571)) ([#56572](https://github.com/frappe/erpnext/issues/56572)) ([5ed946b](5ed946b3b9))
* **lead:** added missing read permission check on `get_lead_details` (backport [#56272](https://github.com/frappe/erpnext/issues/56272)) ([#56273](https://github.com/frappe/erpnext/issues/56273)) ([afddf70](afddf70905))
* negative stock issue ([#56616](https://github.com/frappe/erpnext/issues/56616)) ([ab98d19](ab98d19f26))
* precision issue causing COGS in inter transfer PR (backport [#56420](https://github.com/frappe/erpnext/issues/56420)) ([#56424](https://github.com/frappe/erpnext/issues/56424)) ([db8a26a](db8a26a3af))
* remove dead bundle helper call from purchase receipt print format ([0f9429f](0f9429fd3d))
* remove frappe.utils from jinja context in process statement of accounts ([b176fb2](b176fb292b))
* reserve projected stock for production plan based on BOM qty ([d3df0bf](d3df0bf387))
* set mr status to received when per_received is 100 even if per_ordered < 100 ([53bdcce](53bdccee5f))
* skip qty over-allowance check for non-stock items only ([bba7fe9](bba7fe9831))
* **stock:** value batch/serial return from ledger when original receipt has no bundle (backport [#56631](https://github.com/frappe/erpnext/issues/56631)) ([#56645](https://github.com/frappe/erpnext/issues/56645)) ([3479d65](3479d65bd4))
* sync Stock Reconciliation difference amount with GL after reposting (backport [#56574](https://github.com/frappe/erpnext/issues/56574)) ([#56584](https://github.com/frappe/erpnext/issues/56584)) ([5625635](562563553c))
* update qty in future SLEs when cancelling documents (backport [#56638](https://github.com/frappe/erpnext/issues/56638)) ([#56644](https://github.com/frappe/erpnext/issues/56644)) ([2576452](25764520c2))
* update_qty_in_future_sle skips SLEs with same posting datetime ([#56615](https://github.com/frappe/erpnext/issues/56615)) ([f50e529](f50e529f8a))
* Use correct doctype name for PCV perm-check (backport [#56606](https://github.com/frappe/erpnext/issues/56606)) ([#56610](https://github.com/frappe/erpnext/issues/56610)) ([52d04ad](52d04ad834))
* use correct variable to fetch valuation method ([c2b7718](c2b7718455))

### Features

* **accounts:** add configurable job timeout for Process Period Closing Voucher ([c97be8a](c97be8abe1))
2026-07-01 03:40:41 +00:00
Diptanil Saha
457424f7a4 Merge pull request #56651 from frappe/version-15-hotfix
chore: release v15
2026-07-01 09:08:59 +05:30
Frappe PR Bot
25ee3695f0 chore(release): Bumped to Version 15.114.0
# [15.114.0](https://github.com/frappe/erpnext/compare/v15.113.0...v15.114.0) (2026-06-24)

### Features

* **accounts:** add configurable job timeout for Process Period Closing Voucher ([82a8581](82a85818c2))
2026-06-24 10:25:43 +00:00
ruthra kumar
ff205da810 Merge pull request #56426 from frappe/mergify/bp/version-15/pr-56418
refactor: configurable timeout on process pcv (backport #56417) (backport #56418)
2026-06-24 15:48:58 +05:30
ruthra kumar
2980171007 chore: resolve conflicts
(cherry picked from commit df3c821f98)
2026-06-24 09:56:59 +00:00
ruthra kumar
c6c4815e8d refactor: patch, display depends on and json changes
(cherry picked from commit 3da7eefebb)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
#	erpnext/patches.txt
(cherry picked from commit c33d7e5d7b)
2026-06-24 09:56:59 +00:00
ruthra kumar
82a85818c2 feat(accounts): add configurable job timeout for Process Period Closing Voucher
Adds a `pcv_job_timeout` Int field (default 3600s) to Accounts Settings
so admins can tune the enqueue timeout for PCV background jobs without
a code change. All three `frappe.enqueue` calls in
`process_period_closing_voucher.py` now read this value at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 13b6c4a165)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
(cherry picked from commit c97be8abe1)
2026-06-24 09:56:58 +00:00
Frappe PR Bot
57a2be6b56 chore(release): Bumped to Version 15.113.0
# [15.113.0](https://github.com/frappe/erpnext/compare/v15.112.0...v15.113.0) (2026-06-23)

### Bug Fixes

* add dynamic links for customer and supplier dashboards ([690adf1](690adf1051))
* Add likely missing escapes (backport [#55574](https://github.com/frappe/erpnext/issues/55574)) ([#55580](https://github.com/frappe/erpnext/issues/55580)) ([ce8fce7](ce8fce78f1))
* add partially transferred status and fix button visibility for partial material transfer on job card ([1f5283d](1f5283da58))
* add validation and tests for set_status ([7bea925](7bea925230))
* apply docstatus filter to exclude cancelled Work Orders in Serial No ([90fd057](90fd057fb3))
* attribute error because of missing margin_type field in Supplier Quotation (backport [#48089](https://github.com/frappe/erpnext/issues/48089))  ([506658c](506658c3a6))
* **budget:** ambiguous error message for budget assignment validation (backport [#56390](https://github.com/frappe/erpnext/issues/56390)) ([#56391](https://github.com/frappe/erpnext/issues/56391)) ([53a1122](53a11229ec))
* clear stale payment rows on non-POS returns so they don't surface in bank reconciliation (backport [#55903](https://github.com/frappe/erpnext/issues/55903)) ([#56169](https://github.com/frappe/erpnext/issues/56169)) ([37d2622](37d26222d7))
* disable is_debit_note while creating credit note ([e4370ab](e4370ab332))
* **err:** add missing permission check on `get_account_details` ([041a9ad](041a9adbbf))
* escape user image url on various templates (backport [#56269](https://github.com/frappe/erpnext/issues/56269)) ([#56270](https://github.com/frappe/erpnext/issues/56270)) ([42af4ce](42af4ce7b0))
* **manufacturing:** make item_code mandatory in Job Card Item ([1b4da9d](1b4da9dc96))
* **payment_entry:** recompute base amount when exchange rate changes (backport [#56136](https://github.com/frappe/erpnext/issues/56136)) ([#56397](https://github.com/frappe/erpnext/issues/56397)) ([cef608d](cef608d043))
* **pos:** remove redundant opening balance dialog onchange handler (backport [#54591](https://github.com/frappe/erpnext/issues/54591)) ([#56402](https://github.com/frappe/erpnext/issues/56402)) ([334a0b2](334a0b2137))
* preserve stock ageing on non-serial reconciliation ([1991312](19913127a7))
* **report_utils:** remove unnecessary whitelist decorator on `get_invoiced_item_gross_margin` ([0efebf5](0efebf5d8c))
* resolve backport conflict in accounting dashboard number cards ([f106513](f106513005)), closes [#55548](https://github.com/frappe/erpnext/issues/55548) [#55484](https://github.com/frappe/erpnext/issues/55484)
* set a fallback value if no fiscal year set ([da1ccc2](da1ccc2b62))
* show contextual balance label on party dashboard for net balances ([9b6adc4](9b6adc42b6))
* simplify get_round_off_applicable_accounts function signature ([42121f2](42121f2e36))
* **stock:** allow partial raw material picking/transfer from work order ([a858d77](a858d77461))
* **stock:** apply precision to the additional cost amount in stock entry ([acc1444](acc1444c03))
* **stock:** propagate renamed attribute values to variant items ([27d574d](27d574dad5))
* **stock:** update transfer status for mixed transfer flows ([3f9a88a](3f9a88a5e2))
* **stock:** update variant attributes on value rename ([c7acd88](c7acd88742))
* **stock:** update voucher valuaion rate in sle (backport [#55960](https://github.com/frappe/erpnext/issues/55960)) ([#56262](https://github.com/frappe/erpnext/issues/56262)) ([37f847e](37f847e730))
* tax.base_tax_amount as none when payment entry created using API ([37dffa7](37dffa7273))
* update reference doctype mapping and field visibility in bank guarantee ([e556cbb](e556cbbe6a))
* update round off account functions to accept document context for regional overrides ([#55758](https://github.com/frappe/erpnext/issues/55758)) ([eef075a](eef075a2ba))
* use fiscal year instead of calendar year in accounting dashboard number cards ([81ce5fb](81ce5fbee9))

### Features

* add batch-level option to allow negative stock for batch ([5c4f19e](5c4f19ebdc))
2026-06-23 21:37:17 +00:00
Diptanil Saha
47f54a4725 Merge pull request #56360 from frappe/version-15-hotfix
chore: release v15
2026-06-24 03:05:42 +05:30
28 changed files with 602 additions and 207 deletions

View File

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

View File

@@ -223,7 +223,8 @@
"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 ",
"fieldname": "over_billing_allowance",
"fieldtype": "Currency",
"label": "Over Billing Allowance (%)"
"label": "Over Billing Allowance (%)",
"non_negative": 1
},
{
"default": "1",
@@ -679,7 +680,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-08-14 13:12:47.895908",
"modified": "2026-09-04 10:08:30.115003",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -421,8 +421,8 @@ cur_frm.cscript.update_totals = function (doc) {
tc += flt(accounts[i].credit, precision("credit", accounts[i]));
}
doc = locals[doc.doctype][doc.name];
doc.total_debit = td;
doc.total_credit = tc;
doc.total_debit = flt(td, precision("total_debit"));
doc.total_credit = flt(tc, precision("total_credit"));
doc.difference = flt(td - tc, precision("difference"));
refresh_many(["total_debit", "total_credit", "difference"]);
};

View File

@@ -861,12 +861,14 @@ class JournalEntry(AccountsController):
if d.debit and d.credit:
frappe.throw(_("You cannot credit and debit same account at the same time"))
self.total_debit = flt(self.total_debit) + flt(d.debit, d.precision("debit"))
self.total_credit = flt(self.total_credit) + flt(d.credit, d.precision("credit"))
self.total_debit = flt(
self.total_debit + flt(d.debit, d.precision("debit")), self.precision("total_debit")
)
self.total_credit = flt(
self.total_credit + flt(d.credit, d.precision("credit")), self.precision("total_credit")
)
self.difference = flt(self.total_debit, self.precision("total_debit")) - flt(
self.total_credit, self.precision("total_credit")
)
self.difference = flt(self.total_debit - self.total_credit, self.precision("difference"))
def validate_multi_currency(self):
alternate_currency = []

View File

@@ -410,6 +410,59 @@ class TestJournalEntry(unittest.TestCase):
self.check_gl_entries()
def make_jv_with_fractional_totals(self):
"""0.10 + 0.20 sums to 0.30000000000000004, the residue this guards against."""
jv = frappe.new_doc("Journal Entry")
jv.posting_date = nowdate()
jv.company = "_Test Company"
jv.voucher_type = "Journal Entry"
jv.remark = "test"
for amount in (0.10, 0.20):
jv.append(
"accounts",
{
"account": "_Test Cash - _TC",
"cost_center": "_Test Cost Center - _TC",
"debit_in_account_currency": amount,
},
)
jv.append(
"accounts",
{
"account": "_Test Bank - _TC",
"cost_center": "_Test Cost Center - _TC",
"credit_in_account_currency": 0.30,
},
)
jv.insert()
return jv
def test_totals_are_rounded_to_precision(self):
jv = self.make_jv_with_fractional_totals()
jv.submit()
stored = frappe.db.get_value(
"Journal Entry", jv.name, ["total_debit", "total_credit", "difference"], as_dict=True
)
self.assertEqual(jv.total_debit, flt(jv.total_debit, jv.precision("total_debit")))
self.assertEqual(jv.total_credit, flt(jv.total_credit, jv.precision("total_credit")))
self.assertEqual(jv.total_debit, stored.total_debit)
self.assertEqual(jv.total_credit, stored.total_credit)
self.assertEqual(jv.difference, stored.difference)
def test_update_after_submit_with_fractional_totals(self):
"""An unrounded total is stored rounded, so updating a submitted entry used to throw."""
jv = self.make_jv_with_fractional_totals()
jv.submit()
jv.pay_to_recd_from = "_Test Supplier"
jv.save()
self.assertEqual(jv.docstatus, 1)
self.assertEqual(
jv.pay_to_recd_from, frappe.db.get_value("Journal Entry", jv.name, "pay_to_recd_from")
)
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on

View File

@@ -45,23 +45,27 @@ frappe.ui.form.on("Payment Entry", {
},
setup: function (frm) {
frm.set_query("paid_from", function () {
frm.set_query("paid_from", function (doc) {
frm.events.validate_company(frm);
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
let filters = {
account_type: ["in", account_types],
is_group: 0,
company: doc.company,
};
if (frm.doc.party_type == "Shareholder") {
account_types.push("Equity");
}
if (doc.payment_type == "Internal Transfer" && doc.paid_to) {
filters.name = ["!=", doc.paid_to];
}
return {
filters: {
account_type: ["in", account_types],
is_group: 0,
company: frm.doc.company,
},
filters,
};
});
@@ -105,21 +109,25 @@ frappe.ui.form.on("Payment Entry", {
}
});
frm.set_query("paid_to", function () {
frm.set_query("paid_to", function (doc) {
frm.events.validate_company(frm);
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
let filters = {
account_type: ["in", account_types],
is_group: 0,
company: doc.company,
};
if (frm.doc.party_type == "Shareholder") {
account_types.push("Equity");
}
if (doc.payment_type == "Internal Transfer" && doc.paid_from) {
filters.name = ["!=", doc.paid_from];
}
return {
filters: {
account_type: ["in", account_types],
is_group: 0,
company: frm.doc.company,
},
filters,
};
});

View File

@@ -90,6 +90,7 @@ class PaymentEntry(AccountsController):
self.set_liability_account()
self.set_missing_ref_details(force=True)
self.validate_payment_type()
self.validate_internal_transfer_accounts()
self.validate_party_details()
self.set_exchange_rate()
self.validate_mandatory()
@@ -531,6 +532,10 @@ class PaymentEntry(AccountsController):
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
frappe.throw(_("Payment Type must be one of Receive, Pay and Internal Transfer"))
def validate_internal_transfer_accounts(self):
if self.payment_type == "Internal Transfer" and self.paid_from and self.paid_from == self.paid_to:
frappe.throw(_("Paid From and Paid To accounts must be different for an Internal Transfer."))
def validate_party_details(self):
if self.party and not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))

View File

@@ -733,6 +733,23 @@ class TestPaymentEntry(FrappeTestCase):
self.validate_gl_entries(pe.name, expected_gle)
def test_internal_transfer_rejects_same_account(self):
pe = frappe.new_doc("Payment Entry")
pe.payment_type = "Internal Transfer"
pe.company = "_Test Company"
pe.paid_from = "_Test Bank - _TC"
pe.paid_to = "_Test Bank - _TC"
pe.paid_amount = 100
pe.received_amount = 100
pe.reference_no = "same-account-transfer"
pe.reference_date = nowdate()
self.assertRaisesRegex(
frappe.ValidationError,
"Paid From and Paid To accounts must be different",
pe.insert,
)
def test_payment_against_negative_sales_invoice(self):
si1 = create_sales_invoice()

View File

@@ -375,9 +375,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
get_query_filters: {
docstatus: 1,
status: ["not in", ["Closed", "On Hold"]],
per_billed: ["<", 99.99],
company: me.frm.doc.company,
},
get_query_method:
"erpnext.selling.doctype.sales_order.sales_order.get_potentially_billable_sales_orders",
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],

View File

@@ -1637,7 +1637,7 @@ class SalesInvoice(SellingController):
for payment_mode in self.payments:
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
payment_mode.base_amount -= flt(self.change_amount)
payment_mode.base_amount -= flt(self.base_change_amount)
against_voucher = self.name
if self.is_return and self.return_against and not self.update_outstanding_for_self:

View File

@@ -1235,6 +1235,33 @@ class TestSalesInvoice(FrappeTestCase):
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 1)
def test_pos_change_amount_multi_currency_gl_entry(self):
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 0)
si = create_sales_invoice(do_not_save=True)
si.is_pos = 1
si.currency = "USD"
si.conversion_rate = 50
si.party_account_currency = "USD"
si.account_for_change_amount = "Cash - _TC"
si.change_amount = 50
si.base_change_amount = 2500
si.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 150, "base_amount": 7500},
)
gl_entries = []
si.make_pos_gl_entries(gl_entries)
debtors_entry = next(entry for entry in gl_entries if entry["account"] == si.debit_to)
cash_entry = next(entry for entry in gl_entries if entry["account"] == "Cash - _TC")
self.assertEqual(flt(debtors_entry["credit"]), 5000.0)
self.assertEqual(flt(cash_entry["debit"]), 5000.0)
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 1)
def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
if validate_without_change_gle:
cash_amount -= pos.change_amount

View File

@@ -167,6 +167,7 @@ class ReceivablePayableReport:
party_account=ple.account,
posting_date=ple.posting_date,
account_currency=ple.account_currency,
cost_center=ple.cost_center,
remarks=ple.remarks,
invoiced=0.0,
paid=0.0,

View File

@@ -1175,6 +1175,28 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
row = report[1][0]
self.assertEqual(expected_data_after_payment, [row.voucher_no, row.cost_center, row.outstanding])
def test_cost_center_on_payment_before_invoice(self):
filters = {
"company": self.company,
"party_type": "Customer",
"party": [self.customer],
"report_date": today(),
"range": "30, 60, 90, 120",
}
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.posting_date = add_days(today(), 1)
si.due_date = si.posting_date
si.payment_schedule[0].due_date = si.posting_date
si.save().submit()
pe = self.create_payment_entry(si.name, do_not_submit=True)
pe.cost_center = self.cost_center
pe.save().submit()
row = next(row for row in execute(filters)[1] if row.voucher_no == pe.name)
self.assertEqual(row.cost_center, pe.cost_center)
def test_payment_terms_template_filters(self):
from erpnext.controllers.accounts_controller import get_payment_terms

View File

@@ -99,6 +99,7 @@ def execute(filters=None):
filters={
"account_type": row["account_type"],
"is_group": 0,
"company": filters.company,
},
pluck="name",
)

View File

@@ -180,13 +180,15 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
columns[0]["fieldname"] = "sales_invoice"
columns[0]["options"] = "Item"
columns[0]["width"] = 300
# removing Item Code and Item Name columns
# removing the duplicate Item Code column and moving Item Name before Customer
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")
if supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name":
del columns[4:6]
del columns[4]
columns.insert(1, columns.pop(4))
else:
del columns[5:7]
del columns[5]
columns.insert(1, columns.pop(5))
total_base_amount = 0
total_buying_amount = 0

View File

@@ -231,6 +231,7 @@ class PurchaseOrder(BuyingController):
self.doctype, self.supplier, self.company, self.inter_company_order_reference
)
self.reset_default_field_value("set_warehouse", "items", "warehouse")
self.set_missing_terms()
def set_has_unit_price_items(self):
"""

View File

@@ -889,7 +889,8 @@ class ProductionPlan(Document):
material_request_map = {}
for item in self.mr_items:
if not item.quantity:
qty_to_request = flt(item.quantity, item.precision("quantity"))
if qty_to_request <= 0:
continue
item_doc = frappe.get_cached_doc("Item", item.item_code)
@@ -925,7 +926,7 @@ class ProductionPlan(Document):
"from_warehouse": item.from_warehouse
if material_request_type == "Material Transfer"
else None,
"qty": item.quantity,
"qty": qty_to_request,
"schedule_date": schedule_date,
"warehouse": item.warehouse,
"sales_order": item.sales_order,
@@ -1339,18 +1340,44 @@ def get_material_request_items(
bin_dict,
consumed_qty,
):
required_qty = _required_qty_for_mr(
row, ignore_existing_ordered_qty, warehouse, bin_dict, consumed_qty, include_safety_stock
)
required_qty = 0
item_code = row.get("item_code")
if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0:
required_qty = flt(row.get("qty"))
else:
key = (item_code, warehouse)
available_qty = flt(bin_dict.get("projected_qty", 0)) - consumed_qty[key]
if available_qty > 0:
required_qty = max(0, flt(row.get("qty")) - available_qty)
consumed_qty[key] += min(flt(row.get("qty")), available_qty)
else:
required_qty = flt(row.get("qty"))
if doc.get("consider_minimum_order_qty") and required_qty > 0 and required_qty < row["min_order_qty"]:
required_qty = row["min_order_qty"]
item_group_defaults = get_item_group_defaults(row.item_code, company)
if not row["purchase_uom"]:
row["purchase_uom"] = row["stock_uom"]
if row["purchase_uom"] != row["stock_uom"]:
if not (row["conversion_factor"] or frappe.flags.show_qty_in_stock_uom):
frappe.throw(
_("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
row["purchase_uom"], row["stock_uom"], row.item_code
)
)
required_qty = required_qty / row["conversion_factor"]
if frappe.db.get_value("UOM", row["purchase_uom"], "must_be_whole_number"):
required_qty = ceil(required_qty)
if include_safety_stock:
required_qty += flt(row["safety_stock"])
item_details = frappe.get_cached_value("Item", row.item_code, ["purchase_uom", "stock_uom"], as_dict=1)
conversion_factor = 1.0
@@ -1458,38 +1485,6 @@ def get_sales_orders(self):
return open_so
def _required_qty_for_mr(
row, ignore_existing_ordered_qty, warehouse, bin_dict, consumed_qty, include_safety_stock
):
safety_stock = flt(row["safety_stock"]) if include_safety_stock else 0
qty = flt(row.get("qty"))
projected_qty = max(0, flt(bin_dict.get("projected_qty"))) if not ignore_existing_ordered_qty else 0
key = (row.get("item_code"), warehouse)
available_qty = projected_qty - consumed_qty[key]
required_qty = max(0, qty - (available_qty - safety_stock))
required_qty = _adjust_required_qty_for_uom(row, required_qty)
consumed_qty[key] += qty - required_qty
return required_qty
def _adjust_required_qty_for_uom(row, required_qty):
if not row["purchase_uom"]:
row["purchase_uom"] = row["stock_uom"]
if row["purchase_uom"] != row["stock_uom"]:
if not (row["conversion_factor"] or frappe.flags.show_qty_in_stock_uom):
frappe.throw(
_("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
row["purchase_uom"], row["stock_uom"], row.item_code
)
)
if frappe.db.get_value("UOM", row["purchase_uom"], "must_be_whole_number"):
required_qty = ceil(required_qty)
return required_qty
@frappe.whitelist()
def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
if isinstance(row, str):

View File

@@ -120,141 +120,23 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(len(quantities), len(pln.mr_items) - 1)
self.assertNotIn(0, quantities)
def _plan_for_safety_stock(self, rm_item, qty_per_order):
fg_item = make_item(properties={"is_stock_item": 1}).name
make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
def test_production_plan_material_request_skips_zero_qty_items(self):
pln = create_production_plan(item_code="Test Production Item 1")
zero_qty_item, requested_item = pln.mr_items
zero_qty_item.quantity = "0"
pln = create_production_plan(
item_code=fg_item,
ignore_existing_ordered_qty=0,
do_not_save=1,
skip_getting_mr_items=1,
pln.make_material_request()
material_request_items = frappe.get_all(
"Material Request Item",
filters={"production_plan": pln.name},
fields=["item_code", "qty"],
)
pln.get_items_from = "Sales Order"
for _ in range(2):
so = make_sales_order(item_code=fg_item, qty=qty_per_order)
pln.append(
"sales_orders",
{
"sales_order": so.name,
"sales_order_date": so.transaction_date,
"customer": so.customer,
"grand_total": so.grand_total,
},
)
pln.get_items()
return pln
def test_safety_stock_added_once_for_repeated_raw_material(self):
rm_item = make_item(properties={"is_stock_item": 1, "safety_stock": 10, "valuation_rate": 100}).name
make_stock_entry(item_code=rm_item, qty=100, rate=100, target="_Test Warehouse - _TC")
pln = self._plan_for_safety_stock(rm_item, qty_per_order=50)
pln.include_safety_stock = 1
items = get_items_for_material_requests(pln.as_dict())
quantities = sorted(flt(d.get("quantity")) for d in items if d.get("item_code") == rm_item)
self.assertEqual(quantities, [0, 10])
def test_safety_stock_added_once_with_negative_or_ignored_projected_qty(self):
from erpnext.stock.utils import get_or_make_bin
rm_item = make_item(properties={"is_stock_item": 1, "safety_stock": 100}).name
bin_name = get_or_make_bin(rm_item, "_Test Warehouse - _TC")
pln = self._plan_for_safety_stock(rm_item, qty_per_order=250)
pln.po_items[1].planned_qty = 1000
pln.include_safety_stock = 1
for projected_qty in (-5, 0, 200, 1500):
frappe.db.set_value("Bin", bin_name, "projected_qty", projected_qty)
for consider_projected_qty in (0, 1):
with self.subTest(projected_qty=projected_qty, consider_projected_qty=consider_projected_qty):
pln.ignore_existing_ordered_qty = not consider_projected_qty
items = get_items_for_material_requests(pln.as_dict())
expected_qty = [350, 1000]
if consider_projected_qty and projected_qty > 0:
expected_qty = [150, 1000] if projected_qty == 200 else [0, 0]
self.assertEqual([row["quantity"] for row in items], expected_qty)
self.assertEqual([row["required_bom_qty"] for row in items], [250, 1000])
self.assertEqual([row["safety_stock"] for row in items], [100, 100])
self.assertEqual(
[row["sales_order"] for row in items], [row.sales_order for row in pln.po_items]
)
def test_safety_stock_disabled_with_negative_projected_qty(self):
from erpnext.stock.utils import get_or_make_bin
rm_item = make_item(properties={"is_stock_item": 1, "safety_stock": 100}).name
bin_name = get_or_make_bin(rm_item, "_Test Warehouse - _TC")
frappe.db.set_value("Bin", bin_name, "projected_qty", -5)
pln = self._plan_for_safety_stock(rm_item, qty_per_order=250)
pln.po_items[1].planned_qty = 1000
items = get_items_for_material_requests(pln.as_dict())
self.assertEqual([row["quantity"] for row in items], [250, 1000])
def test_safety_stock_is_separate_for_each_item_and_warehouse(self):
from collections import defaultdict
from erpnext.manufacturing.doctype.production_plan.production_plan import (
_required_qty_for_mr,
self.assertEqual(
material_request_items,
[{"item_code": requested_item.item_code, "qty": requested_item.quantity}],
)
row = frappe._dict(qty=250, safety_stock=100, purchase_uom="Nos", stock_uom="Nos")
items_and_warehouses = [
("Raw Material Item 1", "_Test Warehouse - _TC"),
("Raw Material Item 1", "_Test Warehouse 1 - _TC"),
("Raw Material Item 2", "_Test Warehouse - _TC"),
]
for consider_projected_qty in (0, 1):
with self.subTest(consider_projected_qty=consider_projected_qty):
consumed_qty = defaultdict(float)
quantities = []
for item_code, warehouse in items_and_warehouses * 2:
row.item_code = item_code
quantities.append(
_required_qty_for_mr(
row,
not consider_projected_qty,
warehouse,
{"projected_qty": -5},
consumed_qty,
True,
)
)
self.assertEqual(quantities, [350, 350, 350, 250, 250, 250])
def test_safety_stock_added_once_before_transferring_materials(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_or_make_bin
rm_item = make_item(properties={"is_stock_item": 1, "safety_stock": 100, "min_order_qty": 1234}).name
source_warehouse = create_warehouse("Safety Stock Source Warehouse", company="_Test Company")
make_stock_entry(item_code=rm_item, qty=2000, rate=100, target=source_warehouse)
bin_name = get_or_make_bin(rm_item, "_Test Warehouse - _TC")
frappe.db.set_value("Bin", bin_name, "projected_qty", -5)
pln = self._plan_for_safety_stock(rm_item, qty_per_order=250)
pln.po_items[1].planned_qty = 1000
pln.for_warehouse = "_Test Warehouse - _TC"
pln.include_safety_stock = 1
items = get_items_for_material_requests(pln.as_dict(), warehouses=[{"warehouse": source_warehouse}])
self.assertEqual([row["material_request_type"] for row in items], ["Material Transfer"] * 2)
self.assertEqual([row["quantity"] for row in items], [350, 1000])
def test_safety_stock_with_fractional_minimum_uses_whole_purchase_uom(self):
rm_item = make_item(
properties={"is_stock_item": 1, "stock_uom": "Nos", "safety_stock": 0.5, "min_order_qty": 2.5}
).name
pln = self._plan_for_safety_stock(rm_item, qty_per_order=1)
pln.set("po_items", [pln.po_items[0]])
pln.include_safety_stock = 1
pln.consider_minimum_order_qty = 1
items = get_items_for_material_requests(pln.as_dict())
self.assertEqual(len(items), 1)
self.assertEqual(items[0]["quantity"], 3)
def test_production_plan_start_date(self):
"Test if Work Order has same Planned Start Date as Prod Plan."
planned_date = add_to_date(date=None, days=3)

View File

@@ -602,6 +602,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
},
callback: (r) => {
const doc = frappe.model.sync(r.message);
track_voucher(doc[0].doctype, doc[0].name, this.bank_transaction.name);
frappe.set_route("Form", doc[0].doctype, doc[0].name);
},
});
@@ -622,9 +623,77 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
},
callback: (r) => {
var doc = frappe.model.sync(r.message);
track_voucher(doc[0].doctype, doc[0].name, this.bank_transaction.name);
frappe.set_route("Form", doc[0].doctype, doc[0].name);
},
});
}
}
};
const pending_reconciliations = new Map();
const voucher_key = (doctype, docname) => `${doctype}:${docname}`;
const track_voucher = (doctype, docname, bank_transaction_name) => {
pending_reconciliations.set(voucher_key(doctype, docname), bank_transaction_name);
};
for (const voucher_doctype of ["Payment Entry", "Journal Entry"]) {
frappe.ui.form.on(voucher_doctype, {
before_save(frm) {
frm.__pending_reconciliation_key = voucher_key(frm.doctype, frm.doc.name);
},
after_save(frm) {
const old_key = frm.__pending_reconciliation_key;
delete frm.__pending_reconciliation_key;
const new_key = voucher_key(frm.doctype, frm.doc.name);
if (!old_key || old_key === new_key || !pending_reconciliations.has(old_key)) return;
// Follow the rename so the voucher stays identifiable on submit
pending_reconciliations.set(new_key, pending_reconciliations.get(old_key));
pending_reconciliations.delete(old_key);
},
on_submit(frm) {
const key = voucher_key(frm.doctype, frm.doc.name);
const bank_transaction_name = pending_reconciliations.get(key);
if (!bank_transaction_name) return;
pending_reconciliations.delete(key);
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.reconcile_vouchers",
args: {
bank_transaction_name: bank_transaction_name,
vouchers: [
{
payment_doctype: frm.doctype,
payment_name: frm.doc.name,
},
],
is_new_voucher: true,
},
callback: (r) => {
if (r.exc) return;
frappe.show_alert({
message: __("Bank Transaction {0} Matched", [bank_transaction_name]),
indicator: "green",
});
},
error: () => {
frappe.msgprint({
title: __("Reconciliation Failed"),
indicator: "red",
message: __(
"{0} {1} was submitted but could not be reconciled against Bank Transaction {2}. Match it manually from the Bank Reconciliation Tool.",
[__(frm.doctype), frm.doc.name, bank_transaction_name]
),
});
},
});
},
});
}

View File

@@ -695,7 +695,10 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
}
// sales invoice
if (flt(doc.per_billed) < 100 && frappe.model.can_create("Sales Invoice")) {
if (
doc.__onload?.has_potentially_billable_items &&
frappe.model.can_create("Sales Invoice")
) {
this.frm.add_custom_button(
__("Sales Invoice"),
() => me.make_sales_invoice(),

View File

@@ -13,8 +13,10 @@ from frappe.desk.notifications import clear_doctype_notifications
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Sum
from frappe.query_builder import Case, Criterion
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
from pypika import Order
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
unlink_inter_company_doc,
@@ -22,6 +24,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
validate_inter_company_party,
)
from erpnext.accounts.party import CROSS_PARTY_FIELD_NO_MAP, get_party_account
from erpnext.accounts.utils import build_qb_match_conditions
from erpnext.controllers.mapper import get_qty_already_mapped
from erpnext.controllers.selling_controller import SellingController
from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
@@ -42,6 +45,18 @@ from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
LINK_SEARCH_FIELDTYPES = {
"Autocomplete",
"Data",
"Link",
"Long Text",
"Read Only",
"Select",
"Small Text",
"Text",
"Text Editor",
}
class WarehouseRequired(frappe.ValidationError):
pass
@@ -207,6 +222,12 @@ class SalesOrder(SellingController):
if has_reserved_stock(self.doctype, self.name):
self.set_onload("has_reserved_stock", True)
if self.docstatus == 1 and self.status not in {"Closed", "On Hold"}:
self.set_onload(
"has_potentially_billable_items",
has_potentially_billable_items(self.name),
)
def before_validate(self):
self.set_has_unit_price_items()
self.flags.allow_zero_qty = self.has_unit_price_items
@@ -1142,11 +1163,23 @@ def make_sales_invoice(
has_unit_price_items = frappe.db.get_value("Sales Order", source_name, "has_unit_price_items")
billed_qty_by_item = None
pending_qty_by_item = {}
amount_allowance_by_item = {}
mapped_qty_by_item = get_qty_already_mapped(target_doc, "so_detail")
def is_unit_price_row(source):
return has_unit_price_items and source.qty == 0
def is_amount_billable(source):
from erpnext.controllers.status_updater import get_allowance_for
if source.item_code not in amount_allowance_by_item:
amount_allowance_by_item[source.item_code] = flt(
get_allowance_for(source.item_code, qty_or_amount="amount")[0]
)
allowance = amount_allowance_by_item[source.item_code]
return abs(flt(source.billed_amt)) < abs(flt(source.amount)) * (1 + allowance / 100)
def get_billed_qty_by_item():
nonlocal billed_qty_by_item
@@ -1168,9 +1201,7 @@ def make_sales_invoice(
def get_pending_qty(source):
if source.name not in pending_qty_by_item:
billable_qty = get_qty_net_of_returns(source)
if source.qty and source.billed_amt:
billable_qty -= get_billed_qty_by_item().get(source.name, 0)
billable_qty -= get_billed_qty_by_item().get(source.name, 0)
billable_qty -= mapped_qty_by_item.get(source.name, 0)
pending_qty_by_item[source.name] = max(flt(billable_qty, source.precision("qty")), 0)
@@ -1255,7 +1286,7 @@ def make_sales_invoice(
if is_unit_price_row(doc)
else (
doc.qty
and (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount))
and (doc.base_amount == 0 or is_amount_billable(doc))
and get_pending_qty(doc) > 0
)
),
@@ -1940,3 +1971,87 @@ def get_work_order_items(sales_order, for_raw_material_request=0):
@frappe.whitelist()
def get_stock_reservation_status():
return frappe.db.get_single_value("Stock Settings", "enable_stock_reservation")
def get_potentially_billable_item_criterion(sales_order, sales_order_item, item):
"""Return the amount check for UI candidates. The mapper checks pending quantity."""
global_allowance = flt(frappe.get_cached_value("Accounts Settings", None, "over_billing_allowance"))
allowance = (
Case().when(item.over_billing_allowance != 0, item.over_billing_allowance).else_(global_allowance)
)
has_amount_headroom = (sales_order_item.base_amount == 0) | (
Abs(sales_order_item.billed_amt) < Abs(sales_order_item.amount) * (1 + allowance / 100)
)
is_unit_price_row = (sales_order.has_unit_price_items == 1) & (sales_order_item.qty == 0)
return is_unit_price_row | ((sales_order_item.qty != 0) & has_amount_headroom)
def has_potentially_billable_items(sales_order: str) -> bool:
"""Return whether a Sales Order has an item with billing amount headroom."""
so = qb.DocType("Sales Order")
so_item = qb.DocType("Sales Order Item")
item = qb.DocType("Item")
return bool(
qb.from_(so_item)
.inner_join(so)
.on(so.name == so_item.parent)
.left_join(item)
.on(item.name == so_item.item_code)
.select(so_item.name)
.where((so_item.parent == sales_order) & get_potentially_billable_item_criterion(so, so_item, item))
.limit(1)
.run()
)
def get_text_search_criterion(sales_order, txt: str):
"""Match the search text the way the Sales Order link search does."""
meta = frappe.get_meta("Sales Order")
conditions = []
for fieldname in dict.fromkeys(["name", meta.title_field, *meta.get_search_fields()]):
if not fieldname:
continue
field = meta.get_field(fieldname)
if fieldname == "name" or (field and field.fieldtype in LINK_SEARCH_FIELDTYPES):
conditions.append(sales_order[fieldname].like(f"%{txt}%"))
return Criterion.any(conditions)
@frappe.whitelist(methods=["GET"])
@frappe.validate_and_sanitize_search_inputs
def get_potentially_billable_sales_orders(
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
):
"""Return Sales Orders that have an item with billing amount headroom."""
permission_type = "select" if frappe.only_has_select_perm("Sales Order") else "read"
frappe.has_permission("Sales Order", permission_type, throw=True)
so = qb.DocType("Sales Order")
so_item = qb.DocType("Sales Order Item")
item = qb.DocType("Item")
query = frappe.qb.get_query(
"Sales Order", fields=["name", "customer", "transaction_date"], filters=filters
)
if txt:
query = query.where(get_text_search_criterion(so, txt))
return (
query.inner_join(so_item)
.on(so_item.parent == so.name)
.left_join(item)
.on(item.name == so_item.item_code)
.where(get_potentially_billable_item_criterion(so, so_item, item))
.where(Criterion.all(build_qb_match_conditions("Sales Order")))
.distinct()
.orderby(so.transaction_date, order=Order.desc)
.limit(cint(page_len))
.offset(cint(start))
.run(as_dict=True)
)

View File

@@ -22,6 +22,8 @@ from erpnext.selling.doctype.product_bundle.test_product_bundle import make_prod
from erpnext.selling.doctype.sales_order.sales_order import (
WarehouseRequired,
create_pick_list,
get_potentially_billable_sales_orders,
has_potentially_billable_items,
make_delivery_note,
make_material_request,
make_purchase_order,
@@ -226,6 +228,68 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
si1 = make_sales_invoice(so.name)
self.assertEqual(len(si1.get("items")), 0)
def test_make_sales_invoice_for_pending_qty_with_item_billing_allowance(self):
item = make_item(
"_Test Over Billed Pending Qty Item",
{"is_stock_item": 1, "over_billing_allowance": 0},
).name
so = make_sales_order(item_code=item, qty=390, rate=100)
for _ in range(2):
si = make_sales_invoice(so.name)
si.get("items")[0].qty = 120
si.get("items")[0].rate = 162.50
si.insert()
si.submit()
so.load_from_db()
self.assertEqual(flt(so.per_billed), 100)
self.assertEqual(so.get("items")[0].billed_amt, so.get("items")[0].amount)
filters = {"docstatus": 1, "company": so.company, "customer": so.customer}
def is_offered(txt=""):
rows = get_potentially_billable_sales_orders("Sales Order", txt, "name", 0, 50, filters)
return so.name in [row.name for row in rows]
def forget_cached_allowances():
frappe.local.request_cache.clear()
with change_settings("Accounts Settings", {"over_billing_allowance": 100}):
forget_cached_allowances()
self.assertTrue(has_potentially_billable_items(so.name))
self.assertTrue(is_offered())
self.assertEqual(make_sales_invoice(so.name).get("items")[0].qty, 150)
with change_settings("Accounts Settings", {"over_billing_allowance": 0}):
forget_cached_allowances()
self.assertFalse(has_potentially_billable_items(so.name))
self.assertEqual(len(make_sales_invoice(so.name).get("items")), 0)
frappe.db.set_value("Item", item, "over_billing_allowance", 100)
forget_cached_allowances()
so.run_method("onload")
self.assertTrue(so.get_onload("has_potentially_billable_items"))
self.assertTrue(is_offered(so.customer))
si = make_sales_invoice(so.name)
self.assertEqual(len(si.get("items")), 1)
self.assertEqual(si.get("items")[0].qty, 150)
def test_make_sales_invoice_skips_fully_invoiced_free_item(self):
free_item = make_item("_Test Free Item", {"is_stock_item": 1}).name
so = make_sales_order(qty=10, rate=100, do_not_submit=True)
so.append("items", {"item_code": free_item, "qty": 5, "rate": 0, "warehouse": so.items[0].warehouse})
so.submit()
si = make_sales_invoice(so.name)
self.assertEqual([row.qty for row in si.items], [10, 5])
si.insert()
si.submit()
self.assertEqual(len(make_sales_invoice(so.name).items), 0)
def test_make_sales_invoice_after_return_and_redelivery(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return

View File

@@ -740,6 +740,9 @@ class DeliveryNote(SellingController):
def update_billing_status(self, update_modified=True):
updated_delivery_notes = [self.name]
if self.is_return and self.return_against:
updated_delivery_notes.append(self.return_against)
for d in self.get("items"):
if d.si_detail and not d.so_detail:
d.db_set("billed_amt", d.amount, update_modified=update_modified)
@@ -748,7 +751,12 @@ class DeliveryNote(SellingController):
for dn in set(updated_delivery_notes):
dn_doc = self if (dn == self.name) else frappe.get_doc("Delivery Note", dn)
dn_doc.update_billing_percentage(update_modified=update_modified)
update_dn_modified = update_modified and dn != self.return_against
dn_doc.update_billing_percentage(update_modified=update_dn_modified)
if dn == self.return_against:
dn_doc.load_from_db()
dn_doc.set_status(update=True, update_modified=False)
dn_doc.notify_update()
self.load_from_db()

View File

@@ -1049,6 +1049,56 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.per_billed, 100)
self.assertEqual(dn.status, "Completed")
def test_dn_is_completed_when_unbilled_item_is_returned(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
make_stock_entry(target="_Test Warehouse - _TC", qty=1, basic_rate=100)
make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=1, basic_rate=100)
dn = create_delivery_note(do_not_submit=True)
dn.append(
"items",
{
"item_code": "_Test Item 2",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 100,
"conversion_factor": 1,
"allow_zero_valuation_rate": 1,
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
dn.submit()
si = make_sales_invoice(dn.name)
si.set("items", [item for item in si.items if item.item_code == "_Test Item"])
si.insert()
si.submit()
dn.reload()
self.assertEqual(dn.per_billed, 50)
self.assertEqual(dn.status, "Partially Billed")
return_dn = make_sales_return(dn.name)
return_dn.set("items", [item for item in return_dn.items if item.item_code == "_Test Item 2"])
return_dn.insert()
# Mimic the submit request, which reconstructs the document from client data.
return_dn = frappe.get_doc(return_dn.as_dict())
return_dn.submit()
dn.reload()
self.assertEqual(dn.items[1].returned_qty, 1)
self.assertEqual(dn.per_billed, 100)
self.assertEqual(dn.status, "Completed")
return_dn.cancel()
dn.reload()
self.assertEqual(dn.items[1].returned_qty, 0)
self.assertEqual(dn.per_billed, 50)
self.assertEqual(dn.status, "Partially Billed")
def test_dn_billing_status_case2(self):
# SO -> SI and SO -> DN1, DN2
from erpnext.selling.doctype.sales_order.sales_order import (

View File

@@ -796,6 +796,7 @@
"fieldname": "over_delivery_receipt_allowance",
"fieldtype": "Float",
"label": "Over Delivery/Receipt Allowance (%)",
"non_negative": 1,
"oldfieldname": "tolerance",
"oldfieldtype": "Currency"
},
@@ -803,7 +804,8 @@
"depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
"fieldname": "over_billing_allowance",
"fieldtype": "Float",
"label": "Over Billing Allowance (%)"
"label": "Over Billing Allowance (%)",
"non_negative": 1
},
{
"default": "0",
@@ -898,7 +900,7 @@
"image_field": "image",
"links": [],
"make_attachments_public": 1,
"modified": "2026-07-05 23:24:45.734144",
"modified": "2026-09-04 10:08:30.115003",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -1734,7 +1734,7 @@ class TestPickList(FrappeTestCase):
stock_entry.cancel()
def test_packed_item_in_pick_list(self):
warehouse_1 = "RJ Warehouse - _TC"
warehouse_1 = "_Test Warehouse - _TC"
warehouse_2 = "_Test Warehouse 2 - _TC"
item_1 = make_item(properties={"is_stock_item": 0}).name
item_2 = make_item().name
@@ -1765,7 +1765,7 @@ class TestPickList(FrappeTestCase):
def test_packed_item_multiple_times_in_so(self):
frappe.db.delete("Item Price")
warehouse_1 = "RJ Warehouse - _TC"
warehouse_1 = "_Test Warehouse - _TC"
warehouse_2 = "_Test Warehouse 2 - _TC"
warehouse = "_Test Warehouse - _TC"
item_1 = make_item(properties={"is_stock_item": 0}).name

View File

@@ -192,7 +192,11 @@ def get_rate_locked_source_row(args, doc):
if not source_fields or not doc or args.get("is_return") or not maintain_same_rate_enabled(args):
return None
row = next((d for d in doc.get("items") or [] if d.get("name") == args.child_docname), None)
row = (
next((d for d in doc.get("items") or [] if d.get("name") == args.child_docname), None)
if args.child_docname
else args
)
if not row:
return None

View File

@@ -422,3 +422,65 @@ class TestGetItemDetail(FrappeTestCase):
frappe.set_user("Administrator")
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", original)
frappe.clear_cache(doctype="Buying Settings")
def test_rate_lock_keeps_each_rows_rate_for_batch_items(self):
"""Batch rows mapped PR->PI must each keep their own rate, not collapse onto the first."""
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
get_batch_from_bundle,
)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
original = frappe.db.get_single_value("Buying Settings", "maintain_same_rate")
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
frappe.clear_cache(doctype="Buying Settings")
self.addCleanup(frappe.clear_cache, doctype="Buying Settings")
self.addCleanup(frappe.db.set_single_value, "Buying Settings", "maintain_same_rate", original)
def batched_item():
code = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "FBQ-.#####",
}
).name
se = make_stock_entry(item_code=code, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
return code, get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
item_a, batch_a = batched_item()
item_b, batch_b = batched_item()
# one PO with both items at different rates
po = create_purchase_order(item_code=item_a, qty=5, rate=28, do_not_save=True)
po.append(
"items",
{
"item_code": item_b,
"qty": 5,
"rate": 275,
"warehouse": "_Test Warehouse - _TC",
"schedule_date": frappe.utils.nowdate(),
},
)
po.set_missing_values()
po.insert()
po.submit()
# receive both against their own batches
pr = make_purchase_receipt(po.name)
for row in pr.items:
row.use_serial_batch_fields = 1
pr.items[0].batch_no = batch_a
pr.items[1].batch_no = batch_b
pr.insert()
pr.submit()
# the batch_no branch force-writes the fetched rate during mapping
pi = make_purchase_invoice(pr.name)
self.assertEqual(pi.items[0].rate, 28)
self.assertEqual(pi.items[1].rate, 275) # used to collapse onto the first row (28)