update_meta_data(self::PENDING_META, (string) microtime(true)); $order->save_meta_data(); } public static function orderAction(array $actions): array { $actions['gersync_resend'] = __('Выгрузить в 1С при следующем обмене', 'gersync'); return $actions; } /** * Сколько заказов ждут выгрузки в 1С (для сводки в настройках). */ public static function pendingCount(): int { $statuses = (array) Settings::get('order_statuses'); if (!$statuses) { return 0; } return count(wc_get_orders([ 'type' => 'shop_order', 'status' => $statuses, 'limit' => 1000, 'return' => 'ids', 'meta_key' => self::PENDING_META, // phpcs:ignore 'meta_compare' => 'EXISTS', ])); } public static function export(): string { $statuses = (array) Settings::get('order_statuses'); $orders = $statuses ? wc_get_orders([ 'type' => 'shop_order', 'status' => $statuses, 'limit' => self::BATCH, 'orderby' => 'date', 'order' => 'ASC', // meta_key/meta_compare понимают оба хранилища (HPOS и записи), meta_query — только HPOS. 'meta_key' => self::PENDING_META, // phpcs:ignore 'meta_compare' => 'EXISTS', ]) : []; $xml = new OrderXml(); $sent = []; foreach ($orders as $order) { $mark = $order instanceof WC_Order ? (string) $order->get_meta(self::PENDING_META) : ''; if ($mark !== '') { $xml->add($order); $sent[$order->get_id()] = $mark; } } set_transient(self::SENT_KEY, $sent, DAY_IN_SECONDS); Log::debug('Выгружено заказов: ' . count($sent)); return $xml->xml(); } /** * 1С подтвердила приём (mode=success) — снимаем отметку, если заказ не менялся после выгрузки. */ public static function confirmExported(): void { $sent = get_transient(self::SENT_KEY); if (!is_array($sent)) { return; } foreach ($sent as $id => $mark) { $order = wc_get_order((int) $id); if ($order instanceof WC_Order && (string) $order->get_meta(self::PENDING_META) === $mark) { $order->delete_meta_data(self::PENDING_META); $order->update_meta_data(self::EXPORTED_META, time()); $order->save_meta_data(); } } delete_transient(self::SENT_KEY); } // --- Статусы из 1С -------------------------------------------------------- public static function importStatuses(string $file): void { $reader = Xml::open($file); self::$fromOneC = true; try { $ok = $reader->read(); while ($ok) { if ($reader->nodeType === XMLReader::ELEMENT && $reader->localName === 'Документ') { self::applyDocument(Xml::expand($reader)); $ok = $reader->next(); continue; } $ok = $reader->read(); } } finally { self::$fromOneC = false; $reader->close(); } } private static function applyDocument(SimpleXMLElement $document): void { $order = self::findOrder($document); if (!$order) { return; } $requisites = []; foreach (Xml::nodes($document, 'ЗначенияРеквизитов', 'ЗначениеРеквизита') as $req) { $requisites[mb_strtolower(Xml::text($req, 'Наименование'))] = Xml::text($req, 'Значение'); } $new = self::statusFromRequisites($requisites, $order); if ($new !== null && !$order->has_status($new)) { $order->update_status($new, __('Статус обновлён из 1С.', 'gersync')); Log::debug(sprintf('Заказ #%d: статус из 1С → %s', $order->get_id(), $new)); } } /** * Только заказы, которые сайт сам отдавал в 1С, — чтобы номер документа 1С * случайно не совпал с ID чужого заказа. */ private static function findOrder(SimpleXMLElement $document): ?WC_Order { foreach ([Xml::text($document, 'Ид'), Xml::text($document, 'Номер')] as $candidate) { if (!ctype_digit($candidate)) { continue; } $order = wc_get_order((int) $candidate); if ($order instanceof WC_Order && !$order instanceof \WC_Order_Refund && $order->meta_exists(self::EXPORTED_META)) { return $order; } } return null; } /** * @param array $r реквизиты документа (ключи в нижнем регистре) */ private static function statusFromRequisites(array $r, WC_Order $order): ?string { $status = mb_strtolower($r['статус заказа'] ?? ''); if (($r['отменен'] ?? '') === 'true' || ($r['пометкаудаления'] ?? '') === 'true') { return 'cancelled'; } if (($r['дата отгрузки по 1с'] ?? '') !== '' || in_array($status, ['отгружен', 'закрыт', 'выполнен'], true)) { return 'completed'; } if (($r['дата оплаты по 1с'] ?? '') !== '' && $order->has_status(['pending', 'on-hold'])) { return 'processing'; } return null; } }