fix(api): wire order_item_reason_policy into POST/PUT and persist reason fields (#345)

Adds `reason_code`, `reason_label_snapshot`, `reason_comment` columns to `order_items` and integrates the `order_item_reason_policy` class into the POST and PUT /order/items routes.

Validation order on audited products (consistent across POST and PUT):
1. If `reason_code` is present, validate reason first — emits the most specific error (invalid code, deprecated code, missing reason_comment).
2. If notes are provided but empty/whitespace, return "Notes is required for this product" (the legacy message).
3. Otherwise run reason validation — covers the missing-reason_code case.

PHP api suite went from 284/290 to 290/290 (was 6 OrderItemsApiTest failures, now 0). Wired `addItemToOrder`, `updateOrderItem`, and `getItemAsArray` to persist and return the new columns.
This commit is contained in:
Jeppe B
2026-08-09 17:53:00 +02:00
committed by GitHub
parent b107ba649c
commit 6475f817c7
8 changed files with 293 additions and 6 deletions
+33 -3
View File
@@ -60,6 +60,21 @@ class order_items_o extends db
* @var object_property
*/
public object_property $include_in_invoice;
/**
* Approved reason code (e.g. customer_approved_extra_work) for audited products
* @var object_property
*/
public object_property $reason_code;
/**
* Snapshot of the human-readable label for the reason code at write time
* @var object_property
*/
public object_property $reason_label_snapshot;
/**
* Free-text reason comment when the policy requires one
* @var object_property
*/
public object_property $reason_comment;
public function structure(): void
{
@@ -90,6 +105,9 @@ class order_items_o extends db
$this->quantity = new object_property($this->table, $this->id, 'quantity', 'int', true);
$this->related_item_id = new object_property($this->table, $this->id, 'related_item_id', 'int', false);
$this->include_in_invoice = new object_property($this->table, $this->id, 'include_in_invoice', 'bool', false);
$this->reason_code = new object_property($this->table, $this->id, 'reason_code', 'string', false);
$this->reason_label_snapshot = new object_property($this->table, $this->id, 'reason_label_snapshot', 'string', false);
$this->reason_comment = new object_property($this->table, $this->id, 'reason_comment', 'string', false);
}
private static function validatedRelatedItemId(int $orderId, mixed $relatedItemId): ?int
@@ -204,7 +222,7 @@ class order_items_o extends db
}
}
public function addItemToOrder(int $order_id, int $product_id, int $cashier_id, int $quantity, $related_item_id = null, $notes = null, $forcePrice = null): order_items_o
public function addItemToOrder(int $order_id, int $product_id, int $cashier_id, int $quantity, $related_item_id = null, $notes = null, $forcePrice = null, ?string $reason_code = null, ?string $reason_label_snapshot = null, ?string $reason_comment = null): order_items_o
{
global $db, $response;
try {
@@ -230,7 +248,7 @@ class order_items_o extends db
}
// Create a new record in the database
$sql = "INSERT INTO $this->table (order_id, product_id, price, cashier_id, quantity) VALUES ($order_id, $product_id, $price, $cashier_id, $quantity)";
$sql = "INSERT INTO $this->table (order_id, product_id, price, cashier_id, quantity, reason_code, reason_label_snapshot, reason_comment) VALUES ($order_id, $product_id, $price, $cashier_id, $quantity, " . ($reason_code !== null ? "'" . $db->escape_string($reason_code) . "'" : "NULL") . ", " . ($reason_label_snapshot !== null ? "'" . $db->escape_string($reason_label_snapshot) . "'" : "NULL") . ", " . ($reason_comment !== null ? "'" . $db->escape_string($reason_comment) . "'" : "NULL") . ")";
$db->query($sql);
// Get the id of the new record
$this->id = $db->insert_id();
@@ -288,6 +306,9 @@ class order_items_o extends db
'product' => (array)(new products_o())->getProductById($this->product_id->value())->asArray(),
'cashier' => (array)(new users_o())->getUserById($this->cashier_id->value())->asArray(),
'include_in_invoice' => (bool)$this->include_in_invoice->value(),
'reason_code' => $this->reason_code->value(),
'reason_label_snapshot' => $this->reason_label_snapshot->value(),
'reason_comment' => $this->reason_comment->value(),
];
}
@@ -314,7 +335,7 @@ class order_items_o extends db
/**
* @throws Exception
*/
public function updateOrderItem(int $id, int $price, string $notes, string $reference, int $quantity): void
public function updateOrderItem(int $id, int $price, string $notes, string $reference, int $quantity, ?string $reason_code = null, ?string $reason_label_snapshot = null, ?string $reason_comment = null): void
{
global $db, $response;
$this->select((int)$id);
@@ -332,6 +353,15 @@ class order_items_o extends db
$this->notes->set($notes);
$this->reference->set($reference);
$this->quantity->set($quantity);
if ($reason_code !== null) {
$this->reason_code->set($reason_code);
}
if ($reason_label_snapshot !== null) {
$this->reason_label_snapshot->set($reason_label_snapshot);
}
if ($reason_comment !== null) {
$this->reason_comment->set($reason_comment);
}
// Set the values of the object properties
$this->getObjectProperties();
$this->objectChanged();