update
This commit is contained in:
3
.vscode/ftp-kr.json
vendored
3
.vscode/ftp-kr.json
vendored
@@ -15,6 +15,7 @@
|
||||
"/.vscode",
|
||||
"/.serena",
|
||||
"/.claude",
|
||||
"CLAUDE.md"
|
||||
"CLAUDE.md",
|
||||
"/changelog"
|
||||
]
|
||||
}
|
||||
12
.vscode/ftp-kr.sync.cache.json
vendored
12
.vscode/ftp-kr.sync.cache.json
vendored
@@ -97,6 +97,18 @@
|
||||
"modified": false
|
||||
},
|
||||
"files": {},
|
||||
"ga-db.txt": {
|
||||
"type": "-",
|
||||
"size": 2604,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"ga.txt": {
|
||||
"type": "-",
|
||||
"size": 4111,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
".gitignore": {
|
||||
"type": "-",
|
||||
"size": 7,
|
||||
|
||||
@@ -70,6 +70,6 @@ When creating or modifying overrides, PrestaShop also needs to rebuild the class
|
||||
|
||||
## Custom Assistant Command
|
||||
|
||||
- If the user writes `zapisz-changelog`, create or update monthly changelog file `changelog/YYYY-MM.md` (based on current date).
|
||||
- If the user writes `zapisz-changelog`, create or update monthly changelog file `changelog/YYYY-MM-DD.md` (based on current date).
|
||||
- Add an entry for the current day with a concise summary of code changes made in the current session.
|
||||
- Include touched file paths and relevant line references where possible.
|
||||
|
||||
15
changelog/2026-03-21.md
Normal file
15
changelog/2026-03-21.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 2026-03-21
|
||||
|
||||
## Zmiany
|
||||
|
||||
- Wywołano komendę `zapisz-changelog`.
|
||||
- W repo są aktywne zmiany: rozbudowa `modules/empikmarketplace/src/Processor/OrderProcessor.php` (powiadomienia o błędach importu EMPIK: wielu odbiorców, deduplikacja wysyłki, zapis rejestru powiadomień), dodanie `modules/empikmarketplace/data/import_error_notifications.json`, aktualizacja `CLAUDE.md` (format `zapisz-changelog` na `YYYY-MM-DD`) oraz przejście z `changelog/2026-03.md` na pliki dzienne (`changelog/2026-03-19.md`, `changelog/2026-03-21.md`).
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
- `CLAUDE.md`
|
||||
- `changelog/2026-03.md` (usunięty)
|
||||
- `changelog/2026-03-19.md` (nowy)
|
||||
- `changelog/2026-03-21.md`
|
||||
- `modules/empikmarketplace/data/import_error_notifications.json` (nowy)
|
||||
- `modules/empikmarketplace/src/Processor/OrderProcessor.php`
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"40102298078155-A": {
|
||||
"sent_at": "2026-03-21 18:41:03"
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,12 @@ class OrderProcessor
|
||||
{
|
||||
const CODE_WAITING_ACCEPTANCE = 'WAITING_ACCEPTANCE';
|
||||
const CODE_SHIPPING = 'SHIPPING';
|
||||
const ERROR_NOTIFICATION_EMAIL = 'jacek.pyziak@project-pro.pl';
|
||||
const ERROR_NOTIFICATION_EMAILS = [
|
||||
'jacek.pyziak@project-pro.pl',
|
||||
'biuro@interblue.pl',
|
||||
'wxkwasnik@gmail.com',
|
||||
];
|
||||
const ERROR_NOTIFICATION_STORE_FILE = 'import_error_notifications.json';
|
||||
|
||||
/** @var EmpikClientFactory */
|
||||
protected $empikClientFactory;
|
||||
@@ -138,23 +143,37 @@ class OrderProcessor
|
||||
protected function sendFailureNotification($empikOrderId, Exception $exception, array $orderData = [])
|
||||
{
|
||||
try {
|
||||
$empikNotificationOrderId = $this->resolveEmpikNotificationOrderId($empikOrderId, $orderData);
|
||||
|
||||
if ($this->wasFailureNotificationSent($empikNotificationOrderId)) {
|
||||
$this->logger->logError(sprintf(
|
||||
'Skipping duplicate EMPIK import error notification for order [%s] - notification already sent.',
|
||||
$empikNotificationOrderId
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$shopName = Configuration::get('PS_SHOP_NAME');
|
||||
$subject = sprintf('[%s] Blad importu zamowienia EMPIK: %s', $shopName, $empikOrderId);
|
||||
$orderContext = $this->buildOrderContext($orderData);
|
||||
|
||||
Mail::send(
|
||||
(int) Configuration::get('PS_LANG_DEFAULT'),
|
||||
'empik_import_error',
|
||||
$subject,
|
||||
[
|
||||
$templateVars = [
|
||||
'{empik_order_id}' => $empikOrderId,
|
||||
'{error_message}' => $exception->getMessage(),
|
||||
'{error_date}' => date('Y-m-d H:i:s'),
|
||||
'{order_context}' => $orderContext,
|
||||
'{order_context_html}' => nl2br(htmlspecialchars($orderContext, ENT_QUOTES, 'UTF-8')),
|
||||
'{stack_trace}' => nl2br($exception->getTraceAsString()),
|
||||
],
|
||||
self::ERROR_NOTIFICATION_EMAIL,
|
||||
];
|
||||
|
||||
$notificationSent = false;
|
||||
foreach ($this->getErrorNotificationEmails() as $recipientEmail) {
|
||||
$sent = Mail::send(
|
||||
(int) Configuration::get('PS_LANG_DEFAULT'),
|
||||
'empik_import_error',
|
||||
$subject,
|
||||
$templateVars,
|
||||
$recipientEmail,
|
||||
null,
|
||||
Configuration::get('PS_SHOP_EMAIL'),
|
||||
$shopName,
|
||||
@@ -162,11 +181,153 @@ class OrderProcessor
|
||||
null,
|
||||
_PS_MODULE_DIR_ . 'empikmarketplace/mails/'
|
||||
);
|
||||
|
||||
if (!$sent) {
|
||||
$this->logger->logError(sprintf(
|
||||
'Failed to send EMPIK import error notification for order [%s] to [%s].',
|
||||
$empikOrderId,
|
||||
$recipientEmail
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
$notificationSent = true;
|
||||
}
|
||||
|
||||
if ($notificationSent) {
|
||||
$this->markFailureNotificationAsSent($empikNotificationOrderId);
|
||||
} else {
|
||||
$this->logger->logError(sprintf(
|
||||
'EMPIK import error notification was not sent to any recipient for order [%s].',
|
||||
$empikOrderId
|
||||
));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logger->logError(sprintf('Error sending failure notification email: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getErrorNotificationEmails()
|
||||
{
|
||||
return self::ERROR_NOTIFICATION_EMAILS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplication key must always be EMPIK order_id from API payload.
|
||||
*
|
||||
* @param string $fallbackOrderId
|
||||
* @param array $orderData
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveEmpikNotificationOrderId($fallbackOrderId, array $orderData)
|
||||
{
|
||||
if (!empty($orderData['order_id']) && is_string($orderData['order_id'])) {
|
||||
return $orderData['order_id'];
|
||||
}
|
||||
|
||||
if (!empty($fallbackOrderId) && $fallbackOrderId !== 'unknown') {
|
||||
return $fallbackOrderId;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $empikOrderId
|
||||
* @return bool
|
||||
*/
|
||||
protected function wasFailureNotificationSent($empikOrderId)
|
||||
{
|
||||
if (!$empikOrderId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$notifications = $this->loadNotificationRegistry();
|
||||
|
||||
return isset($notifications[$empikOrderId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $empikOrderId
|
||||
* @return void
|
||||
*/
|
||||
protected function markFailureNotificationAsSent($empikOrderId)
|
||||
{
|
||||
if (!$empikOrderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notifications = $this->loadNotificationRegistry();
|
||||
$notifications[$empikOrderId] = [
|
||||
'sent_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
$this->saveNotificationRegistry($notifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getNotificationRegistryPath()
|
||||
{
|
||||
return _PS_MODULE_DIR_ . 'empikmarketplace/data/' . self::ERROR_NOTIFICATION_STORE_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function loadNotificationRegistry()
|
||||
{
|
||||
$path = $this->getNotificationRegistryPath();
|
||||
|
||||
if (!file_exists($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = @file_get_contents($path);
|
||||
if ($raw === false || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $notifications
|
||||
* @return void
|
||||
*/
|
||||
protected function saveNotificationRegistry(array $notifications)
|
||||
{
|
||||
$path = $this->getNotificationRegistryPath();
|
||||
$directory = dirname($path);
|
||||
|
||||
if (!is_dir($directory)) {
|
||||
@mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
$json = json_encode($notifications, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($json === false) {
|
||||
$this->logger->logError('Unable to serialize EMPIK import error notification registry.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (@file_put_contents($path, $json, LOCK_EX) === false) {
|
||||
$this->logger->logError(sprintf(
|
||||
'Unable to save EMPIK import error notification registry to [%s].',
|
||||
$path
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $orderData
|
||||
* @return string
|
||||
@@ -178,14 +339,35 @@ class OrderProcessor
|
||||
}
|
||||
|
||||
$customer = isset($orderData['customer']) && is_array($orderData['customer']) ? $orderData['customer'] : [];
|
||||
$shippingAddress = isset($customer['shipping_address']) && is_array($customer['shipping_address']) ? $customer['shipping_address'] : [];
|
||||
$billingAddress = isset($customer['billing_address']) && is_array($customer['billing_address']) ? $customer['billing_address'] : [];
|
||||
$additionalFields = isset($orderData['order_additional_fields']) && is_array($orderData['order_additional_fields']) ? $orderData['order_additional_fields'] : [];
|
||||
$customerEmail = $this->extractAdditionalFieldValue($additionalFields, 'customer-email');
|
||||
|
||||
$summary = [
|
||||
'order_id' => isset($orderData['order_id']) ? $orderData['order_id'] : null,
|
||||
'order_state' => isset($orderData['order_state']) ? $orderData['order_state'] : null,
|
||||
'created_date' => isset($orderData['created_date']) ? $orderData['created_date'] : null,
|
||||
'updated_date' => isset($orderData['updated_date']) ? $orderData['updated_date'] : null,
|
||||
'currency_iso_code' => isset($orderData['currency_iso_code']) ? $orderData['currency_iso_code'] : null,
|
||||
'payment_type' => isset($orderData['payment_type']) ? $orderData['payment_type'] : null,
|
||||
'shipping_type_code' => isset($orderData['shipping_type_code']) ? $orderData['shipping_type_code'] : null,
|
||||
'shipping_type_label' => isset($orderData['shipping_type_label']) ? $orderData['shipping_type_label'] : null,
|
||||
'total_price' => isset($orderData['total_price']) ? $orderData['total_price'] : null,
|
||||
'shipping_price' => isset($orderData['shipping_price']) ? $orderData['shipping_price'] : null,
|
||||
'order_lines_count' => isset($orderData['order_lines']) && is_array($orderData['order_lines']) ? count($orderData['order_lines']) : 0,
|
||||
'has_shipping_address' => !empty($customer['shipping_address']) ? 1 : 0,
|
||||
'has_billing_address' => !empty($customer['billing_address']) ? 1 : 0,
|
||||
'customer_email' => $customerEmail,
|
||||
'has_shipping_address' => !empty($shippingAddress) ? 1 : 0,
|
||||
'has_billing_address' => !empty($billingAddress) ? 1 : 0,
|
||||
'customer' => [
|
||||
'firstname' => isset($customer['firstname']) ? $customer['firstname'] : null,
|
||||
'lastname' => isset($customer['lastname']) ? $customer['lastname'] : null,
|
||||
'email' => $customerEmail,
|
||||
],
|
||||
'shipping_address' => $this->buildAddressContext($shippingAddress),
|
||||
'billing_address' => $this->buildAddressContext($billingAddress),
|
||||
'order_lines' => $this->buildOrderLinesContext(isset($orderData['order_lines']) && is_array($orderData['order_lines']) ? $orderData['order_lines'] : []),
|
||||
'order_additional_fields' => $this->buildAdditionalFieldsContext($additionalFields),
|
||||
];
|
||||
|
||||
$json = json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
@@ -193,6 +375,96 @@ class OrderProcessor
|
||||
return $json !== false ? $json : 'Unable to serialize order payload summary.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $addressData
|
||||
* @return array|null
|
||||
*/
|
||||
protected function buildAddressContext(array $addressData)
|
||||
{
|
||||
if (empty($addressData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'firstname' => isset($addressData['firstname']) ? $addressData['firstname'] : null,
|
||||
'lastname' => isset($addressData['lastname']) ? $addressData['lastname'] : null,
|
||||
'company' => isset($addressData['company']) ? $addressData['company'] : null,
|
||||
'street_1' => isset($addressData['street_1']) ? $addressData['street_1'] : null,
|
||||
'street_2' => isset($addressData['street_2']) ? $addressData['street_2'] : null,
|
||||
'city' => isset($addressData['city']) ? $addressData['city'] : null,
|
||||
'zip_code' => isset($addressData['zip_code']) ? $addressData['zip_code'] : null,
|
||||
'country_iso_code' => isset($addressData['country_iso_code']) ? $addressData['country_iso_code'] : null,
|
||||
'phone' => isset($addressData['phone']) ? $addressData['phone'] : null,
|
||||
'additional_info' => isset($addressData['additional_info']) ? $addressData['additional_info'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $orderLines
|
||||
* @return array
|
||||
*/
|
||||
protected function buildOrderLinesContext(array $orderLines)
|
||||
{
|
||||
$context = [];
|
||||
|
||||
foreach ($orderLines as $line) {
|
||||
$context[] = [
|
||||
'order_line_id' => isset($line['order_line_id']) ? $line['order_line_id'] : null,
|
||||
'offer_sku' => isset($line['offer_sku']) ? $line['offer_sku'] : null,
|
||||
'offer_id' => isset($line['offer_id']) ? $line['offer_id'] : null,
|
||||
'product_title' => isset($line['product_title']) ? $line['product_title'] : null,
|
||||
'quantity' => isset($line['quantity']) ? $line['quantity'] : null,
|
||||
'price_unit' => isset($line['price_unit']) ? $line['price_unit'] : null,
|
||||
'price' => isset($line['price']) ? $line['price'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $additionalFields
|
||||
* @return array
|
||||
*/
|
||||
protected function buildAdditionalFieldsContext(array $additionalFields)
|
||||
{
|
||||
$context = [];
|
||||
|
||||
foreach ($additionalFields as $field) {
|
||||
if (!is_array($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context[] = [
|
||||
'code' => isset($field['code']) ? $field['code'] : null,
|
||||
'type' => isset($field['type']) ? $field['type'] : null,
|
||||
'value' => isset($field['value']) ? $field['value'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $additionalFields
|
||||
* @param string $code
|
||||
* @return string|null
|
||||
*/
|
||||
protected function extractAdditionalFieldValue(array $additionalFields, $code)
|
||||
{
|
||||
foreach ($additionalFields as $field) {
|
||||
if (!is_array($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($field['code']) && $field['code'] === $code) {
|
||||
return isset($field['value']) ? $field['value'] : null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function accept(EmpikOrderWrapper $empikOrder)
|
||||
{
|
||||
$acceptLines = $empikOrder->getAcceptanceLines();
|
||||
|
||||
Reference in New Issue
Block a user