svc = app(WarehouseService::class); } public function test_issue_now_consumes_immediately_without_wo_done(): void { $ctx = $this->ctx(); $this->svc->receive($ctx['part'], 10, 25.0); $wop = WorkOrderPart::create([ 'work_order_id' => $ctx['wo']->id, 'part_id' => $ctx['part']->id, 'name' => $ctx['part']->name, 'qty' => 3, 'unit' => 'buc', 'buy_price' => 25, 'sell_price' => 40, ]); $ctx['part']->refresh(); $this->assertEquals(3.0, (float) $ctx['part']->qty_reserved); $n = $this->svc->issueNow($wop); $this->assertGreaterThan(0, $n); $ctx['part']->refresh(); $this->assertEquals(7.0, (float) $ctx['part']->qty, 'on-hand decreased without closing WO'); $this->assertEquals(0.0, (float) $ctx['part']->qty_reserved, 'reservation no longer counted'); $r = PartReservation::where('work_order_part_id', $wop->id)->first(); $this->assertEquals('consumed', $r->status); } public function test_return_part_creates_positive_batch_and_event(): void { $ctx = $this->ctx(); $this->svc->receive($ctx['part'], 10, 30.0); $wop = WorkOrderPart::create([ 'work_order_id' => $ctx['wo']->id, 'part_id' => $ctx['part']->id, 'name' => $ctx['part']->name, 'qty' => 4, 'unit' => 'buc', 'buy_price' => 30, 'sell_price' => 50, ]); $this->svc->issueNow($wop); $ctx['part']->refresh(); $this->assertEquals(6.0, (float) $ctx['part']->qty); $batch = $this->svc->returnPart($wop, 2); $this->assertNotNull($batch); $this->assertEquals(2.0, (float) $batch->qty_in); $this->assertEquals(30.0, (float) $batch->buy_price); $ctx['part']->refresh(); $this->assertEquals(8.0, (float) $ctx['part']->qty, 'returned 2 restored to on-hand'); $this->assertEquals(1, WarehouseEvent::where('part_id', $ctx['part']->id) ->where('type', 'return') ->count()); } public function test_return_more_than_consumed_is_capped(): void { $ctx = $this->ctx(); $this->svc->receive($ctx['part'], 10, 10.0); $wop = WorkOrderPart::create([ 'work_order_id' => $ctx['wo']->id, 'part_id' => $ctx['part']->id, 'name' => $ctx['part']->name, 'qty' => 3, 'unit' => 'buc', 'buy_price' => 10, 'sell_price' => 15, ]); $this->svc->issueNow($wop); $batch = $this->svc->returnPart($wop, 99); $this->assertEquals(3.0, (float) $batch->qty_in, 'capped at consumed total'); } private function ctx(): array { $plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]); $company = Company::create([ 'plan_id' => $plan->id, 'slug' => 'ir-' . uniqid(), 'name' => 'IR Service', 'status' => 'active', ]); app(TenantManager::class)->setCurrent($company); $wh = Warehouse::create([ 'code' => 'MAIN', 'name' => 'Depozit', 'is_default' => true, 'is_active' => true, ]); $company->forceFill(['default_warehouse_id' => $wh->id])->saveQuietly(); $part = Part::create([ 'name' => 'Filtru aer', 'unit' => 'buc', 'qty' => 0, 'buy_price' => 0, 'sell_price' => 0, 'is_active' => true, ]); $client = Client::create([ 'name' => 'WO Client', 'phone' => '+37399' . random_int(100000, 999999), 'type' => 'individual', 'status' => 'active', ]); $vehicle = Vehicle::create([ 'client_id' => $client->id, 'make' => 'A', 'model' => 'B', 'plate' => 'IR' . random_int(100, 999), ]); $wo = WorkOrder::create([ 'number' => WorkOrder::generateNumber($company->id), 'client_id' => $client->id, 'vehicle_id' => $vehicle->id, 'opened_at' => now(), 'status' => 'in_work', ]); return compact('company', 'wh', 'part', 'client', 'vehicle', 'wo'); } }