PASS Modules\Inventory\tests\Feature\BatchExpiryMovementTest ✓ approving a receipt carries batch_number and expiry_date onto the m… 7.87s ✓ approving a receipt without batch/expiry leaves the movement column… 3.16s ✓ expiring-lots report returns batch-tracked lots from the child tabl… 5.81s ✓ expiring-lots report shows EVERY split batch on a line, including a… 6.03s ✓ expiring-lots report sources SERIAL products from product_serials (… 2.98s ✓ expiring-lots report excludes DRAFT receipts (saved lots are not st… 3.11s PASS Modules\Inventory\tests\Feature\ConsignmentTransferTest ✓ shipping + receiving a consignment transfer moves the customer lot… 3.02s ✓ company-owned transfer is unchanged — no consignment ledger touched… 2.99s ✓ cancelling a shipped-not-received consignment transfer restores lot… 3.21s PASS Modules\Inventory\tests\Feature\ConsignmentValuationBarrierTest ✓ it valuation reports OWNED value only and puts the consigned lot in… 3.09s ✓ it valuation without any consignment reports physical == owned (no… 3.30s PASS Modules\Inventory\tests\Feature\CostingApiTest ✓ can get product cost with weighted average 2.22s ✓ can get product cost with FIFO 2.10s ✓ can list cost layers for a product 2.16s ✓ can include exhausted cost layers 2.21s ✓ can filter cost layers by warehouse 2.40s ✓ can get inventory valuation report 2.09s ✓ can get valuation report grouped by warehouse 3.22s ✓ can filter valuation by warehouse 2.04s ✓ can update costing method 2.35s ✓ validates costing method value 2.00s ✓ returns current method when already set 2.09s ✓ increaseStock creates cost layer 2.12s ✓ FIFO decreaseStock consumes oldest layers first 2.10s ✓ WAC decreaseStock does not consume cost layers 2.10s ✓ getIssueCost returns WAC cost by default 2.69s ✓ getIssueCost returns FIFO cost when configured 4.31s ✓ approving receipt creates cost layer automatically 2.21s ✓ approving issue with FIFO consumes cost layers correctly 2.29s ✓ unauthenticated user cannot access costing 0.07s ✓ cost layers are isolated by company 3.12s ✓ valuation report excludes zero quantity products 3.20s PASS Modules\Inventory\tests\Feature\CountSheetVariantGrainTest ✓ a product with two variants returns one row per variant carrying it… 2.11s ✓ a variant with no stock balance still appears with before_quantity… 2.25s ✓ a variant stocked only in another warehouse shows zero here, not th… 2.45s ✓ a product with no variants still returns exactly one row with a nul… 3.09s ✓ a variant-level balance never leaks into a no-variant product row 3.51s ✓ product_variant_name follows the request locale like getLocalizedNa… 2.04s ✓ search and category filters still apply at the variant grain 3.46s ✓ variants of another company are never joined in 2.20s ✓ a display_only product returns ONE base row carrying its real balan… 3.01s ✓ a separate_stock product fans out per variant while a display_only… 3.31s ✓ a fanned-out product with a non-zero base balance also emits the ba… 2.20s ✓ a fanned-out product whose base balance is zero does NOT emit a bas… 2.30s ✓ pagination total is counted at the fanned-out grain 4.70s ✓ a display_only product whose variant holds stock still emits that v… 3.40s ✓ a soft-deleted variant that still holds stock is emitted so it can… 2.11s ✓ a hidden variant holding a zero balance adds no row 4.51s ✓ a stock-bearing variant that also has a balance is emitted exactly… 3.30s ✓ the orphan rescue leaves the no-variant path byte-for-byte unchange… 4.18s ✓ the category_id filter applies at the variant grain 3.41s PASS Modules\Inventory\tests\Feature\ExpiringLotsOwnerTest ✓ it expiring-lots tags a consignment lot with its owner 3.41s ✓ it the owner filter scopes to own vs a specific customer 3.42s PASS Modules\Inventory\tests\Feature\ExpiryVariantGrainTest ✓ the expiry report labels each serial with its variant 2.18s ✓ the expiry report labels a soft-deleted variant rather than leaving… 2.40s ✓ a variant-less company sees the expiry report unchanged 2.58s ✓ expiring-batches splits a batch lot per variant instead of merging… 2.28s ✓ the expiring-batches on-hand anchor is the variant on hand, not the… 2.10s ✓ expiring-batches labels a soft-deleted variant 2.27s ✓ expiring-batches splits the serial source per variant too 2.07s ✓ a variant-less company sees expiring-batches unchanged 2.46s ✓ a lot line with no variant still anchors against the product total… 2.30s PASS Modules\Inventory\tests\Feature\GrainParityCountVsReorderTest ✓ the count sheet and the reorder grain emit exactly the same (produc… 9.61s ✓ the count sheet and the reorder grain agree on the quantity behind… 4.39s ✓ a product with no variants emits one product-wide row on both sides… 2.31s ✓ offsetting balances across warehouses are a row per warehouse and n… 2.51s ✓ a balance keyed to another product variant is dropped by the sheet… 5.58s PASS Modules\Inventory\tests\Feature\InventoryAdjustmentApiTest ✓ can list adjustments 2.19s ✓ can filter adjustments by status 2.01s ✓ can filter adjustments by reason 2.30s ✓ can create a manual adjustment 2.22s ✓ auto-generates adjustment number 2.11s ✓ validates required fields on adjustment create 2.38s ✓ can show an adjustment 2.22s ✓ can approve an adjustment and update stock 2.09s ✓ approve adjustment increase adds stock 2.10s ✓ cannot approve a non-draft adjustment 2.01s ✓ can cancel an approved adjustment and reverse stock 2.09s ✓ can cancel a draft adjustment without stock changes 2.09s ✓ cannot cancel an already cancelled adjustment 2.10s ✓ unauthenticated user cannot access adjustments 0.06s ✓ adjustments are isolated by company 2.12s PASS Modules\Inventory\tests\Feature\InventoryAgingVariantGrainTest ✓ aging breaks out per variant so the old colour is identifiable 2.10s ✓ aging keeps the warehouse dimension alongside the variant 2.23s ✓ aging labels a soft-deleted variant that still holds cost layers 2.07s ✓ a variant-less company gets the same aging rows, numbers and key li… 2.10s PASS Modules\Inventory\tests\Feature\InventoryApprovalWiringTest ✓ NO WORKFLOW: adjustment submits and posts exactly as today — ZERO a… 2.41s ✓ NO WORKFLOW: a draft adjustment still posts in one click (no submit… 2.11s ✓ NO WORKFLOW: issue submits and issues exactly as today — ZERO appro… 2.25s ✓ PENDING adjustment moves ZERO stock and creates ZERO journal entrie… 3.38s ✓ PENDING issue moves ZERO stock and creates ZERO journal entries (no… 3.26s ✓ the wrong approver is refused 403 and the approved adjustment is th… 4.11s ✓ multi-level: level 1 approving is not enough — the issue stays bloc… 5.61s ✓ a REJECTED adjustment stays blocked on retry, and is released only… 3.22s ✓ a never-submitted draft cannot post straight past a configured work… 3.48s ✓ both inventory types report wired:true in the document-type catalog… 2.29s ✓ the approvals inbox renders the document number and amount for both… 3.24s ✓ P1: a 5,000-unit write-off typed at unit_cost 0.01 is priced at SYS… 3.49s ✓ P1: an INCREASE line keeps the USER cost (no system cost exists for… 2.37s ✓ P1: the user cost still wins when it is HIGHER than the system cost… 2.20s ✓ P1: THE SACRED INVARIANT survives the system-cost pricing — no work… 2.21s ✓ P2: a document rejected from the "My Approvals" inbox stays pending… 3.50s ✓ P2: an issue rejected from the inbox is likewise re-submittable 3.49s ✓ P3: a fully-approved, unposted adjustment cannot be re-submitted —… 3.31s ✓ P3: a fully-approved, unissued stock issue cannot be re-submitted e… 3.22s ✓ P5: cancelling a PENDING adjustment closes the cycle — the approver… 3.47s ✓ P5: cancelling an APPROVED (issued) stock issue closes its cycle to… 3.23s ✓ P5: THE SACRED INVARIANT — cancelling with NO workflow writes and t… 2.09s ✓ WP6: a poster who holds ONLY inventory.adjustments.approve is REFUS… 3.19s ✓ WP6: an approver who holds core.approval-logs.act but NO posting pe… 3.29s ✓ WP6: the stock ISSUE governance endpoints are gated on core.approva… 7.27s ✓ WP7: NO WORKFLOW — rejecting a POSTED adjustment is 422; it cannot… 4.31s ✓ WP7: NO WORKFLOW — the LEGITIMATE reject still works: a pending_app… 2.33s ✓ WP7: NO WORKFLOW — rejecting a POSTED (issued) stock issue is 422;… 2.20s ✓ WP7: an approved cycle is CONSUMED by the post — a document that co… 3.29s 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… 2.24s ✓ 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… 2.35s ✓ reorder point equals average daily usage times lead time plus safet… 0.07s ✓ costs endpoint computes ROP = usage×LT + safety from issue history 2.05s ✓ aging buckets sum to total on-hand value per product and warehouse 2.21s ✓ aging excludes exhausted layers and is scoped by company 3.43s ✓ unauthenticated user cannot access cost reports 0.10s ✓ cost reports are isolated by company 3.46s PASS Modules\Inventory\tests\Feature\InventoryCountApiTest ✓ can list inventory counts 2.44s ✓ can filter counts by status 2.16s ✓ can filter counts by warehouse 2.19s ✓ can create an inventory count 2.12s ✓ auto-generates count number 2.29s ✓ populates system quantity from stock balance on create 2.20s ✓ validates required fields on count create 2.35s ✓ can show an inventory count 2.11s ✓ can update a draft count 2.35s ✓ cannot update an approved count 2.42s ✓ can delete a draft count 2.08s ✓ cannot delete an approved count 2.60s ✓ can finalize a count and auto-create adjustment 4.23s ✓ finalize count with no differences does not create adjustment 2.20s ✓ finalize creates a zero stock balance row for a counted product tha… 2.37s ✓ cannot finalize an approved count 2.22s ✓ unauthenticated user cannot access counts 0.07s ✓ counts are isolated by company 2.11s ✓ products-for-warehouse returns every active tracked product with it… 6.32s ✓ products-for-warehouse is company-scoped 3.20s ✓ products-for-warehouse 404s for a warehouse of another company 2.12s ✓ a count line accepts a variant of its own product 2.27s ✓ a count line still accepts a null variant — the legacy base-keyed p… 2.33s ✓ a count line rejects a variant that belongs to ANOTHER product 3.51s ✓ a count line rejects a variant that belongs to ANOTHER company 3.24s ✓ a count line still accepts a SOFT-DELETED variant of its own produc… 2.24s ✓ the per-line variant rule reads the sibling product of the SAME ind… 3.58s ✓ updating a count rejects a variant that belongs to another product 3.09s PASS Modules\Inventory\tests\Feature\InventoryCountVariantDisplayTest ✓ inventory count store response carries product_variant_name 2.09s ✓ inventory count update response carries product_variant_name 2.32s ✓ inventory count show still returns product_variant_name after the v… 2.30s ✓ inventory count show localizes a soft-deleted variant name by Accep… 2.01s ✓ inventory count show emits product_variant_name as null for a line… 2.30s PASS Modules\Inventory\tests\Feature\InventoryIssueApiTest ✓ can list issues 4.88s ✓ can filter issues by status 3.59s ✓ can filter issues by warehouse 3.22s ✓ can filter issues by date range 3.20s ✓ can search issues by issue number 3.20s ✓ can create an issue with items 3.11s ✓ issue number is auto-generated 3.20s ✓ store validates required fields 3.38s ✓ store validates items are required 3.01s ✓ store validates item fields 3.21s ✓ store persists per-item warehouse_id and owner_partner_id 3.43s ✓ store accepts owner_partner_id=0 as the force-own sentinel 3.25s ✓ store leaves per-item routing NULL when omitted (OFF-path unchanged… 3.25s ✓ store rejects a cross-company per-item warehouse 3.37s ✓ update persists per-item warehouse_id and owner_partner_id 3.66s ✓ can show an issue with items 3.92s ✓ cannot show issue from another company 3.23s ✓ can update a draft issue 3.40s ✓ cannot update an approved issue 5.80s ✓ can delete a draft issue 3.39s ✓ cannot delete an approved issue 3.30s ✓ can approve a draft issue with items and stock 3.31s ✓ approve auto-sets unit cost from average cost 3.18s ✓ approve creates stock movements 3.44s ✓ cannot approve issue with insufficient stock 3.19s ✓ can approve issue with negative stock when warehouse allows it 3.31s ✓ cannot approve issue without items 3.68s ✓ cannot approve an already approved issue 3.71s ✓ can cancel an approved issue and stock is restored 3.79s ✓ cannot cancel a draft issue 3.19s ✓ unauthenticated user cannot access issues 0.07s ✓ company isolation: cannot see other company issues 3.35s PASS Modules\Inventory\tests\Feature\InventoryIssueJobTagLinkTest ✓ a job-tagged issue moves stock and posts ZERO accounting effect 3.20s ✓ reference_id is required when the reference type is job_tag 3.08s ✓ a job tag belonging to another company is refused 3.04s ✓ a CLOSED job tag cannot be attached to a new issue 3.39s ✓ a soft-deleted job tag cannot be attached to a new issue 5.00s ✓ the resource returns the job tag name as reference_number in both l… 3.90s ✓ reference_number is null when the job tag was removed, and never le… 3.38s ✓ the issues list resolves every job tag name in a single query 3.20s ✓ an issue whose job tag was CLOSED afterwards can still have its not… 2.99s ✓ an issue on a CLOSED job tag survives the screen resending the whol… 3.01s ✓ re-pointing a job-tagged issue at a CLOSED tag is still refused 3.19s ✓ re-typing a draft to job_tag without naming a tag is refused 3.28s ✓ a job-tagged draft can be re-pointed at another ACTIVE tag 3.03s ✓ re-typing a job-tagged draft to a historical type clears the stale… 3.17s ✓ re-typing a job-tagged draft to a historical type clears the stale… 3.11s ✓ re-typing a job-tagged draft to a historical type clears the stale… 3.32s ✓ re-typing a job-tagged draft to a historical type clears the stale… 3.47s ✓ re-typing a job-tagged draft to a historical type clears the stale… 3.00s ✓ a re-type to a historical type clears the tag id even when the clie… 3.54s ✓ an unrelated edit to a SALE issue keeps its invoice id — the trigge… 3.16s ✓ re-typing a job-tagged draft to production_order still demands a re… 3.21s ✓ the historical reference types keep their loose reference_id rule o… 3.41s ✓ the historical reference types keep their loose reference_id rule o… 6.11s ✓ the historical reference types keep their loose reference_id rule o… 4.09s ✓ the historical reference types keep their loose reference_id rule o… 3.09s ✓ the historical reference types keep their loose reference_id rule o… 3.08s ✓ the historical reference types keep their loose reference_id rule o… 3.01s ✓ the historical reference types keep their loose reference_id rule o… 3.11s ✓ the historical reference types keep their loose reference_id rule o… 3.06s ✓ the historical reference types keep their loose reference_id rule o… 3.22s ✓ the historical reference types keep their loose reference_id rule o… 3.11s ✓ the job_tag case sits between production_order and other so «أخرى»… 0.06s ✓ a job-tagged issue keeps a plain draft/approve lifecycle with no it… 3.36s PASS Modules\Inventory\tests\Feature\InventoryIssueTagReportTest ✓ five approved issues on one job tag in a day report issues_count 5… 3.37s ✓ the detail lists all five أذون صرف for the tag with their lines 3.01s ✓ a CANCELLED issue is excluded from the counts 3.31s ✓ a DRAFT issue is excluded from the counts 3.10s ✓ a PENDING APPROVAL issue is excluded — canPost() means it has not p… 3.17s ✓ the counted statuses are declared in the response so the rule is ne… 3.02s ✓ a PARTIALLY ISSUED issue counts what actually moved, not what was r… 3.00s ✓ a tag whose only issues are draft or cancelled disappears from the… 2.71s ✓ the summary OMITS every value field without inventory.reports.issue… 6.51s ✓ the detail OMITS every value field — per issue AND per line — witho… 4.34s ✓ the value fields ARE present with inventory.reports.issue_tag_value 4.60s ✓ the job-tag value permission is independent of the production-order… 4.29s ✓ a job tag from another company is a 404 on the detail 3.25s ✓ the summary never shows another company job tag 4.48s ✓ an issue of ANOTHER reference type whose reference_id collides with… 3.22s ✓ a soft-deleted issue is excluded from the counts 3.54s ✓ the period filter is inclusive on both ends 3.16s ✓ a tag with no counted issue inside the period is absent from the su… 3.04s ✓ a to date before from is rejected 3.26s ✓ a CLOSED job tag still has a report — an archived job is not an era… 3.08s ✓ the status filter narrows the summary to active or closed tags 3.05s ✓ the summary is sorted busiest-first by default and honours the othe… 3.20s ✓ search matches the tag name 3.27s ✓ the summary paginates while totals stay over the WHOLE period 3.13s ✓ both job-tag report endpoints require authentication 5.28s ✓ both job-tag report endpoints require inventory.reports.view 4.32s PASS Modules\Inventory\tests\Feature\InventoryIssueTagTest ✓ a user with inventory.issue-tags.create can create a job tag 1.11s ✓ creating a job tag is refused 403 without .create — even holding th… 2.16s ✓ update and delete each need their own permission 1.99s ✓ company_id in the payload is ignored — the tenant comes from the au… 1.12s ✓ the list only ever shows the caller company tags 1.04s ✓ another company tag is invisible to show, update and delete 1.14s ✓ a duplicated name inside the same company is refused 1.24s ✓ the same name is allowed in two different companies 2.17s ✓ a NEAR-duplicate is never blocked — only the exact name is 1.10s ✓ closing a tag removes it from the picker without touching anything… 1.24s ✓ an unknown status on the list is a validation error, not an empty l… 1.10s ✓ deleting a tag is a soft delete 1.38s ✓ re-creating a soft-deleted name revives the original row instead of… 1.11s ✓ similar finds صيانة when the storekeeper types صيانه (taa marbuta v… 1.60s ✓ similar finds صيانة when the storekeeper types الصيانة (definite ar… 1.08s ✓ similar returns nothing for a genuinely different job name 1.19s ✓ similar never blocks creation — it only warns 1.21s ✓ similar is company-scoped and honours exclude_id 1.01s ✓ similar matches on the Arabic label of an English-named tag 1.28s ✓ similar caps the answer at five suggestions, closest first 1.11s ✓ similar on an empty name answers with an empty list, not an error 1.31s ✓ similar needs inventory.issue-tags.view 2.32s ✓ the normaliser folds the forms Arabic typists mix freely 0.06s ✓ the normaliser strips the INVISIBLE bidi marks a pasted name carrie… 0.06s ✓ similar scores a pasted name carrying an invisible RLM as an exact… 1.04s ✓ a word that merely starts with a hamzated alef keeps its lam 0.07s PASS Modules\Inventory\tests\Feature\InventoryReceiptApiTest ✓ can list receipts 3.25s ✓ can filter receipts by status 3.47s ✓ can filter receipts by warehouse 3.30s ✓ can filter receipts by date range 3.43s ✓ can search receipts by receipt number 3.07s ✓ can create a receipt with items 3.09s ✓ receipt number is auto-generated 5.34s ✓ store validates required fields 2.89s ✓ store validates items are required 3.19s ✓ store validates item fields 2.91s ✓ can show a receipt with items 2.98s ✓ cannot show receipt from another company 3.31s ✓ can update a draft receipt 3.09s ✓ cannot update an approved receipt 3.19s ✓ can delete a draft receipt 3.01s ✓ cannot delete an approved receipt 3.23s ✓ can approve a draft receipt with items 2.97s ✓ approve creates stock movements 3.33s ✓ approve calculates weighted average cost correctly 3.17s ✓ cannot approve receipt without items 3.10s ✓ cannot approve an already approved receipt 3.30s ✓ can cancel an approved receipt 3.21s ✓ cannot cancel a draft receipt 3.10s ✓ unauthenticated user cannot access receipts 0.07s ✓ company isolation: cannot see other company receipts 3.12s PASS Modules\Inventory\tests\Feature\InventoryReportApiTest ✓ production order issue report returns per-material issued qty and p… 5.19s ✓ production order issue report includes cost with the cost permissio… 5.50s ✓ production order issue report omits cost without the cost permissio… 5.50s ✓ production order issue report rejects an order from another company 4.33s ✓ production order issue report filters by issued_at date range 4.32s ✓ can get movement summary for date range 2.19s ✓ movement summary calculates opening balance from prior movements 2.15s ✓ movement summary requires date range 2.11s ✓ can filter movement summary by warehouse 2.34s ✓ can get slow-moving stock report 2.20s ✓ slow-moving excludes products with recent activity 2.77s ✓ slow-moving includes products with no movement at all 2.60s ✓ can get warehouse utilization summary 4.99s ✓ warehouse summary shows product count per warehouse 4.52s ✓ can get expiry report for items expiring soon 2.11s ✓ expiry report can include already expired items 2.29s ✓ expiry report excludes sold serials 2.17s ✓ unauthenticated user cannot access reports 0.07s ✓ reports are isolated by company 3.26s PASS Modules\Inventory\tests\Feature\InventorySchemaTest ✓ can create inventory count with items 2.47s ✓ can create inventory adjustment with items 4.72s ✓ adjustment can link to inventory count 2.19s ✓ can create cost layer 2.00s ✓ adjustment recalculates totals correctly 2.20s ✓ count and adjustment enums have correct values 0.07s PASS Modules\Inventory\tests\Feature\InventoryScopeInvariantTest ✓ router coverage: every api.inventory route is probed or exempted wi… 0.08s ✓ list/report endpoints return only assigned-warehouse data for a res… 9.94s ✓ by-id endpoints answer 404 for an out-of-scope record and not-404… 10.30s PASS Modules\Inventory\tests\Feature\InventorySettingsGatesTest ✓ receipt stays DRAFT on creation when auto_approve_receipts is OFF (… 3.41s ✓ receipt is AUTO-APPROVED on creation when auto_approve_receipts is… 3.21s ✓ receipt WITHOUT batch_number is allowed when require_batch_tracking… 3.19s ✓ receipt WITHOUT batch_number is REJECTED when require_batch_trackin… 3.20s ✓ receipt WITH batch_number is accepted when require_batch_tracking i… 3.20s ✓ issue WITHOUT batch_number is REJECTED when require_batch_tracking… 3.41s ✓ issue WITH batch_number is accepted when require_batch_tracking is… 3.29s PASS Modules\Inventory\tests\Feature\InventoryTransferApiTest ✓ can list transfers 3.25s ✓ can filter transfers by status 4.97s ✓ can filter transfers by warehouse 3.50s ✓ can filter transfers by date range 3.21s ✓ can search transfers by transfer number 3.36s ✓ can create a transfer with items 3.30s ✓ transfer number is auto-generated 3.23s ✓ store validates required fields 3.21s ✓ store validates warehouses must be different 3.56s ✓ store validates item fields 3.35s ✓ can show a transfer with items 2.97s ✓ cannot show transfer from another company 3.30s ✓ can update a draft transfer 3.30s ✓ cannot update a shipped transfer 3.30s ✓ can delete a draft transfer 3.39s ✓ cannot delete a shipped transfer 3.11s ✓ can ship a draft transfer and stock is deducted 4.19s ✓ ship creates stock movements 3.61s ✓ cannot ship transfer with insufficient stock 3.60s ✓ can ship transfer with negative stock when warehouse allows it 4.98s ✓ cannot ship transfer without items 3.21s ✓ cannot ship an already shipped transfer 3.10s ✓ can receive a shipped transfer and stock is added 3.22s ✓ can receive with partial quantities 3.51s ✓ receive creates stock movements 3.51s ✓ cannot receive a draft transfer 4.59s ✓ can cancel a draft transfer 3.61s ✓ can cancel a shipped transfer and stock is restored 3.21s ✓ cannot cancel an already cancelled transfer 3.59s ✓ cannot cancel a received transfer 3.16s ✓ unauthenticated user cannot access transfers 0.07s ✓ company isolation: cannot see other company transfers 3.04s PASS Modules\Inventory\tests\Feature\InventoryVariantOwnershipGuardTest ✓ issue store rejects a variant that belongs to a different product o… 4.44s ✓ issue store rejects a variant that belongs to another company 4.17s ✓ issue store accepts the line product own variant and persists it 4.01s ✓ issue store saves a null variant 4.35s ✓ issue store still accepts a SOFT-DELETED variant of the line produc… 7.56s ✓ issue store checks each line against ITS OWN product and does not l… 5.39s ✓ issue store accepts two lines of DIFFERENT products each with its o… 5.79s ✓ issue update rejects a variant of a different product 4.38s ✓ issue update rejects a variant of another company 4.31s ✓ issue update accepts a valid variant and persists it 4.31s ✓ issue update accepts a null variant 4.20s ✓ receipt store rejects a variant of a different product and accepts… 4.44s ✓ receipt update rejects a variant of a different product 5.16s ✓ transfer store rejects a variant of a different product and accepts… 4.62s ✓ transfer update rejects a variant of a different product 4.31s ✓ adjustment store rejects a variant of a different product and accep… 4.18s ✓ opening balance store rejects a variant of a different product and… 6.71s ✓ bulk opening balance rejects a variant that belongs to a different… 4.27s ✓ bulk opening balance rejects a variant that belongs to another comp… 4.70s ✓ bulk opening balance accepts the entry product own variant and pers… 4.30s ✓ bulk opening balance accepts a null variant 4.43s ✓ bulk opening balance checks each entry against ITS OWN product 4.10s FAIL Modules\Inventory\tests\Feature\LotAllocationTest ⨯ FEFO allocation consumes the earliest-expiry lot first and records… 1.98s ⨯ a quantity beyond the owner lots leaves an unassigned shortfall, no… 1.99s ✓ reverse restores the exact lots and deletes the allocation rows 2.00s ✓ manual selection over a lot on-hand is rejected (422) 2.41s ✓ manual selection is scoped to the variant — cannot target another v… 2.19s ✓ manual selection consumes exactly the chosen lot 2.19s ✓ FIFO issuing order consumes the earliest-RECEIVED lot first, ignori… 2.11s ✓ default (FEFO) issuing order consumes the earliest-EXPIRY lot first 2.08s ✓ default (block) policy SKIPS expired lots in auto-FEFO — leftover i… 2.09s ✓ allow policy makes expired lots eligible for auto-FEFO (oldest-expi… 2.00s PASS Modules\Inventory\tests\Feature\LotBalanceSeedTest ✓ seeding a receipt creates one company-owned lot balance per batch,… 2.20s ✓ the same batch received on two receipts increments one lot row, not… 1.91s ✓ backfill attributes historical issues to the OLDEST-expiry lots so… 1.91s ✓ backfill refuses by default when live-seeded lots exist, and --forc… 2.09s ✓ a consignment lot is owner-tagged and off-book (memo value only) 1.90s PASS Modules\Inventory\tests\Feature\LotCoverageDetailTest ✓ no lots at all reads as zero coverage under BOTH policies — an ordi… 2.99s ✓ expired-only stock reads as zero under Block and full under Allow —… 4.90s ✓ a valid lot reports no expired quantity even when an expired lot si… 3.40s ✓ a lot expiring TODAY is not expired — one definition, the picker qu… 2.30s ✓ coverage() returns exactly coverageDetail() covered, for every poli… 2.19s ✓ the expired_policy override wins over inventory.expired_issue_polic… 2.13s ✓ what coverageDetail reports is exactly what allocate then consumes 2.40s PASS Modules\Inventory\tests\Feature\LotCoverageGuardTest ✓ setting ON: a batch product whose lots cover the quantity posts and… 2.28s ✓ setting ON: coverage exactly equal to the requested quantity is not… 2.11s ✓ setting ON: a shortfall is refused with a 422 and stock_balances is… 2.10s ✓ setting ON: a batch product with NO lots at all is refused and the… 2.60s ✓ setting ON: refusing does not leave a phantom stock_balances row be… 2.10s ✓ setting ON: the SALE itself is refused end to end and the balance n… 2.89s ✓ setting OFF: the shortfall stays silent — the sale posts and the ba… 2.29s ✓ setting explicitly OFF is identical to the setting being absent 2.02s ✓ tracking_type none is completely unaffected — setting OFF 1.99s ✓ tracking_type none is completely unaffected — setting ON 2.10s ✓ a non-batch product is not refused even if a caller wrongly asks fo… 2.29s ✓ an outbound path that does not allocate lots is untouched by the gu… 2.61s ✓ C3 HANDOFF: with expired_issue_policy=block, an expired-only cover… 2.60s ✓ C3 HANDOFF: the same expired lots POST when expired_issue_policy=al… 2.50s ✓ a shortfall caused by NO lots and one caused by EXPIRED-ONLY lots a… 4.72s ✓ setting ON: a customer-owned lot does not cover a company-owned iss… 1.98s ✓ 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… 7.69s ✓ a product split across warehouses reports each warehouse and rolls… 3.40s ✓ a consignment lot balances the physical invariant but is reported s… 3.38s ✓ a lot ledger with no balance row at all is still reported as drift 3.32s ✓ expired lots that still carry stock are listed, including on a non-… 4.61s ✓ serial and untracked products are not reconciled — only batch-track… 3.49s ✓ only_unbalanced hides the balanced rows but the summary still count… 4.82s ✓ the report is scoped to the caller company and filterable by wareho… 6.98s ✓ the report writes nothing — it repairs no drift and consumes no exp… 3.18s ✓ the report requires authentication 0.07s PASS Modules\Inventory\tests\Feature\MovementSummaryVariantGrainTest ✓ movement summary breaks a product out per variant with per-colour n… 2.04s ✓ the opening balance is carried per variant, not pooled across colou… 2.00s ✓ a variant whose only activity is a prior-period opening balance sti… 2.09s ✓ a soft-deleted variant with movement history is still labelled 2.02s ✓ the category filter still applies at the wider grain 3.19s ✓ a variant-less company gets the same rows, numbers and key list as… 3.08s FAIL Modules\Inventory\tests\Feature\OpeningBalanceApiTest ✓ can list opening balances 3.21s ✓ can filter opening balances by warehouse 2.99s ✓ can show opening balance details 6.13s ✓ cannot show non-opening receipt via opening balances endpoint 3.60s ✓ can create opening balance 3.70s ⨯ opening balance creates inventory movement with type opening 3.90s ✓ opening balance with multiple items creates stock for each 4.40s ✓ opening balance validates required fields 3.21s ✓ opening balance validates item fields 3.15s ✓ opening balance creates serial records for serialized products 4.14s ✓ can bulk create opening balances 4.49s ✓ bulk opening balance validates entries 3.37s ✓ bulk opening balance groups by warehouse 4.52s ✓ can cancel opening balance 3.19s ✓ cannot cancel already cancelled opening balance 3.44s ✓ opening balances are company scoped 3.67s PASS Modules\Inventory\tests\Feature\PartialIssueApprovalTest ✓ partial issue decreases stock by issued_quantity and sets Partially… 3.60s ✓ null issued_quantity falls back to full quantity and sets Approved… 5.50s ✓ issued_quantity exceeding requested quantity throws 422 3.69s ✓ partial issue rejected when allow_partial_issue setting is false 3.70s ✓ InventoryIssueApproved event is dispatched on approval 3.41s ✓ InventoryIssueApproved event is dispatched on partial approval 3.51s PASS Modules\Inventory\tests\Feature\PartialIssueCancelTest ✓ cancelling a partially-issued issue reverses only issued_quantity n… 3.50s ✓ isCancellable returns true for PartiallyIssued status 3.76s ✓ isCancellable returns false for Draft status 3.41s ✓ cancelling a draft issue returns an error 3.38s ✓ cancelling a fully approved issue reverses the full issued quantity 3.63s PASS Modules\Inventory\tests\Feature\PerItemWarehouseIssueTest ✓ each item decrements stock at its own warehouse (NULL falls back to… 4.59s ✓ allow_negative_stock is resolved per item warehouse 4.51s 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.08s ✓ inventory_issue_items issued_quantity defaults to null 2.16s ✓ InventoryIssueItem accepts issued_quantity and source_item_id via f… 0.08s ✓ InventoryIssueItem casts issued_quantity as decimal:3 0.08s ✓ InventoryIssue accepts cogs_journal_entry_id via fillable 0.07s ✓ IssueStatus has PartiallyIssued case 0.07s ✓ IssueStatus::PartiallyIssued label is non-empty 0.09s PASS Modules\Inventory\tests\Feature\Phase2LotCaptureTest ✓ bug(a): a GRN-origin serial product with no pre-existing serials is… 3.32s ✓ bug(b): serials inherit batch dates unless overridden, and both dat… 3.21s ✓ production_date threads onto the stock movement (first-batch mirror… 2.92s ✓ sum mismatch: batch quantities not adding up to the line quantity i… 3.48s ✓ duplicate serial within the payload is 422, keyed to the offending… 5.21s ✓ serial already existing in product_serials is 422 3.43s ✓ production_date not before expiry_date is 422 3.49s ✓ locked fields: changing quantity on a GRN-origin receipt via Save-l… 3.62s ✓ Save-lots on a GRN-origin receipt persists batches without approvin… 3.76s ✓ a bare metadata edit (no items) on a GRN-origin receipt stays hard-… 3.59s ✓ a plain non-lot approve (no batches payload) still works exactly as… 3.29s ✓ a plain legacy serial approve (serials on the line, no batches) sti… 3.55s PASS Modules\Inventory\tests\Feature\ReceiptApprovalWiringTest ✓ NO WORKFLOW: receipt submits and posts exactly as today — ZERO appr… 2.60s ✓ NO WORKFLOW: a draft receipt still posts in one click (no submit) —… 2.47s ✓ NO WORKFLOW: auto_approve_receipts still posts stock on creation —… 2.31s ✓ ISS-2026-0185: a BATCH receipt is NOT auto-approved — it stays draf… 2.71s ✓ ISS-2026-0185: a SERIAL receipt is NOT auto-approved — it stays dra… 2.25s ✓ PENDING receipt adds ZERO stock and creates ZERO journal entries; t… 3.33s ✓ EDGE CASE 1: receipt auto-approve does NOT bypass an active workflo… 3.72s ✓ the batch/expiry capture on the commit still works after the receip… 3.50s ✓ the wrong approver is refused 403; multi-level holds the stock unti… 4.69s ✓ a REJECTED receipt stays blocked on retry, and is released only by… 3.93s ✓ a never-submitted draft receipt cannot post straight past a configu… 5.33s ✓ inventory_receipt reports wired:true and renders in the approvals i… 3.08s ✓ WP6: a keeper who holds ONLY inventory.receipts.approve is REFUSED… 3.14s ✓ WP7: NO WORKFLOW — rejecting a POSTED receipt is 422; stock is not… 2.21s ✓ WP7: NO WORKFLOW — the LEGITIMATE reject still works: a pending_app… 2.19s ✓ WP7 MINOR 4: cancelling a receipt closes its approval cycle — the r… 3.40s ✓ WP7 HIGH: a post landing under the rejector is SEEN (422) — the gua… 2.52s PASS Modules\Inventory\tests\Feature\ReorderAlertApiTest ✓ can list reorder alerts for products below reorder point 3.25s ✓ reorder alerts return critical severity for out-of-stock 2.22s ✓ reorder alerts return critical severity when below min stock 2.47s ✓ can filter reorder alerts by severity 3.35s ✓ can filter reorder alerts by warehouse 2.37s ✓ can filter reorder alerts by category 3.09s ✓ reorder alerts exclude non-inventory-tracked products 2.31s ✓ suggested order qty uses reorder_point * 2 when max_stock_level not… 2.29s ✓ can get reorder report grouped by category 2.40s ✓ can get reorder report grouped by warehouse 2.10s ✓ reorder report shows per-warehouse breakdown 2.59s ✓ can get inventory stats 3.34s ✓ stats count out-of-stock products 2.37s ✓ can send reorder notifications 2.52s ✓ notify returns zero when no products below reorder 4.16s ✓ unauthenticated user cannot access reorder alerts 0.07s ✓ unauthenticated user cannot access reorder report 0.08s ✓ unauthenticated user cannot access inventory stats 0.08s ✓ reorder alerts are isolated by company 3.30s PASS Modules\Inventory\tests\Feature\ReorderAlertVariantTest ✓ a variant below the reorder point alerts even when a sibling varian… 2.40s ✓ every variant of a product is measured against the same product thr… 2.23s ✓ a product with no variants still produces exactly one alert with nu… 2.16s ✓ variant and non-variant products coexist in one alert list 3.70s ✓ warehouse filter still scopes per-variant reorder alerts 2.10s ✓ warehouse filter is unchanged for a product with no variants 2.20s ✓ reorder report is emitted at the variant grain 2.09s ✓ reorder report keeps the legacy single row for a product with no va… 2.22s ✓ inventory stats report the product grain and the variant grain unde… 2.30s ✓ the out-of-stock KPI never mixes a variant count with a product cou… 2.29s ✓ inventory stats are unchanged for products with no variants 3.30s ✓ reorder notifications are raised per variant and carry the variant… 2.43s ✓ reorder notifications keep one row and null variant fields for a pr… 2.29s ✓ product_variant_name follows the request locale like getLocalizedNa… 2.42s ✓ variant reorder alerts stay isolated by company 3.50s ✓ a display_only product yields ONE base row carrying the product-wid… 2.28s ✓ a display_only product collapses on every endpoint, not just the al… 2.33s ✓ a fanned-out product holding base-keyed stock also emits a base row… 2.68s ✓ legacy base-keyed stock above the reorder point is a visible grain… 2.09s ✓ the severity filter applies at the variant grain 2.08s ✓ the category_id and search filters still narrow the fanned-out list 3.72s ✓ one product with more than 25 below-reorder variants paginates 25 r… 4.02s ✓ report group_by=warehouse groups the fanned-out rows per warehouse 2.30s ✓ notify reports distinct products and variant rows under separate na… 2.57s ✓ the notification payload covers ten distinct products, not ten rows 6.41s ✓ report counts distinct products, not rows, when one product has twe… 3.50s ✓ report group_by=warehouse counts distinct products per warehouse to… 2.22s ✓ a base balance that nets to zero across warehouses raises no phanto… 2.29s ✓ a display_only variant that really holds stock is alerted under the… 1.99s ✓ a display_only product holding both base and variant stock emits bo… 2.00s ✓ a soft-deleted variant still holding stock stays visible to the reo… 2.01s ✓ a soft-deleted variant with no stock left does not resurrect as a g… 2.20s ✓ the notification payload caps at ten distinct products 15.70s ✓ the notification payload stays inside the text column when one prod… 7.35s ✓ a product with no variants still produces exactly one uncapped-shap… 1.15s ✓ the alerts list publishes the distinct-product count beside the row… 1.62s ✓ the alerts products_count spans the whole filtered set, not just t… 19.19s PASS Modules\Inventory\tests\Feature\ReorderGrainParityTest ✓ production material-shortage sees the stocked-out variant the reord… 1.18s ✓ production material-shortage collapses a display_only product exact… 1.23s ✓ production material-shortage keeps the legacy single alert for a pr… 1.27s ✓ every below-reorder variant of one product gets its own production… 1.11s ✓ production material-shortage keeps legacy base-keyed stock visible… 1.23s ✓ production material-shortage stays company scoped at the variant gr… 1.67s FAIL Modules\Inventory\tests\Feature\RequireBatchOnReceiptTest ✓ ON: a batch-tracked line approved with no batches at all is refused… 1.81s ✓ ON: a batch row with no expiry_date is refused, naming the line and… 1.72s ✓ ON: a batch whose expiry_date is already past is refused, naming th… 1.89s ✓ ON: a batch row with a blank batch_number is refused 1.68s ✓ ON: a complete batch row posts and the lot row is created as today 1.71s ✓ ON: the rule does not leak to tracking_type = none — a normal produ… 1.69s ✓ ON: the legacy line-column capture (batch_number + expiry_date on t… 1.63s ✓ OFF: a batch-tracked line with no batches posts exactly as it does… 1.66s ✓ UNSET: with the setting never written, behaviour is today's behavio… 1.73s ✓ OFF: an expired batch is still accepted, as today 1.68s ✓ the definition is unlocked after seeding 0.10s ✓ a PUT from the settings screen saves it and lands on inventory.* —… 0.71s ✓ a plain pos.* key still round-trips through the same write path 0.61s ⨯ 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… 1.72s ✓ Fix1-B: sale GDN partial issue in delivery mode is allowed 1.81s ✓ Fix1-C: non-sale (Consumption) GDN partial issue in invoice mode is… 1.70s ✓ Fix1-D: sale GDN full issue in invoice mode is allowed 1.78s ✓ Fix1-E: sale GDN with null issued_quantity (full back-compat) in in… 1.78s ✓ Fix2-F: source_item_id null is accepted and round-trips through upd… 1.81s ✓ Fix2-H: non-null source_item_id is preserved when FE sends it back… 2.54s ✓ Fix2-I: source_item_id becomes null when FE omits it in the update… 1.65s PASS Modules\Inventory\tests\Feature\SerialTrackingApiTest ✓ can list serials for a product 1.77s ✓ can filter serials by status 3.93s ✓ can filter serials by warehouse 2.03s ✓ can search serials by serial number 1.71s ✓ can add serial numbers to a serialized product 1.78s ✓ cannot add serials to non-serialized product 2.28s ✓ cannot add duplicate serial numbers 1.83s ✓ can update a serial number 1.67s ✓ can delete an available serial 1.71s ✓ cannot delete a sold serial 1.80s ✓ receipt approval creates serial records for serialized products 1.91s ✓ receipt approval fails when serial count does not match quantity 1.90s ✓ receipt approval fails when serial number already exists 2.03s ✓ cancel receipt deletes available serials created by that receipt 1.70s ✓ issue approval marks serials as sold 1.80s ✓ issue approval fails when serial is not available 1.65s ✓ issue approval fails when serial count does not match quantity 1.72s ✓ cancel issue returns serials to available status 1.70s ✓ product factory creates serialized product with correct tracking ty… 1.78s ✓ product tracking type can be changed at model level 1.62s ✓ non-serialized product receipt works without serial numbers 2.15s ✓ cannot see serials from another company 1.65s PASS Modules\Inventory\tests\Feature\SerialVariantTraceabilityTest ✓ issue rejects a serial belonging to another variant of the same pro… 1.70s ✓ a wrong-variant serial is refused with its own message, not "not av… 1.83s ✓ a serial that does not exist at all still reports "not available" 1.80s ✓ issue accepts a serial of the line own variant and marks it sold 1.81s ✓ a serial of another variant is left untouched by a successful issue… 1.90s ✓ a base keyed serial is not accepted on a variant keyed line 2.00s ✓ a variant keyed serial is not accepted on a base keyed line 2.31s ✓ a variant less product still issues its serials exactly as before 3.06s ✓ issue still rejects a serial of another product 2.89s ✓ cannot add a serial keyed to a variant of another product 5.81s ✓ cannot add a serial keyed to a variant of another company 2.39s ✓ can add a serial keyed to a variant of the route product 1.80s ✓ cannot rewrite a serial to a variant of another product 2.28s ✓ cannot rewrite the variant of a sold serial 1.71s ✓ re-sending the unchanged variant of a sold serial is still accepted 1.68s ✓ can rewrite the variant of an available serial to a sibling variant 1.70s PASS Modules\Inventory\tests\Feature\SlowMovingVariantGrainTest ✓ a stagnant variant appears in slow-moving even when a sibling varia… 1.21s ✓ a variant that has never moved is not rescued by a sibling that has 1.12s ✓ slow-moving rows carry the variant id and its localized name 1.11s ✓ a soft-deleted variant that still holds stock is labelled, not left… 1.36s ✓ a variant-less company gets the same rows, numbers and columns as b… 1.60s PASS Modules\Inventory\tests\Feature\StockBalanceApiTest ✓ can list stock balances 1.16s ✓ can filter stock balances by warehouse 1.21s ✓ can filter stock balances by product 1.89s ✓ can filter stock balances by category 1.89s ✓ can filter stock balances below reorder point 1.91s ✓ stock balance response includes available quantity 1.39s ✓ includes zero and negative quantity balances by default 2.52s ✓ hide_zero filter excludes zero-quantity balances 2.55s ✓ can get stock by product across warehouses 1.31s ✓ can get stock by warehouse 2.40s ✓ can get stock card for a product 1.30s ✓ can filter stock card by warehouse 1.17s ✓ can filter stock card by date range 1.13s ✓ can filter stock card by movement type 1.18s ✓ stock card response includes movement details 1.16s ✓ can list all movements 1.12s ✓ can filter movements by product 1.62s ✓ unauthenticated user cannot access stock balances 0.07s ✓ unauthenticated user cannot access stock card 0.07s ✓ unauthenticated user cannot access movements 0.08s ✓ stock balances are isolated by company 1.87s ✓ movements are isolated by company 1.13s ✓ productLots scopes lots and physical_total to one variant when prod… 1.18s ✓ productLots keeps the no-variant path working (product with no vari… 1.17s ✓ owner=own returns only rows with owned stock (drops purely-consignm… 2.65s ✓ owner= returns only that customer rows and stamps owner_quantity/ow… 4.25s ✓ owner=own resolves the 0 sentinel without swallowing it (owner=0 ==… 1.70s ✓ lot-owners returns the distinct customers currently holding live co… 1.11s ✓ meta.totals sums the whole company scope, not just the paginated p… 17.20s ✓ meta.totals excludes consignment from owned value and reports it in… 1.20s PASS Modules\Inventory\tests\Feature\StockBalanceExpiryVisibilityTest ✓ nearest_expiry is the earlier of two available serials with source… 1.20s ✓ a serial product with all serials consumed does NOT approximate exp… 1.20s ✓ a non-available serial is ignored when picking the nearest expiry 1.22s ✓ a past-dated available serial flags is_expired true 1.16s ✓ a batch-only product falls back to approx nearest expiry from movem… 1.13s ✓ an available serial takes precedence over movement genealogy 1.13s ✓ a product with no serials and no expiry movements yields null expir… 1.08s ✓ a batch product nearest expiry comes from the live lot ledger with… 1.17s ✓ nearest expiry attributes to the customer when a consignment lot ex… 1.15s ✓ the index response surfaces near_expiry_days in meta 1.16s ✓ the index endpoint issues a bounded number of queries regardless of… 7.60s PASS Modules\Inventory\tests\Feature\StockBalanceTollCustomerTest ✓ a stock-balance row exposes toll_customer_id and toll_customer_name… 1.70s ✓ toll_customer_name falls back to the latin name when the partner ha… 1.21s ✓ byProduct and byWarehouse also carry the toll customer 1.28s ✓ search by the toll customer name returns that customer items 2.23s ✓ the product columns still search after the customer clause was adde… 1.69s ✓ search and warehouse still apply TOGETHER (the nested-closure trap) 2.20s ✓ search still composes with category_id and hide_zero 2.33s ✓ toll_customer_id narrows the list to that customer items 2.18s ✓ toll_customer_id=own returns only items with no toll customer 4.18s ✓ an absent or empty toll_customer_id changes nothing 1.94s ✓ toll_customer_id composes with warehouse_id 1.69s ✓ meta.totals matches the sum over the toll_customer_id-filtered rows 3.11s ✓ meta.totals consignment memo is scoped by toll_customer_id too 1.79s ✓ meta.totals matches the rows when the filter is the customer NAME s… 1.78s ✓ a full page of 25 rows with toll customers issues a bounded number… 13.83s ✓ the owner lens is unaffected by the toll customer work 1.65s ✓ the owner lens and the toll customer filter are independent and com… 2.23s ✓ below_reorder still works and composes with the toll customer filte… 2.10s ✓ the toll customer filter is company-scoped 1.77s PASS Modules\Inventory\tests\Feature\StockCardMovementSourceTest ✓ a movement from a manual production issue resolves to the productio… 1.42s ✓ the raw reference_type and reference_id fields are untouched alongs… 1.18s ✓ a backflush movement resolves to the production order in one hop 1.23s ✓ a production staging movement also resolves to the production order… 1.21s ✓ a finished-goods receipt resolves through the receipt to the produc… 1.17s ✓ a job-tagged issue resolves to the tag by name and is never labelle… 1.13s ✓ a plain consumption issue resolves to the issue itself, with no sec… 1.29s ✓ a consignment receipt resolves to the owning partner and offers no… 1.49s ✓ a consignment return resolves to the owning partner too 1.33s ✓ a consignment borrow and a replenishment resolve to the material ow… 1.20s ✓ a cancelled issue movement resolves to the ORIGINAL source and is f… 1.18s ✓ a cancelled adjustment movement resolves to the original adjustment… 1.21s ✓ a movement whose reference no longer exists returns an unresolved s… 1.20s ✓ a reference_type this build has never heard of degrades to unknown… 1.10s ✓ a reference belonging to another company resolves to nothing 1.61s ✓ a job tag owned by another company never leaks its name through the… 1.09s ✓ the query count for a page of 50 mixed-source movements does not sc… 2.24s ✓ every row on a mixed page carries a source object with the full sta… 1.06s PASS Modules\Inventory\tests\Feature\StockCardVariantScopeTest ✓ omitting product_variant_id returns every movement of the product a… 1.18s ✓ a product with no variants at all is unaffected when the param is o… 3.20s ✓ a numeric product_variant_id returns only that variant with a coher… 1.63s ✓ the other variant gets its own independent ledger 1.40s ✓ the unscoped card interleaves variants so its balance chain is NOT… 1.10s ✓ an explicitly empty product_variant_id returns only the base produc… 1.18s ✓ empty is a distinct value from absent - the two must not return the… 1.18s ✓ warehouse_id still filters when combined with the variant scope 1.12s ✓ date_from date_to and movement_type still filter when combined with… 1.12s ✓ every returned movement carries product_variant_id and the variant… 1.29s ✓ product_variant_name follows the request locale 1.18s ✓ the variant scope never leaks another company movements 1.22s ✓ the movements list honours the same three-valued product_variant_id 1.22s ✓ an array product_variant_id is rejected, not silently cast to varia… 1.19s ✓ a non-numeric product_variant_id is rejected instead of returning a… 1.19s ✓ a zero or negative product_variant_id is rejected (ids are auto-inc… 1.21s ✓ the movements list rejects a malformed product_variant_id the same… 1.26s PASS Modules\Inventory\tests\Feature\StockReservationTest ✓ reserve increments reserved_quantity 1.55s ✓ reserve accumulates across calls 1.58s ✓ reserve with no stock row creates a zero-qty row holding the reserv… 1.68s ✓ release decrements reserved_quantity 1.71s ✓ release floors reserved_quantity at zero on over-release 1.71s ✓ release with no stock row is a no-op (nothing was reserved) 1.63s ✓ availableQuantity returns quantity minus reserved 1.68s ✓ availableQuantity returns zero when no balance row exists 1.70s ✓ approve blocks a non-production issue exceeding available while sto… 1.91s ✓ approve allows a production-order issue against gross even when sto… 1.80s ✓ approve allows a non-production issue within available 1.61s PASS Modules\Inventory\tests\Feature\StockServiceUnitConversionTest ✓ increase then decrease WITHOUT unit_id is unchanged (identity) 1.08s ✓ passing the base unit_id (factor 1.0) equals passing no unit_id 1.10s ✓ increaseStock with a factor-12 unit moves base qty and preserves va… 1.18s ✓ getIssueCost and decreaseStock with a factor-12 unit work on base q… 1.12s ✓ receipt of 2 cartons then issue of 1 carton nets to 12 base on-hand 1.12s ✓ full round-trip of a factor-12 item returns on-hand and value to ze… 1.10s ✓ FIFO cost layer is written and consumed in base units 1.10s FAIL Modules\Inventory\tests\Feature\TransferLotPreservationTest ✓ shipping a transfer decrements the source lot and records the alloc… 1.60s ✓ receiving re-creates the SAME batch/expiry lot at the destination 1.61s ⨯ FEFO across two source lots preserves both batches at the destinati… 1.58s ✓ partial receive re-creates only the received portion at the destina… 1.60s ✓ cancelling a shipped transfer restores the source lot exactly and d… 1.68s PASS Modules\Inventory\tests\Feature\UnitConversionActivationTest ✓ FIFO factor-3 cost-10 round-trip nets value with no 0.001 residual 1.10s ✓ cancel of a factor-12 receipt reverses the stamped base qty even af… 1.52s ✓ issuing 3 cartons is blocked when only 24 kg (2 cartons) are on han… 1.70s ✓ reserve then release with a factor-12 unit is symmetric 1.47s PASS Modules\Inventory\tests\Feature\ValuationVariantLabelTest ✓ valuation rows carry the variant name next to the variant id 3.22s ✓ a base-keyed valuation row reports a null variant name 1.21s ✓ a soft-deleted variant that still holds stock is labelled, not left… 1.21s ✓ the valuation variant label costs no extra query per row 1.19s PASS Modules\Inventory\tests\Feature\WarehouseApiTest ✓ can list warehouses 1.19s ✓ can filter warehouses by type 1.19s ✓ can filter warehouses by branch 1.20s ✓ can filter warehouses by active status 1.31s ✓ can search warehouses by name or code 1.17s ✓ can get warehouse tree 1.12s ✓ can create a warehouse 1.17s ✓ auto-generates warehouse code on create 1.13s ✓ can create a sub-warehouse under a parent 1.11s ✓ validates required fields on create 1.10s ✓ validates type must be a valid enum value 1.20s ✓ can show a warehouse 1.17s ✓ returns 404 for warehouse from another company 1.19s ✓ can update a warehouse 1.13s ✓ can toggle warehouse active status 1.18s ✓ can delete a warehouse without children 1.38s ✓ cannot delete a warehouse with children 1.12s ✓ unauthenticated user cannot access warehouses 0.08s ✓ warehouses are isolated by company 1.14s PASS Modules\Inventory\tests\Feature\WarehouseScopeWiringTest ✓ mode all (no setting row — every upgraded install) keeps full lists… 1.19s ✓ assigned: a keeper of one warehouse sees only his rows and his TOTA… 1.19s ✓ assigned: an out-of-scope id answers 404 on show and on mutation en… 1.19s ✓ a transfer is visible to its source keeper AND its destination keep… 2.24s ✓ assigned + NO assignment = ZERO rows and ZERO totals, never all row… 1.17s ✓ a system context (no authenticated user) still posts a receipt with… 1.09s ──────────────────────────────────────────────────────────────────────────── 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\RequireBatchOnReceiptTest > exact… Failed asserting that actual size 40 matches expected size 39. at Modules/Inventory/tests/Feature/RequireBatchOnReceiptTest.php:388 384▕ (new SettingDefinitionSeeder)->run(); 385▕ 386▕ $locked = SettingDefinition::query()->where('is_implemented', false)->pluck('setting_key'); 387▕ ➜ 388▕ expect($locked)->toHaveCount(39); 389▕ expect($locked->all())->not->toContain(RBOR_KEY); 390▕ expect($locked->all())->not->toContain('pos.enforce_batch_selection'); 391▕ expect($locked->all())->not->toContain('pos.block_expired_sale'); 392▕ }); 1 Modules/Inventory/tests/Feature/RequireBatchOnReceiptTest.php:388 ──────────────────────────────────────────────────────────────────────────── 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: 5 failed, 787 passed (3374 assertions) Duration: 2094.85s