PASS Modules\Inventory\tests\Feature\BatchExpiryMovementTest ✓ approving a receipt carries batch_number and expiry_date onto the m… 6.35s ✓ approving a receipt without batch/expiry leaves the movement column… 1.70s ✓ expiring-lots report returns batch-tracked lots from the child tabl… 3.38s ✓ expiring-lots report shows EVERY split batch on a line, including a… 1.72s ✓ expiring-lots report sources SERIAL products from product_serials (… 1.74s ✓ expiring-lots report excludes DRAFT receipts (saved lots are not st… 1.76s PASS Modules\Inventory\tests\Feature\ConsignmentTransferTest ✓ shipping + receiving a consignment transfer moves the customer lot… 1.62s ✓ company-owned transfer is unchanged — no consignment ledger touched… 1.60s ✓ cancelling a shipped-not-received consignment transfer restores lot… 1.62s PASS Modules\Inventory\tests\Feature\ConsignmentValuationBarrierTest ✓ it valuation reports OWNED value only and puts the consigned lot in… 1.76s ✓ it valuation without any consignment reports physical == owned (no… 1.93s PASS Modules\Inventory\tests\Feature\CostingApiTest ✓ can get product cost with weighted average 1.28s ✓ can get product cost with FIFO 1.23s ✓ can list cost layers for a product 1.18s ✓ can include exhausted cost layers 1.19s ✓ can filter cost layers by warehouse 1.22s ✓ can get inventory valuation report 1.16s ✓ can get valuation report grouped by warehouse 1.70s ✓ can filter valuation by warehouse 1.21s ✓ can update costing method 1.20s ✓ validates costing method value 1.22s ✓ returns current method when already set 1.19s ✓ increaseStock creates cost layer 1.12s ✓ FIFO decreaseStock consumes oldest layers first 1.12s ✓ WAC decreaseStock does not consume cost layers 1.37s ✓ getIssueCost returns WAC cost by default 3.20s ✓ getIssueCost returns FIFO cost when configured 1.10s ✓ approving receipt creates cost layer automatically 1.20s ✓ approving issue with FIFO consumes cost layers correctly 1.38s ✓ unauthenticated user cannot access costing 0.07s ✓ cost layers are isolated by company 1.65s ✓ valuation report excludes zero quantity products 1.79s PASS Modules\Inventory\tests\Feature\CountSheetVariantGrainTest ✓ a product with two variants returns one row per variant carrying it… 1.16s ✓ a variant with no stock balance still appears with before_quantity… 1.16s ✓ a variant stocked only in another warehouse shows zero here, not th… 1.18s ✓ a product with no variants still returns exactly one row with a nul… 1.78s ✓ a variant-level balance never leaks into a no-variant product row 1.73s ✓ product_variant_name follows the request locale like getLocalizedNa… 1.33s ✓ search and category filters still apply at the variant grain 1.81s ✓ variants of another company are never joined in 1.28s ✓ a display_only product returns ONE base row carrying its real balan… 1.78s ✓ a separate_stock product fans out per variant while a display_only… 1.71s ✓ a fanned-out product with a non-zero base balance also emits the ba… 1.27s ✓ a fanned-out product whose base balance is zero does NOT emit a bas… 1.20s ✓ pagination total is counted at the fanned-out grain 2.19s ✓ a display_only product whose variant holds stock still emits that v… 1.76s ✓ a soft-deleted variant that still holds stock is emitted so it can… 1.15s ✓ a hidden variant holding a zero balance adds no row 1.92s ✓ a stock-bearing variant that also has a balance is emitted exactly… 1.19s ✓ the orphan rescue leaves the no-variant path byte-for-byte unchange… 2.39s ✓ the category_id filter applies at the variant grain 1.62s PASS Modules\Inventory\tests\Feature\ExpiringLotsOwnerTest ✓ it expiring-lots tags a consignment lot with its owner 1.62s ✓ it the owner filter scopes to own vs a specific customer 1.67s PASS Modules\Inventory\tests\Feature\ExpiryVariantGrainTest ✓ the expiry report labels each serial with its variant 1.20s ✓ the expiry report labels a soft-deleted variant rather than leaving… 1.20s ✓ a variant-less company sees the expiry report unchanged 1.20s ✓ expiring-batches splits a batch lot per variant instead of merging… 1.24s ✓ the expiring-batches on-hand anchor is the variant on hand, not the… 1.14s ✓ expiring-batches labels a soft-deleted variant 1.11s ✓ expiring-batches splits the serial source per variant too 1.13s ✓ a variant-less company sees expiring-batches unchanged 1.16s ✓ a lot line with no variant still anchors against the product total… 1.14s PASS Modules\Inventory\tests\Feature\GrainParityCountVsReorderTest ✓ the count sheet and the reorder grain emit exactly the same (produc… 4.67s ✓ the count sheet and the reorder grain agree on the quantity behind… 4.94s ✓ a product with no variants emits one product-wide row on both sides… 1.27s ✓ offsetting balances across warehouses are a row per warehouse and n… 1.30s ✓ a balance keyed to another product variant is dropped by the sheet… 1.80s PASS Modules\Inventory\tests\Feature\InventoryAdjustmentApiTest ✓ can list adjustments 1.24s ✓ can filter adjustments by status 1.26s ✓ can filter adjustments by reason 1.22s ✓ can create a manual adjustment 1.18s ✓ auto-generates adjustment number 1.24s ✓ validates required fields on adjustment create 1.16s ✓ can show an adjustment 1.21s ✓ can approve an adjustment and update stock 1.18s ✓ approve adjustment increase adds stock 1.34s ✓ cannot approve a non-draft adjustment 1.11s ✓ can cancel an approved adjustment and reverse stock 1.15s ✓ can cancel a draft adjustment without stock changes 1.23s ✓ cannot cancel an already cancelled adjustment 1.20s ✓ unauthenticated user cannot access adjustments 0.07s ✓ adjustments are isolated by company 1.52s PASS Modules\Inventory\tests\Feature\InventoryAgingVariantGrainTest ✓ aging breaks out per variant so the old colour is identifiable 1.19s ✓ aging keeps the warehouse dimension alongside the variant 1.29s ✓ aging labels a soft-deleted variant that still holds cost layers 1.21s ✓ a variant-less company gets the same aging rows, numbers and key li… 1.21s PASS Modules\Inventory\tests\Feature\InventoryApprovalWiringTest ✓ NO WORKFLOW: adjustment submits and posts exactly as today — ZERO a… 1.17s ✓ NO WORKFLOW: a draft adjustment still posts in one click (no submit… 1.22s ✓ NO WORKFLOW: issue submits and issues exactly as today — ZERO appro… 1.23s ✓ PENDING adjustment moves ZERO stock and creates ZERO journal entrie… 1.87s ✓ PENDING issue moves ZERO stock and creates ZERO journal entries (no… 1.69s ✓ the wrong approver is refused 403 and the approved adjustment is th… 1.83s ✓ multi-level: level 1 approving is not enough — the issue stays bloc… 2.28s ✓ a REJECTED adjustment stays blocked on retry, and is released only… 2.43s ✓ a never-submitted draft cannot post straight past a configured work… 3.37s ✓ both inventory types report wired:true in the document-type catalog… 2.00s ✓ the approvals inbox renders the document number and amount for both… 1.91s ✓ P1: a 5,000-unit write-off typed at unit_cost 0.01 is priced at SYS… 1.71s ✓ P1: an INCREASE line keeps the USER cost (no system cost exists for… 1.17s ✓ P1: the user cost still wins when it is HIGHER than the system cost… 1.20s ✓ P1: THE SACRED INVARIANT survives the system-cost pricing — no work… 1.17s ✓ P2: a document rejected from the "My Approvals" inbox stays pending… 1.77s ✓ P2: an issue rejected from the inbox is likewise re-submittable 2.20s ✓ P3: a fully-approved, unposted adjustment cannot be re-submitted —… 4.00s ✓ P3: a fully-approved, unissued stock issue cannot be re-submitted e… 1.75s ✓ P5: cancelling a PENDING adjustment closes the cycle — the approver… 2.26s ✓ P5: cancelling an APPROVED (issued) stock issue closes its cycle to… 1.77s ✓ P5: THE SACRED INVARIANT — cancelling with NO workflow writes and t… 1.16s ✓ WP6: a poster who holds ONLY inventory.adjustments.approve is REFUS… 1.64s ✓ WP6: an approver who holds core.approval-logs.act but NO posting pe… 1.69s ✓ WP6: the stock ISSUE governance endpoints are gated on core.approva… 2.28s ✓ WP7: NO WORKFLOW — rejecting a POSTED adjustment is 422; it cannot… 1.24s ✓ WP7: NO WORKFLOW — the LEGITIMATE reject still works: a pending_app… 1.20s ✓ WP7: NO WORKFLOW — rejecting a POSTED (issued) stock issue is 422;… 1.43s ✓ WP7: an approved cycle is CONSUMED by the post — a document that co… 1.75s PASS Modules\Inventory\tests\Feature\InventoryCostReportApiTest ✓ InventorySettingDefinitionSeeder seeds the three cost keys with def… 0.07s ✓ InventorySettingDefinitionSeeder is idempotent 0.07s ✓ carrying cost equals inventory value times carrying percent 0.10s ✓ costs endpoint reports carrying cost as on-hand value times carryin… 1.26s ✓ EOQ matches the closed-form on a known dataset 0.07s ✓ EOQ is null when an input is missing (guarded divide-by-zero) 0.07s ✓ costs endpoint returns null EOQ when ordering cost is the default z… 1.27s ✓ reorder point equals average daily usage times lead time plus safet… 0.07s ✓ costs endpoint computes ROP = usage×LT + safety from issue history 1.35s ✓ aging buckets sum to total on-hand value per product and warehouse 1.31s ✓ aging excludes exhausted layers and is scoped by company 1.99s ✓ unauthenticated user cannot access cost reports 0.08s ✓ cost reports are isolated by company 1.94s PASS Modules\Inventory\tests\Feature\InventoryCountApiTest ✓ can list inventory counts 1.36s ✓ can filter counts by status 1.30s ✓ can filter counts by warehouse 1.40s ✓ can create an inventory count 1.37s ✓ auto-generates count number 1.32s ✓ populates system quantity from stock balance on create 1.30s ✓ validates required fields on count create 1.19s ✓ can show an inventory count 1.21s ✓ can update a draft count 1.19s ✓ cannot update an approved count 1.19s ✓ can delete a draft count 1.71s ✓ cannot delete an approved count 2.31s ✓ can finalize a count and auto-create adjustment 2.41s ✓ finalize count with no differences does not create adjustment 1.51s ✓ finalize creates a zero stock balance row for a counted product tha… 1.26s ✓ cannot finalize an approved count 1.23s ✓ unauthenticated user cannot access counts 0.09s ✓ counts are isolated by company 1.19s ✓ products-for-warehouse returns every active tracked product with it… 3.68s ✓ products-for-warehouse is company-scoped 4.24s ✓ products-for-warehouse 404s for a warehouse of another company 1.41s ✓ a count line accepts a variant of its own product 1.30s ✓ a count line still accepts a null variant — the legacy base-keyed p… 1.27s ✓ a count line rejects a variant that belongs to ANOTHER product 1.93s ✓ a count line rejects a variant that belongs to ANOTHER company 1.89s ✓ a count line still accepts a SOFT-DELETED variant of its own produc… 1.54s ✓ the per-line variant rule reads the sibling product of the SAME ind… 1.85s ✓ updating a count rejects a variant that belongs to another product 1.79s PASS Modules\Inventory\tests\Feature\InventoryCountVariantDisplayTest ✓ inventory count store response carries product_variant_name 1.20s ✓ inventory count update response carries product_variant_name 1.21s ✓ inventory count show still returns product_variant_name after the v… 1.19s ✓ inventory count show localizes a soft-deleted variant name by Accep… 1.10s ✓ inventory count show emits product_variant_name as null for a line… 1.15s PASS Modules\Inventory\tests\Feature\InventoryIssueApiTest ✓ can list issues 1.65s ✓ can filter issues by status 1.70s ✓ can filter issues by warehouse 1.70s ✓ can filter issues by date range 1.70s ✓ can search issues by issue number 1.72s ✓ can create an issue with items 1.77s ✓ issue number is auto-generated 1.84s ✓ store validates required fields 1.70s ✓ store validates items are required 1.88s ✓ store validates item fields 2.10s ✓ store persists per-item warehouse_id and owner_partner_id 1.92s ✓ store accepts owner_partner_id=0 as the force-own sentinel 1.98s ✓ store leaves per-item routing NULL when omitted (OFF-path unchanged… 1.89s ✓ store rejects a cross-company per-item warehouse 1.86s ✓ update persists per-item warehouse_id and owner_partner_id 1.68s ✓ can show an issue with items 1.67s ✓ cannot show issue from another company 1.81s ✓ can update a draft issue 2.26s ✓ cannot update an approved issue 2.63s ✓ can delete a draft issue 3.98s ✓ cannot delete an approved issue 1.71s ✓ can approve a draft issue with items and stock 1.70s ✓ approve auto-sets unit cost from average cost 1.59s ✓ approve creates stock movements 1.91s ✓ cannot approve issue with insufficient stock 1.78s ✓ can approve issue with negative stock when warehouse allows it 1.70s ✓ cannot approve issue without items 1.69s ✓ cannot approve an already approved issue 1.69s ✓ can cancel an approved issue and stock is restored 1.72s ✓ cannot cancel a draft issue 1.81s ✓ unauthenticated user cannot access issues 0.07s ✓ company isolation: cannot see other company issues 1.71s PASS Modules\Inventory\tests\Feature\InventoryIssueJobTagLinkTest ✓ a job-tagged issue moves stock and posts ZERO accounting effect 1.86s ✓ reference_id is required when the reference type is job_tag 1.63s ✓ a job tag belonging to another company is refused 1.63s ✓ a CLOSED job tag cannot be attached to a new issue 1.72s ✓ a soft-deleted job tag cannot be attached to a new issue 1.78s ✓ the resource returns the job tag name as reference_number in both l… 1.68s ✓ reference_number is null when the job tag was removed, and never le… 1.73s ✓ the issues list resolves every job tag name in a single query 1.97s ✓ an issue whose job tag was CLOSED afterwards can still have its not… 1.71s ✓ an issue on a CLOSED job tag survives the screen resending the whol… 1.61s ✓ re-pointing a job-tagged issue at a CLOSED tag is still refused 1.67s ✓ re-typing a draft to job_tag without naming a tag is refused 1.72s ✓ a job-tagged draft can be re-pointed at another ACTIVE tag 1.70s ✓ re-typing a job-tagged draft to a historical type clears the stale… 1.72s ✓ re-typing a job-tagged draft to a historical type clears the stale… 1.69s ✓ re-typing a job-tagged draft to a historical type clears the stale… 1.80s ✓ re-typing a job-tagged draft to a historical type clears the stale… 1.71s ✓ re-typing a job-tagged draft to a historical type clears the stale… 1.69s ✓ a re-type to a historical type clears the tag id even when the clie… 1.81s ✓ an unrelated edit to a SALE issue keeps its invoice id — the trigge… 1.71s ✓ re-typing a job-tagged draft to production_order still demands a re… 1.69s ✓ the historical reference types keep their loose reference_id rule o… 2.87s ✓ the historical reference types keep their loose reference_id rule o… 3.04s ✓ the historical reference types keep their loose reference_id rule o… 1.72s ✓ the historical reference types keep their loose reference_id rule o… 1.77s ✓ the historical reference types keep their loose reference_id rule o… 1.79s ✓ the historical reference types keep their loose reference_id rule o… 1.71s ✓ the historical reference types keep their loose reference_id rule o… 1.71s ✓ the historical reference types keep their loose reference_id rule o… 1.77s ✓ the historical reference types keep their loose reference_id rule o… 1.72s ✓ the historical reference types keep their loose reference_id rule o… 1.69s ✓ the job_tag case sits between production_order and other so «أخرى»… 0.08s ✓ a job-tagged issue keeps a plain draft/approve lifecycle with no it… 1.71s PASS Modules\Inventory\tests\Feature\InventoryIssueTagReportTest ✓ five approved issues on one job tag in a day report issues_count 5… 1.80s ✓ the detail lists all five أذون صرف for the tag with their lines 1.72s ✓ a CANCELLED issue is excluded from the counts 1.78s ✓ a DRAFT issue is excluded from the counts 1.80s ✓ a PENDING APPROVAL issue is excluded — canPost() means it has not p… 1.93s ✓ the counted statuses are declared in the response so the rule is ne… 1.67s ✓ a PARTIALLY ISSUED issue counts what actually moved, not what was r… 1.72s ✓ a tag whose only issues are draft or cancelled disappears from the… 1.77s ✓ the summary OMITS every value field without inventory.reports.issue… 2.29s ✓ the detail OMITS every value field — per issue AND per line — witho… 2.20s ✓ the value fields ARE present with inventory.reports.issue_tag_value 2.30s ✓ the job-tag value permission is independent of the production-order… 2.31s ✓ a job tag from another company is a 404 on the detail 1.71s ✓ the summary never shows another company job tag 2.30s ✓ an issue of ANOTHER reference type whose reference_id collides with… 1.62s ✓ a soft-deleted issue is excluded from the counts 1.69s ✓ the period filter is inclusive on both ends 1.69s ✓ a tag with no counted issue inside the period is absent from the su… 1.63s ✓ a to date before from is rejected 1.69s ✓ a CLOSED job tag still has a report — an archived job is not an era… 2.02s ✓ the status filter narrows the summary to active or closed tags 1.72s ✓ the summary is sorted busiest-first by default and honours the othe… 3.97s ✓ search matches the tag name 1.69s ✓ the summary paginates while totals stay over the WHOLE period 1.71s ✓ both job-tag report endpoints require authentication 1.59s ✓ both job-tag report endpoints require inventory.reports.view 2.24s PASS Modules\Inventory\tests\Feature\InventoryIssueTagTest ✓ a user with inventory.issue-tags.create can create a job tag 0.57s ✓ creating a job tag is refused 403 without .create — even holding th… 1.17s ✓ update and delete each need their own permission 1.13s ✓ company_id in the payload is ignored — the tenant comes from the au… 0.68s ✓ the list only ever shows the caller company tags 0.71s ✓ another company tag is invisible to show, update and delete 0.63s ✓ a duplicated name inside the same company is refused 0.60s ✓ the same name is allowed in two different companies 1.18s ✓ a NEAR-duplicate is never blocked — only the exact name is 0.69s ✓ closing a tag removes it from the picker without touching anything… 0.71s ✓ an unknown status on the list is a validation error, not an empty l… 0.72s ✓ deleting a tag is a soft delete 0.67s ✓ re-creating a soft-deleted name revives the original row instead of… 0.63s ✓ similar finds صيانة when the storekeeper types صيانه (taa marbuta v… 0.67s ✓ similar finds صيانة when the storekeeper types الصيانة (definite ar… 0.73s ✓ similar returns nothing for a genuinely different job name 0.68s ✓ similar never blocks creation — it only warns 0.70s ✓ similar is company-scoped and honours exclude_id 0.69s ✓ similar matches on the Arabic label of an English-named tag 0.92s ✓ similar caps the answer at five suggestions, closest first 0.71s ✓ similar on an empty name answers with an empty list, not an error 0.70s ✓ similar needs inventory.issue-tags.view 1.28s ✓ the normaliser folds the forms Arabic typists mix freely 0.07s ✓ the normaliser strips the INVISIBLE bidi marks a pasted name carrie… 0.07s ✓ similar scores a pasted name carrying an invisible RLM as an exact… 0.68s ✓ a word that merely starts with a hamzated alef keeps its lam 0.07s PASS Modules\Inventory\tests\Feature\InventoryReceiptApiTest ✓ can list receipts 1.86s ✓ can filter receipts by status 1.84s ✓ can filter receipts by warehouse 1.84s ✓ can filter receipts by date range 1.79s ✓ can search receipts by receipt number 1.75s ✓ can create a receipt with items 1.71s ✓ receipt number is auto-generated 1.63s ✓ store validates required fields 1.70s ✓ store validates items are required 1.72s ✓ store validates item fields 1.68s ✓ can show a receipt with items 1.70s ✓ cannot show receipt from another company 1.73s ✓ can update a draft receipt 1.87s ✓ cannot update an approved receipt 1.60s ✓ can delete a draft receipt 1.72s ✓ cannot delete an approved receipt 1.67s ✓ can approve a draft receipt with items 1.72s ✓ approve creates stock movements 2.19s ✓ approve calculates weighted average cost correctly 4.63s ✓ cannot approve receipt without items 3.10s ✓ cannot approve an already approved receipt 1.65s ✓ can cancel an approved receipt 1.82s ✓ cannot cancel a draft receipt 1.69s ✓ unauthenticated user cannot access receipts 0.07s ✓ company isolation: cannot see other company receipts 1.63s PASS Modules\Inventory\tests\Feature\InventoryReportApiTest ✓ production order issue report returns per-material issued qty and p… 2.32s ✓ production order issue report includes cost with the cost permissio… 2.50s ✓ production order issue report omits cost without the cost permissio… 2.93s ✓ production order issue report rejects an order from another company 2.24s ✓ production order issue report filters by issued_at date range 2.18s ✓ can get movement summary for date range 1.13s ✓ movement summary calculates opening balance from prior movements 1.32s ✓ movement summary requires date range 1.18s ✓ can filter movement summary by warehouse 1.21s ✓ can get slow-moving stock report 1.19s ✓ slow-moving excludes products with recent activity 1.24s ✓ slow-moving includes products with no movement at all 1.16s ✓ can get warehouse utilization summary 2.32s ✓ warehouse summary shows product count per warehouse 2.28s ✓ can get expiry report for items expiring soon 1.29s ✓ expiry report can include already expired items 1.21s ✓ expiry report excludes sold serials 1.22s ✓ unauthenticated user cannot access reports 0.07s ✓ reports are isolated by company 2.12s PASS Modules\Inventory\tests\Feature\InventorySchemaTest ✓ can create inventory count with items 1.10s ✓ can create inventory adjustment with items 1.11s ✓ adjustment can link to inventory count 1.11s ✓ can create cost layer 1.17s ✓ adjustment recalculates totals correctly 1.11s ✓ count and adjustment enums have correct values 0.07s PASS Modules\Inventory\tests\Feature\InventorySettingsGatesTest ✓ receipt stays DRAFT on creation when auto_approve_receipts is OFF (… 1.83s ✓ receipt is AUTO-APPROVED on creation when auto_approve_receipts is… 1.83s ✓ receipt WITHOUT batch_number is allowed when require_batch_tracking… 1.79s ✓ receipt WITHOUT batch_number is REJECTED when require_batch_trackin… 1.76s ✓ receipt WITH batch_number is accepted when require_batch_tracking i… 2.69s ✓ issue WITHOUT batch_number is REJECTED when require_batch_tracking… 2.62s ✓ issue WITH batch_number is accepted when require_batch_tracking is… 1.69s PASS Modules\Inventory\tests\Feature\InventoryTransferApiTest ✓ can list transfers 1.79s ✓ can filter transfers by status 1.74s ✓ can filter transfers by warehouse 1.70s ✓ can filter transfers by date range 1.79s ✓ can search transfers by transfer number 2.03s ✓ can create a transfer with items 1.71s ✓ transfer number is auto-generated 1.68s ✓ store validates required fields 1.77s ✓ store validates warehouses must be different 1.72s ✓ store validates item fields 1.74s ✓ can show a transfer with items 1.65s ✓ cannot show transfer from another company 1.63s ✓ can update a draft transfer 1.67s ✓ cannot update a shipped transfer 1.60s ✓ can delete a draft transfer 1.61s ✓ cannot delete a shipped transfer 1.70s ✓ can ship a draft transfer and stock is deducted 1.68s ✓ ship creates stock movements 1.75s ✓ cannot ship transfer with insufficient stock 1.87s ✓ can ship transfer with negative stock when warehouse allows it 1.69s ✓ cannot ship transfer without items 1.68s ✓ cannot ship an already shipped transfer 1.74s ✓ can receive a shipped transfer and stock is added 1.71s ✓ can receive with partial quantities 1.66s ✓ receive creates stock movements 1.72s ✓ cannot receive a draft transfer 1.79s ✓ can cancel a draft transfer 1.73s ✓ can cancel a shipped transfer and stock is restored 1.71s ✓ cannot cancel an already cancelled transfer 1.70s ✓ cannot cancel a received transfer 1.72s ✓ unauthenticated user cannot access transfers 0.07s ✓ company isolation: cannot see other company transfers 1.68s PASS Modules\Inventory\tests\Feature\InventoryVariantOwnershipGuardTest ✓ issue store rejects a variant that belongs to a different product o… 4.48s ✓ issue store rejects a variant that belongs to another company 2.42s ✓ issue store accepts the line product own variant and persists it 2.29s ✓ issue store saves a null variant 2.20s ✓ issue store still accepts a SOFT-DELETED variant of the line produc… 2.23s ✓ issue store checks each line against ITS OWN product and does not l… 2.27s ✓ issue store accepts two lines of DIFFERENT products each with its o… 2.24s ✓ issue update rejects a variant of a different product 2.21s ✓ issue update rejects a variant of another company 2.27s ✓ issue update accepts a valid variant and persists it 2.32s ✓ issue update accepts a null variant 2.20s ✓ receipt store rejects a variant of a different product and accepts… 2.19s ✓ receipt update rejects a variant of a different product 2.19s ✓ transfer store rejects a variant of a different product and accepts… 2.55s ✓ transfer update rejects a variant of a different product 2.26s ✓ adjustment store rejects a variant of a different product and accep… 2.20s ✓ opening balance store rejects a variant of a different product and… 2.19s ✓ bulk opening balance rejects a variant that belongs to a different… 2.30s ✓ bulk opening balance rejects a variant that belongs to another comp… 2.19s ✓ bulk opening balance accepts the entry product own variant and pers… 2.23s ✓ bulk opening balance accepts a null variant 2.26s ✓ bulk opening balance checks each entry against ITS OWN product 2.18s FAIL Modules\Inventory\tests\Feature\LotAllocationTest ⨯ FEFO allocation consumes the earliest-expiry lot first and records… 1.04s ⨯ a quantity beyond the owner lots leaves an unassigned shortfall, no… 1.07s ✓ reverse restores the exact lots and deletes the allocation rows 1.10s ✓ manual selection over a lot on-hand is rejected (422) 1.01s ✓ manual selection is scoped to the variant — cannot target another v… 1.08s ✓ manual selection consumes exactly the chosen lot 1.03s ✓ FIFO issuing order consumes the earliest-RECEIVED lot first, ignori… 1.08s ✓ default (FEFO) issuing order consumes the earliest-EXPIRY lot first 1.10s ✓ default (block) policy SKIPS expired lots in auto-FEFO — leftover i… 3.38s ✓ allow policy makes expired lots eligible for auto-FEFO (oldest-expi… 1.23s PASS Modules\Inventory\tests\Feature\LotBalanceSeedTest ✓ seeding a receipt creates one company-owned lot balance per batch,… 1.09s ✓ the same batch received on two receipts increments one lot row, not… 1.11s ✓ backfill attributes historical issues to the OLDEST-expiry lots so… 1.07s ✓ backfill refuses by default when live-seeded lots exist, and --forc… 1.11s ✓ a consignment lot is owner-tagged and off-book (memo value only) 1.09s PASS Modules\Inventory\tests\Feature\LotCoverageDetailTest ✓ no lots at all reads as zero coverage under BOTH policies — an ordi… 1.22s ✓ expired-only stock reads as zero under Block and full under Allow —… 1.10s ✓ a valid lot reports no expired quantity even when an expired lot si… 1.07s ✓ a lot expiring TODAY is not expired — one definition, the picker qu… 1.13s ✓ coverage() returns exactly coverageDetail() covered, for every poli… 1.11s ✓ the expired_policy override wins over inventory.expired_issue_polic… 1.09s ✓ what coverageDetail reports is exactly what allocate then consumes 1.07s PASS Modules\Inventory\tests\Feature\LotCoverageGuardTest ✓ setting ON: a batch product whose lots cover the quantity posts and… 1.12s ✓ setting ON: coverage exactly equal to the requested quantity is not… 1.09s ✓ setting ON: a shortfall is refused with a 422 and stock_balances is… 1.10s ✓ setting ON: a batch product with NO lots at all is refused and the… 1.02s ✓ setting ON: refusing does not leave a phantom stock_balances row be… 1.06s ✓ setting ON: the SALE itself is refused end to end and the balance n… 1.62s ✓ setting OFF: the shortfall stays silent — the sale posts and the ba… 1.09s ✓ setting explicitly OFF is identical to the setting being absent 1.11s ✓ tracking_type none is completely unaffected — setting OFF 1.19s ✓ tracking_type none is completely unaffected — setting ON 1.13s ✓ a non-batch product is not refused even if a caller wrongly asks fo… 1.37s ✓ an outbound path that does not allocate lots is untouched by the gu… 1.10s ✓ C3 HANDOFF: with expired_issue_policy=block, an expired-only cover… 1.11s ✓ C3 HANDOFF: the same expired lots POST when expired_issue_policy=al… 1.13s ✓ a shortfall caused by NO lots and one caused by EXPIRED-ONLY lots a… 2.27s ✓ setting ON: a customer-owned lot does not cover a company-owned iss… 1.02s ✓ pos.enforce_batch_selection is unlocked, defaults to false, and is… 0.11s PASS Modules\Inventory\tests\Feature\LotReconciliationReportTest ✓ the report reports the delta for each batch-tracked product and war… 2.58s ✓ a product split across warehouses reports each warehouse and rolls… 1.71s ✓ a consignment lot balances the physical invariant but is reported s… 1.70s ✓ a lot ledger with no balance row at all is still reported as drift 1.69s ✓ expired lots that still carry stock are listed, including on a non-… 2.21s ✓ serial and untracked products are not reconciled — only batch-track… 1.78s ✓ only_unbalanced hides the balanced rows but the summary still count… 2.21s ✓ the report is scoped to the caller company and filterable by wareho… 3.25s ✓ the report writes nothing — it repairs no drift and consumes no exp… 1.88s ✓ the report requires authentication 0.07s PASS Modules\Inventory\tests\Feature\MovementSummaryVariantGrainTest ✓ movement summary breaks a product out per variant with per-colour n… 1.24s ✓ the opening balance is carried per variant, not pooled across colou… 1.22s ✓ a variant whose only activity is a prior-period opening balance sti… 1.47s ✓ a soft-deleted variant with movement history is still labelled 3.52s ✓ the category filter still applies at the wider grain 2.16s ✓ a variant-less company gets the same rows, numbers and key list as… 1.94s FAIL Modules\Inventory\tests\Feature\OpeningBalanceApiTest ✓ can list opening balances 1.74s ✓ can filter opening balances by warehouse 1.83s ✓ can show opening balance details 1.78s ✓ cannot show non-opening receipt via opening balances endpoint 1.80s ✓ can create opening balance 1.84s ⨯ opening balance creates inventory movement with type opening 1.77s ✓ opening balance with multiple items creates stock for each 2.51s ✓ opening balance validates required fields 1.77s ✓ opening balance validates item fields 2.15s ✓ opening balance creates serial records for serialized products 2.58s ✓ can bulk create opening balances 2.41s ✓ bulk opening balance validates entries 1.80s ✓ bulk opening balance groups by warehouse 2.48s ✓ can cancel opening balance 1.94s ✓ cannot cancel already cancelled opening balance 1.76s ✓ opening balances are company scoped 1.79s PASS Modules\Inventory\tests\Feature\PartialIssueApprovalTest ✓ partial issue decreases stock by issued_quantity and sets Partially… 1.75s ✓ null issued_quantity falls back to full quantity and sets Approved… 1.66s ✓ issued_quantity exceeding requested quantity throws 422 1.69s ✓ partial issue rejected when allow_partial_issue setting is false 1.83s ✓ InventoryIssueApproved event is dispatched on approval 1.70s ✓ InventoryIssueApproved event is dispatched on partial approval 1.90s PASS Modules\Inventory\tests\Feature\PartialIssueCancelTest ✓ cancelling a partially-issued issue reverses only issued_quantity n… 1.77s ✓ isCancellable returns true for PartiallyIssued status 1.51s ✓ isCancellable returns false for Draft status 1.60s ✓ cancelling a draft issue returns an error 1.83s ✓ cancelling a fully approved issue reverses the full issued quantity 1.68s PASS Modules\Inventory\tests\Feature\PerItemWarehouseIssueTest ✓ each item decrements stock at its own warehouse (NULL falls back to… 3.79s ✓ allow_negative_stock is resolved per item warehouse 3.57s PASS Modules\Inventory\tests\Feature\Phase1PartialIssueFoundationsTest ✓ inventory_issue_items has issued_quantity column 0.07s ✓ inventory_issue_items has source_item_id column 0.07s ✓ inventory_issues has cogs_journal_entry_id column 0.09s ✓ inventory_issue_items issued_quantity defaults to null 1.20s ✓ InventoryIssueItem accepts issued_quantity and source_item_id via f… 0.12s ✓ InventoryIssueItem casts issued_quantity as decimal:3 0.10s ✓ InventoryIssue accepts cogs_journal_entry_id via fillable 0.09s ✓ IssueStatus has PartiallyIssued case 0.08s ✓ IssueStatus::PartiallyIssued label is non-empty 0.08s PASS Modules\Inventory\tests\Feature\Phase2LotCaptureTest ✓ bug(a): a GRN-origin serial product with no pre-existing serials is… 2.13s ✓ bug(b): serials inherit batch dates unless overridden, and both dat… 2.14s ✓ production_date threads onto the stock movement (first-batch mirror… 1.87s ✓ sum mismatch: batch quantities not adding up to the line quantity i… 1.90s ✓ duplicate serial within the payload is 422, keyed to the offending… 1.80s ✓ serial already existing in product_serials is 422 1.91s ✓ production_date not before expiry_date is 422 1.82s ✓ locked fields: changing quantity on a GRN-origin receipt via Save-l… 1.86s ✓ Save-lots on a GRN-origin receipt persists batches without approvin… 1.91s ✓ a bare metadata edit (no items) on a GRN-origin receipt stays hard-… 1.79s ✓ a plain non-lot approve (no batches payload) still works exactly as… 1.73s ✓ a plain legacy serial approve (serials on the line, no batches) sti… 1.79s PASS Modules\Inventory\tests\Feature\ReceiptApprovalWiringTest ✓ NO WORKFLOW: receipt submits and posts exactly as today — ZERO appr… 1.19s ✓ NO WORKFLOW: a draft receipt still posts in one click (no submit) —… 1.19s ✓ NO WORKFLOW: auto_approve_receipts still posts stock on creation —… 1.22s ✓ ISS-2026-0185: a BATCH receipt is NOT auto-approved — it stays draf… 1.27s ✓ ISS-2026-0185: a SERIAL receipt is NOT auto-approved — it stays dra… 1.20s ✓ PENDING receipt adds ZERO stock and creates ZERO journal entries; t… 1.72s ✓ EDGE CASE 1: receipt auto-approve does NOT bypass an active workflo… 1.93s ✓ the batch/expiry capture on the commit still works after the receip… 1.94s ✓ the wrong approver is refused 403; multi-level holds the stock unti… 2.68s ✓ a REJECTED receipt stays blocked on retry, and is released only by… 1.93s ✓ a never-submitted draft receipt cannot post straight past a configu… 1.80s ✓ inventory_receipt reports wired:true and renders in the approvals i… 1.88s ✓ WP6: a keeper who holds ONLY inventory.receipts.approve is REFUSED… 2.02s ✓ WP7: NO WORKFLOW — rejecting a POSTED receipt is 422; stock is not… 1.19s ✓ WP7: NO WORKFLOW — the LEGITIMATE reject still works: a pending_app… 1.21s ✓ WP7 MINOR 4: cancelling a receipt closes its approval cycle — the r… 1.73s ✓ WP7 HIGH: a post landing under the rejector is SEEN (422) — the gua… 1.16s PASS Modules\Inventory\tests\Feature\ReorderAlertApiTest ✓ can list reorder alerts for products below reorder point 2.37s ✓ reorder alerts return critical severity for out-of-stock 2.42s ✓ reorder alerts return critical severity when below min stock 1.19s ✓ can filter reorder alerts by severity 1.78s ✓ can filter reorder alerts by warehouse 1.22s ✓ can filter reorder alerts by category 1.72s ✓ reorder alerts exclude non-inventory-tracked products 1.18s ✓ suggested order qty uses reorder_point * 2 when max_stock_level not… 1.21s ✓ can get reorder report grouped by category 1.41s ✓ can get reorder report grouped by warehouse 1.21s ✓ reorder report shows per-warehouse breakdown 1.26s ✓ can get inventory stats 1.81s ✓ stats count out-of-stock products 1.23s ✓ can send reorder notifications 1.28s ✓ notify returns zero when no products below reorder 1.32s ✓ unauthenticated user cannot access reorder alerts 0.07s ✓ unauthenticated user cannot access reorder report 0.07s ✓ unauthenticated user cannot access inventory stats 0.09s ✓ reorder alerts are isolated by company 1.83s PASS Modules\Inventory\tests\Feature\ReorderAlertVariantTest ✓ a variant below the reorder point alerts even when a sibling varian… 1.35s ✓ every variant of a product is measured against the same product thr… 1.28s ✓ a product with no variants still produces exactly one alert with nu… 1.19s ✓ variant and non-variant products coexist in one alert list 1.64s ✓ warehouse filter still scopes per-variant reorder alerts 1.16s ✓ warehouse filter is unchanged for a product with no variants 1.19s ✓ reorder report is emitted at the variant grain 1.22s ✓ reorder report keeps the legacy single row for a product with no va… 1.17s ✓ inventory stats report the product grain and the variant grain unde… 1.11s ✓ the out-of-stock KPI never mixes a variant count with a product cou… 1.18s ✓ inventory stats are unchanged for products with no variants 1.70s ✓ reorder notifications are raised per variant and carry the variant… 1.13s ✓ reorder notifications keep one row and null variant fields for a pr… 1.40s ✓ product_variant_name follows the request locale like getLocalizedNa… 1.19s ✓ variant reorder alerts stay isolated by company 1.67s ✓ a display_only product yields ONE base row carrying the product-wid… 1.22s ✓ a display_only product collapses on every endpoint, not just the al… 1.21s ✓ a fanned-out product holding base-keyed stock also emits a base row… 1.17s ✓ legacy base-keyed stock above the reorder point is a visible grain… 1.13s ✓ the severity filter applies at the variant grain 1.20s ✓ the category_id and search filters still narrow the fanned-out list 1.78s ✓ one product with more than 25 below-reorder variants paginates 25 r… 1.21s ✓ report group_by=warehouse groups the fanned-out rows per warehouse 1.22s ✓ notify reports distinct products and variant rows under separate na… 1.21s ✓ the notification payload covers ten distinct products, not ten rows 3.36s ✓ report counts distinct products, not rows, when one product has twe… 1.73s ✓ report group_by=warehouse counts distinct products per warehouse to… 1.30s ✓ a base balance that nets to zero across warehouses raises no phanto… 3.11s ✓ a display_only variant that really holds stock is alerted under the… 1.19s ✓ a display_only product holding both base and variant stock emits bo… 1.43s ✓ a soft-deleted variant still holding stock stays visible to the reo… 1.25s ✓ a soft-deleted variant with no stock left does not resurrect as a g… 1.22s ✓ the notification payload caps at ten distinct products 13.08s ✓ the notification payload stays inside the text column when one prod… 9.85s ✓ a product with no variants still produces exactly one uncapped-shap… 1.37s ✓ the alerts list publishes the distinct-product count beside the row… 1.69s ✓ the alerts products_count spans the whole filtered set, not just t… 23.71s PASS Modules\Inventory\tests\Feature\ReorderGrainParityTest ✓ production material-shortage sees the stocked-out variant the reord… 1.20s ✓ production material-shortage collapses a display_only product exact… 2.59s ✓ production material-shortage keeps the legacy single alert for a pr… 1.91s ✓ every below-reorder variant of one product gets its own production… 1.10s ✓ production material-shortage keeps legacy base-keyed stock visible… 1.36s ✓ production material-shortage stays company scoped at the variant gr… 2.11s PASS Modules\Inventory\tests\Feature\RequireBatchOnReceiptTest ✓ ON: a batch-tracked line approved with no batches at all is refused… 3.02s ✓ ON: a batch row with no expiry_date is refused, naming the line and… 3.10s ✓ ON: a batch whose expiry_date is already past is refused, naming th… 3.02s ✓ ON: a batch row with a blank batch_number is refused 3.83s ✓ ON: a complete batch row posts and the lot row is created as today 3.98s ✓ ON: the rule does not leak to tracking_type = none — a normal produ… 3.58s ✓ ON: the legacy line-column capture (batch_number + expiry_date on t… 3.41s ✓ OFF: a batch-tracked line with no batches posts exactly as it does… 3.20s ✓ UNSET: with the setting never written, behaviour is today's behavio… 3.39s ✓ OFF: an expired batch is still accepted, as today 3.09s ✓ the definition is unlocked after seeding 0.10s ✓ a PUT from the settings screen saves it and lands on inventory.* —… 1.19s ✓ a plain pos.* key still round-trips through the same write path 1.22s ✓ exactly 39 catalogue settings remain locked 0.10s PASS Modules\Inventory\tests\Feature\SaleGdnGuardTest ✓ Fix1-A: sale GDN partial issue in invoice mode (cogs_recognition_po… 4.03s ✓ Fix1-B: sale GDN partial issue in delivery mode is allowed 3.38s ✓ Fix1-C: non-sale (Consumption) GDN partial issue in invoice mode is… 3.39s ✓ Fix1-D: sale GDN full issue in invoice mode is allowed 3.40s ✓ Fix1-E: sale GDN with null issued_quantity (full back-compat) in in… 5.40s ✓ Fix2-F: source_item_id null is accepted and round-trips through upd… 3.61s ✓ Fix2-H: non-null source_item_id is preserved when FE sends it back… 2.49s ✓ Fix2-I: source_item_id becomes null when FE omits it in the update… 1.88s PASS Modules\Inventory\tests\Feature\SerialTrackingApiTest ✓ can list serials for a product 1.81s ✓ can filter serials by status 1.70s ✓ can filter serials by warehouse 1.69s ✓ can search serials by serial number 2.04s ✓ can add serial numbers to a serialized product 1.78s ✓ cannot add serials to non-serialized product 2.39s ✓ cannot add duplicate serial numbers 1.78s ✓ can update a serial number 1.92s ✓ can delete an available serial 1.80s ✓ cannot delete a sold serial 2.30s ✓ receipt approval creates serial records for serialized products 3.34s ✓ receipt approval fails when serial count does not match quantity 3.55s ✓ receipt approval fails when serial number already exists 3.28s ✓ cancel receipt deletes available serials created by that receipt 3.22s ✓ issue approval marks serials as sold 3.40s ✓ issue approval fails when serial is not available 3.89s ✓ issue approval fails when serial count does not match quantity 3.40s ✓ cancel issue returns serials to available status 3.44s ✓ product factory creates serialized product with correct tracking ty… 4.83s ✓ product tracking type can be changed at model level 4.41s ✓ non-serialized product receipt works without serial numbers 4.71s ✓ cannot see serials from another company 3.59s PASS Modules\Inventory\tests\Feature\SerialVariantTraceabilityTest ✓ issue rejects a serial belonging to another variant of the same pro… 3.99s ✓ a wrong-variant serial is refused with its own message, not "not av… 3.53s ✓ a serial that does not exist at all still reports "not available" 3.19s ✓ issue accepts a serial of the line own variant and marks it sold 3.52s ✓ a serial of another variant is left untouched by a successful issue… 3.50s ✓ a base keyed serial is not accepted on a variant keyed line 3.42s ✓ a variant keyed serial is not accepted on a base keyed line 3.47s ✓ a variant less product still issues its serials exactly as before 4.58s ✓ issue still rejects a serial of another product 4.54s ✓ cannot add a serial keyed to a variant of another product 5.18s ✓ cannot add a serial keyed to a variant of another company 5.41s ✓ can add a serial keyed to a variant of the route product 5.37s ✓ cannot rewrite a serial to a variant of another product 4.62s ✓ cannot rewrite the variant of a sold serial 3.60s ✓ re-sending the unchanged variant of a sold serial is still accepted 3.50s ✓ can rewrite the variant of an available serial to a sibling variant 3.50s PASS Modules\Inventory\tests\Feature\SlowMovingVariantGrainTest ✓ a stagnant variant appears in slow-moving even when a sibling varia… 2.64s ✓ a variant that has never moved is not rescued by a sibling that has 3.06s ✓ slow-moving rows carry the variant id and its localized name 2.41s ✓ a soft-deleted variant that still holds stock is labelled, not left… 2.46s ✓ a variant-less company gets the same rows, numbers and columns as b… 3.20s PASS Modules\Inventory\tests\Feature\StockBalanceApiTest ✓ can list stock balances 2.53s ✓ can filter stock balances by warehouse 2.40s ✓ can filter stock balances by product 3.81s ✓ can filter stock balances by category 3.60s ✓ can filter stock balances below reorder point 3.40s ✓ stock balance response includes available quantity 2.27s ✓ includes zero and negative quantity balances by default 4.21s ✓ hide_zero filter excludes zero-quantity balances 6.38s ✓ can get stock by product across warehouses 2.54s ✓ can get stock by warehouse 3.91s ✓ can get stock card for a product 2.31s ✓ can filter stock card by warehouse 2.27s ✓ can filter stock card by date range 2.28s ✓ can filter stock card by movement type 2.01s ✓ stock card response includes movement details 2.31s ✓ can list all movements 2.19s ✓ can filter movements by product 2.99s ✓ unauthenticated user cannot access stock balances 0.07s ✓ unauthenticated user cannot access stock card 0.08s ✓ unauthenticated user cannot access movements 0.12s ✓ stock balances are isolated by company 3.23s ✓ movements are isolated by company 2.21s ✓ productLots scopes lots and physical_total to one variant when prod… 2.29s ✓ productLots keeps the no-variant path working (product with no vari… 2.31s ✓ owner=own returns only rows with owned stock (drops purely-consignm… 5.09s ✓ owner= returns only that customer rows and stamps owner_quantity/ow… 3.91s ✓ owner=own resolves the 0 sentinel without swallowing it (owner=0 ==… 3.20s ✓ lot-owners returns the distinct customers currently holding live co… 2.18s ✓ meta.totals sums the whole company scope, not just the paginated p… 35.20s ✓ meta.totals excludes consignment from owned value and reports it in… 2.09s PASS Modules\Inventory\tests\Feature\StockBalanceExpiryVisibilityTest ✓ nearest_expiry is the earlier of two available serials with source… 2.52s ✓ a serial product with all serials consumed does NOT approximate exp… 2.62s ✓ a non-available serial is ignored when picking the nearest expiry 2.17s ✓ a past-dated available serial flags is_expired true 2.20s ✓ a batch-only product falls back to approx nearest expiry from movem… 2.10s ✓ an available serial takes precedence over movement genealogy 2.31s ✓ a product with no serials and no expiry movements yields null expir… 2.09s ✓ a batch product nearest expiry comes from the live lot ledger with… 2.40s ✓ nearest expiry attributes to the customer when a consignment lot ex… 2.10s ✓ the index response surfaces near_expiry_days in meta 2.20s ✓ the index endpoint issues a bounded number of queries regardless o… 16.54s PASS Modules\Inventory\tests\Feature\StockBalanceTollCustomerTest ✓ a stock-balance row exposes toll_customer_id and toll_customer_name… 3.19s ✓ toll_customer_name falls back to the latin name when the partner ha… 2.28s ✓ byProduct and byWarehouse also carry the toll customer 1.98s ✓ search by the toll customer name returns that customer items 4.00s ✓ the product columns still search after the customer clause was adde… 3.11s ✓ search and warehouse still apply TOGETHER (the nested-closure trap) 4.23s ✓ search still composes with category_id and hide_zero 4.26s ✓ toll_customer_id narrows the list to that customer items 4.50s ✓ toll_customer_id=own returns only items with no toll customer 3.99s ✓ an absent or empty toll_customer_id changes nothing 3.04s ✓ toll_customer_id composes with warehouse_id 2.96s ✓ meta.totals matches the sum over the toll_customer_id-filtered rows 5.33s ✓ meta.totals consignment memo is scoped by toll_customer_id too 3.40s ✓ meta.totals matches the rows when the filter is the customer NAME s… 3.07s ✓ a full page of 25 rows with toll customers issues a bounded number… 33.25s ✓ the owner lens is unaffected by the toll customer work 3.89s ✓ the owner lens and the toll customer filter are independent and com… 4.60s ✓ below_reorder still works and composes with the toll customer filte… 3.99s ✓ the toll customer filter is company-scoped 3.64s PASS Modules\Inventory\tests\Feature\StockCardMovementSourceTest ✓ a movement from a manual production issue resolves to the productio… 1.98s ✓ the raw reference_type and reference_id fields are untouched alongs… 2.17s ✓ a backflush movement resolves to the production order in one hop 2.11s ✓ a production staging movement also resolves to the production order… 2.12s ✓ a finished-goods receipt resolves through the receipt to the produc… 2.12s ✓ a job-tagged issue resolves to the tag by name and is never labelle… 2.47s ✓ a plain consumption issue resolves to the issue itself, with no sec… 5.73s ✓ a consignment receipt resolves to the owning partner and offers no… 3.20s ✓ a consignment return resolves to the owning partner too 2.80s ✓ a consignment borrow and a replenishment resolve to the material ow… 2.16s ✓ a cancelled issue movement resolves to the ORIGINAL source and is f… 2.30s ✓ a cancelled adjustment movement resolves to the original adjustment… 2.17s ✓ a movement whose reference no longer exists returns an unresolved s… 2.14s ✓ a reference_type this build has never heard of degrades to unknown… 1.99s ✓ a reference belonging to another company resolves to nothing 3.30s ✓ a job tag owned by another company never leaks its name through the… 3.38s ✓ the query count for a page of 50 mixed-source movements does not sc… 5.07s ✓ every row on a mixed page carries a source object with the full sta… 2.14s PASS Modules\Inventory\tests\Feature\StockCardVariantScopeTest ✓ omitting product_variant_id returns every movement of the product a… 2.10s ✓ a product with no variants at all is unaffected when the param is o… 3.00s ✓ a numeric product_variant_id returns only that variant with a coher… 2.10s ✓ the other variant gets its own independent ledger 2.38s ✓ the unscoped card interleaves variants so its balance chain is NOT… 2.39s ✓ an explicitly empty product_variant_id returns only the base produc… 2.72s ✓ empty is a distinct value from absent - the two must not return the… 2.10s ✓ warehouse_id still filters when combined with the variant scope 2.40s ✓ date_from date_to and movement_type still filter when combined with… 2.49s ✓ every returned movement carries product_variant_id and the variant… 2.12s ✓ product_variant_name follows the request locale 4.86s ✓ the variant scope never leaks another company movements 2.22s ✓ the movements list honours the same three-valued product_variant_id 2.23s ✓ an array product_variant_id is rejected, not silently cast to varia… 2.28s ✓ a non-numeric product_variant_id is rejected instead of returning a… 2.12s ✓ a zero or negative product_variant_id is rejected (ids are auto-inc… 2.16s ✓ the movements list rejects a malformed product_variant_id the same… 2.30s PASS Modules\Inventory\tests\Feature\StockReservationTest ✓ reserve increments reserved_quantity 3.09s ✓ reserve accumulates across calls 2.74s ✓ reserve with no stock row creates a zero-qty row holding the reserv… 3.17s ✓ release decrements reserved_quantity 3.02s ✓ release floors reserved_quantity at zero on over-release 2.78s ✓ release with no stock row is a no-op (nothing was reserved) 3.00s ✓ availableQuantity returns quantity minus reserved 3.20s ✓ availableQuantity returns zero when no balance row exists 3.01s ✓ approve blocks a non-production issue exceeding available while sto… 2.70s ✓ approve allows a production-order issue against gross even when sto… 3.09s ✓ approve allows a non-production issue within available 2.93s PASS Modules\Inventory\tests\Feature\StockServiceUnitConversionTest ✓ increase then decrease WITHOUT unit_id is unchanged (identity) 1.27s ✓ passing the base unit_id (factor 1.0) equals passing no unit_id 2.10s ✓ increaseStock with a factor-12 unit moves base qty and preserves va… 2.11s ✓ getIssueCost and decreaseStock with a factor-12 unit work on base q… 1.81s ✓ receipt of 2 cartons then issue of 1 carton nets to 12 base on-hand 3.07s ✓ full round-trip of a factor-12 item returns on-hand and value to ze… 3.63s ✓ FIFO cost layer is written and consumed in base units 2.19s FAIL Modules\Inventory\tests\Feature\TransferLotPreservationTest ✓ shipping a transfer decrements the source lot and records the alloc… 3.18s ✓ receiving re-creates the SAME batch/expiry lot at the destination 3.10s ⨯ FEFO across two source lots preserves both batches at the destinati… 3.52s ✓ partial receive re-creates only the received portion at the destina… 2.81s ✓ cancelling a shipped transfer restores the source lot exactly and d… 3.20s PASS Modules\Inventory\tests\Feature\UnitConversionActivationTest ✓ FIFO factor-3 cost-10 round-trip nets value with no 0.001 residual 2.18s ✓ cancel of a factor-12 receipt reverses the stamped base qty even af… 3.11s ✓ issuing 3 cartons is blocked when only 24 kg (2 cartons) are on han… 2.91s ✓ reserve then release with a factor-12 unit is symmetric 1.58s PASS Modules\Inventory\tests\Feature\ValuationVariantLabelTest ✓ valuation rows carry the variant name next to the variant id 1.22s ✓ a base-keyed valuation row reports a null variant name 1.18s ✓ a soft-deleted variant that still holds stock is labelled, not left… 1.25s ✓ the valuation variant label costs no extra query per row 1.28s PASS Modules\Inventory\tests\Feature\WarehouseApiTest ✓ can list warehouses 1.18s ✓ can filter warehouses by type 1.18s ✓ can filter warehouses by branch 1.23s ✓ can filter warehouses by active status 1.67s ✓ can search warehouses by name or code 2.52s ✓ can get warehouse tree 2.29s ✓ can create a warehouse 2.32s ✓ auto-generates warehouse code on create 2.19s ✓ can create a sub-warehouse under a parent 2.34s ✓ validates required fields on create 2.17s ✓ validates type must be a valid enum value 2.22s ✓ can show a warehouse 2.98s ✓ returns 404 for warehouse from another company 2.09s ✓ can update a warehouse 1.70s ✓ can toggle warehouse active status 1.21s ✓ can delete a warehouse without children 1.27s ✓ cannot delete a warehouse with children 1.73s ✓ unauthenticated user cannot access warehouses 0.07s ✓ warehouses are isolated by company 1.27s ──────────────────────────────────────────────────────────────────────────── FAILED Modules\Inventory\tests\Feature\LotAllocationTest > FEFO allocati… Failed asserting that 30.0 is identical to 0.0. at Modules/Inventory/tests/Feature/LotAllocationTest.php:71 67▕ $result = app(LotAllocationService::class)->allocate(allocateArgs($ctx, 40)); 68▕ 69▕ expect($result['allocated'])->toBe(40.0); 70▕ expect($result['shortfall'])->toBe(0.0); ➜ 71▕ expect((float) $old->fresh()->remaining_quantity)->toBe(0.0); // 30 consumed 72▕ expect((float) $new->fresh()->remaining_quantity)->toBe(40.0); // 10 consumed 73▕ 74▕ $allocs = InventoryIssueLotAllocation::where('reference_id', 77)->get(); 75▕ expect($allocs)->toHaveCount(2); 1 Modules/Inventory/tests/Feature/LotAllocationTest.php:71 ──────────────────────────────────────────────────────────────────────────── FAILED Modules\Inventory\tests\Feature\LotAllocationTest > a quantity be… Failed asserting that 0.0 is identical to 20.0. at Modules/Inventory/tests/Feature/LotAllocationTest.php:85 81▕ allocMakeLot($ctx, ['batch_number' => 'ONLY', 'expiry_date' => '2026-08-01', 'remaining_quantity' => 20]); 82▕ 83▕ $result = app(LotAllocationService::class)->allocate(allocateArgs($ctx, 35)); 84▕ ➜ 85▕ expect($result['allocated'])->toBe(20.0); 86▕ expect($result['shortfall'])->toBe(15.0); // 15 came from untracked "unassigned own" stock 87▕ expect(InventoryLotBalance::where('remaining_quantity', '<', 0)->count())->toBe(0); 88▕ }); 89▕ 1 Modules/Inventory/tests/Feature/LotAllocationTest.php:85 ──────────────────────────────────────────────────────────────────────────── FAILED Modules\Inventory\tests\Feature\OpeningBalanceApiTest > opening b… Failed asserting that two strings are identical. -'receipt' +'opening' at Modules/Inventory/tests/Feature/OpeningBalanceApiTest.php:207 203▕ ->where('warehouse_id', $warehouse->id) 204▕ ->first(); 205▕ 206▕ expect($movement)->not->toBeNull() ➜ 207▕ ->and($movement->movement_type->value)->toBe('receipt') 208▕ ->and((float) $movement->quantity_in)->toBe(50.0) 209▕ ->and((float) $movement->unit_cost)->toBe(5.0); 210▕ }); 211▕ 1 Modules/Inventory/tests/Feature/OpeningBalanceApiTest.php:207 ──────────────────────────────────────────────────────────────────────────── FAILED Modules\Inventory\tests\Feature\TransferLotPreser… ErrorException Attempt to read property "remaining_quantity" on null at Modules/Inventory/tests/Feature/TransferLotPreservationTest.php:192 188▕ 189▕ $destOld = InventoryLotBalance::query()->where('warehouse_id', $ctx['to']->id)->where('batch_number', 'OLD')->first(); 190▕ $destNew = InventoryLotBalance::query()->where('warehouse_id', $ctx['to']->id)->where('batch_number', 'NEW')->first(); 191▕ ➜ 192▕ expect((float) $destOld->remaining_quantity)->toBe(30.0); 193▕ expect((float) $destNew->remaining_quantity)->toBe(10.0); 194▕ 195▕ expect(lotSumAt($ctx, $ctx['from']))->toBe(stockQtyAt($ctx, $ctx['from'])); // 40 == 40 196▕ expect(lotSumAt($ctx, $ctx['to']))->toBe(stockQtyAt($ctx, $ctx['to'])); // 40 == 40 Tests: 4 failed, 779 passed (3327 assertions) Duration: 1613.00s