<?php
// api.php — sotuv + transport + Xtrace aggregatsiya API
declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');
date_default_timezone_set('Asia/Tashkent');

session_start();

require_once __DIR__ . '/db.php';        // $pdo
require_once __DIR__ . '/auth_stub.php'; // current_user_id(), current_user_name()

/**
 * XTRACE / ASL BELGISI OPEN API konfiguratsiyasi
 */
$API_TOKEN         = '4e0d9f49-0eec-4a05-93ec-45eed1f3cc62';
$TIN               = '204083198';
$SERVER            = 'https://xtrace.aslbelgisi.uz';
$BUSINESS_PLACE_ID = 17800;

// SSCC kompaniya prefiksi (10 xonali)
// SSCC: 1 (extension) + 9 (company body) + 7 (serial) + 1 (check) = 18 raqam
// Biz keyiniga boshiga '00' qo‘shib 20 belgili SSCC20 qilamiz
const SSCC_COMPANY_PREFIX = '9478008327';

// Xtrace / ASL endpointlar
// POST hujjat yuborish:
const XTRACE_AGGREGATION_PATH = '/public/api/v1/doc/aggregation';
// GET hujjat statusini tekshirish:
const XTRACE_DOC_STATUS_PATH  = '/public/api/v1/doc/storage/docs/';

/** @var PDO $pdo  db.php ichida keladi */
if (!isset($pdo)) {
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => 'DB ulanishi yo‘q'], JSON_UNESCAPED_UNICODE);
    exit;
}

/* ===========================================================
 *  JSON YORDAMCHI
 * =========================================================== */

function json_ok(array $data = [], int $code = 200): void
{
    http_response_code($code);
    echo json_encode(['status' => 'ok'] + $data, JSON_UNESCAPED_UNICODE);
    exit;
}

function json_error(string $message, int $code = 400, array $extra = []): void
{
    http_response_code($code);
    echo json_encode([
        'status'  => 'error',
        'message' => $message,
    ] + $extra, JSON_UNESCAPED_UNICODE);
    exit;
}

/* ===========================================================
 *  USER ID YORDAMCHI
 * =========================================================== */

function current_user_id_safe(): int
{
    $id = current_user_id();
    if (!$id) {
        return 1; // sessiya yo‘q bo‘lsa ham 1
    }
    return (int)$id;
}

/* ===========================================================
 *  SSCC CHECK DIGIT (GS1 mod10)
 * =========================================================== */

function calculateCheckDigit(string $digits): int
{
    $sum = 0;
    $len = strlen($digits);
    // o‘ngdan chapga: GS1 bo‘yicha (o‘ngdan: 3,1,3,1,...)
    for ($i = $len - 1, $pos = 1; $i >= 0; $i--, $pos++) {
        $n = (int)$digits[$i];
        $sum += ($pos % 2 === 1) ? ($n * 3) : $n;
    }
    $mod = $sum % 10;
    return $mod === 0 ? 0 : 10 - $mod;
}

/* ===========================================================
 *  SSCC GENERATSIYA (HAR BIR SOTUV UCHUN)
 * =========================================================== */

/**
 * sales.id uchun yagona SSCC20 (AI 00 bilan 20 belgili) generatsiya qiladi.
 * Agar oldin bor bo‘lsa — o‘shani qaytaradi.
 */
function ensure_sscc_for_sale(PDO $pdo, int $saleId): string
{
    $stmt = $pdo->prepare("SELECT sscc20 FROM sales WHERE id = :id FOR UPDATE");
    $stmt->execute([':id' => $saleId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$row) {
        json_error("Sotuv topilmadi (ID: {$saleId})", 404);
    }

    if (!empty($row['sscc20'])) {
        // allaqachon generatsiya qilingan SSCC20 (00 + 18 raqam)
        return $row['sscc20'];
    }

    // Company prefix: 10 xonali (biz undan extension + companyBody yasaymiz)
    $rawPrefix   = SSCC_COMPANY_PREFIX;      // masalan '9478008327'
    $baseExt     = (int)$rawPrefix[0];       // 9
    $companyBody = substr($rawPrefix, 1);    // '478008327' (9 raqam)

    // Soddalashtirilgan extension
    $extDigit = $baseExt; // hozircha faqat '9'

    if ($extDigit < 0 || $extDigit > 9) {
        json_error('SSCC extension noto‘g‘ri diapazonda', 500);
    }

    // Serial: 7 xonali, salesId dan olinadi
    $serialNumeric = $saleId;
    if ($serialNumeric <= 0) {
        $serialNumeric = 1;
    }
    $serial = str_pad((string)$serialNumeric, 7, '0', STR_PAD_LEFT); // 0000123

    // 1 (ext) + 9 (companyBody) + 7 (serial) = 17 raqam
    $body17 = $extDigit . $companyBody . $serial;

    $checkDigit = calculateCheckDigit($body17);
    $full18     = $body17 . $checkDigit; // 18 raqam

    // SSCC20: AI (00) + 18 raqam
    $sscc20 = '00' . $full18;

    // Dublikat tekshiruv
    $chk = $pdo->prepare("SELECT COUNT(*) FROM sales WHERE sscc20 = :s");
    $chk->execute([':s' => $sscc20]);
    if ((int)$chk->fetchColumn() > 0) {
        json_error("SSCC20 dublikat topildi, iltimos administrator bilan bog‘laning", 500);
    }

    // Bazaga saqlaymiz
    $upd = $pdo->prepare("UPDATE sales SET sscc20 = :s WHERE id = :id");
    $upd->execute([
        ':s'  => $sscc20,
        ':id' => $saleId,
    ]);

    return $sscc20;
}

/* ===========================================================
 *  ASL xato matnlarini chiroyli qilish
 * =========================================================== */

function asl_build_error_text(?array $apiResp): string
{
    if (!$apiResp) {
        return '';
    }

    // 1) globalErrors bo‘lsa – u yerdan olamiz
    if (isset($apiResp['globalErrors']) && is_array($apiResp['globalErrors'])) {
        $parts = [];
        foreach ($apiResp['globalErrors'] as $err) {
            $p = [];
            if (!empty($err['errorCode'])) {
                $p[] = 'Kod: ' . $err['errorCode'];
            }
            if (!empty($err['error'])) {
                $p[] = $err['error'];
            }
            if (isset($err['context']['description']) && $err['context']['description'] !== '') {
                $p[] = $err['context']['description'];
            }
            if (!empty($p)) {
                $parts[] = implode(' — ', $p);
            }
        }
        if (!empty($parts)) {
            return implode(' | ', $parts);
        }
    }

    // 2) context bo‘lsa (masalan invalid-input-parameter)
    if (isset($apiResp['context']) && is_array($apiResp['context'])) {
        $ctx      = $apiResp['context'];
        $msgParts = [];
        if (!empty($ctx['_message'])) {
            $msgParts[] = $ctx['_message'];
        }
        if (!empty($ctx['description'])) {
            $msgParts[] = $ctx['description'];
        }
        if (!empty($ctx['specificError'])) {
            $msgParts[] = $ctx['specificError'];
        }
        if (!empty($msgParts)) {
            return implode(' | ', $msgParts);
        }
    }

    // 3) oddiy code + errorId bo‘lsa
    $p = [];
    if (!empty($apiResp['code'])) {
        $p[] = 'Kodni identifikatori: ' . $apiResp['code'];
    }
    if (!empty($apiResp['errorId'])) {
        $p[] = 'errorId: ' . $apiResp['errorId'];
    }

    return implode(' | ', $p);
}

/**
 * HTTP kod + ASL javobiga qarab, foydalanuvchiga ko‘rinadigan o‘zbekcha xabar
 */
function asl_map_error_message(int $httpCode, ?array $apiResp): string
{
    $raw     = asl_build_error_text($apiResp);
    $errorId = $apiResp['errorId'] ?? null;
    $suffix  = $errorId ? " (ASL errorId: {$errorId})" : '';

    if ($httpCode === 401) {
        return "ASL BELGISI bilan ulanishda avtorizatsiya xatosi. API kalit (token) yaroqsiz yoki muddati tugagan. Yangi token o‘rnatib qayta urinib ko‘ring." . $suffix;
    }

    if ($httpCode === 403) {
        return "ASL BELGISI sizga bu amaliyotni bajarishga ruxsat bermadi (403). Loyiha profili va huquqlarni tekshiring." . $suffix;
    }

    if ($httpCode === 404) {
        return "ASL BELGISI tomonda kerakli obyekt topilmadi (404). Kodlar yoki hujjat parametrlari noto‘g‘ri bo‘lishi mumkin." . $suffix;
    }

    if ($httpCode === 429) {
        return "ASL BELGISI serveriga juda tez-tez so‘rov yuborildi (429). Bir oz kutib, qayta urinib ko‘ring." . $suffix;
    }

    if ($httpCode >= 500) {
        return "ASL BELGISI serverida ichki xato yuz berdi. Birozdan so‘ng yana urinib ko‘ring." . $suffix;
    }

    if ($httpCode === 400) {
        if (isset($apiResp['code']) && $apiResp['code'] === 'invalid-input-parameter') {
            $msg = $apiResp['context']['_message'] ?? '';
            if (strpos($msg, 'Duplicates found') !== false) {
                return "Yuborilgan kodlar ichida takrorlangan (dublikat) kodlar bor. Iltimos, kodlarni tekshirib, qayta yuboring." . $suffix;
            }
        }

        if ($raw !== '') {
            return "ASL BELGISI yuborilgan maʼlumotlarda xatolik topdi: {$raw}." . $suffix;
        }
        return "ASL BELGISI yuborilgan maʼlumotlarda xatolik topdi (400). Kodlarni va parametrlarni qayta tekshirib ko‘ring." . $suffix;
    }

    if ($raw !== '') {
        return "ASL BELGISI bilan ishlashda nomaʼlum xato (HTTP {$httpCode}). Tafsilot: {$raw}." . $suffix;
    }

    return "ASL BELGISI bilan ishlashda nomaʼlum xato (HTTP {$httpCode})." . $suffix;
}

/* ===========================================================
 *  ASL / XTRACE AGGREGATSIYA — SOTUV UCHUN
 * =========================================================== */

/**
 * Sotuvdagi barcha KMs (iste'mol kodlar) ro‘yxati va transport SSCC bo‘yicha
 * ASL BELGISI / XTRACE doc/aggregation ga hujjat yuboradi.
 *
 * Kirish:
 *  - $codes: sales_items.gs1_code lar (yoki codes.gs1_text lar) massivi
 *  - $fullGroupCode: SSCC20 (AI 00 bilan) — unitSerialNumber
 */
function xtrace_send_aggregation_for_sale(
    array $codes,
    string $fullGroupCode,
    int $businessPlaceId,
    string $apiToken,
    string $serverBase
): array {
    $fullGroupCode = trim($fullGroupCode);

    if ($fullGroupCode === '') {
        return ['ok' => false, 'message' => "Transport SSCC kodi bo'sh"];
    }

    // --- Kodlarni tayyorlab olamiz (SSCC larni SKIP) ---
    $codesRaw = [];
    foreach ($codes as $row) {
        // 1) Asl GS1 matnini olamiz (array bo‘lsa ham, string bo‘lsa ham)
        if (is_array($row)) {
            $original = $row['gs1_code'] ?? $row['gs1_text'] ?? '';
        } else {
            $original = (string)$row;
        }

        $original = trim($original);
        if ($original === '') {
            continue;
        }

        // GS (ASCII 29) belgilarini olib tashlaymiz
        $plain = str_replace(["\x1D", "\\u001d"], '', $original);

        // Faqat raqamlardan iborat ko‘rinishini ajratib olamiz
        $digits = preg_replace('/\D/', '', $plain);

        // === SSCC20 / SSCC18 ni aniqlash va SKIP qilish ===
        $isSscc = false;

        if (strlen($digits) === 20 && strpos($digits, '00') === 0) {
            // 00xxxxxxxxxxxxxxxxxx
            $isSscc = true;
        } elseif (strlen($digits) === 18 && (strpos($plain, '(00') === 0 || strpos($plain, '00') === 0)) {
            // (00)xxxxxxxxxxxxxxxxxx yoki 00xxxxxxxxxxxxxxxxxx
            $isSscc = true;
        }

        if ($isSscc) {
            // Bu SSCC kod — aggregation ichidagi codes massiviga KIRMAYDI
            continue;
        }

        // Endi iste'molchi kodni tayyorlaymiz:
        $kod = $plain;

        // Oxirgi 6 belgini ko‘p hollarda kontrol uchun ishlatiladi — kesib tashlaymiz
        if (strlen($kod) > 6) {
            $kod = substr($kod, 0, -6);
        }

        // Yana bir marta GS belgilari bo‘lsa, olib tashlaymiz
        $kod = str_replace(["\x1D", "\\u001d"], '', $kod);
        $kod = trim($kod);

        if ($kod !== '') {
            $codesRaw[] = $kod;
        }
    }

    if (empty($codesRaw)) {
        return ['ok' => false, 'message' => "API uchun yuboriladigan kodlar bo'sh (faqat SSCC lar yoki bo‘sh qiymatlar bo‘lishi mumkin)"];
    }

    // Takrorlarni olib tashlaymiz — ASL ichida dublikat chiqmasin
    $codesPrepared = array_values(array_unique($codesRaw));

    // Aggregatsiya blok sig‘imi: sotuv bo‘yicha bir transport konteyner
    $capacity = count($codesPrepared);

    // --- doc/aggregation uchun documentBody ni yig‘amiz ---
    $docBody = [
        'aggregationUnits' => [
            [
                'aggregationItemsCount'    => count($codesPrepared),
                'aggregationUnitCapacity'  => $capacity,
                'codes'                    => $codesPrepared,
                'unitSerialNumber'         => $fullGroupCode,  // SSCC20
            ],
        ],
        'businessPlaceId' => $businessPlaceId,
        'documentDate'    => date('c'), // O‘zbekiston vaqti bilan ISO 8601
    ];

    $payload = [
        'documentBody' => base64_encode(json_encode($docBody, JSON_UNESCAPED_UNICODE)),
        // 'signature' => '...', // E-imzo kerak bo‘lsa keyin qo‘shasan
    ];

    $jsonData = json_encode($payload, JSON_UNESCAPED_UNICODE);
    $apiUrl   = rtrim($serverBase, '/') . XTRACE_AGGREGATION_PATH;

    // --- CURL orqali yuboramiz ---
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $apiUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $apiToken,
        'Content-Type: application/json;charset=UTF-8',
        'Accept: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

    $response  = curl_exec($ch);
    $httpCode  = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($response === false) {
        return [
            'ok'      => false,
            'message' => "ASL BELGISI API ga ulanishda xato: " . $curlError,
        ];
    }

    $apiResp = json_decode($response, true);

    // --- Muvaffaqiyatli holat: documentId qaytadi ---
    if ($httpCode === 201 || $httpCode === 200) {
        $documentId = $apiResp['documentId'] ?? null;

        return [
            'ok'          => true,
            'document_id' => $documentId,
            'api_raw'     => $apiResp,
        ];
    }

    // --- Xatolarni ishlash ---
    $humanMessage = asl_map_error_message($httpCode, $apiResp);

    // Qaysi kodlar xato bo‘lganini aniqlashga harakat qilamiz
    $errorCodeValues = [];

    // 1) Yangi format: context.value
    if (isset($apiResp['context']['value']) && is_string($apiResp['context']['value'])) {
        $errorCodeValues[] = trim($apiResp['context']['value']);
    }

    // 2) globalErrors bo‘lsa
    if (isset($apiResp['globalErrors']) && is_array($apiResp['globalErrors'])) {
        foreach ($apiResp['globalErrors'] as $error) {
            if (isset($error['context']['value']) && $error['context']['value'] !== '') {
                $errorCodeValues[] = trim($error['context']['value']);
            }

            $errStr = $error['error'] ?? '';
            if ($errStr !== '') {
                if (preg_match_all('/\bCode\s+([0-9A-Za-z*\'<>;=+\/]+)/u', $errStr, $m1)) {
                    foreach ($m1[1] as $code) {
                        $errorCodeValues[] = $code;
                    }
                }
                // FIX: subject sifatida $errStr dan foydalanamiz
                if (preg_match_all('/Duplicate codes\s+([0-9A-Za-z*\'<>;=+\/\s]+)/u', $errStr, $m2)) {
                    $codesFound = preg_split('/\s+/', trim($m2[1][0]));
                    foreach ($codesFound as $code) {
                        if ($code !== '') {
                            $errorCodeValues[] = $code;
                        }
                    }
                }
            }
        }
    }

    $errorCodeValues = array_values(array_unique($errorCodeValues));

    // Har bir yuborilgan kod uchun flag
    $codesOut = [];
    foreach ($codesPrepared as $c) {
        $codesOut[] = [
            'value'   => $c,
            'isError' => in_array($c, $errorCodeValues, true),
        ];
    }

    return [
        'ok'                     => false,
        'message'                => $humanMessage,
        'codes'                  => $codesOut,
        'api_http_code'          => $httpCode,
        'api_error_code'         => $apiResp['code']    ?? null,
        'api_error_id'           => $apiResp['errorId'] ?? null,
        'api_error_message_raw'  => asl_build_error_text($apiResp),
        'api_raw'                => $apiResp,
    ];
}

/* ===========================================================
 *  XTRACE STATUS TEKSHIRISH (GET) — HELPER
 * =========================================================== */

function xtrace_check_doc_status(string $documentId, string $apiToken, string $serverBase): array
{
    $url = rtrim($serverBase, '/') . XTRACE_DOC_STATUS_PATH . rawurlencode($documentId);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $apiToken,
        'Content-Type: application/json;charset=UTF-8',
        'Accept: application/json',
    ]);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

    $response  = curl_exec($ch);
    $httpCode  = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($response === false) {
        return [
            'ok'       => false,
            'error'    => "ASL BELGISI status API ga ulanishda xato: " . $curlError,
            'httpCode' => null,
            'data'     => null,
            'raw'      => null,
        ];
    }

    $data = json_decode($response, true);
    if (!is_array($data)) {
        return [
            'ok'       => false,
            'error'    => "ASL BELGISI status javobi JSON emas",
            'httpCode' => $httpCode,
            'data'     => null,
            'raw'      => $response,
        ];
    }

    $ok = ($httpCode >= 200 && $httpCode < 300);

    return [
        'ok'       => $ok,
        'error'    => null,
        'httpCode' => $httpCode,
        'data'     => $data,
        'raw'      => $response,
    ];
}

/**
 * Xtrace status kodini o‘zbekcha matnga aylantirish
 */
function xtrace_build_status_text(?string $statusCode): string
{
    if ($statusCode === null || $statusCode === '') {
        return '';
    }

    switch ($statusCode) {
        case 'SUCCESS':
        case 'PROCESSED':
            return "Hujjat muvaffaqiyatli qayta ishlangan";
        case 'IN_PROGRESS':
        case 'PROCESSING':
            return "Hujjat qayta ishlanmoqda";
        case 'ERROR':
            return "Hujjatni qayta ishlashda xatolar bor";
        case 'REJECTED':
            return "Hujjat ASL BELGISI tomonidan rad etilgan";
        case 'FAILED':
            return "Hujjatni qayta ishlash muvaffaqiyatsiz yakunlangan";
        default:
            return "Hujjat statusi: {$statusCode}";
    }
}

/**
 * Xatolik bo‘lgan sotuvni "qayta sotish" uchun orqaga qaytarish:
 *  - codes: is_sold = 0, is_sale = 0, sale_id = NULL
 *  - blocks: is_sale = 0
 *  - sales: agg_doc_id NI O‘ZGARTIRMAYMIZ (docId kerak bo‘lishi mumkin)
 */
function rollback_sale_for_resell(PDO $pdo, int $saleId): void
{
    try {
        $pdo->beginTransaction();

        // 1) Shu sotuvga tegishli BARCHA bloklarni topamiz
        //    (sales_items -> codes -> blocks zanjiri orqali)
        $bStmt = $pdo->prepare("
            SELECT DISTINCT b.id
            FROM sales_items si
            JOIN codes   c ON c.id = si.code_id
            JOIN blocks  b ON c.id BETWEEN b.from_id AND b.until_id
            WHERE si.sale_id = :sid
        ");
        $bStmt->execute([':sid' => $saleId]);
        $blockIds = $bStmt->fetchAll(PDO::FETCH_COLUMN);

        // 2) Shu sotuvga tegishli barcha kodlarni "bo‘shatamiz"
        // EHTIYOT: codes jadvalida is_sale nomli ustun ham bo‘lsa, shuni ham 0 qilamiz
        $updCodes = $pdo->prepare("
            UPDATE codes
            SET is_sold = 0,
                is_sale = 0,
                sale_id = NULL
            WHERE sale_id = :sid
        ");
        $updCodes->execute([':sid' => $saleId]);

        // 3) Bloklarni yana "sotilmagan" holatga qaytaramiz
        if (!empty($blockIds)) {
            $in        = implode(',', array_fill(0, count($blockIds), '?'));
            $updBlocks = $pdo->prepare("UPDATE blocks SET is_sale = 0 WHERE id IN ($in)");
            $updBlocks->execute($blockIds);
        }

        // 4) sales.agg_doc_id ni o‘zgartirmaymiz — doc statusini ko‘rish uchun kerak bo‘lishi mumkin

        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        // Bu yerda json_error QILMAYMIZ, faqat jimcha qaytamiz
        // xohlasang logga yozib qo‘yish mumkin:
        // error_log('rollback_sale_for_resell error: '.$e->getMessage());
    }
}


/* ===========================================================
 *  SESSION CART (BLOKLAR)
 * =========================================================== */

if (!isset($_SESSION['cart'])) {
    // [block_id => [...info...]]
    $_SESSION['cart'] = [];
}

$action = $_GET['action'] ?? $_POST['action'] ?? '';

/* ===========================================================
 *  1. MIJOZLAR RO‘YXATI
 * =========================================================== */
if ($action === 'get_clients') {
    try {
        $stmt = $pdo->query("
            SELECT id, name, region, city, address, phone, is_active
            FROM customers
            ORDER BY name
        ");
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        json_ok(['clients' => $rows]);
    } catch (Throwable $e) {
        json_error("Mijozlar ro‘yxatini olishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  2. BITTA MIJOZ MA’LUMOTI
 * =========================================================== */
if ($action === 'get_client_info') {
    $clientId = isset($_GET['client_id']) ? (int)$_GET['client_id'] : 0;
    if ($clientId <= 0) {
        json_error("client_id noto‘g‘ri", 422);
    }

    try {
        $stmt = $pdo->prepare("
            SELECT id, name, region, city, address, phone, is_active
            FROM customers
            WHERE id = :id
        ");
        $stmt->execute([':id' => $clientId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            json_error("Mijoz topilmadi", 404);
        }

        json_ok(['client' => $row]);
    } catch (Throwable $e) {
        json_error("Mijoz ma’lumotlarini olishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  3. BLOK SKANERLASH (GTIN2 / SSCC20) + AGGR STATUS CHECK
 * =========================================================== */
if ($action === 'scan') {
    $code = trim($_POST['code'] ?? ($_GET['code'] ?? ''));
    if ($code === '') {
        json_error("Guruh/transport GS1 kod bo‘sh bo‘lishi mumkin emas", 422);
    }

    try {
        $stmt = $pdo->prepare("
            SELECT
                b.id              AS block_id,
                b.product_id,
                b.category_id,
                b.gtin2_number,
                b.from_id,
                b.until_id,
                b.is_sale,
                b.report_id,
                p.name           AS product_name,
                p.gtin_number    AS product_gtin,
                p.product_image,
                cat.name         AS category_name
            FROM blocks b
            JOIN products   p   ON p.id  = b.product_id
            JOIN categories cat ON cat.id = b.category_id
            WHERE b.gtin2_number = :g
            LIMIT 1
        ");
        $stmt->execute([':g' => $code]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            json_error("Bu guruh/transport kodi bo‘yicha blok topilmadi", 404);
        }

        if ((int)$row['is_sale'] === 1) {
            json_error("Bu blok allaqachon sotilgan (is_sale=1)", 409);
        }

        $blockId = (int)$row['block_id'];
        if (isset($_SESSION['cart'][$blockId])) {
            json_error("Bu blok hozirgi savatga allaqachon qo‘shilgan", 409);
        }

        // --- YANGI QISM: report_id bo'yicha aggregatsiya statusini tekshirish ---
        $reportId = trim((string)($row['report_id'] ?? ''));

        if ($reportId === '') {
            // report_id bo'lmasa — sotishni bloklaymiz (xohlasang bu joyni yumshatib qo'yishing mumkin)
            json_error("Bu blok uchun aggregatsiya report_id topilmadi, buni sota olmaysiz.", 409);
        }

        global $API_TOKEN, $SERVER;

        $xRes = xtrace_check_doc_status($reportId, $API_TOKEN, $SERVER);

        if (!$xRes['ok']) {
            $httpCode = $xRes['httpCode'];
            $data     = $xRes['data'];
            if ($httpCode !== null) {
                $msg = asl_map_error_message($httpCode, $data);
            } else {
                $msg = $xRes['error'] ?? "Aggregatsiya hujjati statusini tekshirishda nomaʼlum xato";
            }

            json_error(
                "Bu mahsulotda muammo bor, buni sota olmaysiz. {$msg}",
                409,
                [
                    'agg_status_error' => $msg,
                ]
            );
        }

        $httpCode = $xRes['httpCode'];
        $data     = $xRes['data'];

        if ($httpCode < 200 || $httpCode >= 300) {
            $msg = asl_map_error_message($httpCode, $data);

            json_error(
                "Bu mahsulotda muammo bor, buni sota olmaysiz. {$msg}",
                409,
                [
                    'agg_status_http' => $httpCode,
                ]
            );
        }

        $docInfo    = $data['documentInfos'] ?? $data;
        $statusCode = $docInfo['status'] ?? null;
        $upper      = $statusCode ? strtoupper($statusCode) : null;
        $statusText = xtrace_build_status_text($statusCode);

        // list_salesdagi logikani takrorlaymiz:
        // faqat shu statuslar yaxshi: SUCCESS / PROCESSED / IN_PROGRESS / PROCESSING
        $goodStatuses = ['SUCCESS', 'PROCESSED', 'IN_PROGRESS', 'PROCESSING'];
        $isBad        = true;

        if ($upper && in_array($upper, $goodStatuses, true)) {
            $isBad = false;
        }

        if ($isBad) {
            $extraErr = asl_build_error_text($data);
            $msg = 'Bu mahsulotda muammo bor, buni sota olmaysiz.';
            if ($statusText !== '') {
                $msg .= ' ' . $statusText;
            }
            if ($extraErr !== '') {
                $msg .= ' Tafsilot: ' . $extraErr;
            }

            json_error(
                $msg,
                409,
                [
                    'agg_status_code' => $statusCode,
                    'agg_status_text' => $statusText,
                ]
            );
        }
        // --- AGGR STATUS OK bo'lsa, eski logika bo'yicha blokni qaytaramiz ---

        json_ok([
            'block' => [
                'block_id'      => $blockId,
                'gtin2_number'  => $row['gtin2_number'],
                'from_id'       => (int)$row['from_id'],
                'until_id'      => (int)$row['until_id'],
                'product_id'    => (int)$row['product_id'],
                'product_name'  => $row['product_name'],
                'product_gtin'  => $row['product_gtin'],
                'category_id'   => (int)$row['category_id'],
                'category_name' => $row['category_name'],
                'product_image' => $row['product_image'],
                'report_id'     => $reportId,
                'agg_status'    => $statusCode,
                'agg_status_text'=> $statusText,
            ],
        ]);
    } catch (Throwable $e) {
        json_error("Blokni tekshirishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  4. BLOKNI SAVATGA QO‘SHISH
 * =========================================================== */
if ($action === 'add_to_cart') {
    $blockId = isset($_POST['block_id']) ? (int)$_POST['block_id'] : 0;
    if ($blockId <= 0) {
        json_error("block_id noto‘g‘ri", 422);
    }

    try {
        $stmt = $pdo->prepare("
            SELECT
                b.id              AS block_id,
                b.product_id,
                b.category_id,
                b.gtin2_number,
                b.from_id,
                b.until_id,
                b.is_sale,
                p.name           AS product_name,
                p.gtin_number    AS product_gtin,
                p.product_image,
                cat.name         AS category_name
            FROM blocks b
            JOIN products   p   ON p.id  = b.product_id
            JOIN categories cat ON cat.id = b.category_id
            WHERE b.id = :id
            LIMIT 1
        ");
        $stmt->execute([':id' => $blockId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            json_error("Blok bazada topilmadi", 404);
        }

        if ((int)$row['is_sale'] === 1) {
            json_error("Bu blok allaqachon sotilgan (is_sale=1)", 409);
        }

        if (isset($_SESSION['cart'][$blockId])) {
            json_error("Bu blok savatda allaqachon bor", 409);
        }

        $_SESSION['cart'][$blockId] = [
            'block_id'      => (int)$row['block_id'],
            'gtin2_number'  => $row['gtin2_number'],
            'from_id'       => (int)$row['from_id'],
            'until_id'      => (int)$row['until_id'],
            'product_id'    => (int)$row['product_id'],
            'product_name'  => $row['product_name'],
            'product_gtin'  => $row['product_gtin'],
            'category_id'   => (int)$row['category_id'],
            'category_name' => $row['category_name'],
            'product_image' => $row['product_image'],
        ];

        json_ok([
            'cart' => array_values($_SESSION['cart']),
        ]);
    } catch (Throwable $e) {
        json_error("Blokni savatga qo‘shishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  5. BLOKNI SAVATDAN O‘CHIRISH
 * =========================================================== */
if ($action === 'remove_from_cart') {
    $blockId = isset($_POST['block_id']) ? (int)$_POST['block_id'] : 0;
    if ($blockId <= 0) {
        json_error("block_id noto‘g‘ri", 422);
    }

    if (isset($_SESSION['cart'][$blockId])) {
        unset($_SESSION['cart'][$blockId]);
    }

    json_ok([
        'cart' => array_values($_SESSION['cart']),
    ]);
}

/* ===========================================================
 *  6. SAVATNI OLISH
 * =========================================================== */
if ($action === 'get_cart') {
    json_ok([
        'cart' => array_values($_SESSION['cart']),
    ]);
}

/* ===========================================================
 *  7. SAVATNI TOZALASH
 * =========================================================== */
if ($action === 'clear_cart') {
    $_SESSION['cart'] = [];
    json_ok();
}

/* ===========================================================
 *  8. CHECKOUT — SOTUV YARATISH (BLOKLAR BO‘YICHA)
 * =========================================================== */
if ($action === 'checkout') {
    $clientId = isset($_POST['client_id']) ? (int)$_POST['client_id'] : 0;
    if ($clientId <= 0) {
        json_error("client_id noto‘g‘ri", 422);
    }

    if (empty($_SESSION['cart'])) {
        json_error("Savat bo‘sh, birorta blok tanlanmagan", 422);
    }

    $userId       = current_user_id_safe();
    $blocksInCart = $_SESSION['cart'];

    try {
        $pdo->beginTransaction();

        // Mijozni tekshiramiz
        $cStmt = $pdo->prepare("SELECT id, is_active FROM customers WHERE id = :id");
        $cStmt->execute([':id' => $clientId]);
        $customer = $cStmt->fetch(PDO::FETCH_ASSOC);
        if (!$customer) {
            $pdo->rollBack();
            json_error("Mijoz topilmadi", 404);
        }

        // Yangi sotuv
        $sStmt = $pdo->prepare("
            INSERT INTO sales (customer_id, status, created_at, created_by)
            VALUES (:cid, :status, NOW(), :uid)
        ");
        $sStmt->execute([
            ':cid'    => $clientId,
            ':status' => 'tasdiqlangan',
            ':uid'    => $userId,
        ]);
        $saleId = (int)$pdo->lastInsertId();

        // Savatdagi bloklarni qayta o‘qiymiz (FOR UPDATE)
        $blockIds = array_map(fn($b) => (int)$b['block_id'], $blocksInCart);
        $blockIds = array_values(array_unique($blockIds));

        $in    = implode(',', array_fill(0, count($blockIds), '?'));
        $bStmt = $pdo->prepare("
            SELECT *
            FROM blocks
            WHERE id IN ($in)
            FOR UPDATE
        ");
        $bStmt->execute($blockIds);
        $rows = $bStmt->fetchAll(PDO::FETCH_ASSOC);

        if (count($rows) !== count($blockIds)) {
            $pdo->rollBack();
            json_error("Savatdagi ba’zi bloklar bazada topilmadi", 422);
        }

        foreach ($rows as $bRow) {
            if ((int)$bRow['is_sale'] === 1) {
                $pdo->rollBack();
                json_error("Blok ID {$bRow['id']} allaqachon sotilgan (is_sale=1)", 409);
            }
        }

        // 1) blocks.is_sale = 1
        $updBlock = $pdo->prepare("UPDATE blocks SET is_sale = 1 WHERE id = :id");

        // 2) from_id / until_id orqali codes.is_sold = 1, sale_id = :sale_id
        $updCodes = $pdo->prepare("
            UPDATE codes
            SET is_sold = 1,
                sale_id = :sale_id
            WHERE id BETWEEN :from_id AND :until_id
        ");

        foreach ($rows as $bRow) {
            $updBlock->execute([':id' => $bRow['id']]);

            $updCodes->execute([
                ':sale_id' => $saleId,
                ':from_id' => (int)$bRow['from_id'],
                ':until_id'=> (int)$bRow['until_id'],
            ]);
        }

        // 3) sales_items — har bir kodni yozamiz
        $codeStmt = $pdo->prepare("
            SELECT id, gs1_text
            FROM codes
            WHERE sale_id = :sale_id
        ");
        $codeStmt->execute([':sale_id' => $saleId]);
        $codesForSale = $codeStmt->fetchAll(PDO::FETCH_ASSOC);

        if (!$codesForSale) {
            $pdo->rollBack();
            json_error("Bu sotuv uchun kodlar topilmadi (codes.sale_id bo‘sh)", 422);
        }

        $insItem = $pdo->prepare("
            INSERT INTO sales_items (sale_id, code_id, gs1_code)
            VALUES (:sale_id, :code_id, :gs1_code)
        ");

        foreach ($codesForSale as $c) {
            $insItem->execute([
                ':sale_id' => $saleId,
                ':code_id' => (int)$c['id'],
                ':gs1_code'=> $c['gs1_text'],
            ]);
        }

        $pdo->commit();

        // Savatni tozalaymiz
        $_SESSION['cart'] = [];

        json_ok([
            'sale_id' => $saleId,
            'message' => 'Sotuv muvaffaqiyatli saqlandi',
        ]);
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        json_error("Checkout paytida xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  9. Sotuvlar ro‘yxati (transport.php uchun)
 *      + avtomatik status check + xatoli sotuvlarni rollback
 * =========================================================== */
if ($action === 'list_sales') {
    $page    = isset($_GET['page']) ? max((int)$_GET['page'], 1) : 1;
    $perPage = isset($_GET['per_page']) ? max((int)$_GET['per_page'], 10) : 10;
    $offset  = ($page - 1) * $perPage;
    $search  = trim($_GET['search'] ?? '');
    $status  = trim($_GET['status'] ?? 'tasdiqlangan');

    $params = [];
    $where  = "1=1";

    if ($status !== '') {
        $where .= " AND s.status = :status";
        $params[':status'] = $status;
    }

    if ($search !== '') {
        $where .= " AND (c.name LIKE :q OR c.region LIKE :q OR c.city LIKE :q OR c.address LIKE :q)";
        $params[':q'] = '%' . $search . '%';
    }

    try {
        $cntSql = "
            SELECT COUNT(*) AS cnt
            FROM sales s
            JOIN customers c ON c.id = s.customer_id
            WHERE {$where}
        ";
        $cntStmt = $pdo->prepare($cntSql);
        $cntStmt->execute($params);
        $total = (int)$cntStmt->fetchColumn();

        $listSql = "
            SELECT
                s.id,
                s.customer_id,
                s.status,
                s.created_at,
                s.sscc20,
                s.agg_doc_id,
                c.name  AS customer_name,
                c.region,
                c.city,
                c.address
            FROM sales s
            JOIN customers c ON c.id = s.customer_id
            WHERE {$where}
            ORDER BY s.created_at DESC, s.id DESC
            LIMIT :limit OFFSET :offset
        ";
        $listStmt = $pdo->prepare($listSql);
        foreach ($params as $k => $v) {
            $listStmt->bindValue($k, $v);
        }
        $listStmt->bindValue(':limit',  $perPage, PDO::PARAM_INT);
        $listStmt->bindValue(':offset', $offset,  PDO::PARAM_INT);
        $listStmt->execute();
        $rows = $listStmt->fetchAll(PDO::FETCH_ASSOC);

        // === Shu sahifadagi sotuvlar uchun Xtrace statusini avtomatik tekshiramiz ===
        global $API_TOKEN, $SERVER;

        // sscc20 ni "xatolik bor" qilib saqlash uchun tayyor UPDATE
        $updSaleErr = $pdo->prepare("UPDATE sales SET sscc20 = :txt WHERE id = :id");

        foreach ($rows as &$row) {
            $row['agg_status']      = null;
            $row['agg_status_text'] = null;

            // agg_doc_id bo‘sh bo‘lsa, status tekshirmaymiz
            if (empty($row['agg_doc_id'])) {
                continue;
            }

            $xRes = xtrace_check_doc_status($row['agg_doc_id'], $API_TOKEN, $SERVER);

            if (!$xRes['ok']) {
                // API xatosi — faqat matn qaytaramiz, rollback qilmaysiz (tarmoq muammosi bo‘lishi mumkin)
                $row['agg_status']      = null;
                $row['agg_status_text'] = $xRes['error'] ?? "Statusni tekshirishda nomaʼlum xato";
                continue;
            }

            $httpCode = $xRes['httpCode'];
            $data     = $xRes['data'];

            if ($httpCode < 200 || $httpCode >= 300) {
                // 2xx bo‘lmasa, bu ham xato
                $row['agg_status']      = null;
                $row['agg_status_text'] = asl_map_error_message($httpCode, $data);
                continue;
            }

            $docInfo    = $data['documentInfos'] ?? $data;
            $statusCode = $docInfo['status'] ?? null;
            $upper      = $statusCode ? strtoupper($statusCode) : null;

            $row['agg_status']      = $statusCode;
            $row['agg_status_text'] = xtrace_build_status_text($statusCode);

            // ==== MUAMMOLI STATUSNI ANIQLASH ====
            // Faqat quyidagi statuslar yaxshi deb hisoblanadi:
            // SUCCESS / PROCESSED / IN_PROGRESS / PROCESSING
            $goodStatuses = ['SUCCESS', 'PROCESSED', 'IN_PROGRESS', 'PROCESSING'];
            $isBad        = true;

            if ($upper && in_array($upper, $goodStatuses, true)) {
                $isBad = false;
            }

            if ($isBad) {
                // Bu sotuvni qayta sotish uchun orqaga qaytaramiz
                $saleId = (int)$row['id'];
                rollback_sale_for_resell($pdo, $saleId);

                // Bazada ham SSCC20 ni "xatolik bor" qilib qo'yamiz
                $updSaleErr->execute([
                    ':txt' => 'xatolik bor',
                    ':id'  => $saleId,
                ]);

                // JSON javobda ham shu ko‘rinsin
                $row['sscc20'] = 'xatolik bor';
            }
        }
        unset($row); // reference ni tozalash

        json_ok([
            'page'     => $page,
            'per_page' => $perPage,
            'total'    => $total,
            'rows'     => $rows,
        ]);
    } catch (Throwable $e) {
        json_error("Sotuvlar ro‘yxatini olishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  10. Bitta sotuv tafsilotlari (+ aggregatsiya statusi)
 * =========================================================== */
if ($action === 'get_sale_detail') {
    $saleId = isset($_GET['sale_id']) ? (int)$_GET['sale_id'] : 0;
    if ($saleId <= 0) {
        json_error("sale_id noto‘g‘ri", 422);
    }

    try {
        $sStmt = $pdo->prepare("
            SELECT
                s.id,
                s.customer_id,
                s.status,
                s.created_at,
                s.sscc20,
                s.agg_doc_id,
                c.name   AS customer_name,
                c.region,
                c.city,
                c.address,
                c.phone
            FROM sales s
            JOIN customers c ON c.id = s.customer_id
            WHERE s.id = :id
        ");
        $sStmt->execute([':id' => $saleId]);
        $sale = $sStmt->fetch(PDO::FETCH_ASSOC);

        if (!$sale) {
            json_error("Sotuv topilmadi", 404);
        }

        $iStmt = $pdo->prepare("
            SELECT
                si.id,
                si.code_id,
                si.gs1_code,
                c.product_id,
                p.name        AS product_name,
                p.gtin_number AS product_gtin,
                p.product_image
            FROM sales_items si
            JOIN codes    c ON c.id = si.code_id
            JOIN products p ON p.id = c.product_id
            WHERE si.sale_id = :id
            ORDER BY p.name, si.id
        ");
        $iStmt->execute([':id' => $saleId]);
        $items = $iStmt->fetchAll(PDO::FETCH_ASSOC);

        // ==== Aggregatsiya hujjati statusi va sababi (agar agg_doc_id bo‘lsa) ====
        $aggStatusCode = null;
        $aggStatusText = null;
        $aggErrorsList = [];

        if (!empty($sale['agg_doc_id'])) {
            global $API_TOKEN, $SERVER;
            $xRes = xtrace_check_doc_status($sale['agg_doc_id'], $API_TOKEN, $SERVER);

            if (!$xRes['ok']) {
                $aggStatusText = $xRes['error'] ?? "Aggregatsiya hujjati statusini tekshirishda nomaʼlum xato";
            } else {
                $httpCode = $xRes['httpCode'];
                $data     = $xRes['data'];

                if ($httpCode < 200 || $httpCode >= 300) {
                    // HTTP xato bo‘lsa, o‘sha asosida izoh beramiz
                    $aggStatusText = asl_map_error_message($httpCode, $data);
                } else {
                    $docInfo        = $data['documentInfos'] ?? $data;
                    $aggStatusCode  = $docInfo['status'] ?? null;
                    $aggStatusText  = xtrace_build_status_text($aggStatusCode);

                    // Agar errors massivida kodlar bo‘lsa, ularni ham yig‘amiz
                    if (!empty($data['errors']) && is_array($data['errors'])) {
                        foreach ($data['errors'] as $err) {
                            $m = $err['message'] ?? '';
                            $c = $err['code'] ?? '';
                            $aggErrorsList[] = trim($c . ' — ' . $m);
                        }
                    }

                    // Qo‘shimcha ravishda, agar ASL formatidagi globalErrors/context bo‘lsa, ularni ham bitta satrga yig‘ib beramiz
                    $extraError = asl_build_error_text($data);
                    if ($extraError !== '') {
                        $aggErrorsList[] = $extraError;
                    }
                }
            }
        }

        json_ok([
            'sale'            => $sale,
            'items'           => $items,
            'agg_status_code' => $aggStatusCode,
            'agg_status_text' => $aggStatusText,
            'agg_errors_list' => $aggErrorsList,
        ]);
    } catch (Throwable $e) {
        json_error("Sotuv tafsilotlarini olishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  11. Sotuv statusini o‘zgartirish
 * =========================================================== */
if ($action === 'set_sale_status') {
    $saleId = isset($_POST['sale_id']) ? (int)$_POST['sale_id'] : 0;
    $status = trim($_POST['status'] ?? '');

    if ($saleId <= 0 || $status === '') {
        json_error("sale_id yoki status noto‘g‘ri", 422);
    }

    if (!in_array($status, ['qoralama', 'tasdiqlangan', 'bekor'], true)) {
        json_error("status noto‘g‘ri qiymat", 422);
    }

    try {
        $stmt = $pdo->prepare("UPDATE sales SET status = :st WHERE id = :id");
        $stmt->execute([
            ':st' => $status,
            ':id' => $saleId,
        ]);

        json_ok();
    } catch (Throwable $e) {
        json_error("Statusni yangilashda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  12. Transport kodi (SSCC) generatsiya + aggregatsiya
 * =========================================================== */
if ($action === 'generate_transport_code') {
    global $API_TOKEN, $SERVER, $BUSINESS_PLACE_ID;

    $saleId = isset($_POST['sale_id']) ? (int)$_POST['sale_id'] : 0;
    if ($saleId <= 0) {
        json_error("sale_id noto‘g‘ri", 422);
    }

    try {
        $pdo->beginTransaction();

        // Sotuvni FOR UPDATE bilan o‘qiymiz
        $sStmt = $pdo->prepare("
            SELECT *
            FROM sales
            WHERE id = :id
            FOR UPDATE
        ");
        $sStmt->execute([':id' => $saleId]);
        $sale = $sStmt->fetch(PDO::FETCH_ASSOC);

        if (!$sale) {
            $pdo->rollBack();
            json_error("Sotuv topilmadi", 404);
        }

        if ($sale['status'] !== 'tasdiqlangan') {
            $pdo->rollBack();
            json_error("Faqat 'tasdiqlangan' sotuvlar uchun transport kodi generatsiya qilinadi", 422);
        }

        if (!empty($sale['agg_doc_id'])) {
            $pdo->rollBack();
            json_error("Bu sotuv uchun aggregatsiya hujjati allaqachon yuborilgan", 422);
        }

        // Sotuvdagi barcha GS1 kodlar
        $iStmt = $pdo->prepare("
            SELECT si.id, si.gs1_code
            FROM sales_items si
            WHERE si.sale_id = :id
        ");
        $iStmt->execute([':id' => $saleId]);
        $items = $iStmt->fetchAll(PDO::FETCH_ASSOC);

        if (!$items) {
            $pdo->rollBack();
            json_error("Bu sotuvda hech qanday kod topilmadi (sales_items bo‘sh)", 422);
        }

        // === Yangi talab: agar ichida faqat SSCC18/SSCC20 bo‘lsa, aggregatsiya yubormaymiz ===
        $childCisRaw     = [];
        $hasConsumerKms  = false; // iste'molchi KM (kichik kodlar)
        $hasSsccCodes    = false; // SSCC18/20 mavjudligini bilish uchun

        foreach ($items as $row) {
            $gs1 = trim((string)$row['gs1_code']);
            if ($gs1 === '') {
                continue;
            }

            // GS belgilarini olib tashlaymiz
            $plain  = ltrim(str_replace(["\x1D", "\\u001d"], '', $gs1));
            $digits = preg_replace('/\D/', '', $plain);

            $isSscc = false;
            if (strlen($digits) === 20 && strpos($digits, '00') === 0) {
                // 00xxxxxxxxxxxxxxxxxx
                $isSscc = true;
            } elseif (strlen($digits) === 18 && (strpos($plain, '(00') === 0 || strpos($plain, '00') === 0)) {
                // (00)xxxxxxxxxxxxxxxxxx yoki 00xxxxxxxxxxxxxxxxxx
                $isSscc = true;
            }

            if ($isSscc) {
                $hasSsccCodes = true;
                // SSCC ni aggregatsiya ichida child kod sifatida yubormaymiz
                continue;
            }

            // Bu iste'molchi KM
            $hasConsumerKms = true;
            $childCisRaw[]  = $plain;
        }

        // Faqat SSCC bor, iste'molchi KM yo'q
        if (!$hasConsumerKms && $hasSsccCodes) {
            $pdo->rollBack();
            json_error(
                "Bu buyurtma ichida avvaldan agregatsiya transport kodlari (SSCC) mavjud. " .
                "Iltimos, shu SSCC ichidagi iste'molchi kodlarni sotib, keyin yangi transport kodi yarating.",
                422
            );
        }

        // Umuman hech qanday child KM topilmadi
        if (!$hasConsumerKms) {
            $pdo->rollBack();
            json_error("Bu sotuvdagi kodlar orasida aggregatsiya uchun iste'molchi KMs topilmadi", 422);
        }

        // SSCC20 generatsiya
        $sscc20 = ensure_sscc_for_sale($pdo, $saleId); // 00 + 18 raqam

        // ASL / Xtrace doc/aggregation ga yuboramiz
        $xRes = xtrace_send_aggregation_for_sale(
            $childCisRaw,
            $sscc20,
            $BUSINESS_PLACE_ID,
            $API_TOKEN,
            $SERVER
        );

        if (!$xRes['ok']) {
            $pdo->rollBack();
            // Bu yerda ham xohlasang rollback_sale_for_resell chaqirib, sscc20 ni "xatolik bor" qilib,
            // xatoni bazaga o‘tirg‘izib yuborish mumkin. Hozircha faqat frontga chiqaramiz.
            json_error($xRes['message'] ?? 'ASL / Xtrace aggregatsiya xatosi', 502, [
                'codes'         => $xRes['codes']         ?? null,
                'api_http_code' => $xRes['api_http_code'] ?? null,
                'api_error'     => [
                    'code'    => $xRes['api_error_code']        ?? null,
                    'errorId' => $xRes['api_error_id']          ?? null,
                    'raw'     => $xRes['api_error_message_raw'] ?? null,
                ],
            ]);
        }

        $documentId = $xRes['document_id'] ?? null;
        if (!$documentId) {
            $pdo->rollBack();
            json_error("ASL javobida hujjat identifikatori (documentId) topilmadi", 502, [
                'api_raw' => $xRes['api_raw'] ?? null,
            ]);
        }

        // sales.agg_doc_id ga saqlaymiz
        $uStmt = $pdo->prepare("
            UPDATE sales
            SET agg_doc_id = :docId
            WHERE id = :id
        ");
        $uStmt->execute([
            ':docId' => $documentId,
            ':id'    => $saleId,
        ]);

        $pdo->commit();

        json_ok([
            'sale_id'     => $saleId,
            'sscc20'      => $sscc20,
            'document_id' => $documentId,
            'xtrace'      => $xRes['api_raw'] ?? null,
        ]);
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        json_error("Transport kodini generatsiya qilishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  13. Aggregatsiya hujjati statusini tekshirish (alohida action)
 * =========================================================== */
if ($action === 'check_agg_status') {
    global $API_TOKEN, $SERVER;

    $saleId = isset($_GET['sale_id']) ? (int)$_GET['sale_id'] : 0;
    if ($saleId <= 0) {
        json_error("sale_id noto‘g‘ri", 422);
    }

    try {
        $stmt = $pdo->prepare("SELECT id, agg_doc_id FROM sales WHERE id = :id");
        $stmt->execute([':id' => $saleId]);
        $sale = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$sale) {
            json_error("Sotuv topilmadi", 404);
        }

        if (empty($sale['agg_doc_id'])) {
            json_error("Bu sotuv uchun aggregatsiya hujjati hali yuborilmagan (agg_doc_id bo‘sh)", 422);
        }

        $docId = $sale['agg_doc_id'];
        $xRes  = xtrace_check_doc_status($docId, $API_TOKEN, $SERVER);

        if (!$xRes['ok']) {
            $httpCode = $xRes['httpCode'];
            $data     = $xRes['data'];

            if ($httpCode !== null) {
                $msg = asl_map_error_message($httpCode, $data);
            } else {
                $msg = $xRes['error'] ?? "Aggregatsiya hujjati statusini tekshirishda nomaʼlum xato";
            }

            json_error($msg, 502, [
                'httpCode' => $httpCode,
                'raw'      => $xRes['raw'],
                'data'     => $data,
            ]);
        }

        $httpCode = $xRes['httpCode'];
        $data     = $xRes['data'];
        $raw      = $xRes['raw'];

        $docInfo    = $data['documentInfos'] ?? $data;
        $statusCode = $docInfo['status'] ?? null;
        $statusText = xtrace_build_status_text($statusCode);

        $errorsText = [];
        if (!empty($data['errors']) && is_array($data['errors'])) {
            foreach ($data['errors'] as $err) {
                $m = $err['message'] ?? '';
                $c = $err['code'] ?? '';
                $errorsText[] = trim($c . ' — ' . $m);
            }
        }

        // Qo‘shimcha globalErrors/context bo‘lsa, bitta satrga yig‘ib yuboramiz
        $extraErr = asl_build_error_text($data);
        if ($extraErr !== '') {
            $errorsText[] = $extraErr;
        }

        json_ok([
            'sale_id'     => $saleId,
            'document_id' => $docId,
            'httpCode'    => $httpCode,
            'status_raw'  => $data,
            'status_code' => $statusCode,
            'status_text' => $statusText,
            'errors_list' => $errorsText,
        ]);
    } catch (Throwable $e) {
        json_error("Aggregatsiya hujjati statusini tekshirishda xatolik", 500, [
            'error' => $e->getMessage(),
        ]);
    }
}

/* ===========================================================
 *  Agar hech bir action mos kelmasa
 * =========================================================== */
json_error("Noto‘g‘ri action", 404);
