Automate inventory allocation during order import with idempotent rollback
This commit is contained in:
533
order-import.php
533
order-import.php
@@ -430,6 +430,456 @@ function upsert_addresses(PDO $pdo, int $partyId, array $data): void
|
||||
]);
|
||||
}
|
||||
|
||||
function normalize_title_key(string $value): string
|
||||
{
|
||||
$value = trim(strtolower($value));
|
||||
$value = preg_replace('/\s+/', ' ', $value) ?? $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
function find_existing_order_id(PDO $pdo, string $externalRef): ?int
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT id FROM public.sales_order WHERE external_ref = :external_ref LIMIT 1');
|
||||
$stmt->execute([':external_ref' => $externalRef]);
|
||||
$id = $stmt->fetchColumn();
|
||||
return $id === false ? null : (int) $id;
|
||||
}
|
||||
|
||||
function get_default_location_ids(PDO $pdo): array
|
||||
{
|
||||
$storageId = $pdo->query("SELECT id FROM public.location WHERE type = 'storage' ORDER BY id LIMIT 1")->fetchColumn();
|
||||
$dispatchId = $pdo->query("SELECT id FROM public.location WHERE type = 'dispatch' ORDER BY id LIMIT 1")->fetchColumn();
|
||||
|
||||
if ($storageId === false || $dispatchId === false) {
|
||||
$warehouseId = $pdo->query('SELECT id FROM public.warehouse ORDER BY id LIMIT 1')->fetchColumn();
|
||||
if ($warehouseId === false) {
|
||||
$createWarehouse = $pdo->prepare(
|
||||
"INSERT INTO public.warehouse (code, name, created_at, updated_at)
|
||||
VALUES ('MAIN', 'Main Warehouse', NOW(), NOW())
|
||||
RETURNING id"
|
||||
);
|
||||
$createWarehouse->execute();
|
||||
$warehouseId = $createWarehouse->fetchColumn();
|
||||
}
|
||||
$warehouseId = (int) $warehouseId;
|
||||
|
||||
if ($storageId === false) {
|
||||
$existingStorage = $pdo->prepare(
|
||||
"SELECT id FROM public.location
|
||||
WHERE warehouse_id = :warehouse_id AND type = 'storage'
|
||||
ORDER BY id LIMIT 1"
|
||||
);
|
||||
$existingStorage->execute([':warehouse_id' => $warehouseId]);
|
||||
$storageId = $existingStorage->fetchColumn();
|
||||
if ($storageId === false) {
|
||||
$insertStorage = $pdo->prepare(
|
||||
"INSERT INTO public.location (warehouse_id, code, name, type, created_at, updated_at)
|
||||
VALUES (:warehouse_id, 'STORAGE', 'Storage', 'storage', NOW(), NOW())
|
||||
RETURNING id"
|
||||
);
|
||||
$insertStorage->execute([':warehouse_id' => $warehouseId]);
|
||||
$storageId = $insertStorage->fetchColumn();
|
||||
}
|
||||
}
|
||||
|
||||
if ($dispatchId === false) {
|
||||
$existingDispatch = $pdo->prepare(
|
||||
"SELECT id FROM public.location
|
||||
WHERE warehouse_id = :warehouse_id AND type = 'dispatch'
|
||||
ORDER BY id LIMIT 1"
|
||||
);
|
||||
$existingDispatch->execute([':warehouse_id' => $warehouseId]);
|
||||
$dispatchId = $existingDispatch->fetchColumn();
|
||||
if ($dispatchId === false) {
|
||||
$insertDispatch = $pdo->prepare(
|
||||
"INSERT INTO public.location (warehouse_id, code, name, type, created_at, updated_at)
|
||||
VALUES (:warehouse_id, 'DISPATCH', 'Dispatch', 'dispatch', NOW(), NOW())
|
||||
RETURNING id"
|
||||
);
|
||||
$insertDispatch->execute([':warehouse_id' => $warehouseId]);
|
||||
$dispatchId = $insertDispatch->fetchColumn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($storageId === false || $dispatchId === false) {
|
||||
throw new RuntimeException('Missing required locations after auto-bootstrap');
|
||||
}
|
||||
|
||||
return [
|
||||
'storage' => (int) $storageId,
|
||||
'dispatch' => (int) $dispatchId,
|
||||
];
|
||||
}
|
||||
|
||||
function resolve_sellable_item_id(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
$articleNumber = trim($articleNumber);
|
||||
$title = trim($title);
|
||||
$titleNorm = normalize_title_key($title);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT sellable_item_id
|
||||
FROM public.external_item_alias
|
||||
WHERE source_system = 'wix'
|
||||
AND is_active = TRUE
|
||||
AND (
|
||||
(:article_number <> '' AND external_article_number = :article_number)
|
||||
OR (:title_norm <> '' AND title_normalized = :title_norm)
|
||||
OR (:title <> '' AND lower(external_title) = lower(:title))
|
||||
)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN :article_number <> '' AND external_article_number = :article_number THEN 0
|
||||
WHEN :title_norm <> '' AND title_normalized = :title_norm THEN 1
|
||||
ELSE 2
|
||||
END,
|
||||
id
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->execute([
|
||||
':article_number' => $articleNumber,
|
||||
':title_norm' => $titleNorm,
|
||||
':title' => $title,
|
||||
]);
|
||||
$id = $stmt->fetchColumn();
|
||||
|
||||
return $id === false ? null : (int) $id;
|
||||
}
|
||||
|
||||
function get_item_components(PDO $pdo, int $sellableItemId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT product_id, qty_per_item
|
||||
FROM public.sellable_item_component
|
||||
WHERE sellable_item_id = :sellable_item_id
|
||||
ORDER BY id'
|
||||
);
|
||||
$stmt->execute([':sellable_item_id' => $sellableItemId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function resolve_product_id_fallback(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
$articleNumber = trim($articleNumber);
|
||||
$title = trim($title);
|
||||
|
||||
if ($articleNumber !== '') {
|
||||
$stmt = $pdo->prepare('SELECT id FROM public.product WHERE sku = :sku ORDER BY id LIMIT 1');
|
||||
$stmt->execute([':sku' => $articleNumber]);
|
||||
$id = $stmt->fetchColumn();
|
||||
if ($id !== false) {
|
||||
return (int) $id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($title !== '') {
|
||||
$stmt = $pdo->prepare('SELECT id FROM public.product WHERE lower(name) = lower(:name) ORDER BY id LIMIT 1');
|
||||
$stmt->execute([':name' => $title]);
|
||||
$id = $stmt->fetchColumn();
|
||||
if ($id !== false) {
|
||||
return (int) $id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
|
||||
{
|
||||
$lotStmt = $pdo->prepare(
|
||||
"SELECT id
|
||||
FROM public.stock_lot
|
||||
WHERE product_id = :product_id
|
||||
AND status = 'current'
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
FOR UPDATE"
|
||||
);
|
||||
$lotStmt->execute([':product_id' => $productId]);
|
||||
$lotId = $lotStmt->fetchColumn();
|
||||
if ($lotId === false) {
|
||||
throw new RuntimeException("No current lot found for product {$productId}");
|
||||
}
|
||||
|
||||
$balStmt = $pdo->prepare('SELECT qty_net FROM public.v_stock_lot_balance WHERE stock_lot_id = :lot_id');
|
||||
$balStmt->execute([':lot_id' => (int) $lotId]);
|
||||
$qtyNet = $balStmt->fetchColumn();
|
||||
|
||||
return [
|
||||
'lot_id' => (int) $lotId,
|
||||
'qty_net' => $qtyNet === false ? 0.0 : (float) $qtyNet,
|
||||
];
|
||||
}
|
||||
|
||||
function switch_current_lot(PDO $pdo, int $productId, int $oldCurrentLotId): int
|
||||
{
|
||||
$closeStmt = $pdo->prepare(
|
||||
"UPDATE public.stock_lot
|
||||
SET status = 'closed', updated_at = NOW()
|
||||
WHERE id = :id AND status = 'current'"
|
||||
);
|
||||
$closeStmt->execute([':id' => $oldCurrentLotId]);
|
||||
|
||||
$openStmt = $pdo->prepare(
|
||||
"SELECT id
|
||||
FROM public.stock_lot
|
||||
WHERE product_id = :product_id
|
||||
AND status = 'open'
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
FOR UPDATE"
|
||||
);
|
||||
$openStmt->execute([':product_id' => $productId]);
|
||||
$newCurrentLotId = $openStmt->fetchColumn();
|
||||
if ($newCurrentLotId === false) {
|
||||
throw new RuntimeException("No open lot available for product {$productId} during switch");
|
||||
}
|
||||
|
||||
$makeCurrentStmt = $pdo->prepare(
|
||||
"UPDATE public.stock_lot
|
||||
SET status = 'current', updated_at = NOW()
|
||||
WHERE id = :id"
|
||||
);
|
||||
$makeCurrentStmt->execute([':id' => (int) $newCurrentLotId]);
|
||||
|
||||
$createOpenStmt = $pdo->prepare(
|
||||
"INSERT INTO public.stock_lot (product_id, lot_number, status, created_at, updated_at)
|
||||
VALUES (:product_id, NULL, 'open', NOW(), NOW())"
|
||||
);
|
||||
$createOpenStmt->execute([':product_id' => $productId]);
|
||||
|
||||
return (int) $newCurrentLotId;
|
||||
}
|
||||
|
||||
function insert_stock_move_out(
|
||||
PDO $pdo,
|
||||
int $productId,
|
||||
int $lotId,
|
||||
float $qty,
|
||||
int $fromLocationId,
|
||||
int $toLocationId,
|
||||
string $note
|
||||
): int {
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO public.stock_move (
|
||||
product_id, lot_id, from_location_id, to_location_id, qty, move_type, note, move_date, created_at, updated_at
|
||||
) VALUES (
|
||||
:product_id, :lot_id, :from_location_id, :to_location_id, :qty, 'out', :note, NOW(), NOW(), NOW()
|
||||
)
|
||||
RETURNING id"
|
||||
);
|
||||
$stmt->execute([
|
||||
':product_id' => $productId,
|
||||
':lot_id' => $lotId,
|
||||
':from_location_id' => $fromLocationId,
|
||||
':to_location_id' => $toLocationId,
|
||||
':qty' => $qty,
|
||||
':note' => $note,
|
||||
]);
|
||||
$id = $stmt->fetchColumn();
|
||||
if ($id === false) {
|
||||
throw new RuntimeException('Could not create stock_move out');
|
||||
}
|
||||
return (int) $id;
|
||||
}
|
||||
|
||||
function insert_stock_move_in(
|
||||
PDO $pdo,
|
||||
int $productId,
|
||||
int $lotId,
|
||||
float $qty,
|
||||
int $toLocationId,
|
||||
string $note
|
||||
): int {
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO public.stock_move (
|
||||
product_id, lot_id, from_location_id, to_location_id, qty, move_type, note, move_date, created_at, updated_at
|
||||
) VALUES (
|
||||
:product_id, :lot_id, NULL, :to_location_id, :qty, 'in', :note, NOW(), NOW(), NOW()
|
||||
)
|
||||
RETURNING id"
|
||||
);
|
||||
$stmt->execute([
|
||||
':product_id' => $productId,
|
||||
':lot_id' => $lotId,
|
||||
':to_location_id' => $toLocationId,
|
||||
':qty' => $qty,
|
||||
':note' => $note,
|
||||
]);
|
||||
$id = $stmt->fetchColumn();
|
||||
if ($id === false) {
|
||||
throw new RuntimeException('Could not create stock_move in');
|
||||
}
|
||||
return (int) $id;
|
||||
}
|
||||
|
||||
function reverse_existing_allocations_for_order(PDO $pdo, int $orderId, int $fallbackStorageLocationId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT
|
||||
a.id AS allocation_id,
|
||||
a.product_id,
|
||||
a.lot_id,
|
||||
a.qty,
|
||||
a.stock_move_id,
|
||||
sm.from_location_id
|
||||
FROM public.sales_order_line sol
|
||||
JOIN public.sales_order_line_lot_allocation a ON a.sales_order_line_id = sol.id
|
||||
LEFT JOIN public.stock_move sm ON sm.id = a.stock_move_id
|
||||
WHERE sol.sales_order_id = :order_id
|
||||
AND a.stock_move_id IS NOT NULL"
|
||||
);
|
||||
$stmt->execute([':order_id' => $orderId]);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$reversedMoves = 0;
|
||||
$reversedQty = 0.0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$qty = (float) $row['qty'];
|
||||
if ($qty <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$toLocationId = $row['from_location_id'] !== null
|
||||
? (int) $row['from_location_id']
|
||||
: $fallbackStorageLocationId;
|
||||
|
||||
insert_stock_move_in(
|
||||
$pdo,
|
||||
(int) $row['product_id'],
|
||||
(int) $row['lot_id'],
|
||||
$qty,
|
||||
$toLocationId,
|
||||
"order-import-reverse:order={$orderId}:alloc=" . (int) $row['allocation_id']
|
||||
);
|
||||
|
||||
$reversedMoves++;
|
||||
$reversedQty += $qty;
|
||||
}
|
||||
|
||||
return [
|
||||
'reversedMoves' => $reversedMoves,
|
||||
'reversedQty' => round($reversedQty, 4),
|
||||
];
|
||||
}
|
||||
|
||||
function allocate_components_for_line(
|
||||
PDO $pdo,
|
||||
int $orderId,
|
||||
int $lineId,
|
||||
int $lineNo,
|
||||
array $components,
|
||||
float $lineQty,
|
||||
array $locations
|
||||
): array {
|
||||
if ($components === []) {
|
||||
return [
|
||||
'allocated' => false,
|
||||
'reason' => 'no_components',
|
||||
'allocations' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$allocationInsert = $pdo->prepare(
|
||||
"INSERT INTO public.sales_order_line_lot_allocation (
|
||||
sales_order_line_id, product_id, lot_id, qty, allocation_status, stock_move_id, created_at, updated_at
|
||||
) VALUES (
|
||||
:sales_order_line_id, :product_id, :lot_id, :qty, 'allocated', :stock_move_id, NOW(), NOW()
|
||||
)"
|
||||
);
|
||||
|
||||
$allocations = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
$productId = (int) $component['product_id'];
|
||||
$required = $lineQty * (float) $component['qty_per_item'];
|
||||
$remaining = $required;
|
||||
$guard = 0;
|
||||
|
||||
while ($remaining > 0.0000001) {
|
||||
$guard++;
|
||||
if ($guard > 100) {
|
||||
throw new RuntimeException("Inventory allocation loop exceeded guard for product {$productId}");
|
||||
}
|
||||
|
||||
$current = get_current_lot_balance_for_update($pdo, $productId);
|
||||
$lotId = (int) $current['lot_id'];
|
||||
$available = (float) $current['qty_net'];
|
||||
|
||||
if ($available <= 0.0000001) {
|
||||
switch_current_lot($pdo, $productId, $lotId);
|
||||
continue;
|
||||
}
|
||||
|
||||
$take = min($remaining, $available);
|
||||
$note = "order-import:order={$orderId}:line={$lineNo}:product={$productId}";
|
||||
$stockMoveId = insert_stock_move_out(
|
||||
$pdo,
|
||||
$productId,
|
||||
$lotId,
|
||||
$take,
|
||||
$locations['storage'],
|
||||
$locations['dispatch'],
|
||||
$note
|
||||
);
|
||||
|
||||
$allocationInsert->execute([
|
||||
':sales_order_line_id' => $lineId,
|
||||
':product_id' => $productId,
|
||||
':lot_id' => $lotId,
|
||||
':qty' => $take,
|
||||
':stock_move_id' => $stockMoveId,
|
||||
]);
|
||||
|
||||
$allocations[] = [
|
||||
'productId' => $productId,
|
||||
'lotId' => $lotId,
|
||||
'qty' => round($take, 4),
|
||||
'stockMoveId' => $stockMoveId,
|
||||
];
|
||||
|
||||
$remaining -= $take;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'allocated' => true,
|
||||
'reason' => '',
|
||||
'allocations' => $allocations,
|
||||
];
|
||||
}
|
||||
|
||||
function allocate_line_inventory(
|
||||
PDO $pdo,
|
||||
int $orderId,
|
||||
int $lineId,
|
||||
int $lineNo,
|
||||
float $lineQty,
|
||||
int $sellableItemId,
|
||||
array $locations
|
||||
): array {
|
||||
$components = get_item_components($pdo, $sellableItemId);
|
||||
return allocate_components_for_line($pdo, $orderId, $lineId, $lineNo, $components, $lineQty, $locations);
|
||||
}
|
||||
|
||||
function allocate_line_inventory_fallback_product(
|
||||
PDO $pdo,
|
||||
int $orderId,
|
||||
int $lineId,
|
||||
int $lineNo,
|
||||
float $lineQty,
|
||||
int $productId,
|
||||
array $locations
|
||||
): array {
|
||||
$components = [[
|
||||
'product_id' => $productId,
|
||||
'qty_per_item' => 1.0,
|
||||
]];
|
||||
return allocate_components_for_line($pdo, $orderId, $lineId, $lineNo, $components, $lineQty, $locations);
|
||||
}
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
json_response(405, ['ok' => false, 'error' => 'Method Not Allowed']);
|
||||
}
|
||||
@@ -486,6 +936,8 @@ try {
|
||||
$pdo = connect_database($env);
|
||||
ensure_required_tables_exist($pdo);
|
||||
$pdo->beginTransaction();
|
||||
$locations = get_default_location_ids($pdo);
|
||||
$existingOrderId = find_existing_order_id($pdo, $externalRef);
|
||||
|
||||
$partyId = find_or_create_party($pdo, $data);
|
||||
upsert_addresses($pdo, $partyId, $data);
|
||||
@@ -539,6 +991,14 @@ try {
|
||||
}
|
||||
$orderId = (int) $orderId;
|
||||
|
||||
$inventoryRollback = [
|
||||
'reversedMoves' => 0,
|
||||
'reversedQty' => 0.0,
|
||||
];
|
||||
if ($existingOrderId !== null) {
|
||||
$inventoryRollback = reverse_existing_allocations_for_order($pdo, $existingOrderId, $locations['storage']);
|
||||
}
|
||||
|
||||
$deleteLines = $pdo->prepare('DELETE FROM public.sales_order_line WHERE sales_order_id = :sales_order_id');
|
||||
$deleteLines->execute([':sales_order_id' => $orderId]);
|
||||
|
||||
@@ -547,17 +1007,28 @@ try {
|
||||
sales_order_id, line_no, sellable_item_id, raw_external_article_number, raw_external_title,
|
||||
qty, unit_price, line_total, created_at, updated_at
|
||||
) VALUES (
|
||||
:sales_order_id, :line_no, NULL, :article_number, :title,
|
||||
:sales_order_id, :line_no, :sellable_item_id, :article_number, :title,
|
||||
:qty, :unit_price, :line_total, NOW(), NOW()
|
||||
)'
|
||||
)
|
||||
RETURNING id'
|
||||
);
|
||||
|
||||
$insertedLines = 0;
|
||||
$inventory = [
|
||||
'linesMapped' => 0,
|
||||
'linesMappedViaFallbackProduct' => 0,
|
||||
'linesUnmapped' => 0,
|
||||
'allocationCount' => 0,
|
||||
'warnings' => [],
|
||||
];
|
||||
|
||||
foreach ($lineItems as $index => $lineItem) {
|
||||
if (!is_array($lineItem)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$articleNumber = trim((string) ($lineItem['artikelnummer'] ?? ''));
|
||||
$title = trim((string) ($lineItem['titel'] ?? ''));
|
||||
$qty = parse_number($lineItem['artikelanzahl'] ?? null);
|
||||
if ($qty === null || $qty <= 0) {
|
||||
continue;
|
||||
@@ -565,16 +1036,66 @@ try {
|
||||
|
||||
$unitPrice = parse_number($lineItem['preisEinheit'] ?? null);
|
||||
$lineTotal = $unitPrice !== null ? round($qty * $unitPrice, 2) : null;
|
||||
$sellableItemId = resolve_sellable_item_id($pdo, $articleNumber, $title);
|
||||
$lineNo = $index + 1;
|
||||
|
||||
$lineInsert->execute([
|
||||
':sales_order_id' => $orderId,
|
||||
':line_no' => $index + 1,
|
||||
':article_number' => trim((string) ($lineItem['artikelnummer'] ?? '')),
|
||||
':title' => trim((string) ($lineItem['titel'] ?? '')),
|
||||
':line_no' => $lineNo,
|
||||
':sellable_item_id' => $sellableItemId,
|
||||
':article_number' => $articleNumber,
|
||||
':title' => $title,
|
||||
':qty' => $qty,
|
||||
':unit_price' => $unitPrice,
|
||||
':line_total' => $lineTotal,
|
||||
]);
|
||||
$lineId = $lineInsert->fetchColumn();
|
||||
if ($lineId === false) {
|
||||
throw new RuntimeException("Could not insert sales_order_line for line {$lineNo}");
|
||||
}
|
||||
$lineId = (int) $lineId;
|
||||
|
||||
if ($sellableItemId === null) {
|
||||
$fallbackProductId = resolve_product_id_fallback($pdo, $articleNumber, $title);
|
||||
if ($fallbackProductId !== null) {
|
||||
$inventory['linesMappedViaFallbackProduct']++;
|
||||
$allocationResult = allocate_line_inventory_fallback_product(
|
||||
$pdo,
|
||||
$orderId,
|
||||
$lineId,
|
||||
$lineNo,
|
||||
(float) $qty,
|
||||
$fallbackProductId,
|
||||
$locations
|
||||
);
|
||||
if ($allocationResult['allocated'] === false) {
|
||||
$inventory['warnings'][] = "Fallback product allocation missing for line {$lineNo}: " . $allocationResult['reason'];
|
||||
} else {
|
||||
$inventory['allocationCount'] += count($allocationResult['allocations']);
|
||||
$inventory['warnings'][] = "Line {$lineNo} allocated via fallback product mapping (product_id={$fallbackProductId})";
|
||||
}
|
||||
} else {
|
||||
$inventory['linesUnmapped']++;
|
||||
$inventory['warnings'][] = "No sellable item mapping for line {$lineNo} (artikelnummer='{$articleNumber}', titel='{$title}')";
|
||||
}
|
||||
} else {
|
||||
$inventory['linesMapped']++;
|
||||
$allocationResult = allocate_line_inventory(
|
||||
$pdo,
|
||||
$orderId,
|
||||
$lineId,
|
||||
$lineNo,
|
||||
(float) $qty,
|
||||
$sellableItemId,
|
||||
$locations
|
||||
);
|
||||
if ($allocationResult['allocated'] === false) {
|
||||
$inventory['warnings'][] = "No inventory allocation for line {$lineNo}: " . $allocationResult['reason'];
|
||||
} else {
|
||||
$inventory['allocationCount'] += count($allocationResult['allocations']);
|
||||
}
|
||||
}
|
||||
|
||||
$insertedLines++;
|
||||
}
|
||||
|
||||
@@ -587,6 +1108,8 @@ try {
|
||||
'orderId' => $orderId,
|
||||
'externalRef' => $externalRef,
|
||||
'lineItemsImported' => $insertedLines,
|
||||
'inventory' => $inventory,
|
||||
'inventoryRollback' => $inventoryRollback,
|
||||
'labelTrigger' => $labelTrigger,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
|
||||
Reference in New Issue
Block a user