<?php
/*
  cPanel path: /home/jewellersbd/public_html/erp.jewellersbd.com/api/new_stock/transfer_to_barcode.php
  Purpose: Transfer new_stock / old purchase stock to barcode_stock
  Data change: YES

  Safety:
  - NO ALTER / CREATE / DROP
  - transaction + FOR UPDATE lock
  - new_stock available_pcs / available_weight check
  - barcode করার সময় piece অনুযায়ী product/category/karat save
  - 0g barcode block
  - duplicate barcode prevent
  - barcode_stock insert
  - new_stock available_pcs / available_weight reduce
  - audit_log থাকলে entry করবে
*/

ini_set('display_errors', 0);
ini_set('precision', '14');
ini_set('serialize_precision', '-1');
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');

require_once __DIR__ . '/../../config/session.php';
require_once __DIR__ . '/../../config/db.php';

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

function nsb_out($code, $ok, $msg, $data = null) {
    http_response_code($code);
    echo json_encode([
        'success' => (bool)$ok,
        'status'  => (bool)$ok,
        'message' => $msg,
        'data'    => $data
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

if (function_exists('require_login')) {
    require_login();
} elseif (function_exists('is_logged_in') && !is_logged_in()) {
    nsb_out(401, false, 'লগইন প্রয়োজন');
} elseif (empty($_SESSION['user_id']) && empty($_SESSION['user']['id'])) {
    nsb_out(401, false, 'লগইন প্রয়োজন');
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    nsb_out(405, false, 'শুধু POST অনুমোদিত');
}

if (!isset($pdo) || !($pdo instanceof PDO)) {
    nsb_out(500, false, 'PDO DB connection পাওয়া যায়নি');
}

try {
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->exec("SET NAMES utf8mb4");
} catch (Throwable $e) {
    nsb_out(500, false, 'ডাটাবেজ কানেকশন সমস্যা');
}

function nsb_clean($name) {
    return preg_replace('/[^a-zA-Z0-9_]/', '', (string)$name);
}

function nsb_qi($name) {
    return '`' . str_replace('`', '', nsb_clean($name)) . '`';
}

function nsb_table_exists(PDO $pdo, $table) {
    $table = nsb_clean($table);
    if ($table === '') return false;

    $st = $pdo->query("SHOW TABLES LIKE " . $pdo->quote($table));
    return (bool)$st->fetchColumn();
}

function nsb_get_columns(PDO $pdo, $table) {
    $table = nsb_clean($table);
    if ($table === '' || !nsb_table_exists($pdo, $table)) return [];

    $rows = $pdo->query("SHOW COLUMNS FROM " . nsb_qi($table))->fetchAll(PDO::FETCH_ASSOC);
    $cols = [];

    foreach ($rows as $r) {
        if (isset($r['Field'])) {
            $cols[$r['Field']] = true;
        }
    }

    return $cols;
}

function nsb_pick_col($cols, $names) {
    foreach ($names as $n) {
        if (isset($cols[$n])) return $n;
    }

    return null;
}

function nsb_first_value($arr, $keys, $default = '') {
    foreach ($keys as $k) {
        if (array_key_exists($k, $arr) && $arr[$k] !== null && $arr[$k] !== '') {
            return $arr[$k];
        }
    }

    return $default;
}

function nsb_first_int($arr, $keys, $default = 0) {
    foreach ($keys as $k) {
        if (array_key_exists($k, $arr) && $arr[$k] !== null && $arr[$k] !== '') {
            return (int)$arr[$k];
        }
    }

    return (int)$default;
}

function nsb_first_float($arr, $keys, $default = 0) {
    foreach ($keys as $k) {
        if (array_key_exists($k, $arr) && $arr[$k] !== null && $arr[$k] !== '') {
            return (float)$arr[$k];
        }
    }

    return (float)$default;
}

function nsb_user_id() {
    foreach (['user_id', 'id', 'auth_user_id'] as $k) {
        if (isset($_SESSION[$k]) && (int)$_SESSION[$k] > 0) return (int)$_SESSION[$k];
    }

    if (isset($_SESSION['user']) && is_array($_SESSION['user'])) {
        foreach (['id', 'user_id'] as $k) {
            if (isset($_SESSION['user'][$k]) && (int)$_SESSION['user'][$k] > 0) return (int)$_SESSION['user'][$k];
        }
    }

    return 0;
}

function nsb_client_ip() {
    foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'] as $k) {
        if (!empty($_SERVER[$k])) {
            $v = trim((string)$_SERVER[$k]);
            if ($k === 'HTTP_X_FORWARDED_FOR') {
                $parts = explode(',', $v);
                return trim($parts[0]);
            }
            return $v;
        }
    }

    return '';
}

function nsb_json_text($value) {
    return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function nsb_audit(PDO $pdo, $tableName, $recordId, $action, $userId, $oldValues, $newValues, $note) {
    try {
        if (!nsb_table_exists($pdo, 'audit_log')) return;

        $cols = nsb_get_columns($pdo, 'audit_log');
        if (empty($cols)) return;

        $fields = [];
        $marks = [];
        $params = [];

        $add = function($col, $val) use (&$fields, &$marks, &$params, $cols) {
            if (isset($cols[$col])) {
                $fields[] = nsb_qi($col);
                $marks[] = '?';
                $params[] = $val;
            }
        };

        $add('table_name', $tableName);
        $add('record_id', (int)$recordId);
        $add('action', strtoupper((string)$action));

        if (isset($cols['changed_by'])) {
            $add('changed_by', (int)$userId);
        } elseif (isset($cols['user_id'])) {
            $add('user_id', (int)$userId);
        }

        if (isset($cols['old_values'])) {
            $add('old_values', nsb_json_text($oldValues));
        }

        if (isset($cols['new_values'])) {
            $add('new_values', nsb_json_text($newValues));
        }

        $add('ip_address', nsb_client_ip());
        $add('note', $note);

        if (isset($cols['changed_at'])) {
            $fields[] = nsb_qi('changed_at');
            $marks[] = 'NOW()';
        } elseif (isset($cols['created_at'])) {
            $fields[] = nsb_qi('created_at');
            $marks[] = 'NOW()';
        }

        if (empty($fields)) return;

        $sql = "INSERT INTO `audit_log` (" . implode(',', $fields) . ") VALUES (" . implode(',', $marks) . ")";
        $st = $pdo->prepare($sql);
        $st->execute($params);

    } catch (Throwable $e) {
        // audit fail হলেও main operation fail হবে না
    }
}

function nsb_gram_from_parts($vori, $ana, $roti, $point) {
    $VORI = 11.664;

    return round(
        ((float)$vori * $VORI) +
        ((float)$ana * ($VORI / 16)) +
        ((float)$roti * ($VORI / 96)) +
        ((float)$point * ($VORI / 960)),
        3
    );
}

function nsb_gram_to_parts($gram) {
    $VORI = 11.664;
    $totalPoint = (int)round(((float)$gram / $VORI) * 960);

    $vori = intdiv($totalPoint, 960);
    $totalPoint %= 960;

    $ana = intdiv($totalPoint, 60);
    $totalPoint %= 60;

    $roti = intdiv($totalPoint, 10);
    $point = $totalPoint % 10;

    return [
        'vori'  => $vori,
        'ana'   => $ana,
        'roti'  => $roti,
        'rati'  => $roti,
        'point' => $point
    ];
}

function nsb_purity_percent($karat, $fallback = 91.667) {
    $k = strtoupper(trim((string)$karat));

    $map = [
        '24K' => 100,
        '22K' => 91.667,
        '21K' => 87.5,
        '18K' => 75
    ];

    return $map[$k] ?? $fallback;
}

function nsb_safe_weight_from_piece($piece) {
    $g = nsb_first_float($piece, [
        'gross_weight_gram',
        'weight_gram',
        'weight_g',
        'weight',
        'gram'
    ], 0);

    if ($g <= 0) {
        $g = nsb_gram_from_parts(
            nsb_first_float($piece, ['vori', 'bhori'], 0),
            nsb_first_float($piece, ['ana', 'aana'], 0),
            nsb_first_float($piece, ['roti', 'rati'], 0),
            nsb_first_float($piece, ['point', 'points'], 0)
        );
    }

    return round((float)$g, 3);
}

function nsb_barcode_exists(PDO $pdo, $barcode, $bcCols) {
    $parts = [];
    $params = [];

    foreach (['barcode', 'barcode_no', 'barcode_code'] as $c) {
        if (isset($bcCols[$c])) {
            $parts[] = nsb_qi($c) . " = ?";
            $params[] = $barcode;
        }
    }

    if (empty($parts)) return false;

    $sql = "SELECT COUNT(*) FROM `barcode_stock` WHERE " . implode(" OR ", $parts);
    $st = $pdo->prepare($sql);
    $st->execute($params);

    return (int)$st->fetchColumn() > 0;
}

function nsb_next_barcode(PDO $pdo, $stockId, $dateStr, $serial, $bcCols) {
    $stockIdPad = str_pad((string)$stockId, 4, '0', STR_PAD_LEFT);

    for ($i = 0; $i < 1000; $i++) {
        $piecePad = str_pad((string)($serial + $i), 5, '0', STR_PAD_LEFT);
        $barcode = 'BDJ' . $dateStr . $stockIdPad . $piecePad;

        if (!nsb_barcode_exists($pdo, $barcode, $bcCols)) {
            return $barcode;
        }
    }

    return 'BDJ' . $dateStr . $stockIdPad . strtoupper(bin2hex(random_bytes(3)));
}

function nsb_existing_source_count(PDO $pdo, $stockId, $bcCols) {
    $refCols = [
        'new_stock_id',
        'source_new_stock_id',
        'source_stock_id',
        'purchase_stock_id',
        'source_purchase_id',
        'purchase_id',
        'stock_id',
        'source_id'
    ];

    $where = [];
    $params = [];

    foreach ($refCols as $c) {
        if (isset($bcCols[$c])) {
            $where[] = nsb_qi($c) . " = ?";
            $params[] = $stockId;
        }
    }

    if (empty($where)) return 0;

    $extra = [];

    if (isset($bcCols['is_deleted'])) {
        $extra[] = "(`is_deleted` IS NULL OR `is_deleted` = 0)";
    }

    if (isset($bcCols['deleted_at'])) {
        $extra[] = "`deleted_at` IS NULL";
    }

    $sql = "SELECT COUNT(*) FROM `barcode_stock` WHERE (" . implode(" OR ", $where) . ")";
    if (!empty($extra)) {
        $sql .= " AND " . implode(" AND ", $extra);
    }

    $st = $pdo->prepare($sql);
    $st->execute($params);

    return (int)$st->fetchColumn();
}

function nsb_add_field(&$row, $cols, $col, $val) {
    if (isset($cols[$col])) {
        $row[$col] = $val;
    }
}

try {
    $raw = json_decode(file_get_contents('php://input'), true);
    if (!is_array($raw)) {
        $raw = $_POST;
    }

    if (!is_array($raw)) {
        nsb_out(400, false, 'ভুল JSON data');
    }

    $userId = nsb_user_id();

    if ($userId <= 0) {
        nsb_out(401, false, 'Login session পাওয়া যায়নি');
    }

    if (!nsb_table_exists($pdo, 'new_stock')) {
        nsb_out(500, false, 'new_stock table পাওয়া যায়নি');
    }

    if (!nsb_table_exists($pdo, 'barcode_stock')) {
        nsb_out(500, false, 'barcode_stock table পাওয়া যায়নি');
    }

    $nsCols = nsb_get_columns($pdo, 'new_stock');
    $bcCols = nsb_get_columns($pdo, 'barcode_stock');

    $nsIdCol = nsb_pick_col($nsCols, ['id', 'stock_id', 'new_stock_id']);
    $bcIdCol = nsb_pick_col($bcCols, ['id', 'barcode_stock_id', 'barcode_id']);
    $barcodeCol = nsb_pick_col($bcCols, ['barcode', 'barcode_no', 'barcode_code']);

    if (!$nsIdCol) {
        nsb_out(500, false, 'new_stock ID column পাওয়া যায়নি');
    }

    if (!$bcIdCol) {
        nsb_out(500, false, 'barcode_stock ID column পাওয়া যায়নি');
    }

    if (!$barcodeCol) {
        nsb_out(500, false, 'barcode_stock barcode column পাওয়া যায়নি');
    }

    $stockId = nsb_first_int($raw, ['stock_id', 'id', 'new_stock_id'], 0);
    if ($stockId <= 0) {
        nsb_out(400, false, 'Stock ID দিন');
    }

    $pieces = $raw['pieces'] ?? null;
    $numPieces = nsb_first_int($raw, ['num_pieces', 'total_pieces', 'pcs'], 0);

    if (!is_array($pieces) && $numPieces <= 0) {
        nsb_out(400, false, 'কমপক্ষে ১টি পিস দিন');
    }

    $pdo->beginTransaction();

    $where = [];
    $params = [];

    $where[] = nsb_qi($nsIdCol) . " = ?";
    $params[] = $stockId;

    if (isset($nsCols['deleted_at'])) {
        $where[] = "`deleted_at` IS NULL";
    }

    if (isset($nsCols['is_deleted'])) {
        $where[] = "(`is_deleted` IS NULL OR `is_deleted` = 0)";
    }

    $stockQ = $pdo->prepare("
        SELECT *
        FROM `new_stock`
        WHERE " . implode(" AND ", $where) . "
        LIMIT 1
        FOR UPDATE
    ");
    $stockQ->execute($params);
    $stock = $stockQ->fetch(PDO::FETCH_ASSOC);

    if (!$stock) {
        $pdo->rollBack();
        nsb_out(404, false, 'স্টক পাওয়া যায়নি');
    }

    $itemNameDefault = trim((string)nsb_first_value($stock, ['product_name', 'item_name', 'name', 'category'], 'আইটেম'));
    if ($itemNameDefault === '') $itemNameDefault = 'আইটেম';

    $karatDefault = trim((string)nsb_first_value($stock, ['karat', 'purity'], '22K'));
    if ($karatDefault === '') $karatDefault = '22K';

    $availablePcs = nsb_first_int(
        $stock,
        ['available_pcs', 'balance_pcs'],
        nsb_first_int($stock, ['total_pcs', 'pcs', 'quantity', 'qty'], 0)
    );

    $availableWeight = nsb_first_float(
        $stock,
        ['available_weight_gram', 'balance_weight_gram'],
        nsb_first_float($stock, ['total_weight_gram', 'gross_weight_gram', 'weight_gram', 'weight'], 0)
    );

    $locationId = nsb_first_int($stock, ['location_id', 'stock_location_id'], 1);
    if ($locationId <= 0) $locationId = 1;

    $ratePerVori = nsb_first_float($stock, ['rate_per_vori', 'sale_rate_per_vori'], 0);
    $sourceCategory = (string)nsb_first_value($stock, ['category', 'stock_type'], $itemNameDefault);

    if ($availablePcs <= 0) {
        $pdo->rollBack();
        nsb_out(400, false, 'এই stock-এ আর কোনো available pcs নেই');
    }

    if ($availableWeight <= 0) {
        $pdo->rollBack();
        nsb_out(400, false, 'এই stock-এর available weight 0g');
    }

    if (!is_array($pieces)) {
        if ($numPieces > $availablePcs) {
            $pdo->rollBack();
            nsb_out(400, false, 'মাত্র ' . $availablePcs . ' পিস বাকি আছে');
        }

        $avg = round($availableWeight / $availablePcs, 3);
        $pieces = [];

        for ($i = 0; $i < $numPieces; $i++) {
            $w = $avg;

            if ($i === $numPieces - 1) {
                $w = round($availableWeight - ($avg * ($numPieces - 1)), 3);
                if ($w <= 0) $w = $avg;
            }

            $parts = nsb_gram_to_parts($w);

            $pieces[] = [
                'name' => $itemNameDefault,
                'category' => $sourceCategory,
                'karat' => $karatDefault,
                'gross_weight_gram' => $w,
                'vori' => $parts['vori'],
                'ana' => $parts['ana'],
                'roti' => $parts['roti'],
                'rati' => $parts['roti'],
                'point' => $parts['point'],
                'note' => ''
            ];
        }
    }

    if (empty($pieces)) {
        $pdo->rollBack();
        nsb_out(400, false, 'পিস তথ্য দিন');
    }

    $numPieces = count($pieces);

    if ($numPieces <= 0) {
        $pdo->rollBack();
        nsb_out(400, false, 'কমপক্ষে ১টি পিস দিন');
    }

    if ($numPieces > 500) {
        $pdo->rollBack();
        nsb_out(400, false, 'একবারে সর্বোচ্চ 500টি barcode তৈরি করা যাবে');
    }

    if ($numPieces > $availablePcs) {
        $pdo->rollBack();
        nsb_out(400, false, 'মাত্র ' . $availablePcs . ' পিস বাকি আছে');
    }

    $preparedPieces = [];
    $totalTransferredGram = 0;

    foreach ($pieces as $idx => $piece) {
        if (!is_array($piece)) {
            $pdo->rollBack();
            nsb_out(400, false, 'Piece data ভুল আছে');
        }

        $weightGram = nsb_safe_weight_from_piece($piece);

        if ($weightGram <= 0) {
            $pdo->rollBack();
            nsb_out(400, false, 'Piece #' . ($idx + 1) . ' এর ওজন 0g');
        }

        if ($weightGram > $availableWeight) {
            $pdo->rollBack();
            nsb_out(400, false, 'Piece #' . ($idx + 1) . ' এর ওজন available stock থেকে বেশি');
        }

        $pieceName = trim((string)nsb_first_value($piece, ['name', 'item_name', 'product_name'], $itemNameDefault));
        if ($pieceName === '') $pieceName = $itemNameDefault;

        $pieceCategory = trim((string)nsb_first_value($piece, ['category', 'item_category', 'product_category'], $pieceName));
        if ($pieceCategory === '') $pieceCategory = $pieceName;

        $pieceKarat = trim((string)nsb_first_value($piece, ['karat', 'purity'], $karatDefault));
        if ($pieceKarat === '') $pieceKarat = $karatDefault;

        $purity = nsb_first_float($piece, ['purity_percent', 'actual_purity', 'system_purity'], nsb_purity_percent($pieceKarat));
        if ($purity <= 0) $purity = nsb_purity_percent($pieceKarat);
        if ($purity > 100) {
            $pdo->rollBack();
            nsb_out(400, false, 'Piece #' . ($idx + 1) . ' purity 100% এর বেশি');
        }

        $pureGram = nsb_first_float($piece, ['pure_weight_gram', 'pure_weight', 'pure_gold_gram'], 0);
        if ($pureGram <= 0) {
            $pureGram = round($weightGram * ($purity / 100), 3);
        }

        $vori  = nsb_first_float($piece, ['vori', 'bhori'], 0);
        $ana   = nsb_first_float($piece, ['ana', 'aana'], 0);
        $roti  = nsb_first_float($piece, ['roti', 'rati'], 0);
        $point = nsb_first_float($piece, ['point', 'points'], 0);

        if ($vori <= 0 && $ana <= 0 && $roti <= 0 && $point <= 0) {
            $parts = nsb_gram_to_parts($weightGram);
            $vori = $parts['vori'];
            $ana = $parts['ana'];
            $roti = $parts['roti'];
            $point = $parts['point'];
        }

        $note = trim((string)nsb_first_value($piece, ['note', 'remarks'], ''));

        $preparedPieces[] = [
            'name' => $pieceName,
            'category' => $pieceCategory,
            'karat' => $pieceKarat,
            'purity_percent' => round($purity, 3),
            'gross_weight_gram' => round($weightGram, 3),
            'pure_weight_gram' => round($pureGram, 3),
            'vori' => $vori,
            'ana' => $ana,
            'roti' => $roti,
            'rati' => $roti,
            'point' => $point,
            'note' => $note
        ];

        $totalTransferredGram += $weightGram;
    }

    $totalTransferredGram = round($totalTransferredGram, 3);

    if ($totalTransferredGram <= 0) {
        $pdo->rollBack();
        nsb_out(400, false, 'মোট transfer ওজন 0g');
    }

    if ($totalTransferredGram > round($availableWeight + 0.001, 3)) {
        $pdo->rollBack();
        nsb_out(400, false, 'মোট barcode ওজন available stock থেকে বেশি', [
            'available_weight_gram' => round($availableWeight, 3),
            'requested_weight_gram' => $totalTransferredGram
        ]);
    }

    $dateStr = date('ymd');
    $existingCount = nsb_existing_source_count($pdo, $stockId, $bcCols);
    $serialStart = $existingCount + 1;

    $created = [];
    $barcodeIds = [];

    foreach ($preparedPieces as $idx => $piece) {
        $barcode = nsb_next_barcode($pdo, $stockId, $dateStr, $serialStart + $idx, $bcCols);

        $row = [];

        foreach (['barcode', 'barcode_no', 'barcode_code'] as $c) {
            nsb_add_field($row, $bcCols, $c, $barcode);
        }

        foreach (['product_name', 'item_name', 'name'] as $c) {
            nsb_add_field($row, $bcCols, $c, $piece['name']);
        }

        nsb_add_field($row, $bcCols, 'category', $piece['category']);

        nsb_add_field($row, $bcCols, 'karat', $piece['karat']);
        nsb_add_field($row, $bcCols, 'purity', $piece['karat']);
        nsb_add_field($row, $bcCols, 'purity_percent', $piece['purity_percent']);

        nsb_add_field($row, $bcCols, 'vori', $piece['vori']);
        nsb_add_field($row, $bcCols, 'bhori', $piece['vori']);
        nsb_add_field($row, $bcCols, 'ana', $piece['ana']);
        nsb_add_field($row, $bcCols, 'aana', $piece['ana']);
        nsb_add_field($row, $bcCols, 'roti', $piece['roti']);
        nsb_add_field($row, $bcCols, 'rati', $piece['rati']);
        nsb_add_field($row, $bcCols, 'point', $piece['point']);
        nsb_add_field($row, $bcCols, 'points', $piece['point']);

        foreach (['gross_weight_gram', 'gross_weight', 'weight_gram', 'weight_g', 'weight', 'total_weight_gram', 'net_weight'] as $c) {
            nsb_add_field($row, $bcCols, $c, $piece['gross_weight_gram']);
        }

        foreach (['pure_weight_gram', 'pure_weight', 'pure_gold_gram'] as $c) {
            nsb_add_field($row, $bcCols, $c, $piece['pure_weight_gram']);
        }

        nsb_add_field($row, $bcCols, 'location_id', $locationId);
        nsb_add_field($row, $bcCols, 'stock_location_id', $locationId);

        foreach ([
            'new_stock_id',
            'source_new_stock_id',
            'source_stock_id',
            'purchase_stock_id',
            'source_purchase_id',
            'purchase_id',
            'stock_id',
            'source_id'
        ] as $c) {
            nsb_add_field($row, $bcCols, $c, $stockId);
        }

        nsb_add_field($row, $bcCols, 'source_type', 'new_stock');
        nsb_add_field($row, $bcCols, 'source', 'new_stock');

        if (isset($bcCols['source_json'])) {
            $row['source_json'] = nsb_json_text([
                'source_type' => 'new_stock',
                'new_stock_id' => $stockId,
                'stock_item_name' => $itemNameDefault,
                'stock_category' => $sourceCategory,
                'stock_karat' => $karatDefault,
                'barcode_item_name' => $piece['name'],
                'barcode_category' => $piece['category'],
                'barcode_karat' => $piece['karat'],
                'transfer_at' => date('Y-m-d H:i:s')
            ]);
        }

        if ($ratePerVori > 0) {
            nsb_add_field($row, $bcCols, 'rate_per_vori', $ratePerVori);
            nsb_add_field($row, $bcCols, 'sale_rate_per_vori', $ratePerVori);
            nsb_add_field($row, $bcCols, 'rate_per_gram', round($ratePerVori / 11.664, 4));
        }

        nsb_add_field($row, $bcCols, 'status', 'in_stock');
        nsb_add_field($row, $bcCols, 'transfer_status', 'none');
        nsb_add_field($row, $bcCols, 'is_deleted', 0);

        if ($piece['note'] !== '') {
            nsb_add_field($row, $bcCols, 'note', $piece['note']);
            nsb_add_field($row, $bcCols, 'remarks', $piece['note']);
        }

        nsb_add_field($row, $bcCols, 'created_by', $userId);
        nsb_add_field($row, $bcCols, 'user_id', $userId);

        if (isset($bcCols['created_at'])) {
            $row['created_at'] = date('Y-m-d H:i:s');
        }

        if (isset($bcCols['updated_at'])) {
            $row['updated_at'] = date('Y-m-d H:i:s');
        }

        if (empty($row)) {
            throw new RuntimeException('barcode_stock insert করার মতো column পাওয়া যায়নি');
        }

        $fields = array_keys($row);
        $sql = "INSERT INTO `barcode_stock` (" .
            implode(',', array_map('nsb_qi', $fields)) .
            ") VALUES (" .
            implode(',', array_fill(0, count($fields), '?')) .
            ")";

        $ins = $pdo->prepare($sql);
        $ins->execute(array_values($row));

        $barcodeStockId = (int)$pdo->lastInsertId();
        $barcodeIds[] = $barcodeStockId;

        $created[] = [
            'id' => $barcodeStockId,
            'barcode_stock_id' => $barcodeStockId,
            'barcode' => $barcode,
            'barcode_no' => $barcode,
            'item_name' => $piece['name'],
            'product_name' => $piece['name'],
            'category' => $piece['category'],
            'karat' => $piece['karat'],
            'gross_weight_gram' => $piece['gross_weight_gram'],
            'pure_weight_gram' => $piece['pure_weight_gram'],
            'vori' => (int)$piece['vori'],
            'ana' => (int)$piece['ana'],
            'roti' => (int)$piece['roti'],
            'rati' => (int)$piece['roti'],
            'point' => (int)$piece['point'],
            'location_id' => $locationId
        ];

        nsb_audit(
            $pdo,
            'barcode_stock',
            $barcodeStockId,
            'INSERT',
            $userId,
            null,
            [
                'barcode' => $barcode,
                'new_stock_id' => $stockId,
                'item_name' => $piece['name'],
                'category' => $piece['category'],
                'karat' => $piece['karat'],
                'gross_weight_gram' => $piece['gross_weight_gram'],
                'location_id' => $locationId
            ],
            'Barcode stock created from new_stock | ' . $barcode
        );
    }

    $newAvailablePcs = max(0, $availablePcs - $numPieces);
    $newAvailableWeight = max(0, round($availableWeight - $totalTransferredGram, 3));

    $set = [];
    $updParams = [];

    foreach (['available_pcs', 'balance_pcs'] as $c) {
        if (isset($nsCols[$c])) {
            $set[] = nsb_qi($c) . " = ?";
            $updParams[] = $newAvailablePcs;
        }
    }

    foreach (['available_weight_gram', 'balance_weight_gram'] as $c) {
        if (isset($nsCols[$c])) {
            $set[] = nsb_qi($c) . " = ?";
            $updParams[] = $newAvailableWeight;
        }
    }

    if (isset($nsCols['updated_at'])) {
        $set[] = "`updated_at` = NOW()";
    }

    if (empty($set)) {
        throw new RuntimeException('new_stock available update করার মতো column পাওয়া যায়নি');
    }

    $updParams[] = $stockId;

    $updSql = "
        UPDATE `new_stock`
        SET " . implode(', ', $set) . "
        WHERE " . nsb_qi($nsIdCol) . " = ?
    ";

    $upd = $pdo->prepare($updSql);
    $upd->execute($updParams);

    if ($upd->rowCount() !== 1) {
        throw new RuntimeException('new_stock available update failed');
    }

    nsb_audit(
        $pdo,
        'new_stock',
        $stockId,
        'UPDATE',
        $userId,
        [
            'available_pcs' => $availablePcs,
            'available_weight_gram' => round($availableWeight, 3)
        ],
        [
            'available_pcs' => $newAvailablePcs,
            'available_weight_gram' => $newAvailableWeight,
            'created_barcodes' => count($created),
            'transferred_weight_gram' => $totalTransferredGram
        ],
        'New stock transferred to barcode_stock | ' . $itemNameDefault
    );

    $pdo->commit();

    nsb_out(200, true, count($created) . 'টি বারকোড তৈরি হয়েছে', [
        'stock_id' => $stockId,
        'barcodes' => $created,
        'barcode_ids' => $barcodeIds,
        'created_count' => count($created),
        'transferred_weight_gram' => $totalTransferredGram,
        'remaining_pcs' => $newAvailablePcs,
        'remaining_weight_gram' => $newAvailableWeight
    ]);

} catch (Throwable $e) {
    if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
        $pdo->rollBack();
    }

    nsb_out(500, false, 'সার্ভার সমস্যা: ' . $e->getMessage());
}