Fix order import mapping and lot auto-switch robustness
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
2. `db/migrations/0002_phase1_seed_methods.sql`
|
2. `db/migrations/0002_phase1_seed_methods.sql`
|
||||||
3. `db/migrations/0003_phase1_inventory_forecast.sql`
|
3. `db/migrations/0003_phase1_inventory_forecast.sql`
|
||||||
4. `db/migrations/0004_phase1_direct_sales.sql`
|
4. `db/migrations/0004_phase1_direct_sales.sql`
|
||||||
|
5. `db/migrations/0005_phase1_auto_switch_fix.sql`
|
||||||
|
|
||||||
## Beispielausfuehrung
|
## Beispielausfuehrung
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ psql "$DATABASE_URL" -f db/migrations/0001_phase1_core.sql
|
|||||||
psql "$DATABASE_URL" -f db/migrations/0002_phase1_seed_methods.sql
|
psql "$DATABASE_URL" -f db/migrations/0002_phase1_seed_methods.sql
|
||||||
psql "$DATABASE_URL" -f db/migrations/0003_phase1_inventory_forecast.sql
|
psql "$DATABASE_URL" -f db/migrations/0003_phase1_inventory_forecast.sql
|
||||||
psql "$DATABASE_URL" -f db/migrations/0004_phase1_direct_sales.sql
|
psql "$DATABASE_URL" -f db/migrations/0004_phase1_direct_sales.sql
|
||||||
|
psql "$DATABASE_URL" -f db/migrations/0005_phase1_auto_switch_fix.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## Enthaltene Kernlogik in `0001`
|
## Enthaltene Kernlogik in `0001`
|
||||||
|
|||||||
@@ -665,7 +665,10 @@ BEGIN
|
|||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
UPDATE stock_lot
|
UPDATE stock_lot
|
||||||
SET status = 'current', updated_at = NOW()
|
SET
|
||||||
|
status = 'current',
|
||||||
|
lot_number = COALESCE(lot_number, format('AUTO-%s-%s', v_product_id, v_open_lot_id)),
|
||||||
|
updated_at = NOW()
|
||||||
WHERE id = v_open_lot_id;
|
WHERE id = v_open_lot_id;
|
||||||
|
|
||||||
INSERT INTO stock_lot (product_id, lot_number, status)
|
INSERT INTO stock_lot (product_id, lot_number, status)
|
||||||
|
|||||||
99
db/migrations/0005_phase1_auto_switch_fix.sql
Normal file
99
db/migrations/0005_phase1_auto_switch_fix.sql
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Fix lot auto-switch when current lot reaches zero:
|
||||||
|
-- if the next open lot has no lot_number yet, assign one before switching to status=current.
|
||||||
|
-- This avoids violating chk_stock_lot_number_required_for_non_open.
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION fn_auto_switch_lot_when_depleted()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_product_id BIGINT;
|
||||||
|
v_open_lot_id BIGINT;
|
||||||
|
v_current_net NUMERIC;
|
||||||
|
v_event_key TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- only relevant when the updated lot is currently active
|
||||||
|
SELECT product_id
|
||||||
|
INTO v_product_id
|
||||||
|
FROM stock_lot
|
||||||
|
WHERE id = NEW.lot_id;
|
||||||
|
|
||||||
|
-- if lot is not current anymore, nothing to do
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM stock_lot WHERE id = NEW.lot_id AND status = 'current'
|
||||||
|
) THEN
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT qty_net
|
||||||
|
INTO v_current_net
|
||||||
|
FROM v_stock_lot_balance
|
||||||
|
WHERE stock_lot_id = NEW.lot_id;
|
||||||
|
|
||||||
|
IF COALESCE(v_current_net, 0) > 0 THEN
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- lock all lots for this product to avoid race conditions
|
||||||
|
PERFORM 1
|
||||||
|
FROM stock_lot
|
||||||
|
WHERE product_id = v_product_id
|
||||||
|
FOR UPDATE;
|
||||||
|
|
||||||
|
-- confirm current lot is still current after lock
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM stock_lot WHERE id = NEW.lot_id AND status = 'current'
|
||||||
|
) THEN
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
UPDATE stock_lot
|
||||||
|
SET status = 'closed', updated_at = NOW()
|
||||||
|
WHERE id = NEW.lot_id;
|
||||||
|
|
||||||
|
SELECT id
|
||||||
|
INTO v_open_lot_id
|
||||||
|
FROM stock_lot
|
||||||
|
WHERE product_id = v_product_id
|
||||||
|
AND status = 'open'
|
||||||
|
ORDER BY id
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE;
|
||||||
|
|
||||||
|
IF v_open_lot_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'No open lot available for product % during auto switch', v_product_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- current/closed lots must have a lot_number due to chk_stock_lot_number_required_for_non_open.
|
||||||
|
UPDATE stock_lot
|
||||||
|
SET
|
||||||
|
status = 'current',
|
||||||
|
lot_number = COALESCE(lot_number, format('AUTO-%s-%s', v_product_id, v_open_lot_id)),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = v_open_lot_id;
|
||||||
|
|
||||||
|
INSERT INTO stock_lot (product_id, lot_number, status)
|
||||||
|
VALUES (v_product_id, NULL, 'open');
|
||||||
|
|
||||||
|
v_event_key := format('lot.auto_switched:%s:%s', v_product_id, txid_current());
|
||||||
|
|
||||||
|
PERFORM fn_enqueue_event(
|
||||||
|
'lot.auto_switched',
|
||||||
|
v_event_key,
|
||||||
|
'stock_lot',
|
||||||
|
NEW.lot_id::TEXT,
|
||||||
|
jsonb_build_object(
|
||||||
|
'productId', v_product_id,
|
||||||
|
'closedLotId', NEW.lot_id,
|
||||||
|
'newCurrentLotId', v_open_lot_id,
|
||||||
|
'occurredAt', NOW()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
296
order-import.php
296
order-import.php
@@ -437,6 +437,27 @@ function normalize_title_key(string $value): string
|
|||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalize_match_key(string $value): string
|
||||||
|
{
|
||||||
|
$value = trim($value);
|
||||||
|
if ($value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$ascii = $value;
|
||||||
|
if (function_exists('iconv')) {
|
||||||
|
$converted = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
|
||||||
|
if (is_string($converted) && $converted !== '') {
|
||||||
|
$ascii = $converted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ascii = strtolower($ascii);
|
||||||
|
$ascii = preg_replace('/[^a-z0-9]+/', ' ', $ascii) ?? $ascii;
|
||||||
|
$ascii = preg_replace('/\s+/', ' ', trim($ascii)) ?? $ascii;
|
||||||
|
return $ascii;
|
||||||
|
}
|
||||||
|
|
||||||
function find_existing_order_id(PDO $pdo, string $externalRef): ?int
|
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 = $pdo->prepare('SELECT id FROM public.sales_order WHERE external_ref = :external_ref LIMIT 1');
|
||||||
@@ -582,9 +603,234 @@ function resolve_product_id_fallback(PDO $pdo, string $articleNumber, string $ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Business fallback for known product families where Wix title differs from ERP product label.
|
||||||
|
$search = normalize_match_key($articleNumber . ' ' . $title);
|
||||||
|
if ($search !== '') {
|
||||||
|
$familyPatterns = [];
|
||||||
|
|
||||||
|
if (str_contains($search, 'lion') || str_contains($search, 'mane')) {
|
||||||
|
$familyPatterns[] = '%lionsmane%';
|
||||||
|
$familyPatterns[] = '%lion%';
|
||||||
|
}
|
||||||
|
if (str_contains($search, 'shiitake')) {
|
||||||
|
$familyPatterns[] = '%shiitake%';
|
||||||
|
}
|
||||||
|
if (str_contains($search, 'chaga')) {
|
||||||
|
$familyPatterns[] = '%chaga%';
|
||||||
|
}
|
||||||
|
if (str_contains($search, 'reishi')) {
|
||||||
|
$familyPatterns[] = '%reishi%';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($familyPatterns as $pattern) {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT id
|
||||||
|
FROM public.product
|
||||||
|
WHERE lower(name) LIKE :pattern
|
||||||
|
ORDER BY id
|
||||||
|
LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->execute([':pattern' => $pattern]);
|
||||||
|
$id = $stmt->fetchColumn();
|
||||||
|
if ($id !== false) {
|
||||||
|
return (int) $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sellable_item_exists(PDO $pdo, int $sellableItemId): bool
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('SELECT 1 FROM public.sellable_item WHERE id = :id LIMIT 1');
|
||||||
|
$stmt->execute([':id' => $sellableItemId]);
|
||||||
|
return $stmt->fetchColumn() !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_sellable_item_for_product(PDO $pdo, int $productId): ?int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT sellable_item_id
|
||||||
|
FROM public.sellable_item_component
|
||||||
|
WHERE product_id = :product_id
|
||||||
|
ORDER BY id
|
||||||
|
LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->execute([':product_id' => $productId]);
|
||||||
|
$id = $stmt->fetchColumn();
|
||||||
|
return $id === false ? null : (int) $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_product_name(PDO $pdo, int $productId): ?string
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('SELECT name FROM public.product WHERE id = :id LIMIT 1');
|
||||||
|
$stmt->execute([':id' => $productId]);
|
||||||
|
$name = $stmt->fetchColumn();
|
||||||
|
if ($name === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$name = trim((string) $name);
|
||||||
|
return $name === '' ? null : $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_unique_sellable_item_code(PDO $pdo, string $preferred): string
|
||||||
|
{
|
||||||
|
$base = preg_replace('/[^A-Za-z0-9._-]+/', '-', trim($preferred)) ?? '';
|
||||||
|
$base = trim($base, '-');
|
||||||
|
if ($base === '') {
|
||||||
|
$base = 'AUTO-ITEM';
|
||||||
|
}
|
||||||
|
$base = strtoupper(substr($base, 0, 60));
|
||||||
|
|
||||||
|
$existsStmt = $pdo->prepare('SELECT 1 FROM public.sellable_item WHERE item_code = :item_code LIMIT 1');
|
||||||
|
$candidate = $base;
|
||||||
|
$suffix = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$existsStmt->execute([':item_code' => $candidate]);
|
||||||
|
if ($existsStmt->fetchColumn() === false) {
|
||||||
|
return $candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$suffix++;
|
||||||
|
$prefixMaxLen = max(1, 60 - strlen((string) $suffix) - 1);
|
||||||
|
$candidate = substr($base, 0, $prefixMaxLen) . '-' . $suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_alias_points_to_sellable_item(
|
||||||
|
PDO $pdo,
|
||||||
|
int $sellableItemId,
|
||||||
|
string $articleNumber,
|
||||||
|
string $title
|
||||||
|
): bool {
|
||||||
|
$articleNumber = trim($articleNumber);
|
||||||
|
$title = trim($title);
|
||||||
|
$titleNorm = normalize_title_key($title);
|
||||||
|
|
||||||
|
if ($articleNumber === '' && $title === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$findExisting = $pdo->prepare(
|
||||||
|
"SELECT id, sellable_item_id
|
||||||
|
FROM public.external_item_alias
|
||||||
|
WHERE source_system = 'wix'
|
||||||
|
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 id
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE"
|
||||||
|
);
|
||||||
|
$findExisting->execute([
|
||||||
|
':article_number' => $articleNumber,
|
||||||
|
':title_norm' => $titleNorm,
|
||||||
|
':title' => $title,
|
||||||
|
]);
|
||||||
|
$existing = $findExisting->fetch();
|
||||||
|
|
||||||
|
if (is_array($existing)) {
|
||||||
|
$existingSellable = isset($existing['sellable_item_id']) ? (int) $existing['sellable_item_id'] : 0;
|
||||||
|
$aliasId = isset($existing['id']) ? (int) $existing['id'] : 0;
|
||||||
|
if ($existingSellable === $sellableItemId) {
|
||||||
|
$touchStmt = $pdo->prepare('UPDATE public.external_item_alias SET is_active = TRUE, updated_at = NOW() WHERE id = :id');
|
||||||
|
$touchStmt->execute([':id' => $aliasId]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateStmt = $pdo->prepare(
|
||||||
|
'UPDATE public.external_item_alias
|
||||||
|
SET sellable_item_id = :sellable_item_id,
|
||||||
|
external_article_number = :article_number,
|
||||||
|
external_title = :title,
|
||||||
|
title_normalized = :title_norm,
|
||||||
|
is_active = TRUE,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id'
|
||||||
|
);
|
||||||
|
$updateStmt->execute([
|
||||||
|
':sellable_item_id' => $sellableItemId,
|
||||||
|
':article_number' => $articleNumber !== '' ? $articleNumber : null,
|
||||||
|
':title' => $title !== '' ? $title : null,
|
||||||
|
':title_norm' => $titleNorm !== '' ? $titleNorm : null,
|
||||||
|
':id' => $aliasId,
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertStmt = $pdo->prepare(
|
||||||
|
"INSERT INTO public.external_item_alias (
|
||||||
|
source_system, external_article_number, external_title, title_normalized, sellable_item_id, is_active, created_at, updated_at
|
||||||
|
) VALUES (
|
||||||
|
'wix', :article_number, :title, :title_norm, :sellable_item_id, TRUE, NOW(), NOW()
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
$insertStmt->execute([
|
||||||
|
':article_number' => $articleNumber !== '' ? $articleNumber : null,
|
||||||
|
':title' => $title !== '' ? $title : null,
|
||||||
|
':title_norm' => $titleNorm !== '' ? $titleNorm : null,
|
||||||
|
':sellable_item_id' => $sellableItemId,
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_sellable_mapping_from_product_fallback(
|
||||||
|
PDO $pdo,
|
||||||
|
int $productId,
|
||||||
|
string $articleNumber,
|
||||||
|
string $title
|
||||||
|
): array {
|
||||||
|
$articleNumber = trim($articleNumber);
|
||||||
|
$title = trim($title);
|
||||||
|
|
||||||
|
$sellableItemId = find_sellable_item_for_product($pdo, $productId);
|
||||||
|
$createdSellable = false;
|
||||||
|
if ($sellableItemId === null || !sellable_item_exists($pdo, $sellableItemId)) {
|
||||||
|
$productName = find_product_name($pdo, $productId);
|
||||||
|
$itemCodeSeed = $articleNumber !== '' ? $articleNumber : "AUTO-PROD-{$productId}";
|
||||||
|
$itemCode = ensure_unique_sellable_item_code($pdo, $itemCodeSeed);
|
||||||
|
$displayName = $title !== '' ? $title : ($productName ?? $itemCode);
|
||||||
|
|
||||||
|
$insertSellable = $pdo->prepare(
|
||||||
|
'INSERT INTO public.sellable_item (item_code, display_name, status, created_at, updated_at)
|
||||||
|
VALUES (:item_code, :display_name, \'active\', NOW(), NOW())
|
||||||
|
RETURNING id'
|
||||||
|
);
|
||||||
|
$insertSellable->execute([
|
||||||
|
':item_code' => $itemCode,
|
||||||
|
':display_name' => $displayName,
|
||||||
|
]);
|
||||||
|
$id = $insertSellable->fetchColumn();
|
||||||
|
if ($id === false) {
|
||||||
|
throw new RuntimeException("Could not create sellable_item for fallback product {$productId}");
|
||||||
|
}
|
||||||
|
$sellableItemId = (int) $id;
|
||||||
|
$createdSellable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertComponent = $pdo->prepare(
|
||||||
|
'INSERT INTO public.sellable_item_component (sellable_item_id, product_id, qty_per_item, created_at, updated_at)
|
||||||
|
VALUES (:sellable_item_id, :product_id, 1.0, NOW(), NOW())
|
||||||
|
ON CONFLICT (sellable_item_id, product_id) DO NOTHING'
|
||||||
|
);
|
||||||
|
$insertComponent->execute([
|
||||||
|
':sellable_item_id' => $sellableItemId,
|
||||||
|
':product_id' => $productId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$aliasChanged = ensure_alias_points_to_sellable_item($pdo, $sellableItemId, $articleNumber, $title);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'sellableItemId' => $sellableItemId,
|
||||||
|
'createdSellableItem' => $createdSellable,
|
||||||
|
'aliasCreatedOrUpdated' => $aliasChanged,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
|
function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
|
||||||
{
|
{
|
||||||
$lotStmt = $pdo->prepare(
|
$lotStmt = $pdo->prepare(
|
||||||
@@ -638,10 +884,15 @@ function switch_current_lot(PDO $pdo, int $productId, int $oldCurrentLotId): int
|
|||||||
|
|
||||||
$makeCurrentStmt = $pdo->prepare(
|
$makeCurrentStmt = $pdo->prepare(
|
||||||
"UPDATE public.stock_lot
|
"UPDATE public.stock_lot
|
||||||
SET status = 'current', updated_at = NOW()
|
SET status = 'current',
|
||||||
|
lot_number = COALESCE(lot_number, :auto_lot_number),
|
||||||
|
updated_at = NOW()
|
||||||
WHERE id = :id"
|
WHERE id = :id"
|
||||||
);
|
);
|
||||||
$makeCurrentStmt->execute([':id' => (int) $newCurrentLotId]);
|
$makeCurrentStmt->execute([
|
||||||
|
':id' => (int) $newCurrentLotId,
|
||||||
|
':auto_lot_number' => 'AUTO-' . $productId . '-' . (int) $newCurrentLotId,
|
||||||
|
]);
|
||||||
|
|
||||||
$createOpenStmt = $pdo->prepare(
|
$createOpenStmt = $pdo->prepare(
|
||||||
"INSERT INTO public.stock_lot (product_id, lot_number, status, created_at, updated_at)
|
"INSERT INTO public.stock_lot (product_id, lot_number, status, created_at, updated_at)
|
||||||
@@ -1037,6 +1288,19 @@ try {
|
|||||||
$unitPrice = parse_number($lineItem['preisEinheit'] ?? null);
|
$unitPrice = parse_number($lineItem['preisEinheit'] ?? null);
|
||||||
$lineTotal = $unitPrice !== null ? round($qty * $unitPrice, 2) : null;
|
$lineTotal = $unitPrice !== null ? round($qty * $unitPrice, 2) : null;
|
||||||
$sellableItemId = resolve_sellable_item_id($pdo, $articleNumber, $title);
|
$sellableItemId = resolve_sellable_item_id($pdo, $articleNumber, $title);
|
||||||
|
$autoMappingMeta = null;
|
||||||
|
if ($sellableItemId === null) {
|
||||||
|
$fallbackProductId = resolve_product_id_fallback($pdo, $articleNumber, $title);
|
||||||
|
if ($fallbackProductId !== null) {
|
||||||
|
$autoMappingMeta = ensure_sellable_mapping_from_product_fallback(
|
||||||
|
$pdo,
|
||||||
|
$fallbackProductId,
|
||||||
|
$articleNumber,
|
||||||
|
$title
|
||||||
|
);
|
||||||
|
$sellableItemId = (int) $autoMappingMeta['sellableItemId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
$lineNo = $index + 1;
|
$lineNo = $index + 1;
|
||||||
|
|
||||||
$lineInsert->execute([
|
$lineInsert->execute([
|
||||||
@@ -1056,29 +1320,13 @@ try {
|
|||||||
$lineId = (int) $lineId;
|
$lineId = (int) $lineId;
|
||||||
|
|
||||||
if ($sellableItemId === null) {
|
if ($sellableItemId === null) {
|
||||||
$fallbackProductId = resolve_product_id_fallback($pdo, $articleNumber, $title);
|
$inventory['linesUnmapped']++;
|
||||||
if ($fallbackProductId !== null) {
|
$inventory['warnings'][] = "No sellable item mapping for line {$lineNo} (artikelnummer='{$articleNumber}', titel='{$title}')";
|
||||||
$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 {
|
} else {
|
||||||
|
if (is_array($autoMappingMeta)) {
|
||||||
|
$inventory['linesMappedViaFallbackProduct']++;
|
||||||
|
$inventory['warnings'][] = "Line {$lineNo} auto-mapped from product fallback (sellable_item_id={$sellableItemId})";
|
||||||
|
}
|
||||||
$inventory['linesMapped']++;
|
$inventory['linesMapped']++;
|
||||||
$allocationResult = allocate_line_inventory(
|
$allocationResult = allocate_line_inventory(
|
||||||
$pdo,
|
$pdo,
|
||||||
|
|||||||
Reference in New Issue
Block a user