queryOne('SELECT deleted_at FROM order_items WHERE id = ' . $orderItemId . ' LIMIT 1'); return $row['deleted_at'] ?? null; } function bulk_action_order_invoice_collection_id(int $orderId): int { $row = api_test_runtime()->queryOne('SELECT invoice_collection_id FROM orders WHERE id = ' . $orderId . ' LIMIT 1'); return (int)($row['invoice_collection_id'] ?? 0); } it('previews and applies customer rule cleanup only after exact typed confirmation', function (): void { api_test_covers('POST /collected-invoices/bulk-actions/preview', 'customer-rule-cleanup'); api_test_covers('POST /collected-invoices/bulk-actions/apply', 'customer-rule-cleanup'); $customer = api_fixtures()->createUser(['display_name' => 'Bulk Cleanup Customer']); api_fixtures()->addCustomerAttribute((int)$customer['id'], 'restrictSpotFree'); $department = api_fixtures()->createDepartment(); $invoiceCollection = api_fixtures()->createInvoiceCollection([ 'customer_number' => $customer['customer_number'], ]); $order = api_fixtures()->createOrder([ 'customer_id' => $customer['customer_number'], 'department_id' => $department['id'], 'invoice_collection_id' => $invoiceCollection['id'], ]); $product = api_fixtures()->createProduct([ 'name' => 'Spot Free rinse', 'price' => 80, ]); $orderItem = api_fixtures()->createOrderItem([ 'order_id' => $order['id'], 'product_id' => $product['id'], 'cashier_id' => 1, 'price' => 80, ]); $session = api_fixtures()->createUserSession(['reset_collected_invoice_economic']); $previewResponse = api_client()->post('/collected-invoices/bulk-actions/preview', [ 'action' => 'remove_customer_rule_violations', 'invoice_collection_ids' => [$invoiceCollection['id']], 'locale' => 'da', ], $session['headers']); $previewResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $preview = $previewResponse->data(); expect($preview['preview_id'] ?? null)->toBeString() ->and($preview['confirmation_phrase'] ?? null)->toBe('Bekræft') ->and($preview['summary']['changed_count'] ?? null)->toBe(1) ->and($preview['order_items'][0]['order_item_id'] ?? null)->toBe((int)$orderItem['id']) ->and(bulk_action_order_item_deleted_at((int)$orderItem['id']))->toBeNull(); api_client()->post('/collected-invoices/bulk-actions/apply', [ 'preview_id' => $preview['preview_id'], 'action' => 'remove_customer_rule_violations', 'invoice_collection_ids' => [$invoiceCollection['id']], 'confirmation_text' => 'Bekraeft', 'locale' => 'da', ], $session['headers']) ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false); expect(bulk_action_order_item_deleted_at((int)$orderItem['id']))->toBeNull(); $applyResponse = api_client()->post('/collected-invoices/bulk-actions/apply', [ 'preview_id' => $preview['preview_id'], 'action' => 'remove_customer_rule_violations', 'invoice_collection_ids' => [$invoiceCollection['id']], 'confirmation_text' => 'Bekræft', 'locale' => 'da', ], $session['headers']); $applyResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $applied = $applyResponse->data(); expect($applied['preview'] ?? null)->toBeFalse() ->and($applied['result']['changed_count'] ?? null)->toBe(1) ->and(bulk_action_order_item_deleted_at((int)$orderItem['id']))->not->toBeNull(); }); it('previews and applies customer rule cleanup for both spotfree addon products', function (): void { api_test_covers('POST /collected-invoices/bulk-actions/preview', 'customer-rule-cleanup-spotfree-addons'); api_test_covers('POST /collected-invoices/bulk-actions/apply', 'customer-rule-cleanup-spotfree-addons'); $customer = api_fixtures()->createUser(['display_name' => 'Bulk Cleanup Spotfree Addons Customer']); api_fixtures()->addCustomerAttribute((int)$customer['id'], 'restrictSpotFree'); $department = api_fixtures()->createDepartment(); $invoiceCollection = api_fixtures()->createInvoiceCollection([ 'customer_number' => $customer['customer_number'], ]); $order = api_fixtures()->createOrder([ 'customer_id' => $customer['customer_number'], 'department_id' => $department['id'], 'invoice_collection_id' => $invoiceCollection['id'], ]); $spotfreeVanProduct = api_fixtures()->createProduct([ 'id' => 23, 'name' => 'Skylning med RO - Varevogn', 'category' => 4, 'price' => 39, ]); $spotfreeTruckProduct = api_fixtures()->createProduct([ 'id' => 24, 'name' => 'Skylning med RO - Lastbil', 'category' => 4, 'price' => 39, ]); $vanOrderItem = api_fixtures()->createOrderItem([ 'order_id' => $order['id'], 'product_id' => $spotfreeVanProduct['id'], 'cashier_id' => 1, 'price' => 39, ]); $truckOrderItem = api_fixtures()->createOrderItem([ 'order_id' => $order['id'], 'product_id' => $spotfreeTruckProduct['id'], 'cashier_id' => 1, 'price' => 39, ]); $session = api_fixtures()->createUserSession(['reset_collected_invoice_economic']); $previewResponse = api_client()->post('/collected-invoices/bulk-actions/preview', [ 'action' => 'remove_customer_rule_violations', 'invoice_collection_ids' => [$invoiceCollection['id']], 'locale' => 'en', ], $session['headers']); $previewResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $preview = $previewResponse->data(); $previewOrderItemIds = array_map('intval', array_column($preview['order_items'] ?? [], 'order_item_id')); sort($previewOrderItemIds); expect($preview['summary']['changed_count'] ?? null)->toBe(2) ->and($previewOrderItemIds)->toBe([(int)$vanOrderItem['id'], (int)$truckOrderItem['id']]) ->and(bulk_action_order_item_deleted_at((int)$vanOrderItem['id']))->toBeNull() ->and(bulk_action_order_item_deleted_at((int)$truckOrderItem['id']))->toBeNull(); $applyResponse = api_client()->post('/collected-invoices/bulk-actions/apply', [ 'preview_id' => $preview['preview_id'], 'action' => 'remove_customer_rule_violations', 'invoice_collection_ids' => [$invoiceCollection['id']], 'confirmation_text' => 'Confirm', 'locale' => 'en', ], $session['headers']); $applyResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect(bulk_action_order_item_deleted_at((int)$vanOrderItem['id']))->not->toBeNull() ->and(bulk_action_order_item_deleted_at((int)$truckOrderItem['id']))->not->toBeNull(); }); it('merges selected invoice collections into the explicit target after confirmation', function (): void { api_test_covers('POST /collected-invoices/bulk-actions/preview', 'merge'); api_test_covers('POST /collected-invoices/bulk-actions/apply', 'merge'); $customer = api_fixtures()->createUser(['display_name' => 'Bulk Merge Customer']); $department = api_fixtures()->createDepartment(); $targetCollection = api_fixtures()->createInvoiceCollection([ 'customer_number' => $customer['customer_number'], ]); $sourceCollection = api_fixtures()->createInvoiceCollection([ 'customer_number' => $customer['customer_number'], ]); $targetOrder = api_fixtures()->createOrder([ 'customer_id' => $customer['customer_number'], 'department_id' => $department['id'], 'invoice_collection_id' => $targetCollection['id'], ]); $sourceOrder = api_fixtures()->createOrder([ 'customer_id' => $customer['customer_number'], 'department_id' => $department['id'], 'invoice_collection_id' => $sourceCollection['id'], ]); $session = api_fixtures()->createUserSession(['move_collected_invoice']); $previewResponse = api_client()->post('/collected-invoices/bulk-actions/preview', [ 'action' => 'merge_collections', 'invoice_collection_ids' => [$sourceCollection['id'], $targetCollection['id']], 'options' => ['target_invoice_collection_id' => $targetCollection['id']], 'locale' => 'en', ], $session['headers']); $previewResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $preview = $previewResponse->data(); expect($preview['confirmation_phrase'] ?? null)->toBe('Confirm') ->and($preview['target_invoice_collection_id'] ?? null)->toBe((int)$targetCollection['id']) ->and($preview['summary']['orders_to_move'] ?? null)->toBe(1) ->and(bulk_action_order_invoice_collection_id((int)$sourceOrder['id']))->toBe((int)$sourceCollection['id']); $applyResponse = api_client()->post('/collected-invoices/bulk-actions/apply', [ 'preview_id' => $preview['preview_id'], 'action' => 'merge_collections', 'invoice_collection_ids' => [$sourceCollection['id'], $targetCollection['id']], 'options' => ['target_invoice_collection_id' => $targetCollection['id']], 'confirmation_text' => 'Confirm', 'locale' => 'en', ], $session['headers']); $applyResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect(bulk_action_order_invoice_collection_id((int)$sourceOrder['id']))->toBe((int)$targetCollection['id']) ->and(bulk_action_order_invoice_collection_id((int)$targetOrder['id']))->toBe((int)$targetCollection['id']); });