bootstrap(); $wo = $this->makeWo($ctx, discountPct: 10); // Labor: 2h × 400 = 800 WorkOrderWork::create([ 'work_order_id' => $wo->id, 'name' => 'Manoperă', 'hours' => 2, 'price_per_hour' => 400, 'status' => 'todo', ]); // Part: 3 × 150 = 450 $part = Part::create([ 'name' => 'Filtru', 'sell_price' => 150, 'buy_price' => 100, 'qty' => 10, 'unit' => 'buc', 'is_active' => true, ]); WorkOrderPart::create([ 'work_order_id' => $wo->id, 'part_id' => $part->id, 'name' => $part->name, 'qty' => 3, 'buy_price' => 100, 'sell_price' => 150, 'status' => 'needed', ]); // Subcontract: cost 500 × 1.20 markup = 600 client price SubcontractJob::create([ 'work_order_id' => $wo->id, 'category' => 'Turbo', 'cost' => 500, 'markup_pct' => 20, 'status' => 'sent', ]); $wo->refresh(); // Subtotal: 800 + 450 + 600 = 1850. With 10% discount → 1665. $this->assertEquals(1665.0, (float) $wo->total); } public function test_cancelled_subcontract_excluded_from_total(): void { $ctx = $this->bootstrap(); $wo = $this->makeWo($ctx); $sub = SubcontractJob::create([ 'work_order_id' => $wo->id, 'cost' => 1000, 'markup_pct' => 50, 'status' => 'sent', ]); $wo->refresh(); $this->assertEquals(1500.0, (float) $wo->total); $sub->update(['status' => 'cancelled']); $wo->refresh(); $this->assertEquals(0.0, (float) $wo->total); } public function test_deleting_any_line_type_recalcs_total(): void { $ctx = $this->bootstrap(); $wo = $this->makeWo($ctx); $w = WorkOrderWork::create([ 'work_order_id' => $wo->id, 'name' => 'X', 'hours' => 1, 'price_per_hour' => 100, 'status' => 'todo', ]); $wo->refresh(); $this->assertEquals(100.0, (float) $wo->total); $w->delete(); $wo->refresh(); $this->assertEquals(0.0, (float) $wo->total); } public function test_status_done_consumes_part_reservations(): void { $ctx = $this->bootstrap(); $svc = app(WarehouseService::class); // Stock the warehouse with one batch of 10. $part = Part::create([ 'name' => 'Plăcuțe', 'sell_price' => 200, 'buy_price' => 100, 'qty' => 0, 'unit' => 'buc', 'is_active' => true, ]); $svc->receive($part, 10, 100); $this->assertEquals(10.0, (float) $part->fresh()->qty); $wo = $this->makeWo($ctx); WorkOrderPart::create([ 'work_order_id' => $wo->id, 'part_id' => $part->id, 'name' => $part->name, 'qty' => 4, 'buy_price' => 100, 'sell_price' => 200, 'status' => 'needed', ]); // Before close: stock still 10, qty_reserved 4. $part->refresh(); $this->assertEquals(10.0, (float) $part->qty); $this->assertEquals(4.0, (float) $part->qty_reserved); // Close WO → reservations become consume events. $wo->update(['status' => 'done']); $part->refresh(); $this->assertEquals(6.0, (float) $part->qty); $this->assertEquals(0.0, (float) $part->qty_reserved); } public function test_cancelled_wo_releases_reservations(): void { $ctx = $this->bootstrap(); $svc = app(WarehouseService::class); $part = Part::create([ 'name' => 'X', 'sell_price' => 10, 'buy_price' => 5, 'qty' => 0, 'unit' => 'buc', 'is_active' => true, ]); $svc->receive($part, 5, 5); $wo = $this->makeWo($ctx); WorkOrderPart::create([ 'work_order_id' => $wo->id, 'part_id' => $part->id, 'name' => 'X', 'qty' => 2, 'buy_price' => 5, 'sell_price' => 10, 'status' => 'needed', ]); $this->assertEquals(2.0, (float) $part->fresh()->qty_reserved); $wo->update(['status' => 'cancelled']); $this->assertEquals(0.0, (float) $part->fresh()->qty_reserved); $this->assertEquals(5.0, (float) $part->fresh()->qty, 'stock untouched after cancel'); } private function bootstrap(): array { $plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]); $company = Company::create([ 'plan_id' => $plan->id, 'slug' => 'wot-' . uniqid(), 'name' => 'WO Totals', 'status' => 'active', ]); app(TenantManager::class)->setCurrent($company); // Ensure warehouse exists so WarehouseService::receive works. $wh = Warehouse::create([ 'code' => 'MAIN', 'name' => 'Main', 'is_default' => true, 'is_active' => true, ]); $company->forceFill(['default_warehouse_id' => $wh->id])->saveQuietly(); return compact('company'); } private function makeWo(array $ctx, float $discountPct = 0): WorkOrder { $client = Client::create([ 'name' => 'C', 'phone' => '+3736' . random_int(1000000, 9999999), 'type' => 'individual', 'status' => 'active', ]); $vehicle = Vehicle::create([ 'client_id' => $client->id, 'make' => 'X', 'model' => 'Y', 'plate' => 'WO' . random_int(100, 999), ]); return WorkOrder::create([ 'number' => WorkOrder::generateNumber($ctx['company']->id), 'client_id' => $client->id, 'vehicle_id' => $vehicle->id, 'opened_at' => today(), 'status' => 'in_work', 'discount_pct' => $discountPct, ]); } }