# Inventory adjustments, physical counts, and reconciliation

Phase 3B.6 adds controlled compensating evidence. No API edits an authoritative
stock balance or rewrites historical movements.

## Adjustments

`adjust_stock` requires scoped adjust_stock, an active physical location/part,
nonzero bounded integer delta, UUID command key, reference, and explanation.
Controlled reasons are LOSS (negative), FOUND (positive), and CORRECTION (either).
COUNT_VARIANCE is reserved for count reconciliation. Every adjustment creates an
immutable StockAdjustment plus ADJUST_IN or ADJUST_OUT movement and signed ledger
entry. Managed transit/custody positions cannot be manually adjusted.

Negative adjustments cannot spend active reservations or unavailable serial/
anonymous buckets. Positive serialized adjustments accept registered units or
units explicitly REMOVED by a prior negative adjustment. They never resurrect
CONSUMED repair replacements. Found units retain permanent identity. Bringing an
unlocated registered unit into stock requires company-level adjustment scope;
restoring a removed unit also requires adjustment scope at its historical source.
No synthetic identifiers or serialization conversion are provided.

## Physical counts

A StockCount intentionally covers one location and one part. Multiple independent
positions can be counted concurrently. This avoids a warehouse-wide lock for a
small operational count. Lifecycle is DRAFT -> COUNTING -> RECONCILED, with explicit
reasoned cancellation from DRAFT or COUNTING. RECONCILED and CANCELLED are terminal;
a second administrative CLOSED state would add no distinct business fact.

`create_stock_count` stores draft intent only. It creates no stock, serialized
identity, or permanent serialization-policy lock. `start_stock_count` captures
expected ledger quantity and exact serialized units under the stock-position
lock. A partial unique constraint allows one active count per position.

While COUNTING, every common ledger posting and new reservation against that
position is rejected. The freeze covers receipts, transfers, issue/return, and
adjustments; independent positions remain available. Reservation release remains
allowed so a physical shortage can be resolved without spending reserved stock.
Location deactivation is rejected while a count is active.

`record_stock_count` records nonnegative observed quantity and explicit units,
with signed revision protection in Admin. It preserves expected-unit snapshots.
No undocumented missing unit is fabricated. Additional units must already be
registered or explicitly removed, belong to the same company/part, and not be
held in another location or consumed. Counting a REQUIRED_SERIAL part requires
one observed identity per quantity.

`reconcile_stock_count` requires both count_stock and adjust_stock scope. It
rechecks the unchanged ledger snapshot, locks all relevant units, and posts
separate adjustments for missing serials, found serials, and anonymous variance.
Equal total quantities with different observed serials still produce explicit
outbound/inbound evidence. Zero variance with identical identities creates no
synthetic movement. A shortage that would consume reservations is rejected until
those reservations are explicitly released. Any failure rolls back every
adjustment, unit-state change, and count transition.

`cancel_stock_count` retains evidence and releases the freeze without posting.
Cancellation remains available after part deactivation, with active actor/org
authorization. Reconciliation never silently repairs contradictory external data.

## Reconciliation queries and command

`control_positions` uses SQL subqueries to aggregate on-hand and reserved stock,
with bounded queries and joined display references. `inventory_anomalies` returns
scoped lazy querysets for negative stock, reservations exceeding stock, serialized
ledger/current-position disagreement, issued reservations missing issue evidence,
issue/reservation mismatch, over-resolved custody, and empty request documents.
Serialized reconciliation computes net unit movements, not a guessed latest UUID.

Run `python manage.py audit_inventory --actor <active-username>` for read-only
diagnostics within that actor's inventory scope. It prints anomaly record IDs and
returns a failure status when anomalies exist or the actor has no visible scope.
A clean limited-scope result is not a claim about unauthorized locations. The
command never edits history or automatically repairs data. Tests deliberately
create unsupported direct-write anomalies to prove detection without mutation.

Removed defective customer components remain in the separate immutable recovery
register described in SERVICE_PARTS_USAGE.md. They do not inflate replacement
stock or become anonymous stock-count discoveries. Defective/quarantine SparePart
positions remain distinct from usable warehouse/store positions.

## Admin, integrity, and locks

Admin adjustments sign location/part revisions, ledger-entry count, reservation
count, and active reserved quantity. Stock changes invalidate old adjustment
forms, including quantity ABA through compensating movements. Count observations
and start/reconcile/cancel confirmations use signed count revisions. All changes
delegate to services and require CSRF; posted evidence is readonly and undeletable.
Serialized snapshots are visible through readonly count inlines.

Locks follow actor/company/RBAC, part dependencies, locations, stock positions,
count, then serialized units. Reservation writers hold their case/request locks
before stock positions; count writers never acquire a service-case lock. Location
shared locks coordinate count start with deactivation. All physical writes use
the same position lock and count-freeze check, including document workflows.

Database constraints enforce reason/direction, positive bounds, company ownership,
count lifecycle evidence, one active count, immutable expected snapshots and
posted adjustments, unit ownership, and count variance-to-ledger coherence.
Supported APIs enforce sufficiency and scope; privileged raw SQL is outside the
operational API and remains subject to reconciliation diagnostics.

## Verification

The initial focused control suite passed 62 tests in 79.497 seconds. The combined
inventory run then passed 281 tests in 343.829 seconds. Expanded control and
foundation Admin verification passed 83 tests in 95.099 seconds; final serial
query/Admin verification passed 32 tests in 25.515 seconds. Phase 3B.6 adds
69 tests, including 16 real PostgreSQL races. Inventory now contains 288 tests
and 71 concurrency tests. Django checks, migration drift, and diff whitespace
checks passed; new migrations were inspected and exercised on fresh PostgreSQL.
Coverage includes both orders of adjustment/reservation and
adjustment/transfer, count/movement, duplicate reconciliation, competing counts,
serialized loss/found races, stale adjustments, rollback, company/scope boundaries,
Admin/CSRF, deliberate anomaly detection, superuser operational hierarchy checks,
and found-unit scope at its historical origin. This checkpoint passed before
starting the dedicated Phase 3B audit.


## Phase 6C operational access

The operational shell exposes positions, counts and posted adjustments using the
existing control queries/services. Count observation does not post stock;
reconciliation retains count plus adjustment permissions and existing position
locks. Direct position adjustments use the service's position revision and a signed
command UUID; stale/replayed confirmation cannot post extra stock. No new approval
state or stock calculation is introduced.

Current-location identities are selectable operationally. Registered/removed
identity reintroduction remains in guarded Admin with the original origin/company
checks. See [Operational UI](OPERATIONAL_UI.md#phase-6c-parts-and-inventory-operational-workspace)
for navigation, scope, preview limits and verification.
