fix(manufacturing): fall back to UOM Conversion Factor in Production Plan
Production Plan read the conversion factor straight off the item's own
UOM child table, so an item with a purchase UOM but no matching row threw
"UOM Conversion factor not found" while Stock Entry silently resolved it
from the item's variant template or the UOM Conversion Factor doctype.
Resolve it the same way, and keep returning None when nothing is
configured anywhere so the missing-setup error still fires.
Default WIP, Finished Goods and Scrap Warehouse fields on Company listed
warehouses of every company. Filter them by the current company and
exclude group warehouses, matching the other warehouse fields.
(cherry picked from commit 632113c309)
# Conflicts:
# erpnext/setup/doctype/company/company.js
* fix: skip stock expense gl entries for non stock items
(cherry picked from commit 747f4df778dca45cf044c02f0e933d3b230b8334)
* test: use a leaf expense account for the service item invoice
`calculate_rm_cost` skipped rate refresh whenever `bom_creator` was set,
so neither the Update Cost button nor the BOM Update Tool could ever
refresh those BOMs. Every BOM in a multi-level tree carries the field, so
whole trees stayed frozen at their creation rates.
The guard replaced the removed `rm_cost_as_per == "Manual"` check in
0b63dbf, on the assumption that BOM Creator rows hold manual rates. They
do not: BOM Creator recomputes every row from `rm_cost_as_per` on save.
The BOM Creator tree identified a node by the parent's item code
(fg_item) instead of the specific BOM Creator Item row, so every
occurrence of a repeated sub-assembly shared one child set: expanding
any one of them listed the raw materials of all of them, and deleting
one wiped the raw materials of its siblings.
Key the tree on fg_reference_id and make the node value the row name,
matching the framework convention that a tree node's value is its
docname. Item code now travels as its own field for the label and for
the fg_item argument sent back on add/convert.
Fixes#57311
(cherry picked from commit b37152752f)
update_semi_finished_good_details assigned the current job card's
manufactured_qty to Work Order.produced_qty instead of accumulating it,
so a second job card on the same operation overwrote the first. Nothing
corrected it afterwards because StatusService.update_work_order_qty
returns early for track_semi_finished_goods work orders, leaving the
work order stuck below its planned qty with no way to progress.
Aggregate manufactured_qty and completed_qty over the operation's
submitted job cards instead.
(cherry picked from commit 5548f0726a)
calculate_total_row tested each column with `"Link/Currency" in col`, but
based-on and group-by columns are dicts, so the test checked the dict's keys
and never matched. currency_col_idx stayed None and the grand-total row's
currency cell was left unset, so Total(Amt) rendered with the global default
currency instead of the company's.
Match the dict's fieldtype/options instead. Dict columns are never numeric
and string columns are never Link columns, so the two branches are now
mutually exclusive.
Reports like Sales Order Trends and Purchase Order Trends showed the global
default currency symbol instead of the transacting company's currency.
Threads the company currency through conditions["company_currency"] in
trends.get_columns and uses it for both the chart's currency and the Total
row. The chart now skips the grand-total row by its label instead of by a
falsy first periodic cell, so the already-summed Total row is not added into
the datapoints a second time.
Backport of #56561 (frappe/erpnext). Tests from the original PR are not
included: the trends report test files do not exist on this branch.
subcontracting order and subcontracting inward order carry a hidden
title field defaulting to "{supplier_name}" / "{customer_name}", while
their title_field points at supplier_name / customer_name. document.
set_title_field() substitutes the template only when title_field is
"title", so every record stores the placeholder verbatim.
drop the dead default and hidden flags, move title into the other info
tab to match purchase order, and add a patch to repair existing rows.
(cherry picked from commit 5008e6126f)
* fix: exclude landed cost from purchase expense GL entries
* feat: book expenses added to stock GL entries for stock vouchers
* test: enable stock expense gl entries flag for purchase expense test
fix(stock): narrow legacy serial ledger lookup by item (#57499)
Filter legacy Stock Ledger Entry lookups by item code so the existing
item and warehouse index can reduce rows scanned during serial valuation.
(cherry picked from commit 425191e57e)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
A batch is one valuation pool, so any per-slot value difference within a
batch is stale detail from the report's own age slots, not real valuation.
The rebalance only ran when consumption had already driven a slot negative,
so a batch whose receipts landed at different rates kept a skewed split
across age buckets (one bucket free, another double-priced) while the total
stayed correct.
Drop the negative-slot precondition and always spread a batch's pooled value
over its slots in proportion to qty. Redistribution preserves group totals,
so buckets still sum to Stock Balance; only the split across ages changes.
(cherry picked from commit cedaaa3a00)
close a partially-received sco with a reserve warehouse and assert the
raw-material reservation is released and projected qty recovers.
(cherry picked from commit e4b8065a69)
the bin reserved-qty recalc filtered out closed purchase orders but not
closed subcontracting orders, so closing a partially-received sco kept the
reservation for the unreceived qty and left projected qty understated.
apply the same closed-status filter to the subcontracting order path.
(cherry picked from commit db91a79d31)
# Conflicts:
# erpnext/stock/doctype/bin/bin.py
fix: enable the 'Include Zero Stock Items' filter by default to show zero-stock items in the Stock Balance report (#57458)
(cherry picked from commit 4e8f5de5cb)
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
* feat: block sales invoice submit when customer overdue exceeds threshold (#57230)
* feat: block sales invoice submit when customer overdue exceeds threshold
Adds an opt-in, per-customer Overdue Billing Threshold. When enabled in
Accounts Settings, submitting a Sales Invoice is blocked if the customer's
overdue amount exceeds their threshold, unless the current user holds a
configured bypass role. Modeled on the existing credit limit feature.
- Accounts Settings (Credit Limits tab): enable toggle + bypass role.
- Per-customer threshold on the Customer Credit Limit table, shown only
when the feature is enabled via a property setter (same mechanism as
subscription / accounting dimension sections). Table relabeled to
"Credit & Overdue Limits".
- Overdue is read live from the ledger via get_outstanding_invoices
(payments already netted), summing Sales Invoices past their due date.
- Enforced in Sales Invoice on_submit, after the credit-limit check;
returns are exempt.
- validate_credit_limit_on_change no longer trips when a row sets only
the overdue threshold (credit_limit = 0).
Fixes#52960
* fix: compute overdue amount in company currency and format with fmt_money
get_customer_overdue_amount now sums GL Entry debit - credit grouped per
invoice, which is always booked in company currency, instead of using
get_outstanding_invoices which returns the receivable-account currency.
The threshold is in company currency, so the previous comparison could mix
currencies for customers with a foreign-currency receivable account. This
mirrors how get_customer_outstanding computes the figure for the existing
credit-limit check.
The blocking message now formats both amounts with fmt_money using the
company currency.
Adds a test asserting a 100 USD invoice at a conversion rate of 50 is
counted as 5000 in company currency.
* refactor: drop redundant threshold coercion and dead test cleanup
- Coerce the overdue threshold with flt() once when reading it, instead of
calling flt() on it at each of the three use sites.
- Remove a no-op set_overdue_billing_threshold() call in the feature-disabled
block (the threshold was already set to that value) and the trailing reset,
which is dead since each test is rolled back.
No behaviour change.
* fix: compute overdue amount from payment terms, matching the Overdue status
The overdue amount keyed on Sales Invoice.due_date, which set_due_date() sets
to the LAST payment term. An invoice whose first term was past due and unpaid
was therefore counted as zero, even though ERPNext already shows it as Overdue
in the invoice list. The gate and the UI could disagree.
get_customer_overdue_amount now follows the same rule as is_overdue(): per
invoice, the amount that has fallen due (sum of payment schedule terms past
their due date) minus what has been paid, clamped to the outstanding balance.
Invoices without a schedule (POS, opening) still fall back to the invoice due
date, mirroring is_overdue()'s own guard.
The ledger stays the source of truth for what is unpaid: the outstanding per
invoice is still SUM(debit) - SUM(credit) from GL Entry. base_payment_amount is
always stored in company currency, so no currency conversion is needed and the
comparison against the threshold stays consistent.
Adds a test covering a two-term invoice: only the past-due term counts, and
paying it off clears the overdue amount.
* feat: honour the overdue billing threshold set on the customer group
The threshold lives on Customer Credit Limit, which is also rendered on
Customer Group. A threshold set there was stored but never evaluated, so the
configuration was a silent no-op.
get_overdue_billing_threshold now reads the customer's row and falls back to
its customer group, mirroring get_credit_limit. The group's
bypass_credit_limit_check is deliberately not consulted: it is labelled for the
credit limit check at sales order and is unrelated to overdue billing.
get_customer_group_details also dropped the threshold when copying group rows
onto a customer, because it copied a single hardcoded field per table. It now
copies a list of fields per table, so credit_limit and overdue_billing_threshold
both carry over.
* refactor: clearer labels for the overdue billing control (#57298)
refactor: clearer labels and messages, drop "threshold" wording
User-facing text only, no field or behaviour changes:
- Accounts Settings toggle label -> "Restrict Customer Over Billing".
- Bypass role label -> "Role Allowed to Bypass Over Billing Restriction".
- Customer Credit Limit field label -> "Overdue Limit".
- Rewrote the descriptions and the block message to match and to stop
saying "threshold".
* fix: treat zero overdue limit as opt-out and isolate settings in test
get_overdue_billing_threshold treated an explicit 0 on the customer's credit
limit row as "not set" and fell back to the customer group. A customer could
not be exempted from the group restriction while keeping a credit limit row,
and every existing row defaults to 0, so enabling the feature on a group blocked
all its customers that had any credit limit row. Guard the group fallback on
"threshold is None" (no row for the company) instead of a falsy check, so an
explicit 0 acts as an opt-out.
test_overdue_billing_threshold_on_submit mutated the Accounts Settings singleton
without restoring it, so a failed assertion mid-test leaked
enable_overdue_billing_threshold and the bypass role into later tests that submit
sales invoices. Wrap the mutations in try/finally and restore the originals.
* fix: let a zero customer overdue limit inherit the group's limit
A 0 on the customer's credit limit row falls back to the customer group again;
only a non-zero value on the customer overrides the group. Reverts the earlier
opt-out interpretation and updates the fallback test to expect the group's limit.
use frappe.get_list instead of frappe.get_all in get_dashboard_info so
the company list honors user permissions. previously, a party with
invoices across multiple companies would raise "User don't have
permissions to select/read this account" for users restricted to a
subset of companies, since get_party_account was called for companies
the user could not access.
fixesfrappe/erpnext#57428
(cherry picked from commit 903c87bcaa)
The mt940 library exposes ``transaction_reference`` from the :20: tag,
which is the statement-level reference and identical for every
transaction in a statement. The bank-statement-to-CSV conversion was
using it verbatim, so every imported row ended up with the same
reference, making reconciliation impossible.
Read the per-transaction reference from ``customer_reference`` on the
:61: tag instead. Handle two edge cases:
- **Overflow >16 chars.** When a bank emits a single-line :61: whose
reference exceeds 16 characters, the mt940 regex splits the tail into
``extra_details``. Gate the rejoin to cases where
``customer_reference`` is exactly at the 16-char MT940 cap; below
that, ``extra_details`` is genuine supplementary information and must
not be appended.
- **``NONREF`` sentinel.** The MT940 standard marker for "no customer
reference". Check it against the un-concatenated value so that a
``NONREF`` customer reference with populated ``extra_details`` still
falls back to ``bank_reference`` instead of returning a junk
``NONREFsomething`` value.
Also switch the Description column to ``transaction_details`` (the :86:
tag content) so rows carry their real narrative instead of the mostly
empty :61: supplementary field.
(cherry picked from commit 551d709c64)
sort on (expiry is none, expiry) so a null expiry_date is never
order-compared against a datetime.date, which raised typeerror in
python 3 when a warehouse held both dated and never-expiring batches.
(cherry picked from commit 62c9f8ee3e)
A batch is one valuation pool, so consumption is valued at the pooled
rate while slots may carry stale intra-batch detail (e.g. units
reconciled at zero and later merged). Consuming such a slot leaves a
negative value on positive qty. Spread the pool value across the
batch's slots when that happens; non-batchwise slots pool per
warehouse.
feat: make Shipping Rule Cost Center optional with company default fallback (#57355)
feat(shipping-rule): make cost center optional with company default fallback
Cost Center on Shipping Rule is no longer mandatory. When left blank, the
applied shipping tax row falls back to the company default cost center,
avoiding the 'Cost Center is required for Profit and Loss account' error on
submit. The rule's project is also applied to the tax row.
(cherry picked from commit a47f25896b)
Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
* fix: Incorrect creation time at the time cancelling an entry causing an issue especially same posting datetime (#57380)
* fix: shift same-timestamp sibling SLEs when cancelling an entry
update_qty_in_future_sle compared against the reversal SLE's own
creation and skipped same-posting_datetime siblings on cancel, leaving
their qty_after_transaction stale and causing false negative stock
errors.
* fix: revert update_qty_in_future_sle cancel tie-break, it double-counted
(cherry picked from commit 8c0ec3c179)
# Conflicts:
# erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
* chore: fix conflicts
Fix test case for cancelling stock ledger entries with the same timestamp to ensure correct behavior.
* chore: fix conflicts
---------
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
set_route_options_for_new_doc lived in TransactionController, so doctypes
extending StockController directly (Stock Reconciliation, Stock Entry) missed
the Batch/SABB prefill or duplicated it locally. Move it to StockController
and call it from onload_post_render so all descendants inherit it.
- Batch quick entry from Stock Reconciliation items now prefills Item
- SABB route options unified: warehouse || s_warehouse || t_warehouse,
so transaction doctypes now also prefill warehouse
- Stock Entry's duplicate handler removed; its onload_post_render now
calls super
(cherry picked from commit 551559e804)
get_single_value inside _revalue_reconciled_batch_slots runs while
rows stream through the unbuffered cursor on MariaDB, killing the
active iterator. Resolve it once in generate() with the other
prefetches.
stock_value_difference / qty equals the new batch rate only when the
reco entry carries the entire batch, as the split out/in reco SLEs and
batches reconciled from zero do. Partial direct-batch_no entries mix a
qty delta with existing stock, so their slots keep prior values.
Plain items need no such guard: the valuation engine collapses the
FIFO stack to qty_after * valuation_rate on every reconciliation, so
rescaling remaining slots at the reco rate matches the ledger. Lock
that with a test.
Batch items take the batch-slot path, which mirrors the same value
arithmetic: the reco's incoming entry dumps the revaluation remainder
on one slot. Rescale each reconciled batch's slots at its post-reco
rate (stock_value_difference / qty of the incoming bundle entry).
A reconciliation's stock_value_difference includes the revaluation of
stock already in the FIFO queue, but the whole amount was attached to
the qty-delta slot while older slots kept pre-revaluation values. A
downward revaluation therefore produced negative bucket values in the
Stock Ageing report, and repeated recos let the queue total drift away
from Stock Balance.
Re-derive every slot value as qty * valuation_rate after processing a
reco SLE, since a reconciliation values the entire balance at its rate.
Covers both single-SLE recos and the zero-out/re-add pair that flows
through the transfer bucket.
fix: show transaction currency symbol in Payment Request schedule dialog and reference table (#57050)
* fix: show transaction currency symbol in Payment Request schedule dialog and reference table
When company currency (INR) differs from customer currency (USD), the Amount
column in the Select Payment Schedule dialog and the Payment Reference table on
the Payment Request form incorrectly displayed the company currency symbol (₹)
instead of the transaction currency symbol ($).
- Pass `currency` from the parent document on each schedule row returned by
`get_available_payment_schedules` so the dialog can resolve the symbol.
- Add a hidden `currency` field to the dialog table and set `options: "currency"`
on `payment_amount` so Frappe renders the correct symbol.
- Propagate `currency` into Payment Reference rows in `set_payment_references`.
- Add a hidden `currency` Link field to the Payment Reference child DocType and
set `options: "currency"` on its `amount` field so the table renders correctly.
* fix: preserve currency when serializing payment schedule rows
get_available_payment_schedules set `schedule.currency` directly on
the Payment Schedule Document row, but `currency` isn't a field on
that DocType, so the API response serializer stripped it before it
reached the client. The Select Payment Schedule dialog and the
Payment Reference table therefore always fell back to the company
currency symbol, even with the earlier options="currency" changes in
place.
Convert each row to a plain dict via as_dict() first, then set the
currency key on the dict so it survives serialization.
* refactor: source schedule currency in dialog instead of API serializer
get_available_payment_schedules had to convert each child row with
as_dict() and re-attach currency, because currency is not a field on
Payment Schedule and the response serializer drops attributes set on the
Document itself.
The schedule dialog already has the transaction currency on frm.doc, so
set it there and let the API keep returning the schedule rows unchanged.
Payment Reference still stores currency per row.
---------
(cherry picked from commit 83e04dd773)
Co-authored-by: Henil Maru <henil@frappe.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jatin3128 <jatinsarna8@gmail.com>
Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
* refactor: rework appointment booking lifecycle and portal verification (#57270)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 73004c6e4b)
# Conflicts:
# erpnext/crm/doctype/appointment/test_appointment.py
# erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py
# erpnext/www/book_appointment/index.py
* chore: resolve conflicts
* fix: parse contact as native JSON in create_appointment
version-16-hotfix never received develop's 9955adb2fc, so the backported
tests calling create_appointment with a dict contact crashed on
json.loads. Use frappe.parse_json to accept both str and dict, matching
develop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Stock Summary's sort selector only offered 5 of Bin's 10 qty fields; add
the rest (ordered, requested, planned, reserved for production plan,
reserved stock) and extend get_data's or_filters so bins whose only
nonzero qty is one of the new fields show up when sorted by it. Sort
labels now mirror Bin field labels.
Stock Projected Qty report had a column for every Bin qty field except
reserved_stock; add it.
(cherry picked from commit 59c0c15c2e)
Mirrors update_qty's Standard Cost handling and drops fixed test item
names so reruns start from fresh SLE-less items.
(cherry picked from commit 49a43aad81)
Renames the Recalculate Bin Qty button to Recalculate Values and sets
valuation_rate and stock_value from the last SLE (0 when none exists).
(cherry picked from commit df79e85f53)
get_mapped_doc copies same-named fields by default. work order item's
transferred_qty (cumulative across the whole work order) was leaking into
the new pick list item's transferred_qty (meant to track how much of
that pick list row has been converted into a stock entry, starting at 0).
the leaked value then got subtracted again in
get_pending_transfer_stock_qty(), so every pick list after the first
under-transferred raw materials by whatever was already recorded on the
work order, driving material_transferred_for_manufacturing towards zero
across repeated partial pick-list/finish cycles.
backport of #57253fixes#57236, related to #56596
update_item_rates passed price_not_uom_dependent, a key
get_price_list_rate_for never reads, and omitted conversion_factor, so a
stock-UOM price was never converted to the row UOM. The function's
(historically misnamed) price_list_uom_dependant ctx key carries the
Price List's price_not_uom_dependent value: truthy returns the found
rate as-is, falsy multiplies by conversion_factor.
Also guard on_update with is_new(): has_value_changed returns True when
there is no doc_before_save, so every first save re-wrote item rates.
(cherry picked from commit 6dcc0cab3a)
fix: restrict jinja globals in process statement of accounts templates
(cherry picked from commit ecb6d48ec0)
Co-authored-by: Shllokkk <shllokosan23@gmail.com>
The transfer flow ignored Consider Minimum Order Qty twice: the JS
handler force-reset the checkbox before fetching items, and the
purchase remainder left after allocating transfers from other
warehouses was never raised to min_order_qty (the check runs on the
total requirement before the split).
Drop the JS reset and apply min order qty to the purchase remainder,
in stock UOM before the purchase UOM conversion.
The v16 reserved-stock mapper attaches neither a bundle nor row
serial/batch fields when use_serial_batch_fields is enabled, and
update_stock_reservation_entries crashes on the missing bundle (fixed
on develop by 9c5f9218b5, not backported). Deliver via an explicit row
batch_no like the guard test so the bundle is built from row fields
before the reservation update runs.
use_serial_batch_fields delivery of reserved stock crashes on v16 with
'Serial and Batch Bundle None not found' (fixed on develop only), so
deliver through auto-created bundles like test_auto_reserve_serial_and_batch.
validate_reserved_batches compared the voucher's own qty against the
remaining batch qty, so delivering one order's reserved unit threw
Reserved Batch Conflict whenever the remainder exactly matched another
order's reservation. Compare the remaining batch qty against the
aggregated outstanding reserved qty (qty - delivered_qty) of other
vouchers instead, excluding reservations the voucher itself delivers.
erpnext.accounts.dashboard_fixtures and erpnext.buying.dashboard_fixtures
were removed in 2020 when dashboards were exported to JSON fixtures.
The assets module's dashboard_fixtures.py was left behind unreferenced;
its dashboard, charts and number cards already exist as exported JSON.
(cherry picked from commit 14a15cc6f9)
StockReservation.transfer_reservation_entries_to() created the transferred SREs without copying stock_uom, in both the entries_to_reserve dict and the extra-items fallback. get_items_to_reserve() already selects the item's stock_uom, so entry.stock_uom is used.
On sites with a global default stock_uom (e.g. "Nos"), frappe's _set_defaults() backfilled the blank field, so the transfer silently stored the wrong UOM for any item whose stock UOM is not the default. On sites without that default the SRE's validate_mandatory() raised "Stock UOM is required", aborting Work Order submission for the Subcontracting Inward Order / Production Plan flows.
covers sales order, quotation, sales invoice, delivery note and
purchase order, asserting actual_qty from the row warehouse and
company_total_stock across all warehouses of the company.
(cherry picked from commit 4e5e1f6596)
company was passed to get_bin_details only for purchase order, so
company_total_stock was never returned for sales order, quotation,
sales invoice and delivery note and the qty (company) column always
read zero. pass ctx.company for every doctype, which also drops the
dependency on doc being supplied.
on the client, set_actual_qty copied only actual_qty out of the
response, so qty (company) never refreshed on a warehouse change. use
frm.call with child so every bin field is applied, pass
include_child_warehouses to match the server, and include quotation.
(cherry picked from commit ab30bab6cb)
- allow new rows on scan when pick manually is enabled, since only
then are scanned rows not subject to being overridden by
set_item_locations on save
- stop capping picked qty at the default demand qty (1) for rows
added by the scanner itself, so repeat scans of the same barcode
keep incrementing the row instead of failing with "maximum
quantity scanned"
- ignore barcode uom when matching an existing row if new rows
aren't allowed, since there's no alternate-uom row to fall back to
(cherry picked from commit 3ece4a615d)
Match the framework rename of the standard report entry point in the
trial balance, P&L, balance sheet, and general ledger reports.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit ba7b6a47c5)
Replaces the previous over-engineered stub with 7 short functions.
Account data, Account Closing Balance, and all metadata come from
frappe.db as normal; only tabGL Entry is read from the duckdb_conn.
Reuses get_opening_balance() for Account Closing Balance unchanged,
reuses all downstream compute helpers (calculate_values, prepare_data,
etc.) unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 55862f98f4)
Replaces the placeholder stub with 8 focused functions that mirror the
normal execute() flow using parameterized DuckDB SQL queries: account
fetch, period GL entries, opening balances (with Period Closing Voucher
path), and all filters (cost center, project, finance book, accounting
dimensions). Reuses existing pure-Python processing functions unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit b1c8e2cb5c)
The Budget Variance Report chart plotted the actual expense one month
earlier than the table (e.g. July actual shown under June).
build_comparison_chart_data() collected budget columns using
fieldname.startswith("budget_"). The dimension column "budget_against"
also matches that prefix, so it was added as an extra leading entry to
budget_fields and labels, while actual_fields had no such leading entry.
This shifted every actual value one position ahead of its label.
Skip the "budget_against" dimension column so budget/actual values and
labels stay aligned per month.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 48418eadb0)
A Stock Entry created from a Pick List against a job card's Material
Request never set job_card, job_card_item, fg_completed_qty or the
'Material Transfer for Manufacture' purpose, so the Job Card did not
recognize the transfer and blocked submission. The WIP warehouse was
also not populated.
Route such pick lists through a job-card-aware branch mirroring the
direct Material Request -> Stock Entry mapper, and set the purpose to
'Material Transfer for Manufacture' in the work order branch so the
WO -> MR -> Pick List flow updates the work order too.
* fix(stock): recompute moving average item slots
* test(stock): add test to validate the stock value of moving average items
* fix(stock): support lifo valuation in stock ageing report
lifo items were aged as fifo (oldest consumed first), so the report kept the
newest lots on hand and reported the wrong stock value and average age. prefetch
each item's valuation method (it can't be resolved mid-stream without breaking the
unbuffered cursor) and consume from the tail for lifo items. also reuse that shared
lookup in the moving average revaluation pass. scoped to plain items; batch, serial
and same-voucher repack legs stay on fifo.
* test(stock): add test for lifo consumption in stock ageing report
(cherry picked from commit 9cb6610b9e)
# Conflicts:
# erpnext/stock/report/stock_ageing/stock_ageing.py
based_wise_columns_query() and group_wise_column() built column labels as
raw strings, so headers like Item, Item Name, Customer, Supplier, and
Territory never went through the _() translation function and stayed in
English regardless of the user's language, while period and total columns
translated fine. Build these as column dicts with an explicit _()-wrapped
label instead, so they're translated the same way as the rest of the report.
(cherry picked from commit 015fa68fc0)
get_item_warehouse_projected_qty() called frappe.get_doc("Warehouse", ...)
inside the per-bin loop to walk up the warehouse hierarchy, re-fetching the
same parent warehouses over and over on sites with nested warehouses. Preload
the warehouse-to-parent mapping with a single query and walk it in-memory
instead, cutting the DB round-trips from O(bins * hierarchy depth) to one
query.
(cherry picked from commit 6beb3d2509)
* fix: for purchases do voucher based reposting (#56601)
(cherry picked from commit 5523c15ab8)
* chore: fix type hints
---------
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
* fix(stock): fall back to current date/time for serial and batch bundle posting datetime
Pick List has no posting_date/posting_time fields, so creating or updating a
Serial and Batch Bundle from a Pick List row crashed with
"TypeError: combine() argument 1 must be datetime.date, not None". Fall back
to today/now when the parent voucher doesn't carry its own posting date.
Fixes#56951
* fix(stock): accept a plain dict for add_serial_batch_ledgers' doc and child_row
The whitelisted add_serial_batch_ledgers only converted child_row into an
attribute-accessible frappe._dict when it arrived as a JSON string, and doc's
type hint only allowed Document | str. Frappe's JSON API delivers both as
plain dicts (see frappe.app.make_form_dict, which parses the request body
with orjson and only wraps the top-level dict, not nested values), so every
real request was rejected before the handler body ever ran: first with a
FrappeTypeError on doc, and once that's fixed, with an AttributeError on
child_row.serial_and_batch_bundle. parse_json already wraps a plain dict in
frappe._dict (and leaves a real Document instance untouched), so routing
child_row through it unconditionally fixes both.
Cover the pick-list flow where a stock entry moves only one of the work
order's required items: material_transferred_for_manufacturing stays 0 (min
fraction) while the status must move to "in process".
The routing field handler only fetched operations from the routing when
the operations table was empty. When a new BOM version is created (via
"New Version"), operations are copied from the source BOM, so selecting a
different routing left the old operations in place - both in the form and
after saving.
Drop the `!frm.doc.operations.length` guard from the routing handler so
that (re)selecting a routing always refetches the operations from that
routing via the existing get_routing method, which clears and repopulates
the operations table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 758a837de4)
update_current_stock() in delivery_note.py used to call
frappe.db.get_value("Bin", ...) separately for every row in items and
every row in packed_items - so a delivery note with 200 items and 200
packed items made 400 separate database calls on every save.
now it groups item codes by warehouse and fetches bin data with one
query per distinct warehouse, then assigns actual_qty/projected_qty to
each row from that result - same values as before, far fewer database
calls, and no cross-product over-fetch across warehouses.
(cherry picked from commit 5da878d25f)
Add regression coverage for the new abbreviation-rename propagation:
a simple item_code rename, item_name derived from a template whose
item_name differs from its item_code, and a manually customized
item_name getting rebuilt rather than left stale.
(cherry picked from commit e718a70b26)
Item Attribute abbreviations only got baked into a variant's item_code
and item_name at creation time (make_variant_item_code returns early
once item_code is set). Renaming an abbreviation afterwards left every
existing variant stuck with the stale code, silently out of sync with
its own attribute.
Detect abbreviation renames on Item Attribute save, find every variant
using the affected value, and rebuild+rename its item_code via
frappe.rename_doc so linked records follow along. item_name is rebuilt
in lockstep from the template's item_name, even if it had since been
customized, since both fields are meant to be derived from the same
abbreviation.
(cherry picked from commit c0cfe5f363)
Pick Lists transferred before this feature have transferred_qty = 0 and
their Stock Entry rows carry no pick_list_item link, so the new
is_fully_transferred check would never fire and, with the old
duplicate-entry guard removed, they could be transferred again. Set
transferred_qty = picked_qty for non-Delivery submitted pick lists that
already have a linked Stock Entry so they stay completed and locked.
Creating a Stock Entry from a Pick List blocked any further entry
(stock_entry_exists) and flipped the pick list to Completed as soon as
one entry existed, so picked stock could not be transferred in parts.
Track transferred_qty per Pick List Item (summed from submitted Stock
Entry rows via a new pick_list_item link, mirroring delivered_qty), add
a Partially Transferred status, and map each new Stock Entry from the
remaining qty so transfers can continue until fully transferred.
Process Period Closing Voucher and Process Period Closing Voucher
Details are trackers how the jobs are processed. Keep transactions on
them very short.
(cherry picked from commit 7e4045e828)
- Update using child table name to avoid scanning whole table, which
eventually leads to mariadb 1020 (REPEATABLE READ).
- Avoid race condition in final summarization
(cherry picked from commit ff6881764b)
fix: use live source warehouse valuation for internal transfer purchase receipts (#56431)
fix: anchor incoming SLE rate to DN rate for intra-company PR transfers
(cherry picked from commit 35de9deb0a)
Co-authored-by: Shllokkk <140623894+Shllokkk@users.noreply.github.com>
the existing test_cost_center_for_manufacture only checks a raw material
row against an item-level override, which is set independently of the
":company" default guard and never exercised the bug.
(cherry picked from commit a168bb7ea4)
the ":company" default pre-filled every row before set_default_cost_center()
ran, so its "if not row.cost_center" guard was always false and the
project/item group/brand priority chain in get_default_cost_center()
never ran.
(cherry picked from commit edfa0a7a1d)
# Conflicts:
# erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
Address review feedback:
- A typed-but-not-selected value passed validation yet was dropped by
get_selected_attributes (reads committed pills only). Treat any pending
input as an error so it is never silently omitted from creation.
- Escape pill / pending values before interpolating them into the HTML
error message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit d4da9a3d7d)
(cherry picked from commit 04c834d6a9)
The 'Create Multiple Variants' dialog rendered one checkbox per attribute
value and read the numeric config from the variant attribute child row. This
broke in several ways:
- A template whose attribute was made numeric after being added kept
numeric_values=0 on the child row, so the dialog treated it as non-numeric,
queried the empty Item Attribute Value table, and showed no values.
- Enumerating a large range (e.g. 1-100000) into checkboxes froze the browser.
Rework the dialog:
- Read numeric_values / from_range / to_range / increment from the Item
Attribute master, and guard increment > 0.
- Replace the checkbox-per-value list with one MultiSelectPills per attribute,
with a search placeholder.
- Stop enumerating numeric ranges: preview the first few values and validate
typed input against the range on demand, so huge ranges stay instant.
- Block variant creation with a modal error if any selected value or pending
input is invalid (out of range, off-increment, or not a number), so garbage
like '00A' can't reach creation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 99152b8300)
(cherry picked from commit 025d0cd7f3)
Marking an attribute numeric hides the Item Attribute Values grid but leaves
its rows in the doc, whose mandatory Attribute Value / Abbreviation block the
save client-side before the server can clear them. Clear the table on the
client too so the save goes through.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4afbd4d3d9)
(cherry picked from commit 374b340e73)
- restore mutated SLE after test via addCleanup
- explicit return False in has_difference
- comment the fifo_stock_diff guard for non-queue predecessors
(cherry picked from commit ef5f47fafd)
- 'Show Incorrect Entries' always returned an empty result (regression
from #43619); now returns entries from one row before the first
incorrect one
- FIFO queue columns were computed for serialized/batched SLEs that
don't maintain a stock queue, showing false differences; left empty
for such rows
- compare value/valuation differences at currency precision, qty at
float precision
(cherry picked from commit 94ab09e4a3)
# Conflicts:
# erpnext/stock/report/stock_ledger_invariant_check/test_stock_ledger_invariant_check.py
* fix(company): ignore user permissions for link fields having link to `Account` and `Cost Center` (#56748)
(cherry picked from commit 9cea43b006)
# Conflicts:
# erpnext/setup/doctype/company/company.json
* chore: resolves conflict
---------
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
fix: restore Save button on reverse journal entry (#56770)
Reversing a submitted Journal Entry opened a draft with reversal_of set,
which called frm.set_read_only(). That strips the write and submit perms
from frm.perm, so the toolbar never rendered the Save (or later Submit)
button and the reversal could not be saved.
Lock the fields and the accounts grid as read_only instead, leaving perms
intact so Save and Submit still work while nothing stays editable.
Ticket: 72857
(cherry picked from commit 0a05dd4426)
Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
An incoming SLE without resolvable serial/batch details hit the
negative-head branch in _compute_incoming_stock even when the head was
a batch slot, because flt() on the batch number string returns 0.0.
_add_to_negative_fifo_head then crashed with
"TypeError: can only concatenate str (not 'float') to str".
Guard the branch with is_qty_slot, mirroring the existing check in
_add_transfer_slot_to_fifo_queue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit c47a95a4d2)
work order status was decided using a stale transferred-qty value,
computed before the current stock entry's transfer got recomputed.
this left work orders stuck at "not started" for pick-list-driven
transfers, since those entries never set fg_completed_qty and their
transferred qty can only be known from actual item-level transfers.
an earlier attempt fixed this by setting fg_completed_qty from the pick
list's for_qty, but that broke two things tied to fg_completed_qty
being zero: the excess-transfer guard, and the partial-transfer
fraction logic used to avoid marking a work order as fully supplied too
early.
recompute the transferred qty first, then decide status from the fresh
value. revert the fg_completed_qty change since it's no longer needed.
fix: validate reverse GL entries on current date under immutable ledger (#56709)
* fix: validate reverse GL entries on current date under immutable ledger
When Immutable Ledger is enabled, the reverse GL entry is posted on the
current date, but the closed-period checks in make_reverse_gl_entries still
validate against the original (backdated) posting date. This blocks cancelling
a backdated voucher, such as a suspense Journal Entry for a migrated NPA loan,
with a books-closed error even though the reverse entry lands in an open period.
Validate both check_freezing_date and validate_against_pcv against the current
date when Immutable Ledger is enabled. When it is disabled, behaviour is
unchanged.
Follow-up to #55268.
* test: reset frozen till date after reverse entry test
The freeze date set on the company was not reset, so it leaked into the next
test which posts entries in that period. Reset it in a finally block.
* fix: prefer explicit posting_date under immutable ledger
Prefer the posting_date argument before frappe.form_dict and getdate, at both
the validation and the GL entry site, so an explicit date passed by the caller
is honoured and validation still matches the posted date.
(cherry picked from commit cab1b129c0)
Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com>
Address review feedback:
- A typed-but-not-selected value passed validation yet was dropped by
get_selected_attributes (reads committed pills only). Treat any pending
input as an error so it is never silently omitted from creation.
- Escape pill / pending values before interpolating them into the HTML
error message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit d4da9a3d7d)
The 'Create Multiple Variants' dialog rendered one checkbox per attribute
value and read the numeric config from the variant attribute child row. This
broke in several ways:
- A template whose attribute was made numeric after being added kept
numeric_values=0 on the child row, so the dialog treated it as non-numeric,
queried the empty Item Attribute Value table, and showed no values.
- Enumerating a large range (e.g. 1-100000) into checkboxes froze the browser.
Rework the dialog:
- Read numeric_values / from_range / to_range / increment from the Item
Attribute master, and guard increment > 0.
- Replace the checkbox-per-value list with one MultiSelectPills per attribute,
with a search placeholder.
- Stop enumerating numeric ranges: preview the first few values and validate
typed input against the range on demand, so huge ranges stay instant.
- Block variant creation with a modal error if any selected value or pending
input is invalid (out of range, off-increment, or not a number), so garbage
like '00A' can't reach creation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 99152b8300)
Marking an attribute numeric hides the Item Attribute Values grid but leaves
its rows in the doc, whose mandatory Attribute Value / Abbreviation block the
save client-side before the server can clear them. Clear the table on the
client too so the save goes through.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4afbd4d3d9)
fix(banking): handle blank password protected PDFs and negative amounts in CR/DR columns (#56690)
* fix(banking): strip signs from amount if column has CR/DR values
* fix(banking): try decrypting PDF with a blank password
(cherry picked from commit 300471da12)
Co-authored-by: Nikhil Kothari <nik.kothari22@live.com>
fix(banking): use custom renderer for translated strings and parser for rules (#56643)
fix(banking): use custom renderer for translated strings and parser for formula evaluation
(cherry picked from commit 8447f551e7)
Co-authored-by: Nikhil Kothari <nik.kothari22@live.com>
The framework ignores `no_copy` while amending, so a reconciled voucher
carried a stale clearance date into its amendment even though the linked
bank transaction gets unreconciled on cancellation. Reset it via a shared
`before_insert` hook on AccountsController.
Fixes#54909
(cherry picked from commit 1a8d73cbbe)
2026-06-16 09:59:41 +00:00
308 changed files with 109739 additions and 85684 deletions
constcontent=_("Below is a list of all accounting entries posted against the bank account {0} between {1} and {2}.",[`<strong>${bankAccount?.account}</strong>`,`<strong>${formattedFromDate}</strong>`,`<strong>${formattedToDate}</strong>`])
return<divclassName="space-y-4 py-2">
<div>
<ParagraphclassName="text-sm">
<spandangerouslySetInnerHTML={{
__html: _("Below is a list of all accounting entries posted against the bank account {0} between {1} and {2}.",[`<strong>${bankAccount?.account}</strong>`,`<strong>${formattedFromDate}</strong>`,`<strong>${formattedToDate}</strong>`])
constcontent=_("Below is a list of all entries posted against the bank account {0} which have not been cleared till {1}.",[`<strong>${bankAccount?.account}</strong>`,`<strong>${formatDate(dates.toDate)}</strong>`])
return<divclassName="space-y-4 py-2">
<div>
<ParagraphclassName="text-sm">
<spandangerouslySetInnerHTML={{
__html: _("Below is a list of all entries posted against the bank account {0} which have not been cleared till {1}.",[`<strong>${bankAccount?.account}</strong>`,`<strong>${formatDate(dates.toDate)}</strong>`])
constcontent=_("Below is a list of all bank transactions imported in the system for the bank account {0} between {1} and {2}.",[`<strong>${bankAccount?.account_name}</strong>`,`<strong>${formattedFromDate}</strong>`,`<strong>${formattedToDate}</strong>`])
__html: _("Below is a list of all bank transactions imported in the system for the bank account {0} between {1} and {2}.",[`<strong>${bankAccount?.account_name}</strong>`,`<strong>${formattedFromDate}</strong>`,`<strong>${formattedToDate}</strong>`])
constcontent=_("This report shows all entries in the system where the <strong>clearance date is before the posting date</strong> which is incorrect.")
constentriesContent=_("Entries below have a posting date after {0} but the clearance date is before {1}.",[`<strong>${formattedToDate}</strong>`,`<strong>${formattedToDate}</strong>`])
return<divclassName="space-y-4 py-2">
<div>
<ParagraphclassName="text-sm">
<spandangerouslySetInnerHTML={{
__html: _("This report shows all entries in the system where the <strong>clearance date is before the posting date</strong> which is incorrect.")
}}/>
<spanclassName="text-p-sm">
<MarkdownRenderercontent={content}/>
<br/>
{data&&data.message.result.length>0&&<span>
<spandangerouslySetInnerHTML={{
__html: _("Entries below have a posting date after {0} but the clearance date is before {1}.",[`<strong>${formattedToDate}</strong>`,`<strong>${formattedToDate}</strong>`])
}}/>
<MarkdownRenderercontent={entriesContent}/>
<br/>
{_("You can reset the clearing dates of these entries here.")}
"label":"Role Allowed to Bypass Over Billing Restriction",
"options":"Role"
},
{
"fieldname":"period_closing_settings_section",
"fieldtype":"Section Break"
@@ -749,6 +768,18 @@
"description":"Changing the account in any transaction of the DocTypes listed below will trigger a repost. To prevent reposting, remove the relevant DocType from the list.",
"fieldname":"column_break_mfor",
"fieldtype":"Column Break"
},
{
"fieldname":"stock_expense_section",
"fieldtype":"Section Break",
"label":"Stock Expense Accounting"
},
{
"default":"0",
"description":"Books Purchase Expense and Expenses Added To Stock account pairs against stock value. On enabling this, the accounts become mandatory in Company or Item Defaults for Purchase Receipt, Purchase Invoice, Stock Entry, Stock Reconciliation and Landed Cost Voucher",
"""Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account."""
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.