id = -1; $this->booking_id = new object_property('orders', -1, 'booking_id', 'int', false); $this->customer_id = new object_property('orders', -1, 'customer_id', 'int', true); $this->department_id = new object_property('orders', -1, 'department_id', 'int', true); $this->safety_seal = new object_property('orders', -1, 'safety_seal', 'string', false); $this->completed_at = new object_property('orders', -1, 'completed_at', 'timestamp', false); $this->customer_id->set(111111); $this->department_id->set(10); } public bool $washCertificateAttached = false; public bool $containsWashCertificate = false; public function objectChanged(): void { } public function containsWashCertificateItem(): bool { return $this->containsWashCertificate; } public function hasWashCertificateAttached(): bool { return $this->washCertificateAttached; } } } if (!class_exists('OrderBookingsCompletionDouble')) { class OrderBookingsCompletionDouble extends order_bookings_o { public bool $containsWashCertificate = false; public bool $orderWasCreated = false; public bool $orderItemsWereCreated = false; public int $attachCalls = 0; public int $sendCalls = 0; public orders_o $linkedOrder; public function __construct(orders_o $linkedOrder) { $this->id = -1; $this->linkedOrder = $linkedOrder; $this->customer_number = new object_property('order_bookings', -1, 'customer_number', 'int', true); $this->department = new object_property('order_bookings', -1, 'department', 'int', true); $this->order_id = new object_property('order_bookings', -1, 'order_id', 'int', false); $this->items = new object_property('order_bookings', -1, 'items', 'json', false); $this->customer_number->set(111111); $this->department->set(10); $this->items->set([]); } public function createOrderBy(int $user_id): void { $this->orderWasCreated = true; $this->order_id->set(999); } public function createOrderItemsBy(int $user_id): void { $this->orderItemsWereCreated = true; } public function containsWashCertificateItem(): bool { return $this->containsWashCertificate; } public function getOrder(): orders_o { return $this->linkedOrder; } protected function attachWashCertificate(int $user_id, ?string $safety_seal = null): void { $this->attachCalls++; $this->linkedOrder->washCertificateAttached = true; } public function sendWashCertificateToCustomer(): void { $this->sendCalls++; } } } if (!class_exists('OrderBookingsCompletionRefreshDouble')) { class OrderBookingsCompletionRefreshDouble extends OrderBookingsCompletionDouble { public orders_o $refreshedOrder; public function __construct(orders_o $initialOrder, orders_o $refreshedOrder) { parent::__construct($initialOrder); $this->refreshedOrder = $refreshedOrder; } public function getOrder(): orders_o { return $this->attachCalls > 0 ? $this->refreshedOrder : $this->linkedOrder; } protected function attachWashCertificate(int $user_id, ?string $safety_seal = null): void { $this->attachCalls++; $this->refreshedOrder->washCertificateAttached = true; $this->refreshedOrder->safety_seal->set($safety_seal); } } } it('attaches and emails a wash certificate when a booking is already linked to a matching pos order without one', function (): void { $order = new OrderBookingsCompletionOrderDouble(); $booking = new OrderBookingsCompletionDouble($order); $booking->order_id->set(321); $booking->containsWashCertificate = true; $booking->completeBooking(77, 'LINKED-SEAL'); expect($order->getSafetySealValue())->toBe('LINKED-SEAL'); expect($booking->attachCalls)->toBe(1); expect($booking->sendCalls)->toBe(1); }); it('reloads the linked order before deciding whether to email a newly attached wash certificate', function (): void { $initialOrder = new OrderBookingsCompletionOrderDouble(); $refreshedOrder = new OrderBookingsCompletionOrderDouble(); $booking = new OrderBookingsCompletionRefreshDouble($initialOrder, $refreshedOrder); $booking->order_id->set(321); $booking->containsWashCertificate = true; $booking->completeBooking(77, 'REFRESH-SEAL'); expect($initialOrder->washCertificateAttached)->toBeFalse(); expect($refreshedOrder->washCertificateAttached)->toBeTrue(); expect($refreshedOrder->getSafetySealValue())->toBe('REFRESH-SEAL'); expect($booking->attachCalls)->toBe(1); expect($booking->sendCalls)->toBe(1); }); it('rejects wash certificate completion when the linked pos order belongs to another booking context', function (): void { $order = new OrderBookingsCompletionOrderDouble(); $order->customer_id->set(222222); $order->department_id->set(99); $booking = new OrderBookingsCompletionDouble($order); $booking->order_id->set(321); $booking->containsWashCertificate = true; expect(fn() => $booking->completeBooking(77, 'LINKED-SEAL')) ->toThrow(Exception::class, 'Linked order does not match booking customer or department'); expect($order->getSafetySealValue())->toBeNull(); expect($booking->attachCalls)->toBe(0); expect($booking->sendCalls)->toBe(0); }); it('uses the linked pos order wash certificate item added during mobile completion', function (): void { $order = new OrderBookingsCompletionOrderDouble(); $order->containsWashCertificate = true; $booking = new OrderBookingsCompletionDouble($order); $booking->order_id->set(321); $booking->containsWashCertificate = false; $booking->completeBooking(77, 'MOBILE-SEAL'); expect($order->getSafetySealValue())->toBe('MOBILE-SEAL'); expect($booking->attachCalls)->toBe(1); expect($booking->sendCalls)->toBe(1); }); it('does not create or email a duplicate wash certificate when a linked pos order already has one', function (): void { $order = new OrderBookingsCompletionOrderDouble(); $order->washCertificateAttached = true; $booking = new OrderBookingsCompletionDouble($order); $booking->order_id->set(321); $booking->containsWashCertificate = true; $booking->completeBooking(77, 'LINKED-SEAL'); expect($order->getSafetySealValue())->toBe('LINKED-SEAL'); expect($booking->attachCalls)->toBe(0); expect($booking->sendCalls)->toBe(0); }); it('keeps standalone booking completion behavior unchanged for wash certificates', function (): void { $order = new OrderBookingsCompletionOrderDouble(); $booking = new OrderBookingsCompletionDouble($order); $booking->containsWashCertificate = true; $booking->completeBooking(77, null); expect($booking->orderWasCreated)->toBeTrue(); expect($booking->orderItemsWereCreated)->toBeTrue(); expect($booking->attachCalls)->toBe(1); expect($booking->sendCalls)->toBe(1); });