Split ERP flows into modules
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function normalize_title_key(string $value): string
|
||||
{
|
||||
$value = trim(mb_strtolower($value, 'UTF-8'));
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (function_exists('iconv')) {
|
||||
$transliterated = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
|
||||
if ($transliterated !== false) {
|
||||
$value = $transliterated;
|
||||
}
|
||||
}
|
||||
|
||||
$value = preg_replace('/[^a-z0-9]+/', ' ', $value) ?? '';
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
function normalize_match_key(string $value): string
|
||||
{
|
||||
return normalize_title_key($value);
|
||||
}
|
||||
|
||||
function detect_product_family_key(string $normalizedName): ?string
|
||||
{
|
||||
if (str_contains($normalizedName, 'lion') && str_contains($normalizedName, 'mane')) {
|
||||
return 'lionsmane';
|
||||
}
|
||||
if (str_contains($normalizedName, 'chaga')) {
|
||||
return 'chaga';
|
||||
}
|
||||
if (str_contains($normalizedName, 'reishi')) {
|
||||
return 'reishi';
|
||||
}
|
||||
if (str_contains($normalizedName, 'shiitake')) {
|
||||
return 'shiitake';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function title_contains_family(string $normalizedTitle, string $familyKey): bool
|
||||
{
|
||||
return $familyKey !== '' && str_contains($normalizedTitle, $familyKey);
|
||||
}
|
||||
|
||||
function resolve_otc_product(PDO $pdo, string $title): array
|
||||
{
|
||||
$normalizedTitle = normalize_match_key($title);
|
||||
$familyKey = detect_product_family_key($normalizedTitle);
|
||||
if ($familyKey === null) {
|
||||
throw new RuntimeException("Kein Produkt-Matching fuer '{$title}' gefunden");
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SELECT id, sku, name FROM product WHERE status = 'active' ORDER BY id");
|
||||
foreach ($stmt->fetchAll() as $product) {
|
||||
$productName = (string) ($product['name'] ?? '');
|
||||
$productKey = detect_product_family_key(normalize_match_key($productName));
|
||||
if ($productKey === $familyKey) {
|
||||
return [
|
||||
'id' => (int) $product['id'],
|
||||
'sku' => (string) $product['sku'],
|
||||
'name' => $productName,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Kein aktives ERP-Produkt fuer '{$title}' gefunden");
|
||||
}
|
||||
|
||||
function find_alias_sellable_item_id(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT si.id
|
||||
FROM external_item_alias eia
|
||||
JOIN sellable_item si ON si.id = eia.sellable_item_id
|
||||
WHERE eia.source_system = 'wix'
|
||||
AND eia.is_active = TRUE
|
||||
AND (
|
||||
eia.external_article_number = :article_number
|
||||
OR eia.external_title = :title
|
||||
OR eia.title_normalized = :normalized_title
|
||||
)
|
||||
ORDER BY eia.id
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->execute([
|
||||
':article_number' => trim($articleNumber),
|
||||
':title' => trim($title),
|
||||
':normalized_title' => normalize_match_key($title),
|
||||
]);
|
||||
|
||||
$id = $stmt->fetchColumn();
|
||||
return $id === false ? null : (int) $id;
|
||||
}
|
||||
|
||||
function sellable_item_exists(PDO $pdo, int $sellableItemId): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT 1 FROM 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 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 product WHERE id = :id LIMIT 1');
|
||||
$stmt->execute([':id' => $productId]);
|
||||
$name = $stmt->fetchColumn();
|
||||
return $name === false ? null : (string) $name;
|
||||
}
|
||||
|
||||
function ensure_unique_sellable_item_code(PDO $pdo, string $preferred): string
|
||||
{
|
||||
$candidate = trim($preferred);
|
||||
if ($candidate === '') {
|
||||
$candidate = 'si';
|
||||
}
|
||||
|
||||
$base = $candidate;
|
||||
$counter = 1;
|
||||
while (true) {
|
||||
$stmt = $pdo->prepare('SELECT 1 FROM sellable_item WHERE item_code = :item_code LIMIT 1');
|
||||
$stmt->execute([':item_code' => $candidate]);
|
||||
if ($stmt->fetchColumn() === false) {
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
$candidate = $base . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
function resolve_product_id_fallback(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
$normalizedTitle = normalize_match_key($title);
|
||||
$familyKey = detect_product_family_key($normalizedTitle);
|
||||
if ($familyKey === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SELECT id, name FROM product WHERE status = 'active' ORDER BY id");
|
||||
foreach ($stmt->fetchAll() as $product) {
|
||||
$productName = (string) ($product['name'] ?? '');
|
||||
$productKey = detect_product_family_key(normalize_match_key($productName));
|
||||
if ($productKey === $familyKey) {
|
||||
return (int) $product['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function infer_components_from_title(PDO $pdo, string $title): array
|
||||
{
|
||||
$productId = resolve_product_id_fallback($pdo, '', $title);
|
||||
if ($productId === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [['product_id' => $productId, 'qty_per_item' => 1.0]];
|
||||
}
|
||||
|
||||
function ensure_alias_points_to_sellable_item(
|
||||
PDO $pdo,
|
||||
string $articleNumber,
|
||||
string $title,
|
||||
int $sellableItemId
|
||||
): void {
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO 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, :normalized_title, :sellable_item_id, TRUE, NOW(), NOW()
|
||||
)
|
||||
ON CONFLICT DO NOTHING"
|
||||
);
|
||||
$stmt->execute([
|
||||
':article_number' => trim($articleNumber),
|
||||
':title' => trim($title),
|
||||
':normalized_title' => normalize_match_key($title),
|
||||
':sellable_item_id' => $sellableItemId,
|
||||
]);
|
||||
}
|
||||
|
||||
function ensure_sellable_mapping_from_title_components(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
$sellableItemId = find_alias_sellable_item_id($pdo, $articleNumber, $title);
|
||||
if ($sellableItemId !== null) {
|
||||
return $sellableItemId;
|
||||
}
|
||||
|
||||
$components = infer_components_from_title($pdo, $title);
|
||||
if ($components === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$productId = (int) $components[0]['product_id'];
|
||||
$sellableItemId = find_sellable_item_for_product($pdo, $productId);
|
||||
if ($sellableItemId === null) {
|
||||
$productName = find_product_name($pdo, $productId) ?? 'Mapped item';
|
||||
$code = ensure_unique_sellable_item_code($pdo, normalize_match_key($productName));
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO sellable_item (item_code, display_name, status, created_at, updated_at)
|
||||
VALUES (:item_code, :display_name, 'active', NOW(), NOW())
|
||||
RETURNING id"
|
||||
);
|
||||
$stmt->execute([
|
||||
':item_code' => $code,
|
||||
':display_name' => $productName,
|
||||
]);
|
||||
$sellableItemId = (int) $stmt->fetchColumn();
|
||||
|
||||
$componentStmt = $pdo->prepare(
|
||||
"INSERT INTO sellable_item_component (sellable_item_id, product_id, qty_per_item, created_at, updated_at)
|
||||
VALUES (:sellable_item_id, :product_id, 1, NOW(), NOW())"
|
||||
);
|
||||
$componentStmt->execute([
|
||||
':sellable_item_id' => $sellableItemId,
|
||||
':product_id' => $productId,
|
||||
]);
|
||||
}
|
||||
|
||||
ensure_alias_points_to_sellable_item($pdo, $articleNumber, $title, $sellableItemId);
|
||||
|
||||
return $sellableItemId;
|
||||
}
|
||||
|
||||
function ensure_sellable_mapping_from_product_fallback(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
return ensure_sellable_mapping_from_title_components($pdo, $articleNumber, $title);
|
||||
}
|
||||
|
||||
function resolve_sellable_item_id(PDO $pdo, string $articleNumber, string $title): ?int
|
||||
{
|
||||
$id = find_alias_sellable_item_id($pdo, $articleNumber, $title);
|
||||
if ($id !== null) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
return ensure_sellable_mapping_from_product_fallback($pdo, $articleNumber, $title);
|
||||
}
|
||||
|
||||
function get_item_components(PDO $pdo, int $sellableItemId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT product_id, qty_per_item
|
||||
FROM sellable_item_component
|
||||
WHERE sellable_item_id = :sellable_item_id
|
||||
ORDER BY id'
|
||||
);
|
||||
$stmt->execute([':sellable_item_id' => $sellableItemId]);
|
||||
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
Reference in New Issue
Block a user