* test: stop four tests from passing without running
Three advisory-lock tests return early on MariaDB:
if frappe.db.db_type != "postgres":
return
A bare return reports the test as passed, so the MariaDB CI job shows
green for a test it never ran. skipTest reports it as skipped.
test_stock_reco_with_opening_stock_with_diff_inventory returned early
when the custom "Plant" DocType already existed. DocType creation is
DDL and survives the test transaction, so the test ran once on a fresh
site and silently did nothing on every run after that. Create the
DocType only when it is missing and let the test run either way.
Its closing loop also asserted inside an if/elif over the ledger rows,
which verified nothing if the dimension came back unset. Compare the
whole {plant: qty} mapping instead.
* test: give the job card validator tests a real job card
Both tests looked for a submitted Job Card left behind by another test
and returned when they did not find one:
jc_name = frappe.db.get_value("Job Card", {"docstatus": 1})
if not jc_name:
return # skip if no job cards in test data
Run in isolation they asserted nothing and still reported a pass, and
they were the only coverage for validate_job_card_fg_item and
validate_job_card_item.
Move them to test_job_card.py, where the Work Order and BOM fixtures
that produce Job Cards already live, and build the Job Card in the test.
The finished-good case needs a card that carries one, so it goes through
a track_semi_finished_goods BOM. Both now assert on the message text, and
both fail if the validator body is removed.
Four tests in test_stock_entry.py each cover only an early-return guard:
def test_validate_job_card_item_skips_when_no_job_card(self):
se = frappe.new_doc("Stock Entry")
se.job_card = None
se.validate_job_card_item() # must not raise
That exercises `if not self.job_card: return` and nothing else. The
mismatch tests next to them already cover the behaviour these validators
actually implement.
Three tests in test_payment_request.py assert against their own mock.
_is_v2_gateway delegates to payments.utils.is_v2_gateway; all three mock
that delegate to return False and then assert the result is False, for
inputs (None, "", "NonExistentGateway12345") that take an identical code
path. The mock decides the outcome, so the assertion holds regardless of
what ERPNext does. The three tests covering the real branches --
delegation, a False delegate, and the exception fallback -- are kept.
* fix: include rejected qty in Purchase Receipt billing base
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: per billed stays 100% for fully rejected receipt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(accounts): skip received/rejected qty checks on non-stock return
A Purchase Invoice without Update Stock writes no Stock Ledger Entry, so
received_qty and rejected_qty on its rows move no stock and bill no
amount. The server never derives or validates them either:
validate_accepted_rejected_qty only runs when update_stock is set.
validate_quantity still counted both columns against the source invoice.
Whatever value the form last wrote to the read-only received_qty was
tallied as returned, so a partial return that lowered qty locked out the
rest of the invoice with StockOverReturnError.
Restrict the two columns to documents that actually carry an
accepted/rejected split: Purchase Receipt, Subcontracting Receipt, and a
Purchase Invoice with Update Stock. qty stays validated in every case, so
the billed quantity is still capped at what the source invoice billed.
* test(accounts): cover partial returns of a non-stock invoice
Fails before the previous commit with StockOverReturnError on the second
return, because the stale received_qty carried by the first return is
tallied as a full return of the invoice.
* refactor(accounts): pass frm into the Purchase Invoice hide_fields
hide_fields took a doc but reached for cur_frm to get the grid and to
refresh, so it only worked on whichever form happened to be current. Take
frm instead: all three callers already have one.
frm.toggle_display replaces the hide_field / unhide_field globals, which
resolve the docfield through cur_frm the same way. var becomes let/const.
No change in behaviour.
* fix(accounts): hide stock columns without Update Stock
Received Qty, Rejected Qty and the warehouse section were shown on any
return, including one that updates no stock. received_qty is read-only
there and rejected_qty moves neither stock nor billed amount, so the grid
offered values the user could not correct and the form could not keep in
step with qty.
Show the group only when the invoice updates stock, matching an ordinary
Purchase Invoice. Nothing on these rows needs a warehouse either:
validate_warehouse only checks the warehouses that are set.
a dunning was resolved as soon as the invoiced sum was settled, because the
status was derived from the invoice outstanding alone. paying an invoice
without the interest and fee therefore closed the dunning and lost the
interest: a fresh dunning finds nothing overdue to charge it on.
the dunning amount is never a receivable, it only reaches the ledger as a
negative deduction on a payment entry made from the dunning. link that row
to the dunning so what has been collected is known, and resolve a dunning
only once the invoiced sum and the dunning amount are both paid. a dunning
resolved by hand keeps its status, so waiving the interest stays possible.
the deduction is a company currency field, so book and measure the dunning
amount through base_dunning_amount instead of the transaction currency one.
an interest-only payment leaves every invoice outstanding untouched, so
update the linked dunnings from the payment entry itself instead of relying
on the outstanding amount to change. such a payment also has to be built
from what is left to collect, not from the totals the dunning was raised
with, which are stale by then.