Compare commits

..

35 Commits

Author SHA1 Message Date
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
21 changed files with 418 additions and 248 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.0"
def get_default_company(user=None):

View File

@@ -304,9 +304,6 @@ def get_payment_entries(filters, args):
pe.mode_of_payment,
pe.project,
pe.cost_center,
pe.payment_type,
pe.source_exchange_rate,
pe.target_exchange_rate,
)
.where(
(pe.docstatus == 1)
@@ -317,22 +314,6 @@ def get_payment_entries(filters, args):
)
query = apply_common_conditions(filters, query, doctype="Payment Entry", payments=True)
payment_entries = query.run(as_dict=True)
if payment_entries:
ded = frappe.qb.DocType("Payment Entry Deduction")
deduction_totals = frappe._dict(
frappe.qb.from_(ded)
.select(ded.parent, Sum(ded.amount))
.where(ded.parent.isin([d.name for d in payment_entries]) & (ded.is_exchange_gain_loss == 0))
.groupby(ded.parent)
.run()
)
for d in payment_entries:
exchange_rate = (
d.source_exchange_rate if d.payment_type == "Receive" else d.target_exchange_rate
) or 1
d.base_grand_total = flt(d.base_grand_total) + flt(deduction_totals.get(d.name)) / exchange_rate
return payment_entries

View File

@@ -29,18 +29,11 @@ class EmailCampaign(Document):
def validate(self):
self.set_date()
self.validate_recipient_email()
self.validate_email_campaign_already_exists()
self.update_status()
def validate_recipient_email(self):
if not self.recipient:
return
# checking if email is set for lead. Not checking for contact as email is a mandatory field for contact.
if self.email_campaign_for == "Lead":
self.validate_lead()
elif self.email_campaign_for == "Contact":
self.validate_contact()
self.validate_email_campaign_already_exists()
self.update_status()
def set_date(self):
if getdate(self.start_date) < getdate(today()):
@@ -63,13 +56,6 @@ class EmailCampaign(Document):
lead_name = frappe.db.get_value("Lead", self.recipient, "lead_name")
frappe.throw(_("Please set an email id for the Lead {0}").format(lead_name))
def validate_contact(self):
contact = frappe.db.get_value("Contact", self.recipient, ["email_id", "full_name"], as_dict=True)
if contact and not contact.email_id:
frappe.throw(
_("Please set a primary email ID for the Contact {0}").format(frappe.bold(contact.full_name))
)
def validate_email_campaign_already_exists(self):
email_campaign_exists = frappe.db.exists(
"Email Campaign",

View File

@@ -1,84 +1,9 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
import frappe
from frappe.utils import add_days, getdate, today
class TestEmailCampaign(unittest.TestCase):
"""Email Campaign derives its window from the linked Campaign schedule and
guards the start date and the recipient's email."""
def setUp(self):
frappe.set_user("Administrator")
def make_email_template(self):
name = "_Test EC Email Template"
if not frappe.db.exists("Email Template", name):
frappe.get_doc(
{"doctype": "Email Template", "name": name, "subject": "Test", "response": "Hello"}
).insert()
return name
def make_campaign(self, schedules):
campaign = frappe.new_doc("Campaign")
campaign.campaign_name = f"_Test EC Campaign {frappe.generate_hash(length=6)}"
for days in schedules:
campaign.append(
"campaign_schedules",
{"send_after_days": days, "email_template": self.make_email_template()},
)
return campaign.insert()
def make_email_campaign(self, campaign_name, start_date=None):
doc = frappe.new_doc("Email Campaign")
doc.campaign_name = campaign_name
doc.start_date = start_date or today()
return doc
def test_start_date_cannot_be_in_the_past(self):
doc = self.make_email_campaign("irrelevant", start_date=add_days(today(), -1))
self.assertRaises(frappe.ValidationError, doc.set_date)
def test_end_date_is_start_plus_max_send_after_days(self):
campaign = self.make_campaign(schedules=[0, 5])
doc = self.make_email_campaign(campaign.name)
doc.set_date()
self.assertEqual(getdate(doc.end_date), add_days(getdate(today()), 5))
def test_campaign_without_a_schedule_is_rejected(self):
campaign = self.make_campaign(schedules=[])
doc = self.make_email_campaign(campaign.name)
self.assertRaises(frappe.ValidationError, doc.set_date)
def test_lead_without_an_email_is_rejected(self):
lead = frappe.get_doc({"doctype": "Lead", "lead_name": "_Test Lead No Email"}).insert()
doc = frappe.new_doc("Email Campaign")
doc.email_campaign_for = "Lead"
doc.recipient = lead.name
self.assertRaises(frappe.ValidationError, doc.validate_lead)
def test_contact_without_an_email_is_rejected(self):
contact = frappe.get_doc({"doctype": "Contact", "first_name": "_Test Contact No Email"}).insert()
campaign = self.make_campaign(schedules=[0])
doc = self.make_email_campaign(campaign.name)
doc.email_campaign_for = "Contact"
doc.recipient = contact.name
self.assertRaisesRegex(frappe.ValidationError, "primary email ID", doc.insert)
def test_contact_with_an_email_is_accepted(self):
contact = frappe.get_doc(
{
"doctype": "Contact",
"first_name": "_Test Contact With Email",
"email_ids": [{"email_id": "_test_email_campaign@example.com", "is_primary": 1}],
}
).insert()
campaign = self.make_campaign(schedules=[0])
doc = self.make_email_campaign(campaign.name)
doc.email_campaign_for = "Contact"
doc.recipient = contact.name
doc.insert()
self.assertEqual(doc.status, "In Progress")
pass

View File

@@ -516,11 +516,8 @@ def get_lead_with_phone_number(number):
return lead
@frappe.whitelist(methods=["POST"])
def add_lead_to_prospect(lead: str, prospect: str):
if lead:
frappe.has_permission("Lead", "read", lead, throw=True)
@frappe.whitelist()
def add_lead_to_prospect(lead, prospect):
prospect = frappe.get_doc("Prospect", prospect)
prospect.append("leads", {"lead": lead})
prospect.save()

View File

@@ -206,6 +206,43 @@ class TestJobCard(FrappeTestCase):
# transfer was made for 2 fg qty in first transfer Stock Entry
self.assertEqual(transfer_entry_2.fg_completed_qty, 0)
def test_material_request_stock_entry_uses_job_card_coverage(self):
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
self.transfer_material_against = "Job Card"
self.source_warehouse = "Stores - _TC"
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
mr = make_material_request(job_card.name)
mr.schedule_date = today()
for row in mr.items:
row.qty = flt(row.qty) / 2
row.stock_qty = flt(row.stock_qty) / 2
mr.submit()
stock_entry = make_stock_entry(mr.name)
self.assertEqual(stock_entry.fg_completed_qty, job_card.for_quantity / 2)
selected_row = mr.items[0]
try:
frappe.flags.selected_children = {"items": [selected_row.name]}
selected_stock_entry = make_stock_entry(mr.name)
finally:
frappe.flags.selected_children = None
self.assertEqual(
[row.job_card_item for row in selected_stock_entry.items], [selected_row.job_card_item]
)
self.assertEqual(selected_stock_entry.fg_completed_qty, 0)
for row in mr.items:
transferred_qty = flt(row.stock_qty) / 2
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", transferred_qty)
frappe.db.set_value(row.doctype, row.name, "ordered_qty", transferred_qty)
mr.reload()
repeated_stock_entry = make_stock_entry(mr.name)
self.assertEqual(repeated_stock_entry.fg_completed_qty, job_card.for_quantity / 4)
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
def test_job_card_excess_material_transfer(self):
"Test transferring more than required RM against Job Card."
@@ -616,6 +653,7 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(ste.job_card, job_card_name)
self.assertEqual(ste.from_bom, 1.0)
self.assertEqual(ste.bom_no, work_order.bom_no)
self.assertEqual(ste.fg_completed_qty, frappe.get_value("Job Card", job_card_name, "for_quantity"))
def test_job_card_proccess_qty_and_completed_qty(self):
from erpnext.manufacturing.doctype.routing.test_routing import (

View File

@@ -887,9 +887,6 @@ class ProductionPlan(Document):
material_request_map = {}
for item in self.mr_items:
if not item.quantity:
continue
item_doc = frappe.get_cached_doc("Item", item.item_code)
material_request_type = item.material_request_type or item_doc.default_material_request_type

View File

@@ -107,19 +107,6 @@ class TestProductionPlan(FrappeTestCase):
pln = frappe.get_doc("Production Plan", pln.name)
pln.cancel()
def test_production_plan_mr_creation_skips_zero_qty(self):
pln = create_production_plan(item_code="Test Production Item 1", do_not_submit=1)
pln.mr_items[0].quantity = 0
pln.save().submit()
pln.make_material_request()
quantities = frappe.get_all(
"Material Request Item", filters={"production_plan": pln.name}, pluck="qty"
)
self.assertEqual(len(quantities), len(pln.mr_items) - 1)
self.assertNotIn(0, quantities)
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

@@ -0,0 +1 @@

View File

@@ -0,0 +1,22 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from collections.abc import Mapping
from frappe.utils import flt
def get_minimum_material_coverage_fraction(
required_qty: Mapping[str, float], transferred_qty: Mapping[str, float], precision: int
) -> float:
"""Return the least-covered component ratio at the configured quantity precision."""
coverage = []
for item_code, required in required_qty.items():
transferred = flt(transferred_qty.get(item_code))
# Stored values can differ after the digits that the user can enter or see.
if flt(transferred, precision) == flt(required, precision):
coverage.append(1.0)
else:
coverage.append(transferred / required)
return min(coverage, default=0.0)

View File

@@ -1461,9 +1461,11 @@ class TestWorkOrder(FrappeTestCase):
del transfer_entry.get("items")[0] # transfer only one RM
transfer_entry.submit()
# WO's "Material Transferred for Mfg" shows all is transferred, one RM is pending
# One required item is still missing, so no finished-good quantity is covered yet.
work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
self.assertEqual(transfer_entry.fg_completed_qty, 0)
self.assertEqual(work_order.material_transferred_for_manufacturing, 0)
self.assertEqual(work_order.status, "In Process")
self.assertEqual(work_order.required_items[0].transferred_qty, 0)
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
@@ -1483,6 +1485,47 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(work_order.required_items[0].transferred_qty, 1)
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
def test_material_transfer_claim_follows_actual_coverage(self):
work_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
test_stock_entry.make_stock_entry(
item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0
)
test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=20,
basic_rate=1000.0,
)
transfer_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 4)
)
for row in transfer_entry.items:
if row.item_code == "_Test Item":
row.qty = 1
transfer_entry.submit()
work_order.reload()
self.assertEqual(transfer_entry.fg_completed_qty, 1)
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
remainder_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 3)
)
remainder_entry.submit()
work_order.reload()
self.assertEqual(remainder_entry.fg_completed_qty, 3)
self.assertEqual(work_order.material_transferred_for_manufacturing, 4)
def test_material_coverage_cap_skips_manufacture_entry(self):
work_order = make_wo_order_test_record(planned_start_date=now(), qty=1)
manufacture_entry = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
manufacture_entry.pro_doc = work_order
manufacture_entry._action = "submit"
self.assertFalse(manufacture_entry._should_cap_completed_qty())
def test_material_transferred_min_fraction_on_partial_pick_list(self):
"""Pick-list flow (fg_completed_qty = 0): 'Material Transferred for Manufacturing'
must reflect the least-transferred required item (the bottleneck), instead of being
@@ -1545,6 +1588,97 @@ class TestWorkOrder(FrappeTestCase):
work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, 2.0)
def test_material_transferred_ignores_hidden_precision_difference(self):
work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
test_stock_entry.make_stock_entry(
item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0
)
test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=10,
basic_rate=1000.0,
)
precision = work_order.precision("required_qty", "required_items")
hidden_difference = 4 / (10 ** (precision + 1))
row = work_order.required_items[0]
row.db_set("required_qty", flt(row.required_qty) + hidden_difference, update_modified=False)
work_order.reload()
required_qty = {row.item_code: flt(row.required_qty) for row in work_order.required_items}
transfer_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 0)
)
for item in transfer_entry.items:
item.qty = flt(required_qty[item.item_code], precision)
item.transfer_qty = item.qty
transfer_entry.submit()
work_order.reload()
self.assertEqual(
flt(work_order.required_items[0].required_qty, precision),
flt(work_order.required_items[0].transferred_qty, precision),
)
self.assertEqual(work_order.material_transferred_for_manufacturing, work_order.qty)
def test_repair_material_transfer_precision_patch(self):
from erpnext.patches.v16_0.repair_work_order_material_transfer import (
execute,
get_precision_affected_work_orders,
)
precision = frappe.get_precision("Work Order Item", "required_qty")
hidden_difference = 4 / (10 ** (precision + 1))
work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for index, row in enumerate(work_order.required_items):
required_qty = flt(row.required_qty) + (hidden_difference if index == 0 else 0)
row.db_set(
{
"required_qty": required_qty,
"transferred_qty": flt(required_qty, precision),
},
update_modified=False,
)
work_order.db_set("material_transferred_for_manufacturing", 1.99, update_modified=False)
partial_work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for row in partial_work_order.required_items:
row.db_set("transferred_qty", row.required_qty, update_modified=False)
partial_row = partial_work_order.required_items[0]
partial_row.db_set(
"transferred_qty",
flt(partial_row.required_qty, precision) - (1 / (10**precision)),
update_modified=False,
)
partial_work_order.db_set("material_transferred_for_manufacturing", 1.99, update_modified=False)
terminal_work_orders = []
for status in ("Stopped", "Closed", "Completed"):
terminal_work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for row in terminal_work_order.required_items:
row.db_set("transferred_qty", row.required_qty, update_modified=False)
terminal_work_order.db_set(
{"material_transferred_for_manufacturing": 1.99, "status": status},
update_modified=False,
)
terminal_work_orders.append(terminal_work_order)
updates = get_precision_affected_work_orders()
self.assertIn(work_order.name, updates)
self.assertNotIn(partial_work_order.name, updates)
for terminal_work_order in terminal_work_orders:
self.assertNotIn(terminal_work_order.name, updates)
execute()
work_order.reload()
partial_work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, work_order.qty)
self.assertEqual(partial_work_order.material_transferred_for_manufacturing, 1.99)
for terminal_work_order in terminal_work_orders:
terminal_work_order.reload()
self.assertEqual(terminal_work_order.material_transferred_for_manufacturing, 1.99)
def test_status_in_process_when_only_one_required_item_transferred(self):
"""Stock Entry created from a Pick List that picked only one of the required items:
min-fraction keeps material_transferred_for_manufacturing at 0, but the work order must

View File

@@ -31,6 +31,9 @@ from erpnext.manufacturing.doctype.bom.bom import (
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import (
get_mins_between_operations,
)
from erpnext.manufacturing.doctype.work_order.services.material_coverage import (
get_minimum_material_coverage_fraction,
)
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.batch.batch import make_batch
from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
@@ -458,8 +461,7 @@ class WorkOrder(Document):
return status
def has_transferred_material(self):
"""True if any raw material was transferred against this work order via a pick list
(these leave material_transferred_for_manufacturing at 0 via the min-fraction rule)."""
"""True if any raw material was transferred against this work order."""
ste = frappe.qb.DocType("Stock Entry")
ste_child = frappe.qb.DocType("Stock Entry Detail")
qty = (
@@ -472,7 +474,6 @@ class WorkOrder(Document):
& (ste.docstatus == 1)
& (ste.purpose == "Material Transfer for Manufacture")
& (ste.is_return == 0)
& (ste.pick_list.isnotnull())
)
).run()[0][0]
return flt(qty) > 0
@@ -1303,20 +1304,13 @@ class WorkOrder(Document):
self.recompute_material_transferred_for_manufacturing(transferred_items)
def recompute_material_transferred_for_manufacturing(self, transferred_items):
"""Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty."""
"""Set transferred quantity from the raw materials that have actually moved."""
# Job Card transfers use the minimum completed quantity across operations.
if self.operations and self.transfer_material_against == "Job Card":
return
# When fg_completed_qty > 0 (direct stock entries, excess transfer), preserve the
# SUM(fg_completed_qty) approach so excess-transfer tracking works correctly.
sum_fg_completed_qty = self.get_transferred_or_manufactured_qty("Material Transfer for Manufacture")
if sum_fg_completed_qty:
self.db_set("material_transferred_for_manufacturing", sum_fg_completed_qty)
return
claimed_qty = self.get_transferred_or_manufactured_qty("Material Transfer for Manufacture")
# Pick list flow sets fg_completed_qty=0; use min-fraction of actual item transfers
# so partial availability does not prematurely mark the work order as fully transferred.
required_by_item = {}
for row in self.required_items:
if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0:
@@ -1326,12 +1320,13 @@ class WorkOrder(Document):
if not required_by_item:
return
min_fraction = min(
flt(transferred_items.get(item_code) or 0) / required_qty
for item_code, required_qty in required_by_item.items()
min_fraction = get_minimum_material_coverage_fraction(
required_by_item,
transferred_items,
self.precision("required_qty", "required_items"),
)
min_fraction = min(min_fraction, 1.0)
material_transferred = min_fraction * flt(self.qty)
covered_qty = min_fraction * flt(self.qty)
material_transferred = min(covered_qty, max(flt(self.qty), claimed_qty))
self.db_set("material_transferred_for_manufacturing", material_transferred)
def update_returned_qty(self):

View File

@@ -1,60 +0,0 @@
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.query_reports["BOM Stock Analysis"] = {
filters: [
{
fieldname: "bom",
label: __("BOM"),
fieldtype: "Link",
options: "BOM",
reqd: 1,
get_query: () => ({ filters: { docstatus: 1 } }),
},
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
},
{
fieldname: "qty_to_make",
label: __("FG Items to Make"),
fieldtype: "Float",
},
{
fieldname: "show_exploded_view",
label: __("Show availability of exploded items"),
fieldtype: "Check",
default: false,
},
],
formatter(value, row, column, data, default_formatter) {
if (data && data.bold && column.fieldname === "item") {
return value ? `<b>${value}</b>` : "";
}
value = default_formatter(value, row, column, data);
if (column.fieldname === "difference_qty" && value !== "" && value !== undefined) {
const numeric = parseFloat(value.replace(/,/g, "")) || 0;
if (numeric < 0) {
value = `<span style="color: red">${value}</span>`;
} else if (numeric > 0) {
value = `<span style="color: green">${value}</span>`;
}
}
if (data && data.bold) {
if (column.fieldname === "description") {
const qty_to_make = Number(frappe.query_report.get_filter_value("qty_to_make")) || 0;
const producible = Number(String(data.description ?? "").replace(/,/g, "")) || 0;
const colour = qty_to_make && producible < qty_to_make ? "red" : "green";
return `<b style="color: ${colour}">${value}</b>`;
}
return `<b>${value}</b>`;
}
return value;
},
};

View File

@@ -447,6 +447,7 @@ erpnext.patches.v16_0.rename_ar_ap_ageing_filter
erpnext.patches.v15_0.fix_titles
erpnext.patches.v16_0.backfill_repost_accounting_ledger_status
erpnext.patches.v16_0.merge_seeded_item_group_root
erpnext.patches.v16_0.repair_work_order_material_transfer
erpnext.patches.v16_0.remove_frappe_crm_custom_fields
erpnext.patches.v16_0.append_fieldname_to_pos_search_fields
erpnext.patches.v16_0.add_transaction_roles_to_sms_settings

View File

@@ -0,0 +1,65 @@
import frappe
from frappe.utils import flt
from pypika import functions as fn
from erpnext.manufacturing.doctype.work_order.services.material_coverage import (
get_minimum_material_coverage_fraction,
)
def execute():
updates = get_precision_affected_work_orders()
frappe.db.bulk_update("Work Order", updates, update_modified=False)
def get_precision_affected_work_orders():
"""Return Work Orders whose components cover the plan at quantity precision."""
work_orders = {}
for row in _get_candidate_rows():
work_order = work_orders.setdefault(
row.work_order,
{"qty": flt(row.qty), "required_qty": {}, "transferred_qty": {}},
)
item_code = row.item_code
work_order["required_qty"][item_code] = work_order["required_qty"].get(item_code, 0.0) + flt(
row.required_qty
)
work_order["transferred_qty"][item_code] = max(
work_order["transferred_qty"].get(item_code, 0.0), flt(row.transferred_qty)
)
precision = frappe.get_precision("Work Order Item", "required_qty")
return {
name: {"material_transferred_for_manufacturing": values["qty"]}
for name, values in work_orders.items()
if get_minimum_material_coverage_fraction(
values["required_qty"], values["transferred_qty"], precision
)
>= 1.0
}
def _get_candidate_rows():
work_order = frappe.qb.DocType("Work Order")
required_item = frappe.qb.DocType("Work Order Item")
return (
frappe.qb.from_(work_order)
.inner_join(required_item)
.on(required_item.parent == work_order.name)
.select(
work_order.name.as_("work_order"),
work_order.qty,
required_item.item_code,
required_item.required_qty,
required_item.transferred_qty,
)
.where(
(work_order.docstatus == 1)
& (work_order.status.notin(["Stopped", "Closed", "Completed"]))
& (fn.Coalesce(work_order.skip_transfer, 0) == 0)
& (fn.Coalesce(work_order.material_transferred_for_manufacturing, 0) < work_order.qty)
& (fn.Coalesce(work_order.transfer_material_against, "") != "Job Card")
& (required_item.include_item_in_manufacturing == 1)
& (required_item.required_qty > 0)
)
).run(as_dict=True)

View File

@@ -986,12 +986,8 @@ def get_billing_shipping_address(name, billing_address=None, shipping_address=No
return {"primary_address": primary_address, "shipping_address": shipping_address}
@frappe.whitelist(methods=["POST"])
@frappe.whitelist()
def create_transaction_deletion_request(company):
frappe.only_for("System Manager")
# User Permission check
frappe.has_permission("Company", ptype="delete", doc=company, throw=True)
from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import (
is_deletion_doc_running,
)
@@ -999,7 +995,6 @@ def create_transaction_deletion_request(company):
is_deletion_doc_running(company)
tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
tdr.submit()
tdr.start_deletion_tasks()

View File

@@ -1,6 +1,5 @@
{
"actions": [],
"allow_bulk_edit": 1,
"autoname": "TDL.####",
"creation": "2021-04-06 20:17:18.404716",
"doctype": "DocType",
@@ -142,18 +141,19 @@
"read_only": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-09-02 20:32:19.679290",
"modified": "2024-03-21 10:29:19.456413",
"modified_by": "Administrator",
"module": "Setup",
"name": "Transaction Deletion Record",
"naming_rule": "Expression",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
@@ -161,6 +161,7 @@
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
@@ -168,4 +169,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -5,12 +5,12 @@ frappe.listview_settings["Batch"] = {
return [__("Disabled"), "gray", "disabled,=,1"];
} else if (
doc.expiry_date &&
frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) < 0
frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0
) {
return [
__("Expired"),
"red",
"expiry_date,not in,|expiry_date,<,Today|batch_qty,>,0|disabled,=,0",
"expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0",
];
} else if (!doc.batch_qty) {
return [__("Empty"), "gray", "batch_qty,=,0|disabled,=,0"];

View File

@@ -93,27 +93,8 @@ frappe.ui.form.on("Material Request", {
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
if (!frm.doc.buying_price_list) {
const buying_price_list = frappe.defaults.get_default("buying_price_list");
if (buying_price_list) {
const docname = frm.doc.name;
frappe.call({
type: "GET",
method: "frappe.client.has_permission",
no_spinner: true,
args: {
doctype: "Price List",
docname: buying_price_list,
perm_type: "read",
},
callback: ({ message }) => {
if (
message?.has_permission &&
frm.doc.name === docname &&
!frm.doc.buying_price_list
) {
frm.set_value("buying_price_list", buying_price_list);
}
},
});
if (frappe.has_permission("Price List", "read", buying_price_list)) {
frm.set_value("buying_price_list", buying_price_list);
}
}
},

View File

@@ -806,6 +806,8 @@ def make_stock_entry(source_name, target_doc=None):
target.fg_completed_qty = job_card_details[0].for_quantity
target.from_bom = 1
target.cap_completed_qty_to_material_coverage()
doclist = get_mapped_doc(
"Material Request",
source_name,

View File

@@ -32,6 +32,9 @@ from erpnext.manufacturing.doctype.bom.bom import (
get_scrap_items_from_sub_assemblies,
validate_bom_no,
)
from erpnext.manufacturing.doctype.work_order.services.material_coverage import (
get_minimum_material_coverage_fraction,
)
from erpnext.setup.doctype.brand.brand import get_brand_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.batch.batch import get_batch_qty
@@ -262,6 +265,7 @@ class StockEntry(StockController):
self.calculate_rate_and_amount()
self.validate_putaway_capacity()
self.validate_component_and_quantities()
self._cap_completed_qty_to_material_coverage()
self.validate_finished_good_serial_batch_for_work_order()
if not self.get("purpose") == "Manufacture":
@@ -1186,6 +1190,124 @@ class StockEntry(StockController):
title=_("Missing Item"),
)
def _cap_completed_qty_to_material_coverage(self):
if not self._should_cap_completed_qty():
return
# Keep an excessive claim intact so the Work Order allowance check can reject it.
max_qty = flt(self.pro_doc.qty)
overproduction_percentage = flt(
frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
)
to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt(
self.fg_completed_qty
)
transfer_limit_qty = max_qty + (max_qty * overproduction_percentage / 100)
if transfer_limit_qty < to_transfer_qty:
return
self.cap_completed_qty_to_material_coverage()
def cap_completed_qty_to_material_coverage(self):
required_qty, transferred_qty, target_qty, precision = self._get_material_coverage_data()
if not required_qty:
return
covered_before = self._get_covered_qty(required_qty, transferred_qty, target_qty, precision)
for row in self.items:
if self.job_card:
material_reference = row.job_card_item
transferred = flt(row.qty)
else:
material_reference = row.original_item or row.item_code
transferred = flt(row.qty) * flt(row.conversion_factor or 1)
if material_reference in required_qty and (self.job_card or row.s_warehouse):
transferred_qty[material_reference] += transferred
covered_after = self._get_covered_qty(required_qty, transferred_qty, target_qty, precision)
covered_by_entry = flt(max(covered_after - covered_before, 0), self.precision("fg_completed_qty"))
self.fg_completed_qty = min(flt(self.fg_completed_qty), covered_by_entry)
def _should_cap_completed_qty(self):
if self.get("_action") != "submit":
return False
if self.purpose != "Material Transfer for Manufacture":
return False
if not self.pro_doc or not self.fg_completed_qty:
return False
if self.is_return or self.get("is_additional_transfer_entry"):
return False
return not (self.pro_doc.operations and self.pro_doc.transfer_material_against == "Job Card")
def _get_material_coverage_data(self):
if self.job_card:
return self._get_job_card_material_qty()
return self._get_work_order_material_qty()
def _get_job_card_material_qty(self):
job_card = frappe.get_doc("Job Card", self.job_card)
required_qty = {}
transferred_qty = {}
for row in job_card.items:
if flt(row.required_qty) <= 0:
continue
required_qty[row.name] = flt(row.required_qty)
transferred_qty[row.name] = flt(row.transferred_qty)
return (
required_qty,
transferred_qty,
self._get_job_card_target_qty(job_card),
job_card.precision("required_qty", "items"),
)
def _get_job_card_target_qty(self, job_card):
required_by_item = {}
for row in job_card.items:
required_by_item[row.item_code] = required_by_item.get(row.item_code, 0.0) + flt(row.required_qty)
work_order_required_by_item = {}
work_order = frappe.get_doc("Work Order", job_card.work_order)
for row in work_order.required_items:
if job_card.operation != row.operation:
continue
work_order_required_by_item[row.item_code] = work_order_required_by_item.get(
row.item_code, 0.0
) + flt(row.required_qty)
target_qty = [
item_required * flt(work_order.qty) / work_order_required_by_item[item_code]
for item_code, item_required in required_by_item.items()
if work_order_required_by_item.get(item_code)
]
return min(target_qty) if target_qty else job_card.for_quantity
def _get_work_order_material_qty(self):
required_qty = {}
transferred_qty = {}
for row in self.pro_doc.required_items:
if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0:
continue
required_qty[row.item_code] = required_qty.get(row.item_code, 0.0) + flt(row.required_qty)
# Duplicate required-item rows each hold the aggregate transferred quantity.
transferred_qty[row.item_code] = max(
transferred_qty.get(row.item_code, 0.0), flt(row.transferred_qty)
)
return (
required_qty,
transferred_qty,
self.pro_doc.qty,
self.pro_doc.precision("required_qty", "required_items"),
)
def _get_covered_qty(self, required_qty, transferred_qty, target_qty, precision):
min_fraction = get_minimum_material_coverage_fraction(
required_qty,
transferred_qty,
precision,
)
return min_fraction * flt(target_qty)
def _validate_no_excess_transfer(self):
if self.is_return:
return