- Implemented OrdersRepository for handling order data with pagination, filtering, and sorting capabilities. - Added methods for retrieving order status options, quick stats, and detailed order information. - Created OrderStatusRepository for managing order status groups and statuses, including CRUD operations and sorting. - Introduced a bootstrap file for test environment setup and autoloading.
725 lines
33 KiB
PHP
725 lines
33 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use App\Core\Database\ConnectionFactory;
|
|
use App\Core\Support\Env;
|
|
|
|
$basePath = dirname(__DIR__);
|
|
$vendorAutoload = $basePath . '/vendor/autoload.php';
|
|
|
|
if (is_file($vendorAutoload)) {
|
|
require $vendorAutoload;
|
|
} else {
|
|
spl_autoload_register(static function (string $class) use ($basePath): void {
|
|
$prefix = 'App\\';
|
|
if (!str_starts_with($class, $prefix)) {
|
|
return;
|
|
}
|
|
$relative = substr($class, strlen($prefix));
|
|
$file = $basePath . '/src/' . str_replace('\\', '/', $relative) . '.php';
|
|
if (is_file($file)) {
|
|
require $file;
|
|
}
|
|
});
|
|
}
|
|
|
|
Env::load($basePath . '/.env');
|
|
|
|
/** @var array<string, mixed> $dbConfig */
|
|
$dbConfig = require $basePath . '/config/database.php';
|
|
|
|
$args = $argv;
|
|
array_shift($args);
|
|
$useRemote = in_array('--use-remote', $args, true);
|
|
$append = in_array('--append', $args, true);
|
|
$count = 30;
|
|
$profile = 'default';
|
|
foreach ($args as $arg) {
|
|
if (str_starts_with($arg, '--count=')) {
|
|
$count = max(1, min(500, (int) substr($arg, 8)));
|
|
}
|
|
if (str_starts_with($arg, '--profile=')) {
|
|
$parsedProfile = strtolower(trim((string) substr($arg, 10)));
|
|
if (in_array($parsedProfile, ['default', 'realistic'], true)) {
|
|
$profile = $parsedProfile;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($useRemote) {
|
|
$remoteHost = (string) Env::get('DB_HOST_REMOTE', '');
|
|
if ($remoteHost !== '') {
|
|
$dbConfig['host'] = $remoteHost;
|
|
}
|
|
}
|
|
|
|
$pdo = ConnectionFactory::make($dbConfig);
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
echo 'Deploy + seed orders schema' . PHP_EOL;
|
|
echo '[db-host] ' . (string) ($dbConfig['host'] ?? '') . PHP_EOL;
|
|
echo '[count] ' . $count . PHP_EOL;
|
|
echo '[profile] ' . $profile . PHP_EOL;
|
|
echo '[mode] ' . ($append ? 'append' : 'reset+seed') . PHP_EOL;
|
|
|
|
try {
|
|
deploySchema($pdo, $basePath . '/database/drafts/20260302_orders_schema_v1.sql');
|
|
seedData($pdo, $count, $append, $profile);
|
|
printSummary($pdo);
|
|
echo 'Done.' . PHP_EOL;
|
|
} catch (Throwable $exception) {
|
|
fwrite(STDERR, '[error] ' . $exception->getMessage() . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
|
|
function deploySchema(PDO $pdo, string $sqlFile): void
|
|
{
|
|
if (!is_file($sqlFile)) {
|
|
throw new RuntimeException('SQL draft file not found: ' . $sqlFile);
|
|
}
|
|
$rawSql = (string) file_get_contents($sqlFile);
|
|
$withoutLineComments = preg_replace('/^\s*--.*$/m', '', $rawSql) ?? $rawSql;
|
|
$statements = preg_split('/;\s*[\r\n]+/', $withoutLineComments) ?: [];
|
|
foreach ($statements as $statement) {
|
|
$sql = trim($statement);
|
|
if ($sql === '') {
|
|
continue;
|
|
}
|
|
$pdo->exec($sql);
|
|
}
|
|
echo '[schema] applied' . PHP_EOL;
|
|
}
|
|
|
|
function seedData(PDO $pdo, int $count, bool $append, string $profile): void
|
|
{
|
|
if (!$append) {
|
|
$pdo->exec('SET FOREIGN_KEY_CHECKS = 0');
|
|
foreach ([
|
|
'order_tag_links',
|
|
'order_status_history',
|
|
'order_notes',
|
|
'order_documents',
|
|
'order_shipments',
|
|
'order_payments',
|
|
'order_items',
|
|
'order_addresses',
|
|
'orders',
|
|
'order_tags_dict',
|
|
'integration_order_sync_state',
|
|
] as $table) {
|
|
$pdo->exec('TRUNCATE TABLE ' . $table);
|
|
}
|
|
$pdo->exec('SET FOREIGN_KEY_CHECKS = 1');
|
|
}
|
|
|
|
$now = new DateTimeImmutable('now');
|
|
$statuses = loadStatusesForSeed($pdo);
|
|
$paymentTypes = ['transfer', 'card', 'cod', 'blik', 'cash'];
|
|
$carriers = ['dhl', 'inpost', 'dpd', 'ups', 'gls'];
|
|
$itemTypes = ['product', 'shipment', 'service'];
|
|
$currencies = ['PLN', 'EUR', 'USD'];
|
|
$sources = ['shop', 'marketplace', 'api'];
|
|
$noteTypes = ['customer_message', 'internal_note'];
|
|
$tags = ['priority', 'fraud-check', 'gift', 'invoice', 'vip', 'return-risk', 'express'];
|
|
|
|
$insertTag = $pdo->prepare(
|
|
'INSERT INTO order_tags_dict (integration_id, source_tag_id, tag_name, is_active, updated_at_external, payload_json, created_at, updated_at)
|
|
VALUES (:integration_id, :source_tag_id, :tag_name, :is_active, :updated_at_external, :payload_json, :created_at, :updated_at)'
|
|
);
|
|
foreach ($tags as $idx => $tagName) {
|
|
$insertTag->execute([
|
|
'integration_id' => random_int(1, 3),
|
|
'source_tag_id' => 'tag_' . ($idx + 1),
|
|
'tag_name' => $tagName,
|
|
'is_active' => 1,
|
|
'updated_at_external' => $now->format('Y-m-d H:i:s'),
|
|
'payload_json' => json_encode(['name' => $tagName], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$tagRows = $pdo->query('SELECT id FROM order_tags_dict')->fetchAll(PDO::FETCH_COLUMN);
|
|
$tagIds = array_map(static fn ($id) => (int) $id, is_array($tagRows) ? $tagRows : []);
|
|
|
|
$insertOrder = $pdo->prepare(
|
|
'INSERT INTO orders (
|
|
integration_id, source, source_order_id, external_order_id, external_platform_id, external_platform_account_id,
|
|
external_status_id, external_payment_type_id, payment_status, external_carrier_id, external_carrier_account_id,
|
|
customer_login, is_invoice, is_encrypted, is_canceled_by_buyer, currency, total_without_tax, total_with_tax,
|
|
total_paid, send_date_min, send_date_max, ordered_at, source_created_at, source_updated_at, preferences_json,
|
|
payload_json, fetched_at, created_at, updated_at
|
|
) VALUES (
|
|
:integration_id, :source, :source_order_id, :external_order_id, :external_platform_id, :external_platform_account_id,
|
|
:external_status_id, :external_payment_type_id, :payment_status, :external_carrier_id, :external_carrier_account_id,
|
|
:customer_login, :is_invoice, :is_encrypted, :is_canceled_by_buyer, :currency, :total_without_tax, :total_with_tax,
|
|
:total_paid, :send_date_min, :send_date_max, :ordered_at, :source_created_at, :source_updated_at, :preferences_json,
|
|
:payload_json, :fetched_at, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertAddress = $pdo->prepare(
|
|
'INSERT INTO order_addresses (
|
|
order_id, address_type, name, phone, email, street_name, street_number, city, zip_code, country, department,
|
|
parcel_external_id, parcel_name, address_class, company_tax_number, company_name, payload_json, created_at, updated_at
|
|
) VALUES (
|
|
:order_id, :address_type, :name, :phone, :email, :street_name, :street_number, :city, :zip_code, :country, :department,
|
|
:parcel_external_id, :parcel_name, :address_class, :company_tax_number, :company_name, :payload_json, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertItem = $pdo->prepare(
|
|
'INSERT INTO order_items (
|
|
order_id, source_item_id, external_item_id, ean, sku, original_name, original_code, original_price_with_tax,
|
|
original_price_without_tax, media_url, quantity, tax_rate, item_status, unit, item_type, source_product_id,
|
|
source_product_set_id, sort_order, payload_json, created_at, updated_at
|
|
) VALUES (
|
|
:order_id, :source_item_id, :external_item_id, :ean, :sku, :original_name, :original_code, :original_price_with_tax,
|
|
:original_price_without_tax, :media_url, :quantity, :tax_rate, :item_status, :unit, :item_type, :source_product_id,
|
|
:source_product_set_id, :sort_order, :payload_json, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertPayment = $pdo->prepare(
|
|
'INSERT INTO order_payments (
|
|
order_id, source_payment_id, external_payment_id, payment_type_id, payment_date, amount, currency, comment,
|
|
payload_json, created_at, updated_at
|
|
) VALUES (
|
|
:order_id, :source_payment_id, :external_payment_id, :payment_type_id, :payment_date, :amount, :currency, :comment,
|
|
:payload_json, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertShipment = $pdo->prepare(
|
|
'INSERT INTO order_shipments (
|
|
order_id, source_shipment_id, external_shipment_id, tracking_number, carrier_provider_id, posted_at, media_uuid,
|
|
payload_json, created_at, updated_at
|
|
) VALUES (
|
|
:order_id, :source_shipment_id, :external_shipment_id, :tracking_number, :carrier_provider_id, :posted_at, :media_uuid,
|
|
:payload_json, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertDocument = $pdo->prepare(
|
|
'INSERT INTO order_documents (
|
|
order_id, source_document_id, external_document_id, document_number, price_with_tax, price_without_tax, currency,
|
|
currency_value, document_type_id, media_uuid, source_created_at, payload_json, created_at, updated_at
|
|
) VALUES (
|
|
:order_id, :source_document_id, :external_document_id, :document_number, :price_with_tax, :price_without_tax, :currency,
|
|
:currency_value, :document_type_id, :media_uuid, :source_created_at, :payload_json, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertNote = $pdo->prepare(
|
|
'INSERT INTO order_notes (
|
|
order_id, source_note_id, note_type, created_at_external, comment, payload_json, created_at, updated_at
|
|
) VALUES (
|
|
:order_id, :source_note_id, :note_type, :created_at_external, :comment, :payload_json, :created_at, :updated_at
|
|
)'
|
|
);
|
|
|
|
$insertStatusHistory = $pdo->prepare(
|
|
'INSERT INTO order_status_history (
|
|
order_id, from_status_id, to_status_id, changed_at, change_source, comment, payload_json, created_at
|
|
) VALUES (
|
|
:order_id, :from_status_id, :to_status_id, :changed_at, :change_source, :comment, :payload_json, :created_at
|
|
)'
|
|
);
|
|
|
|
$insertTagLink = $pdo->prepare(
|
|
'INSERT INTO order_tag_links (order_id, tag_dict_id, assigned_at, payload_json, created_at)
|
|
VALUES (:order_id, :tag_dict_id, :assigned_at, :payload_json, :created_at)'
|
|
);
|
|
|
|
$firstNames = ['Jan', 'Anna', 'Piotr', 'Katarzyna', 'Marek', 'Ewa', 'Tomasz', 'Magda', 'Pawel', 'Karolina'];
|
|
$lastNames = ['Kowalski', 'Nowak', 'Wisniewski', 'Wojcik', 'Kaczmarek', 'Mazur', 'Krawczyk', 'Zielinski'];
|
|
$cities = ['Warszawa', 'Krakow', 'Gdansk', 'Poznan', 'Wroclaw', 'Lodz', 'Lublin', 'Katowice'];
|
|
|
|
$pdo->beginTransaction();
|
|
try {
|
|
for ($i = 1; $i <= $count; $i++) {
|
|
$integrationId = random_int(1, 3);
|
|
$source = $sources[array_rand($sources)];
|
|
$currency = $currencies[array_rand($currencies)];
|
|
$status = $statuses[array_rand($statuses)];
|
|
$paymentType = $paymentTypes[array_rand($paymentTypes)];
|
|
$carrier = $carriers[array_rand($carriers)];
|
|
$isInvoice = random_int(0, 1) === 1;
|
|
$orderedAt = $now->sub(new DateInterval('P' . random_int(0, 45) . 'DT' . random_int(0, 23) . 'H'));
|
|
$updatedAt = $orderedAt->add(new DateInterval('PT' . random_int(1, 120) . 'H'));
|
|
$sendMin = $orderedAt->add(new DateInterval('P' . random_int(0, 2) . 'D'));
|
|
$sendMax = $sendMin->add(new DateInterval('P' . random_int(0, 4) . 'D'));
|
|
|
|
$totalNet = (float) random_int(5000, 35000) / 100.0;
|
|
$totalGross = round($totalNet * 1.23, 2);
|
|
$paidRatio = [0.0, 0.5, 1.0, 1.1][array_rand([0, 1, 2, 3])];
|
|
$totalPaid = round($totalGross * $paidRatio, 2);
|
|
$paymentStatus = random_int(0, 3);
|
|
|
|
if ($profile === 'realistic') {
|
|
$status = weightedChoice([
|
|
'new' => 10,
|
|
'confirmed' => 16,
|
|
'paid' => 18,
|
|
'processing' => 16,
|
|
'packed' => 12,
|
|
'shipped' => 12,
|
|
'delivered' => 10,
|
|
'cancelled' => 4,
|
|
'returned' => 2,
|
|
]);
|
|
$paymentType = weightedChoice([
|
|
'transfer' => 30,
|
|
'card' => 30,
|
|
'blik' => 22,
|
|
'cod' => 14,
|
|
'cash' => 4,
|
|
]);
|
|
$carrier = weightedChoice([
|
|
'inpost' => 35,
|
|
'dhl' => 25,
|
|
'dpd' => 20,
|
|
'ups' => 12,
|
|
'gls' => 8,
|
|
]);
|
|
$isInvoice = random_int(1, 100) <= 35;
|
|
$orderedAt = $now->sub(new DateInterval('P' . random_int(0, 60) . 'DT' . random_int(0, 23) . 'H'));
|
|
$updatedAt = $orderedAt->add(new DateInterval('PT' . random_int(6, 168) . 'H'));
|
|
$sendMin = $orderedAt->add(new DateInterval('P' . random_int(0, 1) . 'D'));
|
|
$sendMax = $sendMin->add(new DateInterval('P' . random_int(1, 3) . 'D'));
|
|
$totalNet = (float) random_int(6000, 42000) / 100.0;
|
|
$totalGross = round($totalNet * 1.23, 2);
|
|
|
|
$statusFinancialRules = [
|
|
'new' => ['ratio' => [0.0, 0.0, 0.5], 'payment_status' => [0, 0, 1]],
|
|
'confirmed' => ['ratio' => [0.0, 0.5, 1.0], 'payment_status' => [0, 1, 2]],
|
|
'paid' => ['ratio' => [1.0, 1.0, 1.0, 0.5], 'payment_status' => [2, 2, 2, 1]],
|
|
'processing' => ['ratio' => [1.0, 1.0, 1.0], 'payment_status' => [2, 2, 2]],
|
|
'packed' => ['ratio' => [1.0, 1.0, 1.0], 'payment_status' => [2, 2, 2]],
|
|
'shipped' => ['ratio' => [1.0, 1.0, 1.0], 'payment_status' => [2, 2, 2]],
|
|
'delivered' => ['ratio' => [1.0, 1.0, 1.0], 'payment_status' => [2, 2, 2]],
|
|
'cancelled' => ['ratio' => [0.0, 0.0, 0.5], 'payment_status' => [0, 0, 1]],
|
|
'returned' => ['ratio' => [1.0, 1.0, 0.0], 'payment_status' => [3, 3, 0]],
|
|
];
|
|
if (isset($statusFinancialRules[$status])) {
|
|
$rule = $statusFinancialRules[$status];
|
|
$paidRatio = (float) $rule['ratio'][array_rand($rule['ratio'])];
|
|
$paymentStatus = (int) $rule['payment_status'][array_rand($rule['payment_status'])];
|
|
} else {
|
|
$paidRatio = [0.0, 0.5, 1.0][array_rand([0, 1, 2])];
|
|
$paymentStatus = [0, 1, 2][array_rand([0, 1, 2])];
|
|
}
|
|
$totalPaid = round($totalGross * $paidRatio, 2);
|
|
if ($status === 'returned' && $paymentStatus === 3) {
|
|
$totalPaid = 0.00;
|
|
}
|
|
}
|
|
|
|
$sourceOrderId = 'ORD-' . $integrationId . '-' . $orderedAt->format('Ymd') . '-' . str_pad((string) $i, 4, '0', STR_PAD_LEFT);
|
|
$externalOrderId = 'EXT-' . random_int(100000, 999999);
|
|
$platformId = 'platform_' . random_int(1, 5);
|
|
$platformAccountId = 'account_' . random_int(1, 8);
|
|
|
|
$customerName = $firstNames[array_rand($firstNames)] . ' ' . $lastNames[array_rand($lastNames)];
|
|
$customerEmail = strtolower(str_replace(' ', '.', $customerName)) . random_int(1, 99) . '@example.com';
|
|
$city = $cities[array_rand($cities)];
|
|
|
|
$insertOrder->execute([
|
|
'integration_id' => $integrationId,
|
|
'source' => $source,
|
|
'source_order_id' => $sourceOrderId,
|
|
'external_order_id' => $externalOrderId,
|
|
'external_platform_id' => $platformId,
|
|
'external_platform_account_id' => $platformAccountId,
|
|
'external_status_id' => $status,
|
|
'external_payment_type_id' => $paymentType,
|
|
'payment_status' => $paymentStatus,
|
|
'external_carrier_id' => $carrier,
|
|
'external_carrier_account_id' => 'carrier_acc_' . random_int(1, 6),
|
|
'customer_login' => strtolower(str_replace(' ', '.', $customerName)),
|
|
'is_invoice' => $isInvoice ? 1 : 0,
|
|
'is_encrypted' => random_int(0, 1),
|
|
'is_canceled_by_buyer' => $status === 'cancelled' ? 1 : 0,
|
|
'currency' => $currency,
|
|
'total_without_tax' => number_format($totalNet, 2, '.', ''),
|
|
'total_with_tax' => number_format($totalGross, 2, '.', ''),
|
|
'total_paid' => number_format($totalPaid, 2, '.', ''),
|
|
'send_date_min' => $sendMin->format('Y-m-d H:i:s'),
|
|
'send_date_max' => $sendMax->format('Y-m-d H:i:s'),
|
|
'ordered_at' => $orderedAt->format('Y-m-d H:i:s'),
|
|
'source_created_at' => $orderedAt->format('Y-m-d H:i:s'),
|
|
'source_updated_at' => $updatedAt->format('Y-m-d H:i:s'),
|
|
'preferences_json' => json_encode(['gift_wrap' => (bool) random_int(0, 1)], JSON_UNESCAPED_UNICODE),
|
|
'payload_json' => json_encode(['mock' => true, 'v' => 1], JSON_UNESCAPED_UNICODE),
|
|
'fetched_at' => $now->format('Y-m-d H:i:s'),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
$orderId = (int) $pdo->lastInsertId();
|
|
|
|
foreach (['customer', 'delivery'] as $addressType) {
|
|
$insertAddress->execute([
|
|
'order_id' => $orderId,
|
|
'address_type' => $addressType,
|
|
'name' => $customerName,
|
|
'phone' => '+485' . random_int(10000000, 99999999),
|
|
'email' => $customerEmail,
|
|
'street_name' => 'Testowa',
|
|
'street_number' => (string) random_int(1, 120),
|
|
'city' => $city,
|
|
'zip_code' => sprintf('%02d-%03d', random_int(10, 99), random_int(100, 999)),
|
|
'country' => 'PL',
|
|
'department' => null,
|
|
'parcel_external_id' => random_int(0, 3) === 0 ? 'PACZ-' . random_int(1000, 9999) : null,
|
|
'parcel_name' => null,
|
|
'address_class' => $isInvoice ? 'company' : 'house',
|
|
'company_tax_number' => $isInvoice ? 'PL' . random_int(1000000000, 9999999999) : null,
|
|
'company_name' => $isInvoice ? 'Firma ' . $customerName : null,
|
|
'payload_json' => json_encode(['type' => $addressType], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
if ($isInvoice) {
|
|
$insertAddress->execute([
|
|
'order_id' => $orderId,
|
|
'address_type' => 'invoice',
|
|
'name' => $customerName,
|
|
'phone' => '+485' . random_int(10000000, 99999999),
|
|
'email' => $customerEmail,
|
|
'street_name' => 'Firmowa',
|
|
'street_number' => (string) random_int(1, 120),
|
|
'city' => $city,
|
|
'zip_code' => sprintf('%02d-%03d', random_int(10, 99), random_int(100, 999)),
|
|
'country' => 'PL',
|
|
'department' => null,
|
|
'parcel_external_id' => null,
|
|
'parcel_name' => null,
|
|
'address_class' => 'company',
|
|
'company_tax_number' => 'PL' . random_int(1000000000, 9999999999),
|
|
'company_name' => 'Firma ' . $customerName,
|
|
'payload_json' => json_encode(['type' => 'invoice'], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$itemsCount = random_int(1, 5);
|
|
for ($j = 1; $j <= $itemsCount; $j++) {
|
|
$itemType = $itemTypes[array_rand($itemTypes)];
|
|
$qty = $itemType === 'product' ? random_int(1, 3) : 1;
|
|
$priceGross = (float) random_int(1500, 12000) / 100.0;
|
|
$priceNet = round($priceGross / 1.23, 2);
|
|
$insertItem->execute([
|
|
'order_id' => $orderId,
|
|
'source_item_id' => 'ITEM-' . $orderId . '-' . $j,
|
|
'external_item_id' => 'EXTI-' . random_int(10000, 99999),
|
|
'ean' => (string) random_int(1000000000000, 9999999999999),
|
|
'sku' => 'SKU-' . random_int(10000, 99999),
|
|
'original_name' => ucfirst($itemType) . ' ' . random_int(1, 999),
|
|
'original_code' => strtoupper(substr($itemType, 0, 3)) . '-' . random_int(100, 999),
|
|
'original_price_with_tax' => number_format($priceGross, 2, '.', ''),
|
|
'original_price_without_tax' => number_format($priceNet, 2, '.', ''),
|
|
'media_url' => random_int(1, 100) <= 85
|
|
? ('https://picsum.photos/seed/order-item-' . $orderId . '-' . $j . '/120/120')
|
|
: null,
|
|
'quantity' => number_format((float) $qty, 3, '.', ''),
|
|
'tax_rate' => 23.00,
|
|
'item_status' => 'active',
|
|
'unit' => 'szt.',
|
|
'item_type' => $itemType,
|
|
'source_product_id' => 'P-' . random_int(1000, 9999),
|
|
'source_product_set_id' => null,
|
|
'sort_order' => $j - 1,
|
|
'payload_json' => json_encode(['idx' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$paymentsCount = random_int(1, 2);
|
|
if ($profile === 'realistic') {
|
|
if ($totalPaid <= 0.00) {
|
|
$paymentsCount = random_int(0, 1) === 0 ? 0 : 1;
|
|
} elseif ($totalPaid < $totalGross) {
|
|
$paymentsCount = 1;
|
|
} else {
|
|
$paymentsCount = random_int(1, 100) <= 75 ? 1 : 2;
|
|
}
|
|
}
|
|
if ($paymentsCount > 0) {
|
|
for ($j = 1; $j <= $paymentsCount; $j++) {
|
|
$amount = $paymentsCount === 1 || $j < $paymentsCount
|
|
? round($totalPaid / $paymentsCount, 2)
|
|
: round($totalPaid - round(($totalPaid / $paymentsCount) * ($paymentsCount - 1), 2), 2);
|
|
$insertPayment->execute([
|
|
'order_id' => $orderId,
|
|
'source_payment_id' => 'PAY-' . $orderId . '-' . $j,
|
|
'external_payment_id' => 'EXTP-' . random_int(10000, 99999),
|
|
'payment_type_id' => $paymentType,
|
|
'payment_date' => $orderedAt->add(new DateInterval('PT' . random_int(0, 72) . 'H'))->format('Y-m-d H:i:s'),
|
|
'amount' => number_format(max(0.0, $amount), 2, '.', ''),
|
|
'currency' => $currency,
|
|
'comment' => 'Payment #' . $j,
|
|
'payload_json' => json_encode(['part' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
}
|
|
|
|
$shipmentsCount = random_int(0, 2);
|
|
if ($profile === 'realistic') {
|
|
if (in_array($status, ['shipped', 'delivered', 'returned'], true)) {
|
|
$shipmentsCount = random_int(1, 2);
|
|
} elseif ($status === 'packed') {
|
|
$shipmentsCount = random_int(0, 100) <= 35 ? 1 : 0;
|
|
} else {
|
|
$shipmentsCount = 0;
|
|
}
|
|
}
|
|
for ($j = 1; $j <= $shipmentsCount; $j++) {
|
|
$insertShipment->execute([
|
|
'order_id' => $orderId,
|
|
'source_shipment_id' => 'SHIP-' . $orderId . '-' . $j,
|
|
'external_shipment_id' => 'EXTS-' . random_int(10000, 99999),
|
|
'tracking_number' => 'TRK' . random_int(100000000, 999999999),
|
|
'carrier_provider_id' => $carriers[array_rand($carriers)],
|
|
'posted_at' => $orderedAt->add(new DateInterval('P' . random_int(1, 6) . 'D'))->format('Y-m-d H:i:s'),
|
|
'media_uuid' => null,
|
|
'payload_json' => json_encode(['shipment_no' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$documentsCount = random_int(0, 2);
|
|
if ($profile === 'realistic') {
|
|
if ($isInvoice || in_array($status, ['paid', 'processing', 'packed', 'shipped', 'delivered'], true)) {
|
|
$documentsCount = random_int(1, 100) <= 75 ? 1 : 2;
|
|
} else {
|
|
$documentsCount = random_int(1, 100) <= 15 ? 1 : 0;
|
|
}
|
|
}
|
|
for ($j = 1; $j <= $documentsCount; $j++) {
|
|
$insertDocument->execute([
|
|
'order_id' => $orderId,
|
|
'source_document_id' => 'DOC-' . $orderId . '-' . $j,
|
|
'external_document_id' => 'EXTD-' . random_int(10000, 99999),
|
|
'document_number' => 'FV/' . random_int(1, 9999) . '/' . $now->format('Y'),
|
|
'price_with_tax' => number_format($totalGross, 2, '.', ''),
|
|
'price_without_tax' => number_format($totalNet, 2, '.', ''),
|
|
'currency' => $currency,
|
|
'currency_value' => number_format(1.0, 4, '.', ''),
|
|
'document_type_id' => 'invoice',
|
|
'media_uuid' => null,
|
|
'source_created_at' => $orderedAt->add(new DateInterval('P' . random_int(0, 3) . 'D'))->format('Y-m-d H:i:s'),
|
|
'payload_json' => json_encode(['document_no' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$notesCount = random_int(0, 3);
|
|
for ($j = 1; $j <= $notesCount; $j++) {
|
|
$insertNote->execute([
|
|
'order_id' => $orderId,
|
|
'source_note_id' => 'NOTE-' . $orderId . '-' . $j,
|
|
'note_type' => $noteTypes[array_rand($noteTypes)],
|
|
'created_at_external' => $orderedAt->add(new DateInterval('PT' . random_int(1, 96) . 'H'))->format('Y-m-d H:i:s'),
|
|
'comment' => 'Losowa notatka #' . $j . ' dla zamowienia ' . $sourceOrderId,
|
|
'payload_json' => json_encode(['n' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
if ($profile === 'realistic') {
|
|
$historyPath = buildStatusPathForFinal($status);
|
|
$historyDate = $orderedAt;
|
|
for ($j = 1; $j < count($historyPath); $j++) {
|
|
$historyDate = $historyDate->add(new DateInterval('PT' . random_int(2, 30) . 'H'));
|
|
$insertStatusHistory->execute([
|
|
'order_id' => $orderId,
|
|
'from_status_id' => $historyPath[$j - 1],
|
|
'to_status_id' => $historyPath[$j],
|
|
'changed_at' => $historyDate->format('Y-m-d H:i:s'),
|
|
'change_source' => ['import', 'manual', 'api', 'sync'][array_rand([0, 1, 2, 3])],
|
|
'comment' => 'Auto-seed status change',
|
|
'payload_json' => json_encode(['step' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
} else {
|
|
$historySteps = random_int(2, 6);
|
|
$currentStatus = 'new';
|
|
$historyDate = $orderedAt;
|
|
for ($j = 1; $j <= $historySteps; $j++) {
|
|
$nextStatus = $statuses[array_rand($statuses)];
|
|
$historyDate = $historyDate->add(new DateInterval('PT' . random_int(2, 30) . 'H'));
|
|
$insertStatusHistory->execute([
|
|
'order_id' => $orderId,
|
|
'from_status_id' => $currentStatus,
|
|
'to_status_id' => $nextStatus,
|
|
'changed_at' => $historyDate->format('Y-m-d H:i:s'),
|
|
'change_source' => ['import', 'manual', 'api', 'sync'][array_rand([0, 1, 2, 3])],
|
|
'comment' => 'Auto-seed status change',
|
|
'payload_json' => json_encode(['step' => $j], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
$currentStatus = $nextStatus;
|
|
}
|
|
}
|
|
|
|
shuffle($tagIds);
|
|
$assignCount = random_int(0, min(3, count($tagIds)));
|
|
for ($j = 0; $j < $assignCount; $j++) {
|
|
$insertTagLink->execute([
|
|
'order_id' => $orderId,
|
|
'tag_dict_id' => $tagIds[$j],
|
|
'assigned_at' => $orderedAt->add(new DateInterval('PT' . random_int(1, 20) . 'H'))->format('Y-m-d H:i:s'),
|
|
'payload_json' => json_encode(['auto' => true], JSON_UNESCAPED_UNICODE),
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
}
|
|
|
|
$syncInsert = $pdo->prepare(
|
|
'INSERT INTO integration_order_sync_state (
|
|
integration_id, last_synced_order_updated_at, last_synced_source_order_id, last_synced_external_order_id,
|
|
last_run_at, last_success_at, last_error, created_at, updated_at
|
|
) VALUES (
|
|
:integration_id, :last_synced_order_updated_at, :last_synced_source_order_id, :last_synced_external_order_id,
|
|
:last_run_at, :last_success_at, :last_error, :created_at, :updated_at
|
|
)'
|
|
);
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
$syncInsert->execute([
|
|
'integration_id' => $i,
|
|
'last_synced_order_updated_at' => $now->format('Y-m-d H:i:s'),
|
|
'last_synced_source_order_id' => 'ORD-' . $i . '-' . $now->format('Ymd') . '-9999',
|
|
'last_synced_external_order_id' => 'EXT-' . random_int(100000, 999999),
|
|
'last_run_at' => $now->format('Y-m-d H:i:s'),
|
|
'last_success_at' => $now->format('Y-m-d H:i:s'),
|
|
'last_error' => null,
|
|
'created_at' => $now->format('Y-m-d H:i:s'),
|
|
'updated_at' => $now->format('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$pdo->commit();
|
|
} catch (Throwable $exception) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
throw $exception;
|
|
}
|
|
|
|
echo '[seed] inserted orders and child data' . PHP_EOL;
|
|
}
|
|
|
|
/**
|
|
* @param array<string,int> $weights
|
|
*/
|
|
function weightedChoice(array $weights): string
|
|
{
|
|
$sum = array_sum($weights);
|
|
if ($sum <= 0) {
|
|
throw new RuntimeException('Invalid weights.');
|
|
}
|
|
|
|
$pick = random_int(1, $sum);
|
|
$running = 0;
|
|
foreach ($weights as $value => $weight) {
|
|
$running += $weight;
|
|
if ($pick <= $running) {
|
|
return (string) $value;
|
|
}
|
|
}
|
|
|
|
return (string) array_key_first($weights);
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
function buildStatusPathForFinal(string $finalStatus): array
|
|
{
|
|
$happyPath = ['new', 'confirmed', 'paid', 'processing', 'packed', 'shipped', 'delivered'];
|
|
if ($finalStatus === 'new') {
|
|
return ['new'];
|
|
}
|
|
if (in_array($finalStatus, $happyPath, true)) {
|
|
$index = array_search($finalStatus, $happyPath, true);
|
|
return array_slice($happyPath, 0, ((int) $index) + 1);
|
|
}
|
|
if ($finalStatus === 'cancelled') {
|
|
$variants = [
|
|
['new', 'cancelled'],
|
|
['new', 'confirmed', 'cancelled'],
|
|
['new', 'confirmed', 'paid', 'cancelled'],
|
|
['new', 'confirmed', 'paid', 'processing', 'cancelled'],
|
|
];
|
|
return $variants[array_rand($variants)];
|
|
}
|
|
if ($finalStatus === 'returned') {
|
|
return ['new', 'confirmed', 'paid', 'processing', 'packed', 'shipped', 'delivered', 'returned'];
|
|
}
|
|
|
|
return ['new', $finalStatus];
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
function loadStatusesForSeed(PDO $pdo): array
|
|
{
|
|
$fallback = ['new', 'confirmed', 'paid', 'processing', 'packed', 'shipped', 'delivered', 'cancelled', 'returned'];
|
|
|
|
try {
|
|
$rows = $pdo->query('SELECT code FROM order_statuses WHERE is_active = 1 ORDER BY sort_order ASC, id ASC')->fetchAll(PDO::FETCH_COLUMN);
|
|
} catch (Throwable) {
|
|
return $fallback;
|
|
}
|
|
|
|
if (!is_array($rows)) {
|
|
return $fallback;
|
|
}
|
|
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$code = strtolower(trim((string) $row));
|
|
if ($code === '' || in_array($code, $result, true)) {
|
|
continue;
|
|
}
|
|
$result[] = $code;
|
|
}
|
|
|
|
return $result === [] ? $fallback : $result;
|
|
}
|
|
|
|
function printSummary(PDO $pdo): void
|
|
{
|
|
$tables = [
|
|
'orders',
|
|
'order_addresses',
|
|
'order_items',
|
|
'order_payments',
|
|
'order_shipments',
|
|
'order_documents',
|
|
'order_notes',
|
|
'order_status_history',
|
|
'order_tags_dict',
|
|
'order_tag_links',
|
|
'integration_order_sync_state',
|
|
];
|
|
|
|
echo 'Summary counts:' . PHP_EOL;
|
|
foreach ($tables as $table) {
|
|
$count = (int) $pdo->query('SELECT COUNT(*) FROM ' . $table)->fetchColumn();
|
|
echo ' ' . $table . ': ' . $count . PHP_EOL;
|
|
}
|
|
}
|