Commit Graph

3555 Commits

Author SHA1 Message Date
Pandiyan P
fd728dacca fix(buying): allow purchase returns against a closed purchase order (#58126) 2026-08-13 17:55:41 +05:30
Sudharsanan Ashok
1e583725a1 feat: delivery note billing based on quantity (#56149)
* feat(stock): bill delivery note by qty when invoiced amount is short

* test(stock): add test for qty fallback in delivery note billing
2026-08-13 16:28:35 +05:30
Raffael Meyer
e591c7e8b6 fix: mirror rounding adjustment on distributed_discount_amount (#58047) 2026-08-11 18:26:26 +02:00
Mihir Kandoi
e8c890a844 fix(selling): preserve explicit UOM during item selection 2026-08-11 20:54:04 +05:30
Mihir Kandoi
009961edc7 fix(selling): reset stale item details on item change 2026-08-11 20:41:47 +05:30
Mihir Kandoi
13883a00b0 fix(stock): validate warehouse accounts when used 2026-08-11 18:16:47 +05:30
Diptanil Saha
b55c6d16c9 refactor(queries): using frappe.get_query in get_filtered_child_rows (#57991) 2026-08-10 19:33:29 +00:00
Pandiyan P
399ff463cc fix: re-check future sle before queuing repost on submit (#57664)
* test: cover both repost branches and the no-repost case

* fix: queue repost for entries backdated by a concurrent submit

---------

Co-authored-by: nareshkannasln <nareshkannashanmugam@gmail.com>
2026-08-10 11:53:49 +05:30
Mihir Kandoi
0bb322f4d1 Merge pull request #57560 from nishkagosalia/gh-56632
fix: handling negative grand total
2026-08-09 16:45:13 +05:30
Mihir Kandoi
4089f138f2 fix(controllers): correct negative rate settings link 2026-08-09 16:29:28 +05:30
pandiyan
eff29565ae fix: negate stock value difference for outward transfer bundles
make_bundle_for_material_transfer squares stock_value_difference for
outward rows instead of negating it. multiply by -1, matching the qty
negation on the line above.

no behaviour change: set_incoming_rate and calculate_qty_and_amount both
recompute the field from qty * incoming_rate before the bundle is saved.
2026-08-09 13:20:48 +05:30
pandiyan
f7b2775829 fix: tolerate floating-point drift in sales team allocated percentage
the total of allocated_percentage was compared to 100 with exact float
equality, so a correct allocation could be rejected when the sum drifts
in binary floating point (10.0 + 58.02 + 31.98 -> 100.00000000000001).

round the total to the field precision before comparing, in both
SellingController.calculate_contribution and Customer.validate.
2026-08-09 11:25:12 +05:30
Jatin3128
96a6db7387 feat(accounts): split exchange gain and exchange loss accounts (#57839)
* feat(accounts): split exchange gain and exchange loss accounts

Add optional Exchange Gain Account and Exchange Loss Account fields on
Company. When set, realized FX gain/loss from settling an invoice in a
foreign currency (via Payment Entry, Payment Reconciliation, or a
Journal-Entry-based advance) books to the matching account instead of
the single Exchange Gain/Loss account. Either field left blank falls
back to the existing Exchange Gain/Loss account, so companies that
don't configure the new fields are unaffected.

New companies get "Exchange Gain" and "Exchange Loss" accounts
auto-created in their chart of accounts and auto-assigned to the new
fields, same as the existing Exchange Gain/Loss account provisioning.

The Payment Reconciliation tool's per-allocation "Difference Account"
override in its reconcile dialog continues to work as before; the
split accounts only change the computed default shown there.

* test(account_balance): account for new Exchange Gain account in income report

The new auto-provisioned Exchange Gain account under Indirect Income
now shows up in the Income root type report for _Test Company 2.

---------

Co-authored-by: test <test@test.com>
2026-08-06 17:36:43 +05:30
R-Jayaraman
732c884633 test(sales): add coverage for zero-qty return rejection
Greptile flagged that the sales-side zero-qty-return fix had no dedicated
test proving the behavior - the existing suite happened to pass, but
nothing specifically asserted that an all-zero return is rejected while
a normal negative-qty return still succeeds.

Adds two tests covering the doctypes that rely entirely on this check
(no other guard covers them for a non-stock-effect return):
- Delivery Note return with qty 0 -> rejected
- Sales Invoice return with qty 0 (no update_stock) -> rejected

POS Invoice is not covered separately here since it always runs with
update_stock=1, which is already guarded by the pre-existing
validate_zero_qty_for_return_invoices_with_stock check regardless of
this fix.
2026-08-03 12:41:38 +05:30
R-Jayaraman
a3e9d13da3 fix(sales): reject sales returns where every item has zero quantity
validate_returned_items() set items_returned=True whenever a row matched
a valid item from the original document, even if its qty was 0. This let
a Sales Invoice, Delivery Note, or POS Invoice return be submitted with
every line at qty=0 - a no-op document with no stock or financial effect
that still consumed a document number and linked back to the original
transaction.

Scoped to the Sales side only: items_returned now flips to True for
Sales Invoice/Delivery Note/POS Invoice only when qty (or received_qty)
is actually negative, so an all-zero sales return correctly hits the
existing "At least one item should be entered with negative quantity"
check. Purchase Invoice, Purchase Receipt, and Subcontracting Receipt
are unchanged.
2026-08-03 12:40:57 +05:30
Afsal Syed
248873034d fix(stock): scope over deliver/receive role check to delivery and receipt overflow 2026-08-03 12:27:33 +05:30
Mihir Kandoi
f03c1311cd fix(controllers): source trend report labels from the master (#57724)
* fix(controllers): source trend report labels from the master

item_name, customer_name, territory and supplier_name are stored on each
transaction and editable, so they are not functionally dependent on the grouped
key and historical documents can hold different values for the same item,
customer or supplier. Aggregating them with Max() is a text sort, and MariaDB
folds case while PostgreSQL orders by byte value, so the two engines can label
the same row differently.

Read each from its master instead. Those values ARE dependent on the grouped
key, so they can be grouped without splitting rows and agree on both engines by
construction rather than by an assumption about the data. Supplier needed no new
join -- the Supplier master was already joined as t3 for supplier_group.

A Quotation's party_name is a dynamic link to either a Customer or a Lead, so
neither master can be joined without dropping the other; there the values come
from correlated subqueries over both, keyed only on the grouped party_name.

Row counts and every numeric total are unchanged. What changes is that a
renamed record now shows its current name rather than whichever historical
snapshot happened to sort highest.

* test(selling): assert which label the trends report returns

The existing tests assert the customer stays one row but never which territory
or name comes back, so a divergence between engines passes unnoticed. Asserts
both equal the Customer master's values while an order stores a different
territory.

* fix(controllers): resolve a Quotation's party label through quotation_to

party_name is a dynamic link, so looking it up in Customer and Lead alone was
wrong twice over: a Quotation raised against a Prospect or a CRM Deal got a
blank label, and when a Lead shared its name with a Customer the Customer-first
lookup returned the wrong record's name and territory.

Resolve through the quotation_to discriminator instead, mirroring
Quotation.set_customer_name -- Customer, Lead (company_name falling back to
lead_name), Prospect, and CRM Deal. The CRM Deal branch is emitted only when its
table exists, since it ships with the CRM app.

quotation_to joins the GROUP BY as well: two parties of different types can
share a name, and merging them into one row was never right.

* style(controllers): name the quotation CASE branches

semgrep's string-concat-in-list flags adjacent string literals inside a list,
since that shape is usually a missing comma rather than deliberate. Bind each
branch to a name first so the concatenation is unambiguous.
2026-08-03 06:52:25 +00:00
Mihir Kandoi
bf869c3426 Merge pull request #57718 from frappe/pg-audit/search-ranking-case-insensitive
fix(controllers): restore case-insensitive employee/lead/bom search ranking
2026-08-03 12:08:33 +05:30
Mihir Kandoi
d44ed5357d Merge pull request #57645 from aerele/fix/purchase-zero-qty-return-validation
fix(purchase): reject purchase returns where every item has zero quan…
2026-08-03 11:45:36 +05:30
Mihir Kandoi
1968f06cc8 test(controllers): assert lead search ranking, not just result count
The existing query tests assert only how many rows come back, so an ordering
divergence between engines passes unnoticed. Adds a case-adversarial pair: a
lead whose name starts with the search term in upper case, and one containing
it in lower case later on. The first must rank ahead of the second.
2026-08-03 11:38:58 +05:30
Mihir Kandoi
a7a14c82da fix(controllers): restore case-insensitive employee/lead/bom search ranking
Reapplies #56330, which was reverted by #56389 with no recorded reason and has
been absent since 23 June.

The search filter uses .like(), which frappe renders as ILIKE on PostgreSQL, so
a candidate matches regardless of case. The ranking used a bare Locate(), which
frappe renders as strpos() -- case-sensitive there. A candidate can therefore
pass the filter, score no match in the ranking, fall back to 99999 and sort
last, while MariaDB's case-insensitive LOCATE ranks it first.

Same query, different order on the two engines, and a different result page
once page_len cuts between them.

Lower() both operands, matching the item, project, user and pick list handlers
in this same file, which were already correct.
2026-08-03 11:38:57 +05:30
Mihir Kandoi
6477709f7c Merge pull request #57674 from mihir-kandoi/fix/uom-conversion-factor-precision
fix: preserve UOM conversion factor precision in transactions
2026-08-02 11:55:13 +05:30
Diptanil Saha
ceefd4add7 Merge pull request #57201 from diptanilsaha/fix/perms_whitelisted_methods
fix: permission checks on various whitelisted methods
2026-08-01 14:46:34 +05:30
diptanilsaha
3b0cbc972e fix(item_variant): added permission checks on enqueue_multiple_variant_creation 2026-08-01 14:28:29 +05:30
Mihir Kandoi
269cc6ee3b fix: preserve UOM conversion factor precision in transactions
calculate_item_values rounds every Float field on an item row to the
site's Float Precision (3 by default), and conversion_factor was one of
them. The factor is a ratio, not a rate: UOM Conversion Factor.value is
stored at precision 9, and Material Request keeps the full value because
it has no currency field and so never runs the calculation.

Mapping a Material Request to a Purchase Order therefore truncated the
factor - 0.453592292 for Pound -> Kg became 0.454 - and stock_qty, which
is recomputed as qty * conversion_factor, drifted from the quantity that
was requested, leaving the Material Request unable to close.

Exclude conversion_factor from the rounded fields on the server and on
the client. Factors below the site precision would otherwise round to
zero outright.
2026-07-31 21:53:59 +05:30
R-Jayaraman
cde2963da1 test(purchase): add coverage for zero-qty return rejection 2026-07-31 13:31:10 +05:30
R-Jayaraman
b63066ed44 fix(purchase): reject purchase returns where every item has zero quantity
validate_returned_items() set items_returned=True whenever a row matched
a valid item from the original document, even if its qty was 0. This let
a Purchase Invoice, Purchase Receipt, or Subcontracting Receipt return be
submitted with every line at qty=0 - a no-op document with no stock or
financial effect that still consumed a document number and linked back
to the original transaction.

Scoped to the Purchase side only: items_returned now flips to True for
Purchase Invoice/Purchase Receipt/Subcontracting Receipt only when qty
(or received_qty) is actually negative, so an all-zero purchase return
correctly hits the existing "At least one item should be entered with
negative quantity" check. Sales Invoice, Delivery Note, and POS Invoice
are unchanged.

Also applies a corresponding check to the item_name-only fallback branch
(for rows without an item_code - Item Code is not mandatory on Purchase
Invoice Item), which previously bypassed this fix entirely and still set
items_returned=True unconditionally regardless of quantity. For that
branch specifically, only qty is checked (not received_qty): with no
linked Item there's no accepted/rejected split, so received_qty carries
no independent meaning and a qty=0 row must be rejected regardless of
its value.
2026-07-31 13:31:01 +05:30
nishkagosalia
136f92db04 fix: handling negative grand total 2026-07-29 14:31:33 +05:30
Vishnu Priya Baskaran
5125d64b7f fix: clear deferred revenue/expense fields on uncheck (#57140) 2026-07-29 04:18:45 +05:30
Mihir Kandoi
6d748c5523 Merge pull request #57571 from mihir-kandoi/move-warehouse-defaults-to-company
refactor: move warehouse defaults from Stock Settings to Company
2026-07-28 20:28:55 +05:30
Mihir Kandoi
26613d258e refactor: move warehouse defaults from Stock Settings to Company
Default Warehouse and Sample Retention Warehouse were global singles, so every
consumer had to re-check that the warehouse belonged to the transaction's
company before using it. Both now live on Company, under a new Warehouse
Defaults section that also collects the warehouse fields Company already had.

The company check moves to Company.validate_warehouses, which also rejects
group warehouses for all seven fields — a group or cross-company value there
already failed at SLE time, this just surfaces it at the source.

New companies get their Stores warehouse as Default Warehouse via
create_default_warehouses, replacing the setup-wizard and test-fixture code
that seeded the global.
2026-07-28 19:29:18 +05:30
Smit Vora
986cea2331 feat: taxable-base resolver hook for custom charge types (#56175) 2026-07-28 17:21:55 +05:30
rohitwaghchaure
4a0a1db17e fix: skip stock expense GL entries for non-stock items (#57519)
* fix: skip stock expense gl entries for non stock items

* test: use a leaf expense account for the service item invoice
2026-07-28 13:00:32 +05:30
rohitwaghchaure
f077d2edc0 fix: GL entries for purchase expense with LCV (#57475) 2026-07-26 23:08:35 +05:30
Mihir Kandoi
2551543703 fix: accept dict target_doc in mapper endpoints
Native JSON request bodies deliver target_doc as a parsed dict, which the
str | Document type hints rejected, breaking every Get Items From button.
Unify all whitelisted mapper endpoint hints to str | dict | Document | None.
Needs frappe#41190 so get_mapped_doc converts the dict target.
2026-07-23 13:20:40 +05:30
Mihir Kandoi
9b7c36f3d9 fix: do not translate default record lookup keys
Company.create_default_departments named and looked up the root
Department via _("All Departments"), which resolves in the session
language. A site set up in a non-English language stores the root
translated, and a company created later from a session in another
language misses it and inserts a second root, corrupting the tree.
Resolve the root once with get_root_of (falling back to the canonical
English name on fresh installs) and reuse it for the root record, the
exists-guard and the child departments' parent.

Also stop translating lookups of records install_fixtures stores under
English names: Price List "Standard Selling" and Print Headings
"Credit Note" / "Debit Note".

Same class as the root Item Group fix (#57386, issue #57345).
2026-07-23 12:51:30 +05:30
Nabin Hait
7e0c81391b test: use a role-less user for the permission check
test@example.com carries System Manager in the frappe fixtures, so on a
fresh CI site it can read Delivery Note and the lookup legitimately
returns the draft. test1@example.com has no roles, making the
no-permission assertion environment-independent.
2026-07-23 10:27:41 +05:30
Nabin Hait
01f7e45058 Merge branch 'develop' into feat/warn-existing-draft-links 2026-07-22 15:18:39 +05:30
Mihir Kandoi
88b8ce3888 fix: enforce company restrictions at transaction level
Restrict to Companies only filtered list views and document reads, and
only for users with Company user permissions. Any user could still use
a master restricted to Company A in a Company B transaction, and users
without Company user permissions bypassed the feature entirely.

Validate on save of transactions that every linked Item, Customer and
Supplier allows the transaction company, and filter item link queries
by the transaction company so restricted items don't show up in the
item selector.
2026-07-22 14:13:22 +05:30
Nabin Hait
c522a1fdae fix: disable pagination in draft link lookup
frappe.get_list defaults to 20 rows; child-table joins can produce
duplicate parent names that fill the window and hide further drafts.
Also clarify the check-ordering regression test.
2026-07-22 12:06:00 +05:30
Nabin Hait
7d351153bb feat: warn when a draft linked document already exists
When creating a follow-up document (SO->DN, PO->PR, PI->Payment Entry,
etc.), warn the user if a draft of the target doctype already linked to
the source document exists, with links to the drafts and the option to
proceed anyway.

The target doctype comes from the make_mapped_doc response via the new
frappe.model.add_mapped_doc_guard hook, so every open_mapped_doc flow is
covered without per-doctype code or method-name inference. The server
lookup walks parent-level and child-table Link / Dynamic Link fields of
the target doctype and queries through frappe.get_list, so role and user
permissions apply and docstatus filtering happens in the query itself.
Payment Entry creation bypasses open_mapped_doc, so its controller runs
the same guard explicitly.
2026-07-20 18:37:24 +05:30
Poovetha
51a9fc0316 fix(projects): include on hold status in project filters and reports 2026-07-17 15:03:25 +05:30
rohitwaghchaure
7b517a4e64 feat: book Expenses Added To Stock GL entries for stock vouchers (configurable) (#57190)
* feat: book Expenses Added To Stock GL entries for Stock Entry, Stock Reconciliation and LCV

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: make stock expense GL booking configurable via Accounts Settings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: skip stock expense booking for unconfigured companies, check flag once per compose

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 10:04:45 +00:00
Afsal Syed
337a06dfb6 feat(stock): automatically link portal users to their associated contact profiles for customers and suppliers 2026-07-16 15:11:28 +05:30
rohitwaghchaure
1fd2faa68d fix: permission issue (#57112) 2026-07-14 12:22:48 +00:00
Mihir Kandoi
38e8c3b1fe fix: accounting dimension search matching unrelated records
#56871 routed any searchfield without a DocField meta — including
"name", which get_search_fields() always appends — into the
`= cint(txt)` branch. For non-numeric search text cint() yields 0, so
`is_group = 0` (Cost Center) matched every leaf record on both
engines, and `name = 0` matched every non-numeric name on MariaDB.

Skip Check fields from or_filters entirely — a checkbox can't match
search text — and keep LIKE for everything else, including "name".
2026-07-12 18:37:51 +05:30
Nabin Hait
0264202b42 Merge pull request #56978 from nabinhait/refactor-ac-services
refactor(accounts): extract deferred-accounting and document-schedule from AccountsController
2026-07-11 14:28:16 +05:30
Mihir Kandoi
1b6d3ee870 Merge pull request #56561 from aerele/fix/report-currency
fix: use company currency instead of global default in report
2026-07-09 20:38:55 +05:30
Mihir Kandoi
8dd56ecba5 Merge pull request #56913 from aerele/fix/trend-report-column-translations
fix: make trend report based-on and group-by column labels translatable
2026-07-09 20:33:13 +05:30
Nabin Hait
174027bd57 refactor(accounts): extract deferred-accounting and document-schedule out of AccountsController
Continues the AccountsController service decomposition (Phase 5).

- Add accounts/services/deferred_accounting.py with DeferredAccountingService
  owning the deferred revenue/expense validations (income/expense account
  defaulting and service start/end date checks).
- Move the document-schedule orchestration (validate_all_documents_schedule
  and the invoice/non-invoice variants) into PaymentScheduleService, where
  they already delegated, removing the controller-to-service round trip.
- Update the three validate() call sites; keep
  validate_auto_repeat_subscription_dates on the controller (still called by
  buying/selling controllers).

No behavior change. accounts_controller.py 1818 -> 1745 lines.
2026-07-09 17:55:19 +05:30