Fix limited backoffice price save reset

This commit is contained in:
Jeppe Bundgaard
2026-07-06 17:13:04 +02:00
parent 742b15116d
commit 1da02e2486
2 changed files with 86 additions and 15 deletions
@@ -474,26 +474,26 @@ class limited_backoffice_service
$mysqli->begin_transaction();
try {
$priceUpdateAssignments = ['`price` = VALUES(`price`)'];
if ($this->tableHasColumn('product_department_prices', 'updated_at')) {
$priceUpdateAssignments[] = '`updated_at` = CURRENT_TIMESTAMP';
}
$statement = $mysqli->prepare(
'INSERT INTO `product_department_prices` (`department_id`, `product_id`, `price`)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE ' . implode(', ', $priceUpdateAssignments)
$deleteStatement = $mysqli->prepare(
'DELETE FROM `product_department_prices` WHERE `department_id` = ? AND `product_id` = ?'
);
if ($statement === false) {
$insertStatement = $mysqli->prepare(
'INSERT INTO `product_department_prices` (`department_id`, `product_id`, `price`) VALUES (?, ?, ?)'
);
if ($deleteStatement === false || $insertStatement === false) {
throw new \RuntimeException('Unable to prepare department price update.');
}
foreach ($normalizedPrices as $productId => $price) {
$statement->bind_param('iii', $departmentId, $productId, $price);
$statement->execute();
$deleteStatement->bind_param('ii', $departmentId, $productId);
$deleteStatement->execute();
$insertStatement->bind_param('iii', $departmentId, $productId, $price);
$insertStatement->execute();
}
$statement->close();
$deleteStatement->close();
$insertStatement->close();
$mysqli->commit();
} catch (\Throwable $throwable) {
$mysqli->rollback();
@@ -868,8 +868,14 @@ class limited_backoffice_service
INNER JOIN `categories` c ON c.`id` = dc.`category_id`
INNER JOIN `products` p ON p.`category` = dc.`category_id`
LEFT JOIN `product_department_prices` pdp
ON pdp.`department_id` = dc.`department_id`
AND pdp.`product_id` = p.`id`
ON pdp.`id` = (
SELECT pdp_latest.`id`
FROM `product_department_prices` pdp_latest
WHERE pdp_latest.`department_id` = dc.`department_id`
AND pdp_latest.`product_id` = p.`id`
ORDER BY pdp_latest.`id` DESC
LIMIT 1
)
WHERE ' . implode(' AND ', $where) . '
ORDER BY c.`name` ASC, c.`id` ASC, p.`order_priority` ASC, p.`name` ASC, p.`id` ASC'
);
@@ -75,6 +75,14 @@ function limited_backoffice_price_value(int $departmentId, int $productId): ?int
return $row === null ? null : (int)$row['price'];
}
function limited_backoffice_price_rows(int $departmentId, int $productId): array
{
return api_test_runtime()->db()->query(
'SELECT `id`, `price` FROM `product_department_prices` WHERE `department_id` = ' . $departmentId .
' AND `product_id` = ' . $productId . ' ORDER BY `id` ASC'
)->fetch_all(MYSQLI_ASSOC);
}
function limited_backoffice_cleanup_created_employee(int $employeeId): void
{
$row = api_test_runtime()->queryOne(
@@ -200,6 +208,63 @@ it('lists and updates explicit prices only for assigned departments', function (
expect(limited_backoffice_price_value((int)$department['id'], (int)$product['id']))->toBe(2222);
expect(limited_backoffice_price_value((int)$otherDepartment['id'], (int)$product['id']))->toBe(4321);
expect($updated->data()['categories'][0]['products'][0]['price'] ?? null)->toBe(2222);
});
it('returns saved prices and collapses legacy duplicate department price rows', function (): void {
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/prices', 'legacy duplicates');
$department = api_fixtures()->createDepartment(['name' => 'Limited Legacy Duplicate Prices']);
$category = api_fixtures()->createCategory(['name' => 'Limited Legacy Duplicate Category']);
$product = api_fixtures()->createProduct([
'name' => 'Legacy Duplicate Price',
'category' => $category['id'],
]);
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
$db = api_test_runtime()->db();
$index = $db->query("SHOW INDEX FROM `product_department_prices` WHERE `Key_name` = 'uniq_product_department_prices_lookup'");
if ($index === false) {
throw new RuntimeException('Unable to inspect product_department_prices lookup index.');
}
$hadIndex = (int)$index->num_rows > 0;
if ($hadIndex) {
$db->query('ALTER TABLE `product_department_prices` DROP INDEX `uniq_product_department_prices_lookup`');
}
try {
limited_backoffice_price_insert((int)$department['id'], (int)$product['id'], 111);
limited_backoffice_price_insert((int)$department['id'], (int)$product['id'], 222);
expect(limited_backoffice_price_rows((int)$department['id'], (int)$product['id']))->toHaveCount(2);
$session = limited_backoffice_manager_session([(int)$department['id']]);
$updated = api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
'prices' => [
['product_id' => (int)$product['id'], 'price' => 333],
],
], $session['headers']);
$updated
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$rows = limited_backoffice_price_rows((int)$department['id'], (int)$product['id']);
expect($rows)->toHaveCount(1);
expect((int)$rows[0]['price'])->toBe(333);
expect($updated->data()['categories'][0]['products'][0]['price'] ?? null)->toBe(333);
} finally {
$db->query(
'DELETE FROM `product_department_prices` WHERE `department_id` = ' . (int)$department['id'] .
' AND `product_id` = ' . (int)$product['id']
);
if ($hadIndex) {
$db->query(
'ALTER TABLE `product_department_prices`
ADD UNIQUE KEY `uniq_product_department_prices_lookup` (`department_id`, `product_id`)'
);
}
}
});
it('updates department prices when the price table has no updated_at column', function (): void {