<?php

require_once __DIR__ . '/../../includes/bootstrap.php';
require_login();
$pageTitle='Restaurant Planner';
const REST_POS_CARD_SURCHARGE_RATE = 0.03;
const REST_OPEN_ORDER_STATUSES_SQL = "'open','sent','preparing','served','bill_requested'";
if(!function_exists('h')){function h($v):string{return htmlspecialchars((string)$v,ENT_QUOTES,'UTF-8');}}
if(!function_exists('money_fmt')){function money_fmt($v):string{return number_format((float)$v,2);}}
if(!function_exists('str_starts_with')){
    function str_starts_with($haystack, $needle): bool {
        $haystack = (string)$haystack;
        $needle = (string)$needle;
        return $needle === '' || strncmp($haystack, $needle, strlen($needle)) === 0;
    }
}
if(!function_exists('str_contains')){
    function str_contains($haystack, $needle): bool {
        return $needle === '' || strpos((string)$haystack, (string)$needle) !== false;
    }
}
function r_current_user_id(): ?int { foreach(['user_id','ss_user_id','id','uid'] as $k){ if(isset($_SESSION[$k])&&(int)$_SESSION[$k]>0)return (int)$_SESSION[$k]; } return null; }
function r_current_user_name(): string { foreach(['currentUserName','current_user_name','user_name','username','full_name','name'] as $k){ if(isset($_SESSION[$k]) && trim((string)$_SESSION[$k])!=='') return trim((string)$_SESSION[$k]); } if(isset($GLOBALS['currentUserName']) && trim((string)$GLOBALS['currentUserName'])!=='') return trim((string)$GLOBALS['currentUserName']); return 'Logged cashier'; }
function r_flash($t,$m){$_SESSION['restaurant_flash'][]=['type'=>$t,'message'=>$m];}
function r_get_flash(){ $x=$_SESSION['restaurant_flash']??[]; unset($_SESSION['restaurant_flash']); return $x; }
function r_order_no(PDO $pdo): string {
    /*
       Generate a unique restaurant order number safely.

       Old logic used COUNT(*) + 1, which can recreate an existing number when:
       - old order numbers have gaps,
       - two users open tables at the same time,
       - an insert fails after another request already generated the same number.

       This version uses a small sequence table and LAST_INSERT_ID(), which is atomic
       in MySQL/MariaDB, so every request receives the next number only once.
    */
    $prefix = 'RST-' . date('ymd') . '-';

    try {
        $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_order_sequences (
            order_date DATE NOT NULL PRIMARY KEY,
            last_no INT NOT NULL DEFAULT 0,
            updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $stmt = $pdo->prepare("INSERT INTO restaurant_order_sequences (order_date, last_no)
            VALUES (CURDATE(), LAST_INSERT_ID(1))
            ON DUPLICATE KEY UPDATE last_no = LAST_INSERT_ID(last_no + 1)");
        $stmt->execute();

        $next = (int)$pdo->lastInsertId();
        if ($next <= 0) {
            $next = (int)$pdo->query("SELECT LAST_INSERT_ID()")->fetchColumn();
        }

        $orderNo = $prefix . str_pad((string)$next, 4, '0', STR_PAD_LEFT);

        /* Extra safety: if the sequence table was introduced after existing orders,
           advance until the generated number is not already present. */
        $check = $pdo->prepare("SELECT COUNT(*) FROM restaurant_orders WHERE order_no = :no");
        $guard = 0;
        while ($guard < 100) {
            $check->execute([':no' => $orderNo]);
            if ((int)$check->fetchColumn() === 0) {
                return $orderNo;
            }

            $stmt->execute();
            $next = (int)$pdo->lastInsertId();
            if ($next <= 0) {
                $next = (int)$pdo->query("SELECT LAST_INSERT_ID()")->fetchColumn();
            }
            $orderNo = $prefix . str_pad((string)$next, 4, '0', STR_PAD_LEFT);
            $guard++;
        }
    } catch (Throwable $e) {
        /* Fallback for restricted DB users: use MAX suffix, then loop until free. */
        try {
            $stmt = $pdo->prepare("SELECT COALESCE(MAX(CAST(SUBSTRING(order_no, :prefix_len) AS UNSIGNED)), 0)
                FROM restaurant_orders
                WHERE order_no LIKE :prefix_like");
            $stmt->execute([
                ':prefix_len'  => strlen($prefix) + 1,
                ':prefix_like' => $prefix . '%'
            ]);
            $next = (int)$stmt->fetchColumn() + 1;
            $check = $pdo->prepare("SELECT COUNT(*) FROM restaurant_orders WHERE order_no = :no");
            for ($i = 0; $i < 1000; $i++) {
                $orderNo = $prefix . str_pad((string)($next + $i), 4, '0', STR_PAD_LEFT);
                $check->execute([':no' => $orderNo]);
                if ((int)$check->fetchColumn() === 0) return $orderNo;
            }
        } catch (Throwable $ignore) {}
    }

    return $prefix . date('His') . '-' . random_int(100, 999);
}
function r_recalc(PDO $pdo, int $rid): void { $s=$pdo->prepare("SELECT COALESCE(SUM(line_total),0) sub, COALESCE(SUM(tax_amount),0) tax FROM restaurant_order_items WHERE restaurant_order_id=:id AND status='active'");$s->execute([':id'=>$rid]);$r=$s->fetch(PDO::FETCH_ASSOC)?:[];$sub=(float)($r['sub']??0);$tax=(float)($r['tax']??0);$p=$pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:id AND status='posted'");$p->execute([':id'=>$rid]);$paid=(float)$p->fetchColumn();/* Restaurant POS prices are VAT/tax inclusive. line_total stores the gross customer price; tax_total is informational and must not be added again. */$total=$sub;$status=$paid<=0?'unpaid':($paid+0.0001>=$total?'paid':'part_paid');/* Once a bill has been posted to a room folio or an internal costing department, that status must stick - a later recalc (e.g. from a void or another item change) must not silently revert it back to unpaid, since that erases the record that the full bill was already posted somewhere. */$pdo->prepare("UPDATE restaurant_orders SET subtotal=:s,tax_total=:t,total_amount=:g,payment_status=(CASE WHEN payment_status IN ('room_charged','part_room_charged','cost_center_charged','part_cost_center_charged') THEN payment_status ELSE :ps END),updated_at=NOW() WHERE id=:id")->execute([':s'=>round($sub,2),':t'=>round($tax,2),':g'=>round($total,2),':ps'=>$status,':id'=>$rid]); }
function r_recalc_folio(PDO $pdo,int $folioId):void{ $s=$pdo->prepare("SELECT COALESCE(SUM(CASE WHEN COALESCE(is_voided,0)=0 AND LOWER(COALESCE(tx_type,'')) NOT IN ('payment','receipt','deposit','payment_received','folio_payment','guest_payment','payment_void','void_payment','payment_reversal','reversal','refund','payment_refund') THEN COALESCE(debit,0) ELSE 0 END),0) charges, COALESCE(SUM(CASE WHEN COALESCE(is_voided,0)=0 AND LOWER(COALESCE(tx_type,'')) IN ('payment','receipt','deposit','payment_received','folio_payment','guest_payment') THEN COALESCE(credit,0) ELSE 0 END),0) payments FROM folio_transactions WHERE folio_id=:id");$s->execute([':id'=>$folioId]);$r=$s->fetch(PDO::FETCH_ASSOC)?:[];$c=(float)$r['charges'];$p=(float)$r['payments'];$pdo->prepare("UPDATE folios SET total_charges=:c,total_payments=:p,balance=:b,updated_at=NOW() WHERE id=:id")->execute([':c'=>$c,':p'=>$p,':b'=>$c-$p,':id'=>$folioId]); }
function r_sync_order_status(PDO $pdo,int $orderId):void{ $s=$pdo->prepare("SELECT COALESCE(SUM(total_charges),0)c,COALESCE(SUM(total_payments),0)p,COALESCE(SUM(balance),0)b FROM folios WHERE order_id=:id");$s->execute([':id'=>$orderId]);$r=$s->fetch(PDO::FETCH_ASSOC)?:[];$p=(float)$r['p'];$b=(float)$r['b'];$st=$p<=0?0:($b<=0.0001?2:1);$pdo->prepare("UPDATE orders SET payment_status=:s WHERE id=:id")->execute([':s'=>$st,':id'=>$orderId]); }
function centerText($text, $width = 32)
{
    $text = trim($text);
    $padding = max(0, floor(($width - strlen($text)) / 2));
    return str_repeat(' ', $padding) . $text . "\n";
}
function r_resolve_inhouse_guest_booking(PDO $pdo,int $orderId):array{
    $result=['order_id'=>$orderId,'property_id'=>null,'guest_name'=>'','room_no'=>''];
    if($orderId<=0) return $result;
    try{
        $guestSql="TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,'')))";
        $roomSql="COALESCE(NULLIF(TRIM(CAST(rm.room_no AS CHAR)),''), NULLIF(TRIM(CAST(rmg.room_no AS CHAR)),''), '')";
        $sql="
            SELECT
                o.id AS order_id,
                o.property_id,
                {$guestSql} AS guest_name,
                GROUP_CONCAT(DISTINCT {$roomSql} ORDER BY {$roomSql} SEPARATOR ', ') AS room_no
            FROM orders o
            LEFT JOIN guests g ON g.id=CAST(o.guest_id AS UNSIGNED)
            LEFT JOIN order_rooming rmg
                ON rmg.order_id=o.id
               AND DATE(COALESCE(rmg.check_in,o.check_in)) <= CURDATE()
               AND DATE(COALESCE(rmg.check_out,o.check_out)) > CURDATE()
            LEFT JOIN rooms rm ON rm.id=rmg.room_id
            WHERE o.id=:id
            GROUP BY o.id,o.property_id,guest_name
            LIMIT 1
        ";
        $st=$pdo->prepare($sql);
        $st->execute([':id'=>$orderId]);
        $row=$st->fetch(PDO::FETCH_ASSOC)?:[];
        if($row){
            $result['property_id']=!empty($row['property_id'])?(int)$row['property_id']:null;
            $result['guest_name']=trim((string)($row['guest_name']??''));
            $result['room_no']=trim((string)($row['room_no']??''));
        }
    }catch(Throwable $ignore){
        try{
            $st=$pdo->prepare("SELECT o.property_id, TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) AS guest_name FROM orders o LEFT JOIN guests g ON g.id=CAST(o.guest_id AS UNSIGNED) WHERE o.id=:id LIMIT 1");
            $st->execute([':id'=>$orderId]);
            $row=$st->fetch(PDO::FETCH_ASSOC)?:[];
            if($row){
                $result['property_id']=!empty($row['property_id'])?(int)$row['property_id']:null;
                $result['guest_name']=trim((string)($row['guest_name']??''));
            }
        }catch(Throwable $ignore2){}
    }
    return $result;
}


function r_release_restaurant_tables(PDO $pdo,int $restaurantOrderId,?int $fallbackTableId=null):void{
    $ids=[];
    try{
        if(r_table_exists($pdo,'restaurant_order_tables')){
            $s=$pdo->prepare("SELECT table_id FROM restaurant_order_tables WHERE restaurant_order_id=:rid");
            $s->execute([':rid'=>$restaurantOrderId]);
            $ids=array_map('intval',$s->fetchAll(PDO::FETCH_COLUMN)?:[]);
        }
    }catch(Throwable $e){ $ids=[]; }
    if($fallbackTableId && $fallbackTableId>0) $ids[]=(int)$fallbackTableId;
    $ids=array_values(array_unique(array_filter($ids)));
    if(!$ids) return;
    $in=implode(',',array_fill(0,count($ids),'?'));
    $pdo->prepare("UPDATE restaurant_tables SET status='available' WHERE id IN ($in)")->execute($ids);
}


function r_next_folio_no(PDO $pdo, int $propertyId = 0): string{
    do{
        $prefix = 'FOL';
        if($propertyId > 0 && r_table_exists($pdo,'properties')){
            try{
                $stmt = $pdo->prepare("SELECT code FROM properties WHERE id = :id LIMIT 1");
                $stmt->execute([':id'=>$propertyId]);
                $code = trim((string)($stmt->fetchColumn() ?: ''));
                if($code !== '') $prefix = 'FOL-' . strtoupper($code);
            }catch(Throwable $ignore){}
        }
        $folioNo = $prefix . '-' . date('YmdHis') . '-' . random_int(100,999);
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM folios WHERE folio_no = :folio_no");
        $stmt->execute([':folio_no'=>$folioNo]);
        $exists = (int)$stmt->fetchColumn() > 0;
    }while($exists);
    return $folioNo;
}

function r_get_room_folios_like_payments(PDO $pdo, int $orderId): array{
    if($orderId <= 0 || !r_table_exists($pdo,'order_rooming')) return [];
    $rows = $pdo->prepare("
        SELECT
            orm.id AS rooming_id,
            orm.room_id,
            orm.room_type_id,
            rt.title AS room_type_title,
            COALESCE(r.room_no, orm.room_no) AS room_no,
            CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,'')) AS guest
        FROM order_rooming orm
        LEFT JOIN rooms r ON r.id = orm.room_id
        LEFT JOIN room_types rt ON rt.id = orm.room_type_id
        LEFT JOIN orders o ON o.id = orm.order_id
        LEFT JOIN guests g ON g.id = o.guest_id
        WHERE orm.order_id = ?
        ORDER BY r.room_no ASC, orm.room_no ASC, orm.id ASC
    ");
    $rows->execute([$orderId]);
    return $rows->fetchAll(PDO::FETCH_ASSOC) ?: [];
}

function r_get_guest_folios_like_payments(PDO $pdo, int $orderId): array{
    if($orderId <= 0 || !r_table_exists($pdo,'folios')) return [];
    $stmt = $pdo->prepare("
        SELECT
            f.id AS folio_id,
            f.order_id,
            f.room_id,
            f.rooming_id,
            f.guest_id,
            f.occupant_id,
            f.occupant_name,
            f.folio_no,
            f.folio_name,
            f.balance,
            r.room_no,
            rt.title AS room_type_title
        FROM folios f
        LEFT JOIN rooms r ON r.id = f.room_id
        LEFT JOIN order_rooming orm ON orm.id = f.rooming_id
        LEFT JOIN room_types rt ON rt.id = orm.room_type_id
        WHERE f.order_id = :order_id
          AND f.folio_type = 'guest'
          AND COALESCE(f.status,1) = 1
        ORDER BY COALESCE(r.room_no, ''), f.occupant_name ASC, f.id ASC
    ");
    $stmt->execute([':order_id'=>$orderId]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
}

function r_get_or_create_room_folio_like_payments(PDO $pdo, int $orderId, array $rooming): int{
    $roomingId = (int)($rooming['rooming_id'] ?? 0);
    if($roomingId <= 0) throw new RuntimeException('Invalid room folio target.');

    $stmt = $pdo->prepare("SELECT id FROM folios WHERE order_id = ? AND rooming_id = ? AND folio_type = 'room' LIMIT 1");
    $stmt->execute([$orderId, $roomingId]);
    $id = (int)($stmt->fetchColumn() ?: 0);
    if($id > 0) return $id;

    $stmt = $pdo->prepare("SELECT property_id, order_no FROM orders WHERE id = ? LIMIT 1");
    $stmt->execute([$orderId]);
    $order = $stmt->fetch(PDO::FETCH_ASSOC);
    if(!$order) throw new RuntimeException('Booking not found while creating room folio.');

    $roomNo = trim((string)($rooming['room_no'] ?? ''));
    $guest = trim((string)($rooming['guest'] ?? ''));
    $roomTypeTitle = trim((string)($rooming['room_type_title'] ?? ''));
    if($guest === '') $guest = 'Guest';

    $folioNo = 'FOL-R-' . ($roomNo !== '' ? $roomNo : $roomingId) . '-' . $orderId;
    $folioName = 'Room ' . ($roomNo !== '' ? $roomNo : 'N/A') . ' - ' . $guest;
    if($roomTypeTitle !== '') $folioName .= ' • ' . $roomTypeTitle;

    $pdo->prepare("
        INSERT INTO folios
        (order_id, property_id, folio_no, folio_name, folio_type, room_id, rooming_id, status, total_charges, total_payments, balance)
        VALUES (?, ?, ?, ?, 'room', ?, ?, 1, 0, 0, 0)
    ")->execute([
        $orderId,
        (int)($order['property_id'] ?? 0),
        $folioNo,
        $folioName,
        (int)($rooming['room_id'] ?? 0),
        $roomingId
    ]);

    return (int)$pdo->lastInsertId();
}

function r_get_payment_style_target_folio(PDO $pdo, int $orderId, string $postTo, int $masterFolioId): int{
    if($postTo === '' || $postTo === 'master') return $masterFolioId;

    $target = json_decode($postTo, true);
    if(!is_array($target)) throw new RuntimeException('Invalid folio selection.');

    $targetType = strtolower(trim((string)($target['target_type'] ?? $target['folio_type'] ?? 'room')));

    if($targetType === 'guest'){
        $folioId = (int)($target['folio_id'] ?? 0);
        if($folioId <= 0) throw new RuntimeException('Invalid guest folio selected.');

        $stmt = $pdo->prepare("
            SELECT id
            FROM folios
            WHERE id = :folio_id
              AND order_id = :order_id
              AND folio_type = 'guest'
              AND COALESCE(status,1) = 1
            LIMIT 1
        ");
        $stmt->execute([':folio_id'=>$folioId, ':order_id'=>$orderId]);
        $validFolioId = (int)($stmt->fetchColumn() ?: 0);
        if($validFolioId <= 0) throw new RuntimeException('Selected guest folio does not belong to this booking or is inactive.');

        return $validFolioId;
    }

    return r_get_or_create_room_folio_like_payments($pdo, $orderId, $target);
}

function r_get_booking_folios_for_room_charge(PDO $pdo, int $orderId): array{
    if($orderId <= 0 || !r_table_exists($pdo,'folios')) return [];
    $stmt = $pdo->prepare("
        SELECT
            f.*,
            r.room_no
        FROM folios f
        LEFT JOIN rooms r ON r.id = f.room_id
        WHERE f.order_id = :order_id
          AND COALESCE(f.status,1) = 1
        ORDER BY
            CASE f.folio_type
                WHEN 'master' THEN 1
                WHEN 'room' THEN 2
                WHEN 'guest' THEN 3
                ELSE 9
            END,
            r.room_no ASC,
            f.id ASC
    ");
    $stmt->execute([':order_id'=>$orderId]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
}


function r_get_restaurant_routing(PDO $pdo,int $orderId):array{
    $default=['folio_type'=>'room','window_no'=>2,'folio_id'=>0];
    try{
        if(!r_table_exists($pdo,'pms_routing_rules')) return $default;
        $st=$pdo->prepare("SELECT target_folio_type,target_window_no,target_folio_id FROM pms_routing_rules WHERE is_active=1 AND source_module='restaurant' AND (order_id=:oid OR order_id IS NULL) ORDER BY order_id DESC,id DESC LIMIT 1");
        $st->execute([':oid'=>$orderId]);
        $r=$st->fetch(PDO::FETCH_ASSOC);
        if(!$r) return $default;
        return [
            'folio_type'=>in_array(($r['target_folio_type']??'room'),['master','room','guest'],true)?$r['target_folio_type']:'room',
            'window_no'=>(int)($r['target_window_no'] ?: 2),
            'folio_id'=>(int)($r['target_folio_id'] ?: 0)
        ];
    }catch(Throwable $e){ return $default; }
}

function r_find_active_folio(PDO $pdo,int $orderId,string $target,int $folioId,int $windowNo=2):int{
    $ft=$target==='guest'?'guest':($target==='room'?'room':'master');

    /*
       Folio type compatibility:
       Some PMS installs store folio_type as room/guest/master.
       Others store room_account, guest_account, Room Account, etc.
       Match by containing the target word instead of exact equality.
    */
    $typeSql = "LOWER(REPLACE(REPLACE(CAST(folio_type AS CHAR),' ','_'),'-','_')) LIKE :ft_like";
    $ftLike = '%'.$ft.'%';

    if($folioId>0){
        $sql="SELECT id FROM folios WHERE id=:fid AND order_id=:oid AND {$typeSql} AND COALESCE(status,1)=1 LIMIT 1";
        $st=$pdo->prepare($sql);
        $st->execute([':fid'=>$folioId,':oid'=>$orderId,':ft_like'=>$ftLike]);
        $id=(int)$st->fetchColumn();
        if($id>0) return $id;
        throw new RuntimeException('Selected folio does not belong to this booking/account type. Choose a valid '.$ft.' folio or leave folio blank for auto-select.');
    }

    if(r_column_exists($pdo,'folios','window_no')){
        $st=$pdo->prepare("SELECT id FROM folios WHERE order_id=:oid AND {$typeSql} AND COALESCE(status,1)=1 AND COALESCE(window_no,1)=:wn ORDER BY id ASC LIMIT 1");
        $st->execute([':oid'=>$orderId,':ft_like'=>$ftLike,':wn'=>$windowNo]);
        $id=(int)$st->fetchColumn();
        if($id>0) return $id;
    }

    $st=$pdo->prepare("SELECT id FROM folios WHERE order_id=:oid AND {$typeSql} AND COALESCE(status,1)=1 ORDER BY id ASC LIMIT 1");
    $st->execute([':oid'=>$orderId,':ft_like'=>$ftLike]);
    $id=(int)$st->fetchColumn();

    if($id<=0){
        /*
           Last fallback: do not silently post to master when Room/Guest was selected.
           Return a clear error for the selected target.
        */
        throw new RuntimeException('No active '.$ft.' folio found for this booking. Check that Room/Guest folios exist and are active.');
    }

    return $id;
}


function r_find_active_folio_scoped(PDO $pdo,int $orderId,string $target,int $folioId,int $windowNo=2,string $preferredRoomNo='',string $preferredGuestName=''):int{
    $ft=$target==='guest'?'guest':($target==='room'?'room':'master');
    $preferredRoomNo=trim($preferredRoomNo);
    $preferredGuestName=trim($preferredGuestName);

    if($folioId>0){
        return r_find_active_folio($pdo,$orderId,$target,$folioId,$windowNo);
    }

    /*
       Prefer the folio that belongs to the room/guest originally selected
       when the restaurant table was opened. This avoids posting Room Account
       charges to the first room folio on a multi-room booking.
    */
    if($ft==='room' && $preferredRoomNo!==''){
        $roomJoin = '';
        $roomExprParts = [];
        if(r_column_exists($pdo,'folios','room_no')) $roomExprParts[] = "NULLIF(TRIM(CAST(f.room_no AS CHAR)),'')";
        if(r_column_exists($pdo,'folios','folio_name')) $roomExprParts[] = "NULLIF(TRIM(CAST(f.folio_name AS CHAR)),'')";
        if(r_column_exists($pdo,'folios','room_id') && r_table_exists($pdo,'rooms')){
            $roomJoin = " LEFT JOIN rooms rm ON rm.id=f.room_id ";
            foreach(['room_no','room_number','number','name'] as $col){
                if(r_column_exists($pdo,'rooms',$col)) $roomExprParts[] = "NULLIF(TRIM(CAST(rm.`$col` AS CHAR)),'')";
            }
        }
        if(!$roomExprParts) $roomExprParts[] = "''";
        $roomExpr = "COALESCE(".implode(',', $roomExprParts).",'')";
        try{
            $st=$pdo->prepare("
                SELECT f.id
                FROM folios f
                {$roomJoin}
                WHERE f.order_id=:oid
                  AND LOWER(REPLACE(REPLACE(CAST(f.folio_type AS CHAR),' ','_'),'-','_')) LIKE '%room%'
                  AND COALESCE(f.status,1)=1
                  AND (
                        {$roomExpr} = :room_no
                        OR {$roomExpr} LIKE :room_like
                        OR :room_no_rev LIKE CONCAT('%', {$roomExpr}, '%')
                  )
                ORDER BY f.id ASC
                LIMIT 1
            ");
            $st->execute([':oid'=>$orderId, ':room_no'=>$preferredRoomNo, ':room_like'=>'%'.$preferredRoomNo.'%', ':room_no_rev'=>$preferredRoomNo]);
            $id=(int)$st->fetchColumn();
            if($id>0) return $id;
        }catch(Throwable $ignore){}
    }

    if($ft==='guest' && $preferredGuestName!==''){
        try{
            $st=$pdo->prepare("
                SELECT id
                FROM folios
                WHERE order_id=:oid
                  AND LOWER(REPLACE(REPLACE(CAST(folio_type AS CHAR),' ','_'),'-','_')) LIKE '%guest%'
                  AND COALESCE(status,1)=1
                  AND (
                        COALESCE(folio_name,'') LIKE :guest_like_name
                        OR COALESCE(folio_no,'') LIKE :guest_like_no
                  )
                ORDER BY id ASC
                LIMIT 1
            ");
            $st->execute([':oid'=>$orderId, ':guest_like_name'=>'%'.$preferredGuestName.'%', ':guest_like_no'=>'%'.$preferredGuestName.'%']);
            $id=(int)$st->fetchColumn();
            if($id>0) return $id;
        }catch(Throwable $ignore){}
    }

    return r_find_active_folio($pdo,$orderId,$target,0,$windowNo);
}


function r_post_restaurant_amount_to_folio(PDO $pdo,int $restaurantOrderId,int $folioId,float $amount,string $description,string $source='restaurant_split'):void{
    if($restaurantOrderId<=0 || $folioId<=0 || $amount<=0) return;

    $f=$pdo->prepare("SELECT f.id,f.order_id,COALESCE(f.window_no,1) AS window_no,o.property_id FROM folios f INNER JOIN orders o ON o.id=f.order_id WHERE f.id=:fid AND COALESCE(f.status,1)=1 LIMIT 1");
    $f->execute([':fid'=>$folioId]);
    $folio=$f->fetch(PDO::FETCH_ASSOC);
    if(!$folio) throw new RuntimeException('Selected folio is not active or does not exist.');

    $pdo->prepare("INSERT INTO folio_transactions(property_id,folio_id,order_id,tx_date,tx_type,description,debit,credit,source_table,source_id) VALUES(:p,:f,:o,NOW(),'restaurant',:d,:amt,0,:src,:rid)")->execute([
        ':p'=>(int)$folio['property_id'],
        ':f'=>$folioId,
        ':o'=>(int)$folio['order_id'],
        ':d'=>$description,
        ':amt'=>round($amount,2),
        ':src'=>$source,
        ':rid'=>$restaurantOrderId
    ]);

    if(r_table_exists($pdo,'restaurant_folio_posting_queue')){
        $pdo->prepare("INSERT INTO restaurant_folio_posting_queue(restaurant_order_id,pms_order_id,folio_id,window_no,amount,description,status,posted_at) VALUES(:rid,:oid,:fid,:wn,:amt,:d,'posted',NOW())")->execute([
            ':rid'=>$restaurantOrderId,
            ':oid'=>(int)$folio['order_id'],
            ':fid'=>$folioId,
            ':wn'=>(int)$folio['window_no'],
            ':amt'=>round($amount,2),
            ':d'=>$description
        ]);
    }

    r_recalc_folio($pdo,$folioId);
    r_sync_order_status($pdo,(int)$folio['order_id']);
}

function r_record_hotel_split(PDO $pdo,int $restaurantOrderId,string $splitType,string $payer,string $destinationType,string $paymentMethod,float $amount,string $reference='',?int $folioId=null,?string $note=null):int{
    $payer=trim($payer) ?: 'Guest';
    $destinationType=strtolower(trim($destinationType ?: 'payment'));
    $paymentMethod=trim($paymentMethod ?: $destinationType);
    $reference=trim($reference);
    $amount=round($amount,2);
    if($amount<=0) return 0;

    $pdo->prepare("INSERT INTO restaurant_bill_splits(restaurant_order_id,split_type,payer_name,payment_method,split_amount,reference_no,created_by) VALUES(:rid,:type,:payer,:method,:amount,:ref,:u)")->execute([
        ':rid'=>$restaurantOrderId,
        ':type'=>$splitType,
        ':payer'=>$payer,
        ':method'=>$paymentMethod,
        ':amount'=>$amount,
        ':ref'=>$reference!==''?$reference:null,
        ':u'=>r_current_user_id()
    ]);
    $splitId=(int)$pdo->lastInsertId();

    if(r_table_exists($pdo,'restaurant_split_settlements')){
        $pdo->prepare("INSERT INTO restaurant_split_settlements(restaurant_order_id,split_id,destination_type,folio_id,payer_name,amount,reference_no,note,created_by) VALUES(:rid,:sid,:dest,:fid,:payer,:amount,:ref,:note,:u)")->execute([
            ':rid'=>$restaurantOrderId,
            ':sid'=>$splitId,
            ':dest'=>$destinationType,
            ':fid'=>$folioId ?: null,
            ':payer'=>$payer,
            ':amount'=>$amount,
            ':ref'=>$reference!==''?$reference:null,
            ':note'=>$note,
            ':u'=>r_current_user_id()
        ]);
    }

    if(in_array($destinationType,['room_folio','guest_folio','master_folio','group_master','company_account','folio'],true)){
        if(!$folioId || $folioId<=0) throw new RuntimeException('A folio must be selected for '.$payer.'.');
        r_post_restaurant_amount_to_folio($pdo,$restaurantOrderId,$folioId,$amount,'Restaurant split - '.$payer.($note?' - '.$note:''),'restaurant_bill_splits');
    }elseif(in_array($destinationType,['comp','house_account','dispute','allowance','package'],true)){
        if(r_table_exists($pdo,'restaurant_house_adjustments')){
            $pdo->prepare("INSERT INTO restaurant_house_adjustments(restaurant_order_id,split_id,adjustment_type,amount,reason,created_by) VALUES(:rid,:sid,:type,:amount,:reason,:u)")->execute([
                ':rid'=>$restaurantOrderId,
                ':sid'=>$splitId,
                ':type'=>$destinationType,
                ':amount'=>$amount,
                ':reason'=>$reference!==''?$reference:($note ?: ucfirst(str_replace('_',' ',$destinationType))),
                ':u'=>r_current_user_id()
            ]);
        }
        r_add_restaurant_payment($pdo,$restaurantOrderId,ucwords(str_replace('_',' ',$destinationType)),$amount,$reference);
    }else{
        r_add_restaurant_payment($pdo,$restaurantOrderId,$paymentMethod,$amount,$reference);
    }

    return $splitId;
}

function r_get_or_create_transfer_order(PDO $pdo,array $sourceOrder,int $targetTableId):int{
    $s=$pdo->prepare("SELECT id FROM restaurant_orders WHERE table_id=:tid AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.") ORDER BY id ASC LIMIT 1");
    $s->execute([':tid'=>$targetTableId]);
    $existing=(int)$s->fetchColumn();
    if($existing>0) return $existing;

    $tbl=$pdo->prepare("SELECT * FROM restaurant_tables WHERE id=:id LIMIT 1");
    $tbl->execute([':id'=>$targetTableId]);
    $tr=$tbl->fetch(PDO::FETCH_ASSOC);
    if(!$tr) throw new RuntimeException('Target table not found.');

    $pdo->prepare("INSERT INTO restaurant_orders(property_id,area_id,table_id,order_no,order_type,linked_order_id,guest_name,room_no,group_name,covers,status,opened_by,cashier_name,notes) VALUES(:p,:a,:t,:no,:type,:linked,:guest,:room,:group_name,:covers,'open',:u,:cashier,:notes)")->execute([
        ':p'=>$tr['property_id']??($sourceOrder['property_id']??null),
        ':a'=>$tr['area_id']??($sourceOrder['area_id']??null),
        ':t'=>$targetTableId,
        ':no'=>r_order_no($pdo),
        ':type'=>$sourceOrder['order_type']??'table',
        ':linked'=>$sourceOrder['linked_order_id']??null,
        ':guest'=>$sourceOrder['guest_name']??null,
        ':room'=>$sourceOrder['room_no']??null,
        ':group_name'=>$sourceOrder['group_name']??null,
        ':covers'=>$sourceOrder['covers']??1,
        ':u'=>r_current_user_id(),
        ':cashier'=>r_current_user_name(),
        ':notes'=>'Created by check/item transfer from '.$sourceOrder['order_no']
    ]);
    $newId=(int)$pdo->lastInsertId();
    $pdo->prepare("UPDATE restaurant_tables SET status='occupied' WHERE id=:id")->execute([':id'=>$targetTableId]);
    $pdo->prepare("INSERT IGNORE INTO restaurant_order_tables(restaurant_order_id,table_id,joined_by) VALUES(:rid,:tid,:u)")->execute([':rid'=>$newId,':tid'=>$targetTableId,':u'=>r_current_user_id()]);
    return $newId;
}

function r_active_payment_total(PDO $pdo,int $restaurantOrderId):float{
    $s=$pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:id AND status='posted'");
    $s->execute([':id'=>$restaurantOrderId]);
    return (float)$s->fetchColumn();
}

/*
   Amount considered settled for deciding whether to close a restaurant check.

   Important:
   - Cash/Card/POS/Mobile/Bank settlements already create restaurant_payments.
   - Room/Guest/Master/Group/Company folio settlements post to PMS folios instead,
     so they do not appear in restaurant_payments.
   - If we only check restaurant_payments, the POS balance can show zero but the
     restaurant table remains occupied/open.
*/
function r_restaurant_settled_total(PDO $pdo,int $restaurantOrderId):float{
    $paid = r_active_payment_total($pdo,$restaurantOrderId);

    if(r_table_exists($pdo,'restaurant_split_settlements')){
        try{
            $s=$pdo->prepare("
                SELECT COALESCE(SUM(amount),0)
                FROM restaurant_split_settlements
                WHERE restaurant_order_id=:id
                  AND COALESCE(status,'posted')='posted'
                  AND destination_type IN ('room_folio','guest_folio','master_folio','group_master','company_account','folio')
            ");
            $s->execute([':id'=>$restaurantOrderId]);
            $paid += (float)$s->fetchColumn();
        }catch(Throwable $ignore){}
    }

    return round($paid,2);
}

function r_is_card_surcharge_method(string $method): bool{
    $m=strtolower(trim($method));
    return in_array($m,['pos','card','credit card','debit card','visa','mastercard','card payment','pos/card'],true);
}

function r_rest_payment_method_from_destination(string $destination): string{
    $d=strtolower(trim($destination));
    $map=[
        'cash'=>'Cash',
        'card'=>'Card',
        'pos'=>'POS',
        'mobile_money'=>'Mobile Money',
        'bank_transfer'=>'Bank Transfer',
        'room_folio'=>'Room Folio',
        'guest_folio'=>'Guest Folio',
        'master_folio'=>'Master Folio',
        'group_master'=>'Group Master',
        'company_account'=>'Company Account',
        'folio'=>'Folio',
        'comp'=>'Comp',
        'house_account'=>'House Account',
        'dispute'=>'Dispute Hold',
        'allowance'=>'Allowance',
        'package'=>'Package / Allowance'
    ];
    return $map[$d] ?? ucwords(str_replace('_',' ',$d ?: 'cash'));
}


function r_normalize_folio_type_value(string $value): string{
    $v = strtolower(trim($value));
    $v = str_replace([' ', '-'], '_', $v);
    if($v === '') return '';
    if(strpos($v, 'master') !== false) return 'master';
    if(strpos($v, 'room') !== false) return 'room';
    if(strpos($v, 'guest') !== false) return 'guest';
    if(strpos($v, 'group') !== false) return 'group';
    if(strpos($v, 'company') !== false) return 'company';
    return $v;
}

function r_add_restaurant_payment(PDO $pdo,int $restaurantOrderId,string $method,float $amount,string $reference=''):void{
    $method=trim($method);
    if($restaurantOrderId<=0 || $amount<=0 || $method==='') return;

    $baseAmount=round($amount,2);
    $surcharge=0.00;
    $paymentAmount=$baseAmount;

    if(r_is_card_surcharge_method($method)){
        /*
           Gratuity is not subject to the 3% card/POS surcharge.
           Surcharge is calculated only on the remaining non-gratuity part of the check.
        */
        $gratuityTotal = r_order_gratuity_total($pdo, $restaurantOrderId);
        $paidBefore = r_order_paid_total($pdo, $restaurantOrderId);
        $orderTotalForSurcharge = 0.0;
        try{
            $stTot=$pdo->prepare("SELECT COALESCE(total_amount,0) FROM restaurant_orders WHERE id=:id LIMIT 1");
            $stTot->execute([':id'=>$restaurantOrderId]);
            $orderTotalForSurcharge=(float)$stTot->fetchColumn();
        }catch(Throwable $ignore){}
        $nonGratuityTotal = max(0, round($orderTotalForSurcharge - $gratuityTotal, 2));
        $nonGratuityPaidBefore = min($paidBefore, $nonGratuityTotal);
        $surchargeBase = min($baseAmount, max(0, $nonGratuityTotal - $nonGratuityPaidBefore));

        $surcharge=round($surchargeBase * REST_POS_CARD_SURCHARGE_RATE, 2);
        $paymentAmount=round($baseAmount + $surcharge, 2);

        if($surcharge>0){
            $desc='POS/Card surcharge 3% on '.money_fmt($baseAmount);
            $pdo->prepare("INSERT INTO restaurant_order_items(restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status) VALUES(:rid,NULL,NULL,:name,1,:unit_amount,0,0,:line_amount,'master','sent',:notes,'active')")->execute([
                ':rid'=>$restaurantOrderId,
                ':name'=>'POS/Card Surcharge 3%',
                ':unit_amount'=>$surcharge,
                ':line_amount'=>$surcharge,
                ':notes'=>$desc
            ]);
        }
    }

    $refNote=trim($reference);
    if(r_is_card_surcharge_method($method) && $surcharge>0){
        $refNote=trim($refNote . ($refNote!==''?' | ':'') . 'Base '.money_fmt($baseAmount).' + 3% surcharge '.money_fmt($surcharge).' = '.money_fmt($paymentAmount));
    }

    $pdo->prepare("INSERT INTO restaurant_payments(restaurant_order_id,payment_method,amount,reference_no,created_by) VALUES(:rid,:m,:a,:r,:u)")->execute([
        ':rid'=>$restaurantOrderId,
        ':m'=>$method,
        ':a'=>round($paymentAmount,2),
        ':r'=>$refNote!==''?$refNote:null,
        ':u'=>r_current_user_id()
    ]);
    r_recalc($pdo,$restaurantOrderId);
}

function r_close_if_paid(PDO $pdo,int $restaurantOrderId):void{
    r_recalc($pdo,$restaurantOrderId);

    $ord=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id LIMIT 1");
    $ord->execute([':id'=>$restaurantOrderId]);
    $o=$ord->fetch(PDO::FETCH_ASSOC);
    if(!$o) return;

    $total = round((float)($o['total_amount'] ?? 0), 2);
    $settled = r_restaurant_settled_total($pdo,$restaurantOrderId);
    $balance = round($total - $settled, 2);

    if($balance <= 0.0001){
        $pdo->prepare("
            UPDATE restaurant_orders
            SET status='closed',
                payment_status=IF(payment_status='room_charged','room_charged','paid'),
                closed_by=:u,
                closed_at=COALESCE(closed_at,NOW()),
                updated_at=NOW()
            WHERE id=:id
        ")->execute([':u'=>r_current_user_id(),':id'=>$restaurantOrderId]);

        r_recipe_stock_flash(r_post_recipe_stock_sale_movements($pdo,$restaurantOrderId));
        r_release_restaurant_tables($pdo,$restaurantOrderId,!empty($o['table_id'])?(int)$o['table_id']:null);

        try{
            if(r_table_exists($pdo,'restaurant_order_tables')){
                $pdo->prepare("DELETE FROM restaurant_order_tables WHERE restaurant_order_id=:rid")->execute([':rid'=>$restaurantOrderId]);
            }
        }catch(Throwable $ignore){}
    }
}

/*
   Recipe stock deduction for Restaurant POS:
   - Does NOT change order totals, payments, folios, split logic or kitchen flow.
   - Posts ingredient SALE movements only when a restaurant order closes.
   - Non-blocking by design: recipe posting errors are recorded on the order notes
     and do not stop payment/table close.
*/
function r_ensure_recipe_stock_schema(PDO $pdo):void{
    if(r_table_exists($pdo,'restaurant_orders')){
        if(!r_column_exists($pdo,'restaurant_orders','recipe_stock_posted')){
            try{ $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN recipe_stock_posted TINYINT(1) NOT NULL DEFAULT 0 AFTER payment_status"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'restaurant_orders','recipe_stock_posted_at')){
            try{ $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN recipe_stock_posted_at DATETIME NULL AFTER recipe_stock_posted"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'restaurant_orders','recipe_stock_error')){
            try{ $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN recipe_stock_error TEXT NULL AFTER recipe_stock_posted_at"); }catch(Throwable $e){}
        }
    }

    $pdo->exec("CREATE TABLE IF NOT EXISTS stock_movements (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        item_id INT UNSIGNED NOT NULL,
        store_id INT UNSIGNED NOT NULL,
        movement_type ENUM('OPENING','GRN','ISSUE','SALE','TRANSFER_IN','TRANSFER_OUT','ADJUSTMENT_IN','ADJUSTMENT_OUT','WASTAGE','RETURN') NOT NULL,
        qty_in DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        qty_out DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        unit_cost DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        total_cost DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        reference_type VARCHAR(60) NULL,
        reference_id BIGINT UNSIGNED NULL,
        notes TEXT NULL,
        movement_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_stock_item_store (item_id, store_id),
        INDEX idx_stock_reference (reference_type, reference_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    if(r_table_exists($pdo,'inventory_recipes')){
        if(!r_column_exists($pdo,'inventory_recipes','restaurant_menu_item_id')){
            try{ $pdo->exec("ALTER TABLE inventory_recipes ADD COLUMN restaurant_menu_item_id INT UNSIGNED NULL AFTER id"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'inventory_recipes','recipe_type')){
            try{ $pdo->exec("ALTER TABLE inventory_recipes ADD COLUMN recipe_type ENUM('food','cocktail','mocktail','modifier','prep') NOT NULL DEFAULT 'food' AFTER restaurant_menu_item_id"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'inventory_recipes','recipe_category_id')){
            try{ $pdo->exec("ALTER TABLE inventory_recipes ADD COLUMN recipe_category_id INT UNSIGNED NULL AFTER recipe_type"); }catch(Throwable $e){}
        }
    }
}

function r_recipe_default_store_id(PDO $pdo,array $recipe,array $menuItem):int{
    $defaultStoreId=(int)($recipe['default_store_id'] ?? 0);
    if($defaultStoreId>0) return $defaultStoreId;

    if(!r_table_exists($pdo,'store_locations')) return 0;

    $preferred=[];
    if((int)($menuItem['is_bar'] ?? 0)===1 || strtolower((string)($recipe['recipe_type'] ?? ''))==='cocktail' || strtolower((string)($recipe['recipe_type'] ?? ''))==='mocktail'){
        $preferred=['bar','restaurant','kitchen','main_store'];
    }else{
        $preferred=['kitchen','restaurant','bar','main_store'];
    }

    foreach($preferred as $type){
        try{
            $st=$pdo->prepare("SELECT id FROM store_locations WHERE is_active=1 AND location_type=:t ORDER BY is_pos_location DESC, id ASC LIMIT 1");
            $st->execute([':t'=>$type]);
            $id=(int)$st->fetchColumn();
            if($id>0) return $id;
        }catch(Throwable $e){}
    }

    try{
        $id=(int)$pdo->query("SELECT id FROM store_locations WHERE is_active=1 ORDER BY id ASC LIMIT 1")->fetchColumn();
        return $id>0?$id:0;
    }catch(Throwable $e){ return 0; }
}

function r_find_recipe_for_menu_item(PDO $pdo,int $menuItemId):?array{
    if($menuItemId<=0 || !r_table_exists($pdo,'inventory_recipes')) return null;

    $hasMenuCol=r_column_exists($pdo,'inventory_recipes','restaurant_menu_item_id');

    if($hasMenuCol){
        $st=$pdo->prepare("SELECT * FROM inventory_recipes WHERE restaurant_menu_item_id=:mid AND is_active=1 ORDER BY id DESC LIMIT 1");
        $st->execute([':mid'=>$menuItemId]);
        $r=$st->fetch(PDO::FETCH_ASSOC);
        if($r) return $r;
    }

    /*
       Backward compatibility for older recipes.php:
       If a recipe was linked to inventory_items.recipe_item_id and the
       restaurant menu item points to that same inventory item, still deduct it.
    */
    if(r_table_exists($pdo,'restaurant_menu_items') && r_column_exists($pdo,'restaurant_menu_items','inventory_item_id') && r_column_exists($pdo,'inventory_recipes','recipe_item_id')){
        $st=$pdo->prepare("
            SELECT ir.*
            FROM restaurant_menu_items mi
            INNER JOIN inventory_recipes ir ON ir.recipe_item_id = mi.inventory_item_id
            WHERE mi.id = :mid
              AND ir.is_active = 1
            ORDER BY ir.id DESC
            LIMIT 1
        ");
        $st->execute([':mid'=>$menuItemId]);
        $r=$st->fetch(PDO::FETCH_ASSOC);
        if($r) return $r;
    }

    return null;
}

function r_recipe_stock_flash(array $result):void{
    $posted=(int)($result['posted_count'] ?? 0);
    $recipes=(int)($result['recipes_count'] ?? 0);
    if(!empty($result['already_posted'])){
        r_flash('info','Recipe stock was already posted for this restaurant order. No duplicate stock movement was created.');
        return;
    }
    if(!empty($result['error'])){
        r_flash('warning','Restaurant closed, but recipe stock posting failed: '.$result['error']);
        return;
    }
    if($recipes>0 && $posted<=0){
        r_flash('warning','Restaurant closed. Recipe(s) were found, but no ingredient stock movement was created. Check recipe ingredients and default store/location.');
        return;
    }
    if($posted>0){
        $parts=[];
        foreach(array_slice(($result['lines'] ?? []),0,8) as $line){
            $parts[]='#'.(int)($line['movement_id'] ?? 0).' '.($line['ingredient_name'] ?? 'Ingredient').' OUT '.number_format((float)($line['qty_out'] ?? 0),4).' from '.($line['store_name'] ?? ('Store '.(int)($line['store_id'] ?? 0)));
        }
        $extra=count($result['lines'] ?? [])>8 ? ' +'.(count($result['lines'])-8).' more' : '';
        r_flash('success','Recipe stock deducted: '.$posted.' SALE movement(s), cost value '.money_fmt($result['total_cost'] ?? 0).'. '.implode(' | ',$parts).$extra);
    }
}

function r_post_recipe_stock_sale_movements(PDO $pdo,int $restaurantOrderId):array{
    $result=[
        'posted_count'=>0,
        'recipes_count'=>0,
        'total_cost'=>0.0,
        'lines'=>[],
        'already_posted'=>false,
        'error'=>null,
    ];
    if($restaurantOrderId<=0) return $result;

    try{
        r_ensure_recipe_stock_schema($pdo);

        $ord=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id LIMIT 1");
        $ord->execute([':id'=>$restaurantOrderId]);
        $order=$ord->fetch(PDO::FETCH_ASSOC);
        if(!$order) return $result;
        if((int)($order['recipe_stock_posted'] ?? 0)===1){
            $result['already_posted']=true;
            return $result;
        }

        if(!r_table_exists($pdo,'inventory_recipes') || !r_table_exists($pdo,'inventory_recipe_lines') || !r_table_exists($pdo,'stock_movements')){
            return $result;
        }

        $lines=$pdo->prepare("
            SELECT
                roi.id AS order_item_id,
                roi.menu_item_id,
                roi.item_name,
                roi.qty,
                mi.is_bar,
                mi.is_food,
                mi.inventory_item_id
            FROM restaurant_order_items roi
            LEFT JOIN restaurant_menu_items mi ON mi.id = roi.menu_item_id
            WHERE roi.restaurant_order_id = :rid
              AND roi.status = 'active'
              AND COALESCE(roi.is_condiment,0) = 0
              AND roi.menu_item_id IS NOT NULL
        ");
        $lines->execute([':rid'=>$restaurantOrderId]);
        $orderItems=$lines->fetchAll(PDO::FETCH_ASSOC);

        if(!$orderItems){
            $pdo->prepare("UPDATE restaurant_orders SET recipe_stock_posted=1, recipe_stock_posted_at=NOW(), recipe_stock_error=NULL WHERE id=:id")->execute([':id'=>$restaurantOrderId]);
            return $result;
        }

        $insert=$pdo->prepare("
            INSERT INTO stock_movements
            (item_id, store_id, movement_type, qty_in, qty_out, unit_cost, total_cost, reference_type, reference_id, notes, movement_date)
            VALUES
            (:item_id, :store_id, 'SALE', 0, :qty_out, :unit_cost, :total_cost, 'RESTAURANT_RECIPE', :reference_id, :notes, NOW())
        ");

        foreach($orderItems as $orderItem){
            $menuItemId=(int)($orderItem['menu_item_id'] ?? 0);
            $recipe=r_find_recipe_for_menu_item($pdo,$menuItemId);
            if(!$recipe) continue;
            $result['recipes_count']++;

            $yieldQty=(float)($recipe['yield_qty'] ?? 1);
            if($yieldQty<=0) $yieldQty=1;

            $menuMeta=[
                'is_bar'=>(int)($orderItem['is_bar'] ?? 0),
                'is_food'=>(int)($orderItem['is_food'] ?? 0),
            ];
            $storeId=r_recipe_default_store_id($pdo,$recipe,$menuMeta);
            if($storeId<=0) continue;

            $storeName='Store '.$storeId;
            try{
                if(r_table_exists($pdo,'store_locations')){
                    $stStore=$pdo->prepare("SELECT location_name FROM store_locations WHERE id=:id LIMIT 1");
                    $stStore->execute([':id'=>$storeId]);
                    $storeName=(string)($stStore->fetchColumn() ?: $storeName);
                }
            }catch(Throwable $ignore){}

            $recipeLines=$pdo->prepare("
                SELECT
                    rl.*,
                    ii.item_name,
                    COALESCE(ii.avg_cost,0) AS avg_cost,
                    COALESCE(ii.cost_price,0) AS cost_price
                FROM inventory_recipe_lines rl
                INNER JOIN inventory_items ii ON ii.id = rl.ingredient_item_id
                WHERE rl.recipe_id = :recipe_id
            ");
            $recipeLines->execute([':recipe_id'=>(int)$recipe['id']]);

            foreach($recipeLines->fetchAll(PDO::FETCH_ASSOC) as $rl){
                $ingredientId=(int)$rl['ingredient_item_id'];
                $baseQty=(float)$rl['qty_required'];
                $waste=(float)($rl['wastage_percent'] ?? 0);
                $soldQty=(float)$orderItem['qty'];

                /* Recipe preparation logic:
                   ingredient out = (recipe qty required / recipe yield) * sold menu quantity,
                   then add wastage percent if set on the recipe line. */
                $qtyOut=($baseQty / $yieldQty) * $soldQty;
                if($waste>0) $qtyOut *= (1 + ($waste/100));
                $qtyOut=round($qtyOut,4);

                if($qtyOut<=0) continue;

                $unitCost=(float)($rl['avg_cost'] ?: $rl['cost_price']);
                $totalCost=round($qtyOut*$unitCost,4);
                $result['total_cost'] += $totalCost;

                $insert->execute([
                    ':item_id'=>$ingredientId,
                    ':store_id'=>$storeId,
                    ':qty_out'=>$qtyOut,
                    ':unit_cost'=>$unitCost,
                    ':total_cost'=>$totalCost,
                    ':reference_id'=>(int)$orderItem['order_item_id'],
                    ':notes'=>'Recipe ingredient sale for restaurant order '.$order['order_no'].' | Menu: '.$orderItem['item_name'].' | Recipe: '.($recipe['recipe_name'] ?? 'Recipe'),
                ]);

                $movementId=(int)$pdo->lastInsertId();
                $result['posted_count']++;
                $result['lines'][]=[
                    'movement_id'=>$movementId,
                    'ingredient_id'=>$ingredientId,
                    'ingredient_name'=>(string)($rl['item_name'] ?? ('Item '.$ingredientId)),
                    'store_id'=>$storeId,
                    'store_name'=>$storeName,
                    'qty_out'=>$qtyOut,
                    'unit_cost'=>$unitCost,
                    'total_cost'=>$totalCost,
                    'menu_item'=>(string)($orderItem['item_name'] ?? ''),
                    'recipe_name'=>(string)($recipe['recipe_name'] ?? 'Recipe'),
                ];
            }
        }

        $pdo->prepare("UPDATE restaurant_orders SET recipe_stock_posted=1, recipe_stock_posted_at=NOW(), recipe_stock_error=NULL WHERE id=:id")->execute([':id'=>$restaurantOrderId]);

        if($result['posted_count']>0){
            $noteLines=[];
            foreach($result['lines'] as $line){
                $noteLines[]='Movement #'.(int)$line['movement_id'].' '.$line['ingredient_name'].' OUT '.number_format((float)$line['qty_out'],4).' from '.$line['store_name'].' for '.$line['menu_item'];
            }
            $noteAdd='Recipe stock posted: '.$result['posted_count'].' ingredient SALE movement(s), value '.money_fmt($result['total_cost']).' on '.date('Y-m-d H:i:s')."\n".implode("\n",$noteLines);
            $oldNote=trim((string)($order['notes'] ?? ''));
            $newNote=$oldNote!=='' ? ($oldNote."\n".$noteAdd) : $noteAdd;
            $pdo->prepare("UPDATE restaurant_orders SET notes=:notes WHERE id=:id")->execute([':notes'=>$newNote,':id'=>$restaurantOrderId]);
        }

        return $result;
    }catch(Throwable $e){
        $result['error']=$e->getMessage();
        try{
            $pdo->prepare("UPDATE restaurant_orders SET recipe_stock_error=:err WHERE id=:id")->execute([
                ':err'=>$e->getMessage(),
                ':id'=>$restaurantOrderId
            ]);
        }catch(Throwable $ignore){}
        return $result;
    }
}


function r_ensure_qz_printer_schema(PDO $pdo): void{
    /*
       PrintNode printer setup.
       Legacy QZ columns are kept for old data, but printing now uses:
       restaurant_printers.printnode_printer_id
    */
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_printers (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        property_id INT UNSIGNED NULL,
        printer_name VARCHAR(120) NOT NULL,
        printer_role VARCHAR(60) NOT NULL,
        printnode_printer_id BIGINT UNSIGNED NULL,
        qz_printer_name VARCHAR(180) NULL,
        printer_ip VARCHAR(100) NULL,
        printer_port INT NOT NULL DEFAULT 9100,
        printer_type VARCHAR(40) NOT NULL DEFAULT 'printnode',
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        sort_order INT NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_rest_printer_role(printer_role,is_active),
        INDEX idx_rest_printer_property(property_id,is_active),
        INDEX idx_rest_printer_printnode(printnode_printer_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    foreach([
        "property_id INT UNSIGNED NULL AFTER id",
        "printer_name VARCHAR(120) NOT NULL DEFAULT 'Printer' AFTER property_id",
        "printer_role VARCHAR(60) NOT NULL DEFAULT 'kitchen' AFTER printer_name",
        "printnode_printer_id BIGINT UNSIGNED NULL AFTER printer_role",
        "qz_printer_name VARCHAR(180) NULL AFTER printnode_printer_id",
        "printer_ip VARCHAR(100) NULL AFTER qz_printer_name",
        "printer_port INT NOT NULL DEFAULT 9100 AFTER printer_ip",
        "printer_type VARCHAR(40) NOT NULL DEFAULT 'printnode' AFTER printer_port",
        "is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER printer_type",
        "sort_order INT NOT NULL DEFAULT 1 AFTER is_active",
        "created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP AFTER sort_order",
        "updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at"
    ] as $def){
        $col=trim(strtok($def,' '));
        if(!r_column_exists($pdo,'restaurant_printers',$col)){
            try{ $pdo->exec("ALTER TABLE restaurant_printers ADD COLUMN $def"); }catch(Throwable $e){}
        }
    }

    try{ $pdo->exec("ALTER TABLE restaurant_printers MODIFY printer_type VARCHAR(40) NOT NULL DEFAULT 'printnode'"); }catch(Throwable $e){}
    try{ $pdo->exec("ALTER TABLE restaurant_printers ADD INDEX idx_rest_printer_role(printer_role,is_active)"); }catch(Throwable $e){}
    try{ $pdo->exec("ALTER TABLE restaurant_printers ADD INDEX idx_rest_printer_property(property_id,is_active)"); }catch(Throwable $e){}
    try{ $pdo->exec("ALTER TABLE restaurant_printers ADD INDEX idx_rest_printer_printnode(printnode_printer_id)"); }catch(Throwable $e){}

    try{
        $pdo->exec("INSERT IGNORE INTO restaurant_printers
            (id,property_id,printer_name,printer_role,printnode_printer_id,qz_printer_name,printer_ip,printer_port,printer_type,is_active,sort_order)
            VALUES
            (1,NULL,'Kitchen Printer','kitchen',NULL,'Kitchen Printer',NULL,9100,'printnode',1,1),
            (2,NULL,'Bar Printer','bar',NULL,'Bar Printer',NULL,9100,'printnode',1,2),
            (3,NULL,'Master / Receipt Printer','master',NULL,'Receipt Printer',NULL,9100,'printnode',1,3),
            (4,NULL,'Receipt Printer','receipt',NULL,'Receipt Printer',NULL,9100,'printnode',1,4)");
    }catch(Throwable $e){}
}

function r_load_restaurant_printers(PDO $pdo): array{
    if(!r_table_exists($pdo,'restaurant_printers')) return [];
    try{
        return $pdo->query("SELECT * FROM restaurant_printers ORDER BY is_active DESC, sort_order ASC, printer_role ASC, printer_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }catch(Throwable $e){ return []; }
}

function r_qz_print_jobs(PDO $pdo, array $jobIds): array{
    $jobIds=array_values(array_unique(array_filter(array_map('intval',$jobIds))));
    if(!$jobIds) return [];
    $in=implode(',',array_fill(0,count($jobIds),'?'));

    /*
       Print routing priority:
       1. restaurant_print_jobs.printer_id -> exact configured printer
       2. restaurant_print_jobs.printer_role -> first active printer for that role

       IMPORTANT:
       Do not use GROUP BY j.id here. On servers with ONLY_FULL_GROUP_BY enabled,
       that query fails because the printer columns are not grouped/aggregated.
       Test Print can still work because it does not use this loader, which is why
       Test Print may print while Send/Reprint appears to do nothing.
    */
    $hasPrinterId = r_column_exists($pdo,'restaurant_print_jobs','printer_id');
    $printerIdExpr = $hasPrinterId ? 'j.printer_id' : 'NULL';

    $sql="
        SELECT
            j.id,
            j.restaurant_order_id,
            j.printer_role,
            {$printerIdExpr} AS printer_id,
            j.job_type,
            j.payload,
            COALESCE(p_exact.printnode_printer_id,p_role.printnode_printer_id,p_exact.printer_name,p_role.printer_name,j.printer_role) AS printnode_printer_id,
            COALESCE(p_exact.printer_name,p_role.printer_name,j.printer_role) AS printer_name,
            COALESCE(p_exact.printer_ip,p_role.printer_ip,'') AS printer_ip,
            COALESCE(p_exact.printer_port,p_role.printer_port,9100) AS printer_port,
            COALESCE(p_exact.printer_type,p_role.printer_type,'qz_windows') AS printer_type
        FROM restaurant_print_jobs j
        LEFT JOIN restaurant_printers p_exact
          ON {$printerIdExpr} IS NOT NULL
         AND p_exact.id = {$printerIdExpr}
         AND p_exact.is_active = 1
        LEFT JOIN restaurant_printers p_role
          ON ({$printerIdExpr} IS NULL OR p_exact.id IS NULL)
         AND p_role.id = (
                SELECT p2.id
                FROM restaurant_printers p2
                WHERE p2.printer_role = j.printer_role
                  AND p2.is_active = 1
                ORDER BY p2.sort_order ASC, p2.id ASC
                LIMIT 1
            )
        WHERE j.id IN ($in)
        ORDER BY j.id ASC
    ";
    $st=$pdo->prepare($sql);
    $st->execute($jobIds);
    return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
}

function r_resolve_menu_item_printer_role(array $menuItem): string{
    $role=strtolower(trim((string)($menuItem['printer_role'] ?? '')));
    if($role!=='') return $role;
    if((int)($menuItem['is_bar'] ?? 0)===1) return 'bar';
    return 'kitchen';
}

function r_ensure_print_routing_columns(PDO $pdo): void{
    if(r_table_exists($pdo,'restaurant_order_items')){
        /*
           Send prints only lines with print_status='pending'.
           Some older installs had print_status defaulting to 'sent', so new
           items were saved as already-sent and Send showed "No pending items".
           Force the default back to pending and also add printer_id support.
        */
        try{ $pdo->exec("ALTER TABLE restaurant_order_items MODIFY print_status ENUM('pending','sent','printed','void') NOT NULL DEFAULT 'pending'"); }catch(Throwable $e){}
        if(!r_column_exists($pdo,'restaurant_order_items','printer_id')){
            try{ $pdo->exec("ALTER TABLE restaurant_order_items ADD COLUMN printer_id INT UNSIGNED NULL AFTER printer_role"); }catch(Throwable $e){}
        }
    }
    if(r_table_exists($pdo,'restaurant_print_jobs')){
        if(!r_column_exists($pdo,'restaurant_print_jobs','printer_id')){
            try{ $pdo->exec("ALTER TABLE restaurant_print_jobs ADD COLUMN printer_id INT UNSIGNED NULL AFTER printer_role"); }catch(Throwable $e){}
        }
    }
}


function r_printer_row_matches_role(PDO $pdo, int $printerRowId, string $role): bool{
    if($printerRowId<=0 || trim($role)==='') return false;
    try{
        $st=$pdo->prepare("SELECT printer_role FROM restaurant_printers WHERE id=:id AND is_active=1 LIMIT 1");
        $st->execute([':id'=>$printerRowId]);
        $dbRole=strtolower(trim((string)$st->fetchColumn()));
        return $dbRole !== '' && $dbRole === strtolower(trim($role));
    }catch(Throwable $e){
        return false;
    }
}

function r_create_station_print_jobs(PDO $pdo, int $restaurantOrderId, bool $reprint=false): array{
    r_ensure_qz_printer_schema($pdo);
    r_ensure_print_routing_columns($pdo);

    if($restaurantOrderId<=0) throw new RuntimeException('Invalid restaurant order.');

    $statusClause = $reprint ? "oi.status='active'" : "oi.status='active' AND oi.print_status='pending'";
    $hasOiPrinterId = r_column_exists($pdo,'restaurant_order_items','printer_id');
    $oiPrinterIdExpr = $hasOiPrinterId ? 'oi.printer_id' : 'NULL';

    $hasCategoryTable = r_table_exists($pdo,'restaurant_menu_categories');
    $categorySelect = $hasCategoryTable
        ? "mc.category_name AS menu_category_name, mc.sort_order AS menu_category_sort, mc.parent_id AS menu_category_parent_id,"
        : "'' AS menu_category_name, 999 AS menu_category_sort, NULL AS menu_category_parent_id,";
    $categoryJoin = $hasCategoryTable
        ? "LEFT JOIN restaurant_menu_categories mc ON mc.id = mi.category_id"
        : "";

    $sql="
        SELECT
            oi.*,
            {$oiPrinterIdExpr} AS order_printer_id,
            mi.default_printer_id AS menu_default_printer_id,
            mi.printer_role AS menu_printer_role,
            mi.is_bar,
            mi.is_food,
            mi.category_id AS menu_category_id,
            {$categorySelect}
            COALESCE(mi.sort_order, oi.id) AS menu_item_sort
        FROM restaurant_order_items oi
        LEFT JOIN restaurant_menu_items mi ON mi.id = oi.menu_item_id
        {$categoryJoin}
        WHERE oi.restaurant_order_id=:rid
          AND {$statusClause}
        ORDER BY COALESCE({$oiPrinterIdExpr}, mi.default_printer_id, 0), COALESCE(NULLIF(oi.printer_role,''), NULLIF(mi.printer_role,''), 'kitchen'), oi.id ASC
    ";
    $s=$pdo->prepare($sql);
    $s->execute([':rid'=>$restaurantOrderId]);
    $rows=$s->fetchAll(PDO::FETCH_ASSOC);

    if(!$rows){
        if(!$reprint){
            $dbg=$pdo->prepare("SELECT print_status, COUNT(*) AS cnt FROM restaurant_order_items WHERE restaurant_order_id=:rid AND status='active' GROUP BY print_status ORDER BY print_status");
            $dbg->execute([':rid'=>$restaurantOrderId]);
            $parts=[];
            foreach($dbg->fetchAll(PDO::FETCH_ASSOC) as $dr){ $parts[]=($dr['print_status'] ?: 'blank').':'.(int)$dr['cnt']; }
            $suffix=$parts ? ' Current active item statuses: '.implode(', ',$parts).'.' : '';
            throw new RuntimeException('No pending items to print. Add new items or use Reprint to print the whole docket again.'.$suffix);
        }
        throw new RuntimeException('No active items to reprint.');
    }

    $orderInfo=[];
    try{
        $ost=$pdo->prepare("SELECT order_no,table_id,guest_name,room_no,group_name,covers,cashier_name FROM restaurant_orders WHERE id=:id LIMIT 1");
        $ost->execute([':id'=>$restaurantOrderId]);
        $orderInfo=$ost->fetch(PDO::FETCH_ASSOC)?:[];
    }catch(Throwable $ignore){}

    $groups=[];
    foreach($rows as $r){
        /*
           Correct routing order:
           1. Resolve the station role from the saved order item / menu item / is_bar.
           2. Only use an exact printer row ID if that row has the SAME role.
              This prevents a bar item with default_printer_id=Kitchen from being
              sent to the kitchen printer.
           3. Otherwise route by role, e.g. printer_role='bar' -> active bar printer.
        */
        $role=strtolower(trim((string)($r['printer_role'] ?: ($r['menu_printer_role'] ?? ''))));
        if($role===''){
            $role=((int)($r['is_bar'] ?? 0)===1) ? 'bar' : 'kitchen';
        }

        $printerId=0;
        $orderPrinterId=(int)($r['order_printer_id'] ?? 0);
        $menuDefaultPrinterId=(int)($r['menu_default_printer_id'] ?? 0);

        if($orderPrinterId>0 && r_printer_row_matches_role($pdo,$orderPrinterId,$role)){
            $printerId=$orderPrinterId;
        }elseif($menuDefaultPrinterId>0 && r_printer_row_matches_role($pdo,$menuDefaultPrinterId,$role)){
            $printerId=$menuDefaultPrinterId;
        }

        $key=($printerId>0 ? ('id_'.$printerId) : ('role_'.$role));
        if(!isset($groups[$key])) $groups[$key]=['printer_id'=>$printerId>0?$printerId:null,'role'=>$role,'lines'=>[]];
        $groups[$key]['lines'][]=$r;
    }

    $jobIds=[];
    $insertHasPrinterId = r_column_exists($pdo,'restaurant_print_jobs','printer_id');

    $insertPrintJob = function(string $role, ?int $printerId, string $jobType, string $payload) use ($pdo, $restaurantOrderId, $insertHasPrinterId, &$jobIds): void {
        $role = strtolower(trim($role)) !== '' ? strtolower(trim($role)) : 'kitchen';
        if($insertHasPrinterId){
            $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,printer_id,job_type,payload,status) VALUES(:rid,:role,:printer_id,:job_type,:p,'pending')")
                ->execute([':rid'=>$restaurantOrderId,':role'=>$role,':printer_id'=>$printerId,':job_type'=>$jobType,':p'=>$payload]);
        }else{
            $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,job_type,payload,status) VALUES(:rid,:role,:job_type,:p,'pending')")
                ->execute([':rid'=>$restaurantOrderId,':role'=>$role,':job_type'=>$jobType,':p'=>$payload]);
        }
        $jobIds[]=(int)$pdo->lastInsertId();
    };

    foreach($groups as $group){
        $role=$group['role'] ?: 'kitchen';
        $printerId=$group['printer_id'];
        $lines=$group['lines'];

        $payload="";
        $payload.="*** ".strtoupper($role).($reprint ? " REPRINT" : " TICKET")." ***\n";
        $payload.="Order: ".($orderInfo['order_no']??('#'.$restaurantOrderId))."\n";
        if(!empty($orderInfo['table_id'])) $payload.="Table: ".$orderInfo['table_id']."\n";
        if(!empty($orderInfo['guest_name'])) $payload.="Guest: ".$orderInfo['guest_name']."\n";
        if(!empty($orderInfo['room_no'])) $payload.="Room: ".$orderInfo['room_no']."\n";
        if(!empty($orderInfo['group_name'])) $payload.="Group: ".$orderInfo['group_name']."\n";
        $payload.=($reprint ? "Reprint" : "Sent").": ".date('Y-m-d H:i:s')."\n";
        $payload.="Cashier: ".r_current_user_name()."\n";
        $payload.="------------------------------\n";
        foreach($lines as $l){
            $qty=rtrim(rtrim(number_format((float)$l['qty'],2,'.',''),'0'),'.');
            $payload.=$qty.' x '.$l['item_name']."\n";
            if(trim((string)($l['notes']??''))!=='') $payload.="   Note: ".$l['notes']."\n";
        }
        $payload.="------------------------------\n\n";

        $insertPrintJob($role, $printerId, 'station', $payload);
    }

    /*
       MASTER CONSOLIDATED COPY
       ----------------------------------------------------
       Every Send/Reprint now creates one extra consolidated docket.
       It is routed to printer_role='kitchen' so the kitchen printer receives
       a complete master copy while each item still prints to its own station.

       To change the destination later, create/use a restaurant_printers row
       with printer_role='kitchen' pointing to the printer that must receive
       master copies.
    */
    $sectionOrder = [
        'bar' => 10,
        'beverage' => 10,
        'beverages' => 10,
        'drinks' => 10,
        'starter' => 20,
        'starters' => 20,
        'kitchen' => 30,
        'food' => 30,
        'main' => 30,
        'mains' => 30,
        'dessert' => 40,
        'desserts' => 40,
        'desert' => 40,
        'deserts' => 40,
    ];

    $masterSections = [];
    foreach($rows as $r){
        $role = strtolower(trim((string)($r['printer_role'] ?: ($r['menu_printer_role'] ?? ''))));
        $catName = strtolower(trim((string)($r['menu_category_name'] ?? '')));

        if($role === ''){
            if((int)($r['is_bar'] ?? 0) === 1) $role = 'bar';
            else $role = 'kitchen';
        }

        $sectionKey = $role;
        $sectionLabel = $role;

        $isBarItem = ($role === 'bar')
            || strpos($catName,'bar') !== false
            || strpos($catName,'drink') !== false
            || strpos($catName,'beer') !== false
            || strpos($catName,'beverage') !== false;

        if($isBarItem){
            continue; // Bar items never print on the kitchen master copy.
        }

        if(strpos($catName,'dessert') !== false || strpos($catName,'desert') !== false){
            $sectionKey = 'dessert';
            $sectionLabel = 'Desserts';
        }elseif(strpos($catName,'starter') !== false || strpos($catName,'start') !== false){
            $sectionKey = 'starter';
            $sectionLabel = 'Starters';
        }elseif($role === 'starter' || $role === 'starters'){
            $sectionKey = 'starter';
            $sectionLabel = 'Starters';
        }elseif($role === 'dessert' || $role === 'desserts' || $role === 'desert' || $role === 'deserts'){
            $sectionKey = 'dessert';
            $sectionLabel = 'Desserts';
        }else{
            $sectionKey = 'kitchen';
            $sectionLabel = 'Kitchen';
        }

        if(!isset($masterSections[$sectionKey])){
            $masterSections[$sectionKey] = [
                'label' => $sectionLabel,
                'sort' => $sectionOrder[$sectionKey] ?? 99,
                'lines' => []
            ];
        }
        $r['_master_sort'] = (int)($r['menu_category_sort'] ?? 999) * 100000 + (int)($r['menu_item_sort'] ?? 999) * 100 + (int)($r['id'] ?? 0);
        $masterSections[$sectionKey]['lines'][] = $r;
    }

    uasort($masterSections, function($a,$b){
        return ($a['sort'] <=> $b['sort']) ?: strcmp((string)$a['label'], (string)$b['label']);
    });

    foreach($masterSections as &$masterSection){
        usort($masterSection['lines'], function($a,$b){ return ((int)($a['_master_sort'] ?? 0)) <=> ((int)($b['_master_sort'] ?? 0)); });
    }
    unset($masterSection);

    $masterPayload="";
    $masterPayload.="***** MASTER COPY *****\n";
    $masterPayload.=($reprint ? "*** REPRINT ***\n" : "*** SEND ***\n");
    $masterPayload.="Order: ".($orderInfo['order_no']??('#'.$restaurantOrderId))."\n";
    if(!empty($orderInfo['table_id'])) $masterPayload.="Table: ".$orderInfo['table_id']."\n";
    if(!empty($orderInfo['guest_name'])) $masterPayload.="Guest: ".$orderInfo['guest_name']."\n";
    if(!empty($orderInfo['room_no'])) $masterPayload.="Room: ".$orderInfo['room_no']."\n";
    if(!empty($orderInfo['group_name'])) $masterPayload.="Group: ".$orderInfo['group_name']."\n";
    $masterPayload.=($reprint ? "Reprint" : "Sent").": ".date('Y-m-d H:i:s')."\n";
    $masterPayload.="Cashier: ".r_current_user_name()."\n";
    $masterPayload.="==============================\n";

    foreach($masterSections as $section){
        if(empty($section['lines'])) continue;
        $masterPayload.="\n=== ".strtoupper((string)$section['label'])." ===\n";
        foreach($section['lines'] as $l){
            $qty=rtrim(rtrim(number_format((float)$l['qty'],2,'.',''),'0'),'.');
            $masterPayload.=$qty.' x '.$l['item_name']."\n";
            if(trim((string)($l['notes']??''))!=='') $masterPayload.="   Note: ".$l['notes']."\n";
        }
    }

    $masterPayload.="\n******************************\n\n";

    if(!empty($masterSections)){
        $insertPrintJob('kitchen', null, 'master', $masterPayload);
    }
    // If every item on this check is a bar/drink item, $masterSections stays empty
    // and no Master Copy job is created - there is nothing for the kitchen to see.

    if(!$reprint){
        $pdo->prepare("UPDATE restaurant_order_items SET print_status='sent' WHERE restaurant_order_id=:rid AND status='active' AND print_status='pending'")
            ->execute([':rid'=>$restaurantOrderId]);
        $pdo->prepare("UPDATE restaurant_orders SET status='sent' WHERE id=:id AND status='open'")
            ->execute([':id'=>$restaurantOrderId]);
    }

    return $jobIds;
}


function r_printnode_printer_id_for_role(PDO $pdo, string $role, ?int $configuredPrinterRowId=null): int{
    r_ensure_qz_printer_schema($pdo);

    $role = strtolower(trim($role ?: 'kitchen'));

    if($configuredPrinterRowId && $configuredPrinterRowId > 0){
        try{
            $st=$pdo->prepare("SELECT COALESCE(printnode_printer_id,0) AS pnid, LOWER(TRIM(printer_role)) AS db_role FROM restaurant_printers WHERE id=:id AND is_active=1 LIMIT 1");
            $st->execute([':id'=>$configuredPrinterRowId]);
            $row=$st->fetch(PDO::FETCH_ASSOC) ?: [];
            $id=(int)($row['pnid'] ?? 0);
            $dbRole=strtolower(trim((string)($row['db_role'] ?? '')));
            if($id>0 && $dbRole === $role) return $id;
        }catch(Throwable $ignore){}
    }

    try{
        $st=$pdo->prepare("SELECT COALESCE(printnode_printer_id,0) FROM restaurant_printers WHERE printer_role=:role AND is_active=1 ORDER BY sort_order ASC,id ASC LIMIT 1");
        $st->execute([':role'=>$role]);
        $id=(int)$st->fetchColumn();
        if($id>0) return $id;
    }catch(Throwable $ignore){}

    return 0;
}

function r_printnode_build_planner_ticket(string $payload, string $jobType='station'): string{
    $out  = "\x1B\x40";
    $out .= "\x1B\x74\x00";
    $out .= "\x1C\x2E";

    foreach(explode("\n", $payload) as $line){
        $line = preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', (string)$line);
        $trimmed = trim($line);

        if(str_starts_with($trimmed, '***') || str_starts_with($trimmed, '===') || str_starts_with($trimmed, '*****')){
            $out .= "\x1B\x45\x01" . "\x1B\x61\x01" . $trimmed . "\n" . "\x1B\x45\x00" . "\x1B\x61\x00";
        }elseif(preg_match('/^[-=]{6,}$/', $trimmed)){
            $out .= $trimmed . "\n";
        }elseif($trimmed === ''){
            $out .= "\n";
        }elseif(preg_match('/^\s*\d[\d.]*\s+x\s+/i', $line)){
            $out .= "\x1B\x45\x01" . $line . "\n" . "\x1B\x45\x00";
        }else{
            $out .= $line . "\n";
        }
    }

    $out .= "\n\n\n\n" . "\x1D\x56\x42\x00";
    return $out;
}


function r_wrap_receipt_line(string $text, int $width = 42): array{
    $text = trim(preg_replace('/\s+/', ' ', $text));
    if($text === '') return [''];
    $out = [];
    while(strlen($text) > $width){
        $cut = strrpos(substr($text, 0, $width + 1), ' ');
        if($cut === false || $cut < 12) $cut = $width;
        $out[] = rtrim(substr($text, 0, $cut));
        $text = ltrim(substr($text, $cut));
    }
    $out[] = $text;
    return $out;
}

function r_bill_money_line(string $label, float $amount, int $width = 42): string{
    $value = money_fmt($amount);
    $space = max(1, $width - strlen($label) - strlen($value));
    return $label . str_repeat(' ', $space) . $value;
}

function r_create_bill_preview_bar_job(PDO $pdo, int $restaurantOrderId, bool $finalBill=false, bool $ccSurcharge=false): array{
    /*
       Customer bill printing:
       - Preview bill ($finalBill=false): shows charges/VAT/gross total plus blank gratuity/total/signature lines for the customer.
       - Final bill ($finalBill=true): shows actual Total Gratuity and Gross Total after gratuity has been captured.
       - Routes EVERYTHING to printer_role='bar'.
       - Does not mark pending kitchen/bar items as sent.
       - $ccSurcharge=true adds the 3% POS/Card surcharge (REST_POS_CARD_SURCHARGE_RATE) to the printed
         total. Gratuity is excluded from the surcharge base, matching the settlement-time surcharge rule.
    */
    r_ensure_qz_printer_schema($pdo);
    if($restaurantOrderId <= 0) throw new RuntimeException('Invalid restaurant order.');

    r_recalc($pdo, $restaurantOrderId);

    $orderStmt = $pdo->prepare("
        SELECT
            ro.*,
            COALESCE(rt.table_no, rt.table_name, ro.table_id) AS table_label
        FROM restaurant_orders ro
        LEFT JOIN restaurant_tables rt ON rt.id = ro.table_id
        WHERE ro.id = :id
        LIMIT 1
    ");
    $orderStmt->execute([':id' => $restaurantOrderId]);
    $order = $orderStmt->fetch(PDO::FETCH_ASSOC);
    if(!$order) throw new RuntimeException('Restaurant order not found.');

    $lineStmt = $pdo->prepare("
        SELECT item_name, qty, unit_price, line_total, notes
        FROM restaurant_order_items
        WHERE restaurant_order_id = :rid
          AND status = 'active'
        ORDER BY id ASC
    ");
    $lineStmt->execute([':rid' => $restaurantOrderId]);
    $lines = $lineStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    if(!$lines) throw new RuntimeException('There are no active items to print on this bill.');

    $paidStmt = $pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:rid AND status='posted'");
    $paidStmt->execute([':rid'=>$restaurantOrderId]);
    $paid = round((float)$paidStmt->fetchColumn(), 2);

    $subtotal = round((float)($order['subtotal'] ?? 0), 2);
    $tax = round((float)($order['tax_total'] ?? 0), 2);
    $total = round((float)($order['total_amount'] ?? 0), 2);
    $gratuityTotal = r_order_gratuity_total($pdo, $restaurantOrderId);
    $totalCharges = max(0, round($total - $gratuityTotal, 2));
    $ccSurchargeAmount = $ccSurcharge ? round($totalCharges * REST_POS_CARD_SURCHARGE_RATE, 2) : 0.0;
    $grossTotal = round($totalCharges + $ccSurchargeAmount + $gratuityTotal, 2);
    $balance = max(0, round($grossTotal - $paid, 2));

    $payload = "";
    $payload .= $ccSurcharge
        ? "***** CREDIT CARD BILL (3% SURCHARGE) *****\n"
        : ($finalBill ? "******** FINAL BILL ********\n" : "******** CUSTOMER BILL ********\n");
    $payload .= centerText("Pioneers Lodge");
    $payload .= centerText("Reg# TIN 2001134870");
    $payload .= centerText("VAT# : 220068351");
    $payload .= centerText("info@pioneersvicalls.com");
    $payload .= centerText("www.pioneersvicalls.com");    
    $payload .= "Order: " . ($order['order_no'] ?? ('#'.$restaurantOrderId)) . "\n";
    if(!empty($order['table_label'])) $payload .= "Table: " . $order['table_label'] . "\n";
    if(!empty($order['guest_name'])) $payload .= "Guest: " . $order['guest_name'] . "\n";
    if(!empty($order['room_no'])) $payload .= "Room: " . $order['room_no'] . "\n";
    if(!empty($order['group_name'])) $payload .= "Group: " . $order['group_name'] . "\n";
    $payload .= "Printed: " . date('Y-m-d H:i:s') . "\n";
    $payload .= "Cashier: " . r_current_user_name() . "\n";
    $payload .= "==========================================\n";

    foreach($lines as $line){
        $qty = rtrim(rtrim(number_format((float)$line['qty'], 2, '.', ''), '0'), '.');
        $unit = money_fmt((float)$line['unit_price']);
        $lineTotal = money_fmt((float)$line['line_total']);
        $left = $qty . " x " . (string)$line['item_name'];

        $first = true;
        foreach(r_wrap_receipt_line($left, 30) as $wrapped){
            if($first){
                $payload .= str_pad(substr($wrapped, 0, 30), 31) . str_pad($lineTotal, 10, ' ', STR_PAD_LEFT) . "\n";
                $payload .= "   @ " . $unit . "\n";
                $first = false;
            }else{
                $payload .= "   " . $wrapped . "\n";
            }
        }
        if(trim((string)($line['notes'] ?? '')) !== ''){
            foreach(r_wrap_receipt_line('Note: '.$line['notes'], 38) as $noteLine){
                $payload .= "   " . $noteLine . "\n";
            }
        }
    }

    $payload .= "------------------------------------------\n";
    $payload .= r_bill_money_line("Total Charges", $totalCharges) . "\n";
    $payload .= r_bill_money_line("Tax/VAT incl.", $tax) . "\n";
    if($ccSurcharge){
        $payload .= r_bill_money_line("Card Surcharge 3%", $ccSurchargeAmount) . "\n";
    }

    if($finalBill){
        $payload .= r_bill_money_line("Total Gratuity", $gratuityTotal) . "\n";
        $payload .= r_bill_money_line("GROSS TOTAL", $grossTotal) . "\n";
    }else{
        /* Preview stage: gratuity is not yet captured, so only show the charge total. */
        $payload .= r_bill_money_line("GROSS TOTAL", $grossTotal) . "\n";
    }

    if($paid > 0){
        $payload .= r_bill_money_line("Paid", $paid) . "\n";
        $payload .= r_bill_money_line("Balance", $balance) . "\n";
    }
    $payload .= "==========================================\n";

    if($ccSurcharge){
        $payload .= "Note: 3% card surcharge applies to the\n";
        $payload .= "bill total only. Gratuity below is not\n";
        $payload .= "subject to the surcharge.\n";
        $payload .= "==========================================\n";
    }
    else
    {
        $payload .= "Note: All card payments are subject\nto 3% surcharge\n";
        $payload .= "==========================================\n";
    }    

    if(!$finalBill){
        $payload .= "\n";
        $payload .= "Gratuity: __________________________\n";
        $payload .= "\n";
        $payload .= "Total:     __________________________\n";
        $payload .= "\n";
        $payload .= "Room/Folio:    __________________________\n";
        $payload .= "\n";
        $payload .= "Customer Name: __________________________\n";
        $payload .= "\n";
        $payload .= "Signature:     __________________________\n";
    }
  
    $payload .= "\n";
    $payload .= centerText("Thank you.\n");

    $hasPrinterId = r_column_exists($pdo, 'restaurant_print_jobs', 'printer_id');
    $hasBillEnum = true;
    try{
        $pdo->exec("ALTER TABLE restaurant_print_jobs MODIFY job_type VARCHAR(40) NOT NULL DEFAULT 'station'");
    }catch(Throwable $ignore){}

    if($hasPrinterId){
        $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,printer_id,job_type,payload,status) VALUES(:rid,'bar',NULL,'bill',:payload,'pending')")
            ->execute([':rid'=>$restaurantOrderId, ':payload'=>$payload]);
    }else{
        $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,job_type,payload,status) VALUES(:rid,'bar','bill',:payload,'pending')")
            ->execute([':rid'=>$restaurantOrderId, ':payload'=>$payload]);
    }

    return [(int)$pdo->lastInsertId()];
}


function r_dispatch_printnode_jobs(PDO $pdo, array $jobIds): array{
    $jobIds = array_values(array_unique(array_filter(array_map('intval', $jobIds))));
    if(!$jobIds) return ['ok'=>0,'failed'=>0,'messages'=>[]];

    if(!function_exists('printnode_send_raw')){
        $pnFile = __DIR__ . '/printnode_print.php';
        if(is_file($pnFile)){
            require_once $pnFile;
        }
    }

    if(!function_exists('printnode_send_raw')){
        throw new RuntimeException('PrintNode file printnode_print.php was not found, or printnode_send_raw() is missing.');
    }

    r_ensure_qz_printer_schema($pdo);

    $in = implode(',', array_fill(0, count($jobIds), '?'));
    $st = $pdo->prepare("SELECT * FROM restaurant_print_jobs WHERE id IN ($in) ORDER BY id ASC");
    $st->execute($jobIds);
    $jobs = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];

    $ok=0;
    $failed=0;
    $messages=[];

    foreach($jobs as $job){
        $role = strtolower(trim((string)($job['printer_role'] ?? 'kitchen')));
        $printerRowId = isset($job['printer_id']) ? (int)$job['printer_id'] : null;
        $printNodeId = r_printnode_printer_id_for_role($pdo, $role, $printerRowId);

        if($printNodeId <= 0){
            $failed++;
            $msg='No ACTIVE PrintNode Printer ID configured for role '.$role.'.';
            $messages[]=$msg;
            try{ $pdo->prepare("UPDATE restaurant_print_jobs SET status='failed', error_message=:e WHERE id=:id")->execute([':e'=>$msg,':id'=>(int)$job['id']]); }catch(Throwable $ignore){}
            continue;
        }

        $payload=(string)($job['payload'] ?? '');
        if(trim($payload)===''){
            $failed++;
            $msg='Empty print payload for job #'.(int)$job['id'];
            $messages[]=$msg;
            try{ $pdo->prepare("UPDATE restaurant_print_jobs SET status='failed', error_message=:e WHERE id=:id")->execute([':e'=>$msg,':id'=>(int)$job['id']]); }catch(Throwable $ignore){}
            continue;
        }

        $bytes = r_printnode_build_planner_ticket($payload, (string)($job['job_type'] ?? 'station'));
        $res = printnode_send_raw($printNodeId, $bytes, 'Restaurant '.($job['job_type'] ?? 'ticket').' - Order '.($job['restaurant_order_id'] ?? ''));

        if(!empty($res['ok'])){
            $ok++;
            try{ $pdo->prepare("UPDATE restaurant_print_jobs SET status='printed', printed_at=NOW(), error_message=NULL WHERE id=:id")->execute([':id'=>(int)$job['id']]); }catch(Throwable $ignore){}
        }else{
            $failed++;
            $err=(string)($res['error'] ?? 'PrintNode error');
            $messages[]=$role.': '.$err;
            try{ $pdo->prepare("UPDATE restaurant_print_jobs SET status='failed', error_message=:e WHERE id=:id")->execute([':e'=>$err,':id'=>(int)$job['id']]); }catch(Throwable $ignore){}
        }
    }

    return ['ok'=>$ok,'failed'=>$failed,'messages'=>$messages];
}

function r_table_exists(PDO $pdo,string $table):bool{ $s=$pdo->prepare("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=:t");$s->execute([':t'=>$table]);return (int)$s->fetchColumn()>0; }
function r_column_exists(PDO $pdo,string $table,string $column):bool{ $s=$pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=:t AND column_name=:c");$s->execute([':t'=>$table,':c'=>$column]);return (int)$s->fetchColumn()>0; }

function r_value_matches_pin(string $stored, string $pin): bool{
    $stored=trim($stored); $pin=trim($pin);
    if($stored==='' || $pin==='') return false;
    if(hash_equals($stored,$pin)) return true;
    if(strlen($stored)>=50 && function_exists('password_verify') && password_verify($pin,$stored)) return true;
    if(hash_equals(strtolower($stored),md5($pin))) return true;
    if(hash_equals(strtolower($stored),sha1($pin))) return true;
    return false;
}
function r_role_is_supervisor(string $role): bool{
    $role=strtolower(trim($role));
    if($role==='') return false;
    foreach(['super_admin','superadmin','administrator','admin','master','owner','director','manager','supervisor'] as $ok){
        if($role===$ok || strpos($role,$ok)!==false) return true;
    }
    return false;
}
function r_current_user_is_admin(PDO $pdo): bool{
    $uid = r_current_user_id();
    if(!$uid) return false;
    $tables=['tblconsultant','consultants','users','system_users'];
    $roleCols=['role_key','user_role_key','ss_role_key','role','user_role','type','Role'];
    foreach($tables as $table){
        if(!r_table_exists($pdo,$table)) continue;
        if(!r_column_exists($pdo,$table,'id')) continue;
        try{
            $stmt=$pdo->prepare("SELECT * FROM `{$table}` WHERE id=:id LIMIT 1");
            $stmt->execute([':id'=>$uid]);
            $row=$stmt->fetch(PDO::FETCH_ASSOC);
            if(!$row) continue;
            foreach($roleCols as $rc){
                if(array_key_exists($rc,$row) && r_role_is_supervisor((string)$row[$rc])) return true;
            }
            if(isset($row['type']) && in_array((string)$row['type'],['1','master'],true)) return true;
        }catch(Throwable $ignore){continue;}
    }
    return false;
}
function r_supervisor_pin_valid(PDO $pdo,string $pin): bool{
    $pin=trim($pin);
    if($pin==='') return false;
    $tables=['tblconsultant','consultants','users','system_users'];
    $pinCols=['supervisor_pin','manager_pin','admin_pin','void_pin','pin','auth_pin','password','Password','user_password','password_hash'];
    $roleCols=['role_key','user_role_key','ss_role_key','role','user_role','type','Role'];
    $activeCols=['is_active','active','status'];
    foreach($tables as $table){
        if(!r_table_exists($pdo,$table)) continue;
        $existingPinCols=[];
        foreach($pinCols as $pc){ if(r_column_exists($pdo,$table,$pc)) $existingPinCols[]=$pc; }
        if(!$existingPinCols) continue;
        $sql="SELECT * FROM `{$table}` WHERE 1=1";
        foreach($activeCols as $ac){
            if(r_column_exists($pdo,$table,$ac)){
                if($ac==='status') $sql.=" AND LOWER(CAST(`{$ac}` AS CHAR)) NOT IN ('0','inactive','disabled','deleted')";
                else $sql.=" AND COALESCE(`{$ac}`,1)=1";
                break;
            }
        }
        $sql.=" LIMIT 500";
        try{
            foreach($pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) as $row){
                $roleOk=false;
                foreach($roleCols as $rc){
                    if(array_key_exists($rc,$row) && r_role_is_supervisor((string)$row[$rc])){ $roleOk=true; break; }
                }
                if(!$roleOk && isset($row['type']) && in_array((string)$row['type'],['1','master'],true)) $roleOk=true;
                if(!$roleOk) continue;
                foreach($existingPinCols as $pc){
                    if(array_key_exists($pc,$row) && r_value_matches_pin((string)$row[$pc],$pin)) return true;
                }
            }
        }catch(Throwable $ignore){}
    }
    return false;
}
function r_ensure_restaurant_adjustment_schema(PDO $pdo): void{
    if(r_table_exists($pdo,'restaurant_order_items')){
        foreach([
            'original_unit_price' => "DECIMAL(14,2) NULL",
            'adjustment_type' => "VARCHAR(40) NULL",
            'adjustment_reason' => "VARCHAR(120) NULL",
            'adjustment_note' => "VARCHAR(255) NULL",
            'adjusted_by' => "INT UNSIGNED NULL",
            'adjusted_at' => "DATETIME NULL",
            'void_reason' => "VARCHAR(120) NULL",
            'void_note' => "VARCHAR(255) NULL",
            'voided_by' => "INT UNSIGNED NULL",
            'voided_at' => "DATETIME NULL"
        ] as $col=>$def){
            if(!r_column_exists($pdo,'restaurant_order_items',$col)){
                try{ $pdo->exec("ALTER TABLE restaurant_order_items ADD COLUMN {$col} {$def}"); }catch(Throwable $ignore){}
            }
        }
    }
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_order_item_adjustments (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        restaurant_order_id BIGINT UNSIGNED NOT NULL,
        restaurant_order_item_id BIGINT UNSIGNED NULL,
        action_type VARCHAR(40) NOT NULL,
        old_unit_price DECIMAL(14,2) NULL,
        new_unit_price DECIMAL(14,2) NULL,
        old_line_total DECIMAL(14,2) NULL,
        new_line_total DECIMAL(14,2) NULL,
        amount DECIMAL(14,2) NULL,
        reason VARCHAR(120) NULL,
        notes VARCHAR(255) NULL,
        authorised_by INT UNSIGNED NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_adj_order(restaurant_order_id),
        INDEX idx_rest_adj_item(restaurant_order_item_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}
function r_order_gratuity_total(PDO $pdo,int $restaurantOrderId): float{
    if($restaurantOrderId<=0) return 0.0;
    try{
        $s=$pdo->prepare("SELECT COALESCE(SUM(line_total),0) FROM restaurant_order_items WHERE restaurant_order_id=:rid AND status='active' AND LOWER(TRIM(item_name))='gratuity'");
        $s->execute([':rid'=>$restaurantOrderId]);
        return round((float)$s->fetchColumn(),2);
    }catch(Throwable $ignore){ return 0.0; }
}
function r_order_paid_total(PDO $pdo,int $restaurantOrderId): float{
    try{
        $s=$pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:rid AND status='posted'");
        $s->execute([':rid'=>$restaurantOrderId]);
        return round((float)$s->fetchColumn(),2);
    }catch(Throwable $ignore){ return 0.0; }
}
function r_add_gratuity_line(PDO $pdo,int $restaurantOrderId,float $amount,string $note=''): void{
    if($restaurantOrderId<=0 || $amount<=0) throw new RuntimeException('Enter a valid gratuity amount.');
    r_ensure_restaurant_adjustment_schema($pdo);
    $pdo->prepare("INSERT INTO restaurant_order_items(restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status) VALUES(:rid,NULL,NULL,'Gratuity',1,:unit_amt,0,0,:line_amt,'bar','sent',:notes,'active')")->execute([
        ':rid'=>$restaurantOrderId,
        ':unit_amt'=>round($amount,2),
        ':line_amt'=>round($amount,2),
        ':notes'=>$note!==''?$note:'Customer gratuity'
    ]);
    r_recalc($pdo,$restaurantOrderId);
}
function r_adjust_restaurant_item(PDO $pdo,int $restaurantOrderId,int $itemId,string $adjustmentType,float $value,string $reason,string $pin,string $note=''): void{
    r_ensure_restaurant_adjustment_schema($pdo);
    $reason=trim($reason); $note=trim($note);
    if($restaurantOrderId<=0 || $itemId<=0) throw new RuntimeException('Invalid item adjustment.');
    if(!in_array($adjustmentType,['change_price','discount_amount','discount_percent','waiver'],true)) throw new RuntimeException('Invalid adjustment type.');
    if($reason==='') throw new RuntimeException('Please select/enter a reason for the adjustment.');
    if(!r_supervisor_pin_valid($pdo,$pin)) throw new RuntimeException('Invalid supervisor PIN or insufficient authority.');

    $st=$pdo->prepare("SELECT * FROM restaurant_order_items WHERE id=:id AND restaurant_order_id=:rid AND status='active' LIMIT 1");
    $st->execute([':id'=>$itemId,':rid'=>$restaurantOrderId]);
    $it=$st->fetch(PDO::FETCH_ASSOC);
    if(!$it) throw new RuntimeException('Active item not found.');
    if(strtolower(trim((string)$it['item_name']))==='gratuity') throw new RuntimeException('Gratuity cannot be discounted or waived.');

    $qty=max(0.0001,(float)($it['qty']??1));
    $oldUnit=round((float)($it['unit_price']??0),2);
    $oldLine=round((float)($it['line_total']??0),2);
    $newUnit=$oldUnit;
    if($adjustmentType==='change_price'){
        if($value<0) throw new RuntimeException('New price cannot be negative.');
        $newUnit=round($value,2);
    }elseif($adjustmentType==='discount_amount'){
        if($value<=0) throw new RuntimeException('Discount amount must be greater than zero.');
        $newUnit=round(max(0,($oldLine-$value)/$qty),2);
    }elseif($adjustmentType==='discount_percent'){
        if($value<=0 || $value>100) throw new RuntimeException('Discount percentage must be between 0 and 100.');
        $newUnit=round($oldUnit*(1-($value/100)),2);
    }elseif($adjustmentType==='waiver'){
        $newUnit=0.00;
    }
    $newLine=round($newUnit*$qty,2);
    $taxRate=(float)($it['tax_rate']??0);
    $newTax=round($newLine*($taxRate/100),2);

    $pdo->prepare("UPDATE restaurant_order_items SET original_unit_price=COALESCE(original_unit_price,unit_price), unit_price=:unit, tax_amount=:tax, line_total=:line, adjustment_type=:atype, adjustment_reason=:reason, adjustment_note=:note, adjusted_by=:u, adjusted_at=NOW(), notes=TRIM(CONCAT(COALESCE(notes,''), :note_add_text)) WHERE id=:id AND restaurant_order_id=:rid")->execute([
        ':unit'=>$newUnit, ':tax'=>$newTax, ':line'=>$newLine, ':atype'=>$adjustmentType, ':reason'=>$reason, ':note'=>$note!==''?$note:null, ':u'=>r_current_user_id(), ':id'=>$itemId, ':rid'=>$restaurantOrderId,
        ':note_add_text'=>' | Supervisor '.str_replace('_',' ',$adjustmentType).': '.$reason.($note!==''?' - '.$note:'')
    ]);
    $pdo->prepare("INSERT INTO restaurant_order_item_adjustments(restaurant_order_id,restaurant_order_item_id,action_type,old_unit_price,new_unit_price,old_line_total,new_line_total,amount,reason,notes,authorised_by) VALUES(:rid,:iid,:atype,:ou,:nu,:ol,:nl,:amt,:reason,:notes,:u)")->execute([
        ':rid'=>$restaurantOrderId, ':iid'=>$itemId, ':atype'=>$adjustmentType, ':ou'=>$oldUnit, ':nu'=>$newUnit, ':ol'=>$oldLine, ':nl'=>$newLine, ':amt'=>abs($oldLine-$newLine), ':reason'=>$reason, ':notes'=>$note!==''?$note:null, ':u'=>r_current_user_id()
    ]);
    r_recalc($pdo,$restaurantOrderId);
}
function r_void_restaurant_item(PDO $pdo,int $restaurantOrderId,int $itemId,string $reason='',string $pin='',string $note=''): void{
    r_ensure_restaurant_adjustment_schema($pdo);
    $st=$pdo->prepare("SELECT * FROM restaurant_order_items WHERE id=:id AND restaurant_order_id=:rid AND status='active' LIMIT 1");
    $st->execute([':id'=>$itemId,':rid'=>$restaurantOrderId]);
    $it=$st->fetch(PDO::FETCH_ASSOC);
    if(!$it) throw new RuntimeException('Active item not found.');

    $sent = !in_array(strtolower((string)($it['print_status']??'pending')), ['','pending'], true);
    if($sent){
        $reason=trim($reason); $note=trim($note);
        if($reason==='') throw new RuntimeException('Please select a reason for supervisor void.');
        if(!r_supervisor_pin_valid($pdo,$pin)) throw new RuntimeException('Invalid supervisor PIN or insufficient authority.');
    }else{
        $reason=trim($reason) ?: 'Before send';
    }

    $pdo->prepare("UPDATE restaurant_order_items SET status='void', print_status='void', void_reason=:reason, void_note=:note, voided_by=:u, voided_at=NOW(), notes=TRIM(CONCAT(COALESCE(notes,''), :void_note_add_text)) WHERE id=:id AND restaurant_order_id=:rid")->execute([
        ':reason'=>$reason, ':note'=>trim($note)!==''?trim($note):null, ':u'=>r_current_user_id(), ':id'=>$itemId, ':rid'=>$restaurantOrderId,
        ':void_note_add_text'=>' | VOID: '.$reason.(trim($note)!==''?' - '.trim($note):'')
    ]);
    $pdo->prepare("INSERT INTO restaurant_order_item_adjustments(restaurant_order_id,restaurant_order_item_id,action_type,old_unit_price,new_unit_price,old_line_total,new_line_total,amount,reason,notes,authorised_by) VALUES(:rid,:iid,'void',:ou,0,:ol,0,:amt,:reason,:notes,:u)")->execute([
        ':rid'=>$restaurantOrderId, ':iid'=>$itemId, ':ou'=>(float)($it['unit_price']??0), ':ol'=>(float)($it['line_total']??0), ':amt'=>(float)($it['line_total']??0), ':reason'=>$reason, ':notes'=>trim($note)!==''?trim($note):null, ':u'=>r_current_user_id()
    ]);
    r_recalc($pdo,$restaurantOrderId);
}
function r_print_bill_to_bar_now(PDO $pdo,int $restaurantOrderId): void{
    if($restaurantOrderId<=0) return;
    try{
        $jobIds=r_create_bill_preview_bar_job($pdo,$restaurantOrderId,true);
        $pn=r_dispatch_printnode_jobs($pdo,$jobIds);
        if(!empty($pn['failed'])) r_flash('warning','Posting completed, but bill print to Bar failed: '.implode(' | ',$pn['messages']));
        else r_flash('success','Customer bill printed to Bar printer.');
    }catch(Throwable $e){
        r_flash('warning','Posting completed, but customer bill was not printed: '.$e->getMessage());
    }
}


function r_create_selected_bill_preview_bar_job(PDO $pdo,int $restaurantOrderId,array $itemIds,string $title='POSTED SELECTED ITEMS BILL'): array{
    r_ensure_qz_printer_schema($pdo);
    $restaurantOrderId=(int)$restaurantOrderId;
    $itemIds=array_values(array_unique(array_filter(array_map('intval',$itemIds))));
    if($restaurantOrderId<=0) throw new RuntimeException('Invalid restaurant order.');
    if(!$itemIds) return r_create_bill_preview_bar_job($pdo,$restaurantOrderId,true);

    $orderStmt=$pdo->prepare("SELECT ro.*, COALESCE(rt.table_no, rt.table_name, ro.table_id) AS table_label FROM restaurant_orders ro LEFT JOIN restaurant_tables rt ON rt.id=ro.table_id WHERE ro.id=:id LIMIT 1");
    $orderStmt->execute([':id'=>$restaurantOrderId]);
    $order=$orderStmt->fetch(PDO::FETCH_ASSOC);
    if(!$order) throw new RuntimeException('Restaurant order not found.');

    $in=implode(',',array_fill(0,count($itemIds),'?'));
    $params=array_merge([$restaurantOrderId],$itemIds);
    $lineStmt=$pdo->prepare("SELECT id,item_name,qty,unit_price,line_total,tax_amount,notes FROM restaurant_order_items WHERE restaurant_order_id=? AND id IN ($in) AND status='active' ORDER BY id ASC");
    $lineStmt->execute($params);
    $lines=$lineStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    if(!$lines) throw new RuntimeException('No selected active items were found to print.');

    $totalCharges=0.00;
    $tax=0.00;
    foreach($lines as $line){
        $totalCharges += round((float)($line['line_total'] ?? 0),2);
        $tax += round((float)($line['tax_amount'] ?? 0),2);
    }
    $totalCharges=round($totalCharges,2);
    $tax=round($tax,2);

    $payload="";
    $payload.="******** ".strtoupper($title)." ********\n";
    $payload.=centerText("Pioneers Lodge");
    $payload.=centerText("Reg# TIN 2001134870");
    $payload.=centerText("VAT# : 220068351");
    $payload.=centerText("info@pioneersvicalls.com");
    $payload.=centerText("www.pioneersvicalls.com");
    $payload.="Order: ".($order['order_no'] ?? ('#'.$restaurantOrderId))."\n";
    if(!empty($order['table_label'])) $payload.="Table: ".$order['table_label']."\n";
    if(!empty($order['guest_name'])) $payload.="Guest: ".$order['guest_name']."\n";
    if(!empty($order['room_no'])) $payload.="Room: ".$order['room_no']."\n";
    if(!empty($order['group_name'])) $payload.="Group: ".$order['group_name']."\n";
    $payload.="Printed: ".date('Y-m-d H:i:s')."\n";
    $payload.="Cashier: ".r_current_user_name()."\n";
    $payload.="==========================================\n";

    foreach($lines as $line){
        $qty=rtrim(rtrim(number_format((float)($line['qty'] ?? 1),2,'.',''),'0'),'.');
        $unit=money_fmt((float)($line['unit_price'] ?? 0));
        $lineTotal=money_fmt((float)($line['line_total'] ?? 0));
        $left=$qty." x ".(string)($line['item_name'] ?? 'Item');
        $first=true;
        foreach(r_wrap_receipt_line($left,30) as $wrapped){
            if($first){
                $payload.=str_pad(substr($wrapped,0,30),31).str_pad($lineTotal,10,' ',STR_PAD_LEFT)."\n";
                $payload.="   @ ".$unit."\n";
                $first=false;
            }else{
                $payload.="   ".$wrapped."\n";
            }
        }
        if(trim((string)($line['notes'] ?? ''))!==''){
            foreach(r_wrap_receipt_line('Note: '.$line['notes'],38) as $noteLine){ $payload.="   ".$noteLine."\n"; }
        }
    }

    $payload.="------------------------------------------\n";
    $payload.=r_bill_money_line("Selected Items",$totalCharges)."\n";
    $payload.=r_bill_money_line("Tax/VAT incl.",$tax)."\n";
    $payload.=r_bill_money_line("TOTAL POSTED",$totalCharges)."\n";
    $payload.="==========================================\n";
    $payload.=centerText("Thank you.\n");

    try{ $pdo->exec("ALTER TABLE restaurant_print_jobs MODIFY job_type VARCHAR(40) NOT NULL DEFAULT 'station'"); }catch(Throwable $ignore){}
    $printRole = r_split_bill_print_role($pdo);
    if(r_column_exists($pdo,'restaurant_print_jobs','printer_id')){
        $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,printer_id,job_type,payload,status) VALUES(:rid,:role,NULL,'bill',:payload,'pending')")
            ->execute([':rid'=>$restaurantOrderId,':role'=>$printRole,':payload'=>$payload]);
    }else{
        $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,job_type,payload,status) VALUES(:rid,:role,'bill',:payload,'pending')")
            ->execute([':rid'=>$restaurantOrderId,':role'=>$printRole,':payload'=>$payload]);
    }
    return [(int)$pdo->lastInsertId()];
}

function r_print_selected_bill_to_bar_now(PDO $pdo,int $restaurantOrderId,array $itemIds,string $title='POSTED SELECTED ITEMS BILL'): void{
    if($restaurantOrderId<=0) return;
    try{
        $jobIds=r_create_selected_bill_preview_bar_job($pdo,$restaurantOrderId,$itemIds,$title);
        $pn=r_dispatch_printnode_jobs($pdo,$jobIds);
        if(!empty($pn['failed'])) r_flash('warning','Posting completed, but selected item bill print to Bar failed: '.implode(' | ',$pn['messages']));
        else r_flash('success','Selected item bill printed to Bar printer.');
    }catch(Throwable $e){
        r_flash('warning','Posting completed, but selected item bill was not printed: '.$e->getMessage());
    }
}


function r_folio_print_label(PDO $pdo, int $folioId): array{
    $out=['folio_no'=>'','folio_name'=>'','room_no'=>'','label'=>''];
    if($folioId<=0 || !r_table_exists($pdo,'folios')) return $out;
    try{
        $roomSelect="'' AS room_no";
        $roomJoin='';
        if(r_table_exists($pdo,'rooms') && r_column_exists($pdo,'folios','room_id')){
            $roomJoin=" LEFT JOIN rooms r ON r.id=f.room_id ";
            $roomSelect="COALESCE(r.room_no,'') AS room_no";
        }elseif(r_column_exists($pdo,'folios','room_no')){
            $roomSelect="COALESCE(f.room_no,'') AS room_no";
        }
        $st=$pdo->prepare("SELECT f.folio_no, f.folio_name, f.occupant_name, {$roomSelect} FROM folios f {$roomJoin} WHERE f.id=:id LIMIT 1");
        $st->execute([':id'=>$folioId]);
        $r=$st->fetch(PDO::FETCH_ASSOC)?:[];
        if($r){
            $out['folio_no']=trim((string)($r['folio_no']??''));
            $out['folio_name']=trim((string)($r['folio_name']??($r['occupant_name']??'')));
            $out['room_no']=trim((string)($r['room_no']??''));
            $bits=[];
            if($out['room_no']!=='') $bits[]='Room '.$out['room_no'];
            if($out['folio_name']!=='') $bits[]=$out['folio_name'];
            if($out['folio_no']!=='') $bits[]=$out['folio_no'];
            $out['label']=implode(' / ',array_values(array_unique($bits)));
        }
    }catch(Throwable $ignore){}
    return $out;
}


function r_ensure_cost_center_schema(PDO $pdo): void{
    /*
       Internal costing departments (Staff Account, Management Account, Director
       Account, Marketing, etc.) let a meal be closed off without it being a real
       guest payment or a PMS room/master folio charge. It's an internal write-off,
       tracked in restaurant_house_adjustments for audit, same table already used
       by the split-bill "house_account"/"comp" destinations.
    */
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_cost_centers (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        property_id INT UNSIGNED NULL,
        name VARCHAR(120) NOT NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        sort_order INT NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_cost_center_active(is_active,sort_order)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    try{
        $count=(int)$pdo->query("SELECT COUNT(*) FROM restaurant_cost_centers")->fetchColumn();
        if($count<=0){
            $pdo->exec("INSERT INTO restaurant_cost_centers (name,sort_order) VALUES
                ('Staff Account',1),
                ('Management Account',2),
                ('Director Account',3),
                ('Marketing Department',4)");
        }
    }catch(Throwable $ignore){}

    if(r_table_exists($pdo,'restaurant_orders')){
        try{
            $pdo->exec("ALTER TABLE restaurant_orders MODIFY payment_status ENUM('unpaid','part_paid','paid','room_charged','part_room_charged','cost_center_charged','part_cost_center_charged','void') NOT NULL DEFAULT 'unpaid'");
        }catch(Throwable $ignore){}
        if(!r_column_exists($pdo,'restaurant_orders','cost_center_id')){
            try{ $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN cost_center_id INT UNSIGNED NULL AFTER linked_folio_id"); }catch(Throwable $e){}
        }
    }

    if(!r_table_exists($pdo,'restaurant_house_adjustments')){
        r_ensure_simphony_style_schema($pdo);
    }
    if(r_table_exists($pdo,'restaurant_house_adjustments') && !r_column_exists($pdo,'restaurant_house_adjustments','cost_center_id')){
        try{ $pdo->exec("ALTER TABLE restaurant_house_adjustments ADD COLUMN cost_center_id INT UNSIGNED NULL AFTER adjustment_type"); }catch(Throwable $e){}
        try{ $pdo->exec("ALTER TABLE restaurant_house_adjustments ADD INDEX idx_rest_house_adj_cost_center(cost_center_id)"); }catch(Throwable $e){}
    }
}

function r_cost_center_balances(PDO $pdo): array{
    /*
       Running balance per costing department - the total ever posted against
       it (all-time). This is what makes "posting to account" actually behave
       like an account: charges accumulate and stay visible, rather than
       vanishing once the restaurant check itself closes.
    */
    r_ensure_cost_center_schema($pdo);
    $balances = [];
    if(!r_table_exists($pdo,'restaurant_house_adjustments') || !r_column_exists($pdo,'restaurant_house_adjustments','cost_center_id')){
        return $balances;
    }
    try{
        $stmt = $pdo->query("
            SELECT cost_center_id, COALESCE(SUM(amount),0) AS total_amount, COUNT(*) AS posting_count
            FROM restaurant_house_adjustments
            WHERE cost_center_id IS NOT NULL
            GROUP BY cost_center_id
        ");
        foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){
            $balances[(int)$row['cost_center_id']] = [
                'total' => (float)$row['total_amount'],
                'count' => (int)$row['posting_count'],
            ];
        }
    }catch(Throwable $ignore){}
    return $balances;
}

function r_load_cost_centers(PDO $pdo): array{
    r_ensure_cost_center_schema($pdo);
    try{
        return $pdo->query("SELECT * FROM restaurant_cost_centers WHERE is_active=1 ORDER BY sort_order ASC, name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }catch(Throwable $e){ return []; }
}

function r_post_items_to_cost_center(PDO $pdo, int $restaurantOrderId, int $costCenterId, string $costCenterName, array $selectedItemIds, string $reason, string $pin, string $note='', string $discountType='none', float $discountValue=0.0): void{
    r_ensure_cost_center_schema($pdo);

    if($restaurantOrderId<=0) throw new RuntimeException('Invalid restaurant order.');
    $reason=trim($reason);
    if($reason==='') throw new RuntimeException('Please enter a reason for this internal charge.');
    if(!r_supervisor_pin_valid($pdo,$pin)) throw new RuntimeException('Invalid supervisor PIN or insufficient authority.');

    $discountType=strtolower(trim($discountType));
    if(!in_array($discountType,['none','amount','percent'],true)) $discountType='none';
    if($discountType!=='none' && $discountValue<0) throw new RuntimeException('Discount value cannot be negative.');
    if($discountType==='percent' && $discountValue>100) throw new RuntimeException('Discount percentage cannot exceed 100%.');

    r_recalc($pdo,$restaurantOrderId);

    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id LIMIT 1");
    $ro->execute([':id'=>$restaurantOrderId]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Restaurant order not found.');

    $postingSelectedItems = !empty($selectedItemIds);
    $baseAmount = round((float)($ord['total_amount'] ?? 0), 2);
    $postedItemNames = [];

    if($postingSelectedItems){
        $in=implode(',',array_fill(0,count($selectedItemIds),'?'));
        $params=array_merge([$restaurantOrderId],$selectedItemIds);
        $stItems=$pdo->prepare("SELECT id,item_name,qty,line_total FROM restaurant_order_items WHERE restaurant_order_id=? AND id IN ($in) AND status='active' ORDER BY id ASC");
        $stItems->execute($params);
        $selectedRows=$stItems->fetchAll(PDO::FETCH_ASSOC) ?: [];
        if(!$selectedRows) throw new RuntimeException('Select at least one active item to post, or untick all items to post the full bill.');

        $baseAmount=0.00;
        foreach($selectedRows as $sr){
            $lineAmount=round((float)($sr['line_total'] ?? 0),2);
            $baseAmount += $lineAmount;
            $postedItemNames[] = rtrim(rtrim(number_format((float)($sr['qty'] ?? 1),2,'.',''),'0'),'.').' x '.trim((string)($sr['item_name'] ?? 'Item'));
            r_mark_restaurant_item_split_allocation($pdo,$restaurantOrderId,(int)$sr['id'],0,0,'cost_center',0,$costCenterName,$lineAmount);
        }
        $baseAmount=round($baseAmount,2);
    }

    if($baseAmount<=0) throw new RuntimeException('There is no amount to post to this costing department.');

    $discountAmount = 0.00;
    if($discountType==='amount'){
        $discountAmount = min($baseAmount, round($discountValue,2));
    }elseif($discountType==='percent'){
        $discountAmount = round($baseAmount * ($discountValue/100), 2);
    }
    $amount = round(max(0, $baseAmount - $discountAmount), 2);
    if($amount<=0) throw new RuntimeException('The discount leaves nothing to post. Check the discount value.');

    $adjReason = $costCenterName.': '.$reason.($note!==''?' - '.$note:'');
    if($discountAmount>0){
        $discountLabel = $discountType==='percent' ? ($discountValue.'%') : money_fmt($discountValue);
        $adjReason .= ' | Discount '.$discountLabel.' ('.money_fmt($discountAmount).' off '.money_fmt($baseAmount).')';
    }
    if($postingSelectedItems){
        $adjReason .= ' | Items: '.implode('; ',array_slice($postedItemNames,0,12));
        if(count($postedItemNames)>12) $adjReason .= '; +'.(count($postedItemNames)-12).' more';
    }

    $pdo->prepare("INSERT INTO restaurant_house_adjustments(restaurant_order_id,split_id,adjustment_type,cost_center_id,amount,reason,created_by) VALUES(:rid,NULL,'house_account',:cc,:amount,:reason,:u)")->execute([
        ':rid'=>$restaurantOrderId,
        ':cc'=>$costCenterId,
        ':amount'=>$amount,
        ':reason'=>$adjReason,
        ':u'=>r_current_user_id()
    ]);

    /*
       Internal Account postings always close the table, whether the full
       bill or only selected items were posted. Unlike Room Folio (which
       supports genuinely leaving a check open for further guest billing),
       once a check is written off to an internal department it is treated
       as settled and the table is released.
    */
    $noteAdd='Posted to costing department '.$costCenterName.' on '.date('Y-m-d H:i:s').' | Amount '.money_fmt($amount)
        .($discountAmount>0 ? ' (after '.money_fmt($discountAmount).' discount off '.money_fmt($baseAmount).')' : '')
        .' | Reason: '.$reason.($note!==''?' - '.$note:'')
        .($postingSelectedItems ? (' | '.implode('; ',array_slice($postedItemNames,0,12))) : ' | Full bill');

    $pdo->prepare("UPDATE restaurant_orders SET cost_center_id=:cc, status='closed', payment_status='cost_center_charged', close_method=:cm, closed_by=:u, closed_at=NOW(), notes=TRIM(CONCAT(COALESCE(notes,''), :note_add)) WHERE id=:rid")->execute([
        ':cc'=>$costCenterId,
        ':cm'=>'Posted to '.$costCenterName.($discountAmount>0?' (discounted)':''),
        ':u'=>r_current_user_id(),
        ':note_add'=>"\n".$noteAdd,
        ':rid'=>$restaurantOrderId
    ]);
    r_recipe_stock_flash(r_post_recipe_stock_sale_movements($pdo,$restaurantOrderId));
    r_release_restaurant_tables($pdo,$restaurantOrderId,!empty($ord['table_id'])?(int)$ord['table_id']:null);
}


function r_ensure_split_item_allocation_schema(PDO $pdo): void{
    if(r_table_exists($pdo,'restaurant_order_items')){
        $cols=[
            'split_target_type' => "VARCHAR(40) NULL",
            'split_folio_id' => "INT UNSIGNED NULL",
            'split_room_no' => "VARCHAR(80) NULL",
            'split_guest_name' => "VARCHAR(180) NULL",
            'split_payer_name' => "VARCHAR(180) NULL",
            'split_batch_id' => "BIGINT UNSIGNED NULL",
            'split_posted_at' => "DATETIME NULL",
            'split_posted_by' => "INT UNSIGNED NULL"
        ];
        foreach($cols as $col=>$def){
            if(!r_column_exists($pdo,'restaurant_order_items',$col)){
                try{ $pdo->exec("ALTER TABLE restaurant_order_items ADD COLUMN {$col} {$def}"); }catch(Throwable $ignore){}
            }
        }
    }

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_item_allocations (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        restaurant_order_id BIGINT UNSIGNED NOT NULL,
        restaurant_order_item_id BIGINT UNSIGNED NOT NULL,
        split_id BIGINT UNSIGNED NULL,
        unit_no INT UNSIGNED NULL,
        destination_type VARCHAR(40) NULL,
        folio_id INT UNSIGNED NULL,
        room_no VARCHAR(80) NULL,
        guest_name VARCHAR(180) NULL,
        payer_name VARCHAR(180) NULL,
        amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        created_by INT UNSIGNED NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_item_alloc_order(restaurant_order_id),
        INDEX idx_rest_item_alloc_item(restaurant_order_item_id),
        INDEX idx_rest_item_alloc_folio(folio_id),
        INDEX idx_rest_item_alloc_split(split_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    /*
       Defensive fix: if this table already existed (e.g. from before cost-center
       postings, which have no folio at all) with folio_id/split_id/room_no/etc.
       defined as NOT NULL, the CREATE TABLE IF NOT EXISTS above is a no-op and
       never relaxes those columns. Force them nullable explicitly so a cost
       center posting (folio_id = NULL) doesn't fail with
       "Column 'folio_id' cannot be null".
    */
    foreach(['folio_id'=>'INT UNSIGNED NULL','split_id'=>'BIGINT UNSIGNED NULL','unit_no'=>'INT UNSIGNED NULL','room_no'=>'VARCHAR(80) NULL','guest_name'=>'VARCHAR(180) NULL','payer_name'=>'VARCHAR(180) NULL','destination_type'=>'VARCHAR(40) NULL'] as $col=>$def){
        try{ $pdo->exec("ALTER TABLE restaurant_item_allocations MODIFY {$col} {$def}"); }catch(Throwable $ignore){}
    }
}

function r_split_destination_badge_label(PDO $pdo, string $destinationType, int $folioId, string $payer=''): array{
    $dest=strtolower(trim($destinationType));
    $payer=trim($payer);
    $folio=$folioId>0 ? r_folio_print_label($pdo,$folioId) : ['room_no'=>'','folio_name'=>'','label'=>''];
    $roomNo=trim((string)($folio['room_no'] ?? ''));
    $guestName=trim((string)($folio['folio_name'] ?? ''));
    $label='';

    if($roomNo!==''){
        $label='Room '.$roomNo;
    }elseif($guestName!==''){
        $label=$guestName;
    }elseif($payer!==''){
        $label=$payer;
    }else{
        $label=ucwords(str_replace('_',' ',$dest ?: 'Split'));
    }

    if(strpos($dest,'master')!==false && $roomNo==='') $label='Master Folio';
    if(strpos($dest,'guest')!==false && $roomNo==='' && $guestName!=='') $label='Guest: '.$guestName;

    return ['room_no'=>$roomNo,'guest_name'=>$guestName,'label'=>$label];
}

function r_mark_restaurant_item_split_allocation(PDO $pdo,int $restaurantOrderId,int $itemId,int $splitId,int $unitNo,string $destinationType,int $folioId,string $payer,float $amount): void{
    if($restaurantOrderId<=0 || $itemId<=0) return;
    r_ensure_split_item_allocation_schema($pdo);
    $meta=r_split_destination_badge_label($pdo,$destinationType,$folioId,$payer);
    $roomNo=(string)($meta['room_no'] ?? '');
    $guestName=(string)($meta['guest_name'] ?? '');
    $payer=trim($payer);
    $dest=strtolower(trim($destinationType));

    $pdo->prepare("INSERT INTO restaurant_item_allocations
        (restaurant_order_id,restaurant_order_item_id,split_id,unit_no,destination_type,folio_id,room_no,guest_name,payer_name,amount,created_by)
        VALUES(:rid,:iid,:sid,:unit_no,:dest,:folio_id,:room_no,:guest_name,:payer,:amount,:created_by)")
        ->execute([
            ':rid'=>$restaurantOrderId,
            ':iid'=>$itemId,
            ':sid'=>$splitId>0?$splitId:null,
            ':unit_no'=>$unitNo>0?$unitNo:null,
            ':dest'=>$dest!==''?$dest:null,
            ':folio_id'=>$folioId>0?$folioId:null,
            ':room_no'=>$roomNo!==''?$roomNo:null,
            ':guest_name'=>$guestName!==''?$guestName:null,
            ':payer'=>$payer!==''?$payer:null,
            ':amount'=>round($amount,2),
            ':created_by'=>r_current_user_id()
        ]);

    if(r_table_exists($pdo,'restaurant_order_items')){
        try{
            $pdo->prepare("UPDATE restaurant_order_items
                SET split_target_type=:dest,
                    split_folio_id=:folio_id,
                    split_room_no=:room_no,
                    split_guest_name=:guest_name,
                    split_payer_name=:payer,
                    split_batch_id=:sid,
                    split_posted_at=NOW(),
                    split_posted_by=:created_by
                WHERE id=:iid AND restaurant_order_id=:rid")
                ->execute([
                    ':dest'=>$dest!==''?$dest:null,
                    ':folio_id'=>$folioId>0?$folioId:null,
                    ':room_no'=>$roomNo!==''?$roomNo:null,
                    ':guest_name'=>$guestName!==''?$guestName:null,
                    ':payer'=>$payer!==''?$payer:null,
                    ':sid'=>$splitId>0?$splitId:null,
                    ':created_by'=>r_current_user_id(),
                    ':iid'=>$itemId,
                    ':rid'=>$restaurantOrderId
                ]);
        }catch(Throwable $ignore){}
    }
}

function r_attach_item_allocation_badges(PDO $pdo,int $restaurantOrderId,array $orderItems): array{
    if($restaurantOrderId<=0 || !$orderItems || !r_table_exists($pdo,'restaurant_item_allocations')) return $orderItems;
    try{
        $st=$pdo->prepare("SELECT restaurant_order_item_id, destination_type, folio_id, room_no, guest_name, payer_name, amount, unit_no
            FROM restaurant_item_allocations
            WHERE restaurant_order_id=:rid
            ORDER BY id ASC");
        $st->execute([':rid'=>$restaurantOrderId]);
        $byItem=[];
        foreach($st->fetchAll(PDO::FETCH_ASSOC)?:[] as $r){
            $iid=(int)($r['restaurant_order_item_id'] ?? 0);
            if($iid<=0) continue;
            $dest=strtolower(trim((string)($r['destination_type'] ?? '')));
            $room=trim((string)($r['room_no'] ?? ''));
            $guest=trim((string)($r['guest_name'] ?? ''));
            $payer=trim((string)($r['payer_name'] ?? ''));
            if($room!=='') $label='Room '.$room;
            elseif($guest!=='') $label=$guest;
            elseif($payer!=='') $label=$payer;
            else $label=ucwords(str_replace('_',' ',$dest ?: 'Posted'));
            $key=$label.'|'.$dest.'|'.(int)($r['folio_id']??0);
            if(!isset($byItem[$iid][$key])) $byItem[$iid][$key]=['label'=>$label,'dest'=>$dest,'amount'=>0.0,'count'=>0];
            $byItem[$iid][$key]['amount']+=round((float)($r['amount']??0),2);
            $byItem[$iid][$key]['count']++;
        }
        foreach($orderItems as &$it){
            $iid=(int)($it['id'] ?? 0);
            $badges=[];
            foreach(($byItem[$iid] ?? []) as $b){
                $badges[]=$b['label'].' '.money_fmt((float)$b['amount']);
            }
            $it['_split_allocation_badges']=$badges;
        }
        unset($it);
    }catch(Throwable $ignore){}
    return $orderItems;
}

function r_split_bill_print_role(PDO $pdo): string{
    try{
        if(r_printnode_printer_id_for_role($pdo,'receipt')>0) return 'receipt';
        if(r_printnode_printer_id_for_role($pdo,'bar')>0) return 'bar';
    }catch(Throwable $ignore){}
    return 'receipt';
}

function r_create_split_group_bill_bar_job(PDO $pdo,int $restaurantOrderId,array $bill): array{
    r_ensure_qz_printer_schema($pdo);
    if($restaurantOrderId<=0) throw new RuntimeException('Invalid restaurant order.');

    $orderStmt=$pdo->prepare("SELECT ro.*, COALESCE(rt.table_no, rt.table_name, ro.table_id) AS table_label FROM restaurant_orders ro LEFT JOIN restaurant_tables rt ON rt.id=ro.table_id WHERE ro.id=:id LIMIT 1");
    $orderStmt->execute([':id'=>$restaurantOrderId]);
    $order=$orderStmt->fetch(PDO::FETCH_ASSOC);
    if(!$order) throw new RuntimeException('Restaurant order not found.');

    $items=is_array($bill['items']??null) ? $bill['items'] : [];
    $itemIds=array_values(array_unique(array_filter(array_map(static fn($x)=>(int)($x['id']??0),$items))));
    $itemMap=[];
    if($itemIds){
        $in=implode(',',array_fill(0,count($itemIds),'?'));
        $params=array_merge([$restaurantOrderId],$itemIds);
        $st=$pdo->prepare("SELECT id,item_name,qty,unit_price,line_total,tax_amount,notes FROM restaurant_order_items WHERE restaurant_order_id=? AND id IN ($in) AND status='active'");
        $st->execute($params);
        foreach($st->fetchAll(PDO::FETCH_ASSOC)?:[] as $row){ $itemMap[(int)$row['id']]=$row; }
    }

    $lines=[];
    $totalCharges=0.0;
    $tax=0.0;
    if($items){
        foreach($items as $it){
            $iid=(int)($it['id']??0);
            $amount=round((float)($it['amount']??0),2);
            if($amount<=0) continue;
            $meta=$itemMap[$iid]??[];
            $name=trim((string)($meta['item_name']??('Item #'.$iid)));
            $qty=(float)($it['qty']??1);
            if($qty<=0) $qty=1;
            $key=$iid.'|'.$name.'|'.round($amount/max($qty,0.0001),2);
            if(!isset($lines[$key])) $lines[$key]=['name'=>$name,'qty'=>0.0,'amount'=>0.0,'notes'=>trim((string)($meta['notes']??''))];
            $lines[$key]['qty'] += $qty;
            $lines[$key]['amount'] += $amount;
            $totalCharges += $amount;
            $lineTotal=(float)($meta['line_total']??0);
            $lineTax=(float)($meta['tax_amount']??0);
            if($lineTotal>0 && $lineTax>0) $tax += round($amount*($lineTax/$lineTotal),2);
        }
    }

    $fallbackAmount=round((float)($bill['amount']??0),2);
    if(!$lines && $fallbackAmount>0){
        $lines[]=['name'=>trim((string)($bill['line_label']??'Split settlement')) ?: 'Split settlement','qty'=>1,'amount'=>$fallbackAmount,'notes'=>''];
        $totalCharges=$fallbackAmount;
    }

    $totalCharges=round($totalCharges,2);
    $tax=round($tax,2);
    if($totalCharges<=0) return [];

    $folioId=(int)($bill['folio_id']??0);
    $folio=r_folio_print_label($pdo,$folioId);
    $payer=trim((string)($bill['payer']??''));
    $dest=trim((string)($bill['dest']??''));
    $method=trim((string)($bill['method']??''));
    $ref=trim((string)($bill['ref']??''));

    $payload="";
    $payload.="******** ROOM SPLIT BILL ********\n";
    $payload.=centerText("Pioneers Lodge");
    $payload.=centerText("Reg# TIN 2001134870");
    $payload.=centerText("VAT# : 220068351");
    $payload.=centerText("info@pioneersvicalls.com");
    $payload.=centerText("www.pioneersvicalls.com");
    $payload.="Order: ".($order['order_no'] ?? ('#'.$restaurantOrderId))."\n";
    if(!empty($order['table_label'])) $payload.="Table: ".$order['table_label']."\n";
    if($payer!=='') $payload.="Payer: ".$payer."\n";
    if($folio['room_no']!=='') $payload.="Room: ".$folio['room_no']."\n";
    if($folio['label']!=='') $payload.="Folio: ".$folio['label']."\n";
    elseif(!empty($order['room_no'])) $payload.="Room: ".$order['room_no']."\n";
    if($dest!=='' || $method!=='') $payload.="Posted To: ".($method!==''?$method:ucwords(str_replace('_',' ',$dest)))."\n";
    if($ref!=='') $payload.="Reference: ".$ref."\n";
    $payload.="Printed: ".date('Y-m-d H:i:s')."\n";
    $payload.="Cashier: ".r_current_user_name()."\n";
    $payload.="==========================================\n";

    foreach($lines as $line){
        $qty=rtrim(rtrim(number_format((float)$line['qty'],2,'.',''),'0'),'.');
        $lineTotal=money_fmt((float)$line['amount']);
        $unit=(float)$line['qty']>0 ? money_fmt(((float)$line['amount'])/(float)$line['qty']) : money_fmt((float)$line['amount']);
        $left=$qty." x ".(string)$line['name'];
        $first=true;
        foreach(r_wrap_receipt_line($left,30) as $wrapped){
            if($first){
                $payload.=str_pad(substr($wrapped,0,30),31).str_pad($lineTotal,10,' ',STR_PAD_LEFT)."\n";
                $payload.="   @ ".$unit."\n";
                $first=false;
            }else{
                $payload.="   ".$wrapped."\n";
            }
        }
        if(trim((string)($line['notes']??''))!==''){
            foreach(r_wrap_receipt_line('Note: '.$line['notes'],38) as $noteLine){ $payload.="   ".$noteLine."\n"; }
        }
    }

    $payload.="------------------------------------------\n";
    $payload.=r_bill_money_line("Posted Charges",$totalCharges)."\n";
    $payload.=r_bill_money_line("Tax/VAT incl.",$tax)."\n";
    $payload.=r_bill_money_line("TOTAL POSTED",$totalCharges)."\n";
    $payload.="==========================================\n";
    $payload.=centerText("Thank you.\n");

    try{ $pdo->exec("ALTER TABLE restaurant_print_jobs MODIFY job_type VARCHAR(40) NOT NULL DEFAULT 'station'"); }catch(Throwable $ignore){}
    $printRole = r_split_bill_print_role($pdo);
    if(r_column_exists($pdo,'restaurant_print_jobs','printer_id')){
        $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,printer_id,job_type,payload,status) VALUES(:rid,:role,NULL,'bill',:payload,'pending')")
            ->execute([':rid'=>$restaurantOrderId,':role'=>$printRole,':payload'=>$payload]);
    }else{
        $pdo->prepare("INSERT INTO restaurant_print_jobs(restaurant_order_id,printer_role,job_type,payload,status) VALUES(:rid,:role,'bill',:payload,'pending')")
            ->execute([':rid'=>$restaurantOrderId,':role'=>$printRole,':payload'=>$payload]);
    }
    return [(int)$pdo->lastInsertId()];
}

function r_print_split_group_bills_to_bar_now(PDO $pdo,int $restaurantOrderId,array $bills): void{
    if($restaurantOrderId<=0 || !$bills) return;
    $jobIds=[];
    try{
        foreach($bills as $bill){
            foreach(r_create_split_group_bill_bar_job($pdo,$restaurantOrderId,$bill) as $jid){ $jobIds[]=(int)$jid; }
        }
        if(!$jobIds) return;
        $pn=r_dispatch_printnode_jobs($pdo,$jobIds);
        if(!empty($pn['failed'])) r_flash('warning','Split posted, but one or more room bills failed to print: '.implode(' | ',$pn['messages']));
        else r_flash('success',count($jobIds).' separate split room bill(s) printed to Bar printer.');
    }catch(Throwable $e){
        r_flash('warning','Split posted, but separate room bills were not printed: '.$e->getMessage());
    }
}

function r_ensure_table_layout_columns(PDO $pdo):void{ if(!r_table_exists($pdo,'restaurant_tables')) return; if(!r_column_exists($pdo,'restaurant_tables','orientation')){ $pdo->exec("ALTER TABLE restaurant_tables ADD COLUMN orientation ENUM('horizontal','vertical') NOT NULL DEFAULT 'horizontal' AFTER shape"); } }
function r_ensure_order_guest_columns(PDO $pdo):void{
    if(!r_table_exists($pdo,'restaurant_orders')) return;

    /*
       Group / In-house Group support:
       - order_type includes group
       - group_name stores the displayed group name
       This is kept additive so existing room and walk-in orders continue working.
    */
    try{
        $pdo->exec("ALTER TABLE restaurant_orders MODIFY order_type ENUM('table','room','group','walkin','takeaway') NOT NULL DEFAULT 'table'");
    }catch(Throwable $e){}

    if(!r_column_exists($pdo,'restaurant_orders','room_no')){
        $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN room_no VARCHAR(80) NULL AFTER guest_name");
    }

    if(!r_column_exists($pdo,'restaurant_orders','group_name')){
        $after = r_column_exists($pdo,'restaurant_orders','room_no') ? 'AFTER room_no' : 'AFTER guest_name';
        $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN group_name VARCHAR(180) NULL ".$after);
    }

    if(!r_column_exists($pdo,'restaurant_orders','cashier_name')){
        $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN cashier_name VARCHAR(180) NULL AFTER opened_by");
    }
}

function r_ensure_menu_hierarchy_schema(PDO $pdo):void{
    if(!r_table_exists($pdo,'restaurant_menu_categories')){
        $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_menu_categories (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, property_id INT UNSIGNED NULL, parent_id INT UNSIGNED NULL, category_name VARCHAR(120) NOT NULL, button_color VARCHAR(30) NULL, depth INT UNSIGNED NOT NULL DEFAULT 0, printer_role VARCHAR(60) NULL, sort_order INT NOT NULL DEFAULT 1, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_rest_cat_parent(parent_id), INDEX idx_rest_cat_active(is_active)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    }else{
        if(!r_column_exists($pdo,'restaurant_menu_categories','parent_id')) $pdo->exec("ALTER TABLE restaurant_menu_categories ADD COLUMN parent_id INT UNSIGNED NULL AFTER property_id");
        if(!r_column_exists($pdo,'restaurant_menu_categories','button_color')) $pdo->exec("ALTER TABLE restaurant_menu_categories ADD COLUMN button_color VARCHAR(30) NULL AFTER category_name");
        if(!r_column_exists($pdo,'restaurant_menu_categories','depth')) $pdo->exec("ALTER TABLE restaurant_menu_categories ADD COLUMN depth INT UNSIGNED NOT NULL DEFAULT 0 AFTER category_name");
    }
    if(!r_table_exists($pdo,'restaurant_menu_items')){
        $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_menu_items (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, property_id INT UNSIGNED NULL, category_id INT UNSIGNED NULL, inventory_item_id INT UNSIGNED NULL, item_name VARCHAR(180) NOT NULL, item_code VARCHAR(60) NULL, description TEXT NULL, price DECIMAL(14,2) NOT NULL DEFAULT 0.00, tax_rate DECIMAL(7,4) NOT NULL DEFAULT 0.0000, tags VARCHAR(255) NULL, button_color VARCHAR(30) NULL, printer_role VARCHAR(60) NULL, default_printer_id INT UNSIGNED NULL, is_food TINYINT(1) NOT NULL DEFAULT 0, is_bar TINYINT(1) NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, sort_order INT NOT NULL DEFAULT 1, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_rest_item_cat(category_id), INDEX idx_rest_item_active(is_active)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    }else{
        if(!r_column_exists($pdo,'restaurant_menu_items','tags')) $pdo->exec("ALTER TABLE restaurant_menu_items ADD COLUMN tags VARCHAR(255) NULL AFTER tax_rate");
        if(!r_column_exists($pdo,'restaurant_menu_items','button_color')) $pdo->exec("ALTER TABLE restaurant_menu_items ADD COLUMN button_color VARCHAR(30) NULL AFTER tags");
    }
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_menu_modifiers (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, property_id INT UNSIGNED NULL, modifier_name VARCHAR(120) NOT NULL, price_delta DECIMAL(14,2) NOT NULL DEFAULT 0.00, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_menu_item_modifiers (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, menu_item_id INT UNSIGNED NOT NULL, modifier_id INT UNSIGNED NOT NULL, is_default TINYINT(1) NOT NULL DEFAULT 0, sort_order INT NOT NULL DEFAULT 1, UNIQUE KEY uq_rest_item_modifier(menu_item_id, modifier_id), INDEX idx_rest_item_mod_item(menu_item_id), INDEX idx_rest_item_mod_modifier(modifier_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    /*
       Reusable modifier architecture:
       - restaurant_menu_modifiers now stores the master modifier once.
       - restaurant_modifier_group_items links the same modifier to one or more groups.
       - restaurant_menu_item_modifier_groups links groups to menu items.
       This keeps backwards compatibility with old restaurant_menu_modifiers.modifier_group_id data.
    */
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_modifier_groups (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        property_id INT UNSIGNED NULL,
        group_name VARCHAR(120) NOT NULL,
        group_type ENUM('required','optional','default') NOT NULL DEFAULT 'optional',
        min_select INT UNSIGNED NOT NULL DEFAULT 0,
        max_select INT UNSIGNED NOT NULL DEFAULT 1,
        sort_order INT NOT NULL DEFAULT 1,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_rest_mod_group_active(is_active)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_menu_item_modifier_groups (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        menu_item_id INT UNSIGNED NOT NULL,
        modifier_group_id INT UNSIGNED NOT NULL,
        sort_order INT NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uq_rest_item_group(menu_item_id, modifier_group_id),
        INDEX idx_rest_item_group_item(menu_item_id),
        INDEX idx_rest_item_group_group(modifier_group_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_modifier_group_items (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        modifier_group_id INT UNSIGNED NOT NULL,
        modifier_id INT UNSIGNED NOT NULL,
        sort_order INT NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uq_group_modifier(modifier_group_id, modifier_id),
        INDEX idx_group(modifier_group_id),
        INDEX idx_modifier(modifier_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    /*
       IMPORTANT compatibility note:
       The enhanced menu.php saves reusable modifier links in
       restaurant_modifier_group_options. Older planner drafts used
       restaurant_modifier_group_items. The POS must support both, otherwise
       menu items show their selected modifier group but no modifier options.
    */
    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_modifier_group_options (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        modifier_group_id INT UNSIGNED NOT NULL,
        modifier_id INT UNSIGNED NOT NULL,
        sort_order INT NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uq_rest_group_modifier(modifier_group_id, modifier_id),
        INDEX idx_rest_group_option_group(modifier_group_id),
        INDEX idx_rest_group_option_modifier(modifier_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    try{
        $pdo->exec("INSERT IGNORE INTO restaurant_modifier_group_items (modifier_group_id, modifier_id, sort_order)
            SELECT modifier_group_id, modifier_id, COALESCE(sort_order,1)
            FROM restaurant_modifier_group_options");
        $pdo->exec("INSERT IGNORE INTO restaurant_modifier_group_options (modifier_group_id, modifier_id, sort_order)
            SELECT modifier_group_id, modifier_id, COALESCE(sort_order,1)
            FROM restaurant_modifier_group_items");
    }catch(Throwable $e){}

    if(r_table_exists($pdo,'restaurant_menu_modifiers')){
        if(!r_column_exists($pdo,'restaurant_menu_modifiers','parent_modifier_id')){
            try{ $pdo->exec("ALTER TABLE restaurant_menu_modifiers ADD COLUMN parent_modifier_id INT UNSIGNED NULL AFTER property_id"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'restaurant_menu_modifiers','prefix_allowed')){
            try{ $pdo->exec("ALTER TABLE restaurant_menu_modifiers ADD COLUMN prefix_allowed TINYINT(1) NOT NULL DEFAULT 1 AFTER price_delta"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'restaurant_menu_modifiers','sort_order')){
            try{ $pdo->exec("ALTER TABLE restaurant_menu_modifiers ADD COLUMN sort_order INT NOT NULL DEFAULT 1 AFTER prefix_allowed"); }catch(Throwable $e){}
        }
    }

    if(r_column_exists($pdo,'restaurant_menu_modifiers','modifier_group_id')){
        try{
            $pdo->exec("INSERT IGNORE INTO restaurant_modifier_group_items (modifier_group_id, modifier_id, sort_order)
                SELECT modifier_group_id, id, COALESCE(sort_order,1)
                FROM restaurant_menu_modifiers
                WHERE modifier_group_id IS NOT NULL AND modifier_group_id > 0");
        }catch(Throwable $e){}
    }

    try{
        $pdo->exec("UPDATE restaurant_menu_categories c LEFT JOIN restaurant_menu_categories p ON p.id=c.parent_id SET c.depth=CASE WHEN c.parent_id IS NULL THEN 0 WHEN p.parent_id IS NULL THEN 1 ELSE 2 END WHERE c.depth IS NULL OR c.depth=0");
    }catch(Throwable $e){/* ignore */}
}


function r_ensure_simphony_style_schema(PDO $pdo):void{
    if(r_table_exists($pdo,'restaurant_order_items')){
        $cols=[
            "seat_no INT NULL AFTER qty",
            "parent_item_id BIGINT UNSIGNED NULL AFTER seat_no",
            "condiment_prefix VARCHAR(30) NULL AFTER parent_item_id",
            "is_condiment TINYINT(1) NOT NULL DEFAULT 0 AFTER condiment_prefix",
            "modifier_group_id INT UNSIGNED NULL AFTER is_condiment"
        ];
        foreach($cols as $def){
            $col=trim(strtok($def,' '));
            if(!r_column_exists($pdo,'restaurant_order_items',$col)){
                try{ $pdo->exec("ALTER TABLE restaurant_order_items ADD COLUMN $def"); }catch(Throwable $e){}
            }
        }
    }

    if(r_table_exists($pdo,'folios')){
        if(!r_column_exists($pdo,'folios','window_no')){
            try{ $pdo->exec("ALTER TABLE folios ADD COLUMN window_no INT NOT NULL DEFAULT 1 AFTER folio_type"); }catch(Throwable $e){}
        }
        if(!r_column_exists($pdo,'folios','window_name')){
            try{ $pdo->exec("ALTER TABLE folios ADD COLUMN window_name VARCHAR(120) NULL AFTER window_no"); }catch(Throwable $e){}
        }
    }

    if(r_table_exists($pdo,'guests')){
        foreach([
            "loyalty_tier VARCHAR(80) NULL",
            "dietary_notes TEXT NULL",
            "allergy_notes TEXT NULL",
            "billing_preference VARCHAR(120) NULL"
        ] as $def){
            $col=trim(strtok($def,' '));
            if(!r_column_exists($pdo,'guests',$col)){
                try{ $pdo->exec("ALTER TABLE guests ADD COLUMN $def"); }catch(Throwable $e){}
            }
        }
    }

    $pdo->exec("CREATE TABLE IF NOT EXISTS pms_routing_rules (
        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        order_id INT UNSIGNED NULL,
        guest_id INT UNSIGNED NULL,
        source_module VARCHAR(60) NOT NULL DEFAULT 'restaurant',
        transaction_code VARCHAR(60) NULL,
        target_folio_type ENUM('master','room','guest') NOT NULL DEFAULT 'room',
        target_window_no INT NOT NULL DEFAULT 2,
        target_folio_id INT UNSIGNED NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_pms_routing_order(order_id,source_module,is_active)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_folio_posting_queue (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        restaurant_order_id BIGINT UNSIGNED NOT NULL,
        pms_order_id INT UNSIGNED NOT NULL,
        folio_id INT UNSIGNED NULL,
        window_no INT NOT NULL DEFAULT 2,
        amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        description VARCHAR(255) NULL,
        status ENUM('pending','posted','failed') NOT NULL DEFAULT 'pending',
        retry_count INT NOT NULL DEFAULT 0,
        error_message TEXT NULL,
        posted_at DATETIME NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_folio_queue_status(status),
        INDEX idx_rest_folio_queue_order(restaurant_order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_kitchen_stations (
        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        property_id INT UNSIGNED NULL,
        station_name VARCHAR(120) NOT NULL,
        station_code VARCHAR(60) NOT NULL,
        is_bar TINYINT(1) NOT NULL DEFAULT 0,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        sort_order INT NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uq_rest_station_code(station_code)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    if(r_table_exists($pdo,'restaurant_menu_items') && !r_column_exists($pdo,'restaurant_menu_items','kitchen_station_id')){
        try{ $pdo->exec("ALTER TABLE restaurant_menu_items ADD COLUMN kitchen_station_id INT UNSIGNED NULL AFTER printer_role"); }catch(Throwable $e){}
    }

    $pdo->exec("INSERT IGNORE INTO restaurant_kitchen_stations (id,station_name,station_code,is_bar,sort_order) VALUES
        (1,'Cold Kitchen','COLD',0,1),
        (2,'Grill','GRILL',0,2),
        (3,'Main Kitchen','KITCHEN',0,3),
        (4,'Bar','BAR',1,4),
        (5,'Expo / Runner','EXPO',0,5)");

    try{
        $pdo->exec("ALTER TABLE restaurant_bill_splits MODIFY split_type ENUM('equal','covers','seat','item','custom','mixed','course','folio','transfer') NOT NULL");
    }catch(Throwable $e){}

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_split_settlements (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        restaurant_order_id BIGINT UNSIGNED NOT NULL,
        split_id BIGINT UNSIGNED NULL,
        destination_type ENUM('cash','card','pos','mobile_money','bank_transfer','room_folio','guest_folio','master_folio','group_master','company_account','folio','comp','house_account','dispute','allowance','package') NOT NULL DEFAULT 'cash',
        folio_id INT UNSIGNED NULL,
        payer_name VARCHAR(180) NULL,
        amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        reference_no VARCHAR(120) NULL,
        note VARCHAR(255) NULL,
        status ENUM('posted','void') NOT NULL DEFAULT 'posted',
        created_by INT UNSIGNED NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_split_settlement_order(restaurant_order_id),
        INDEX idx_rest_split_settlement_folio(folio_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_house_adjustments (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        restaurant_order_id BIGINT UNSIGNED NOT NULL,
        split_id BIGINT UNSIGNED NULL,
        adjustment_type ENUM('comp','house_account','dispute','allowance','package') NOT NULL DEFAULT 'comp',
        amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        reason VARCHAR(255) NULL,
        created_by INT UNSIGNED NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_house_adj_order(restaurant_order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_check_transfers (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        from_restaurant_order_id BIGINT UNSIGNED NOT NULL,
        to_restaurant_order_id BIGINT UNSIGNED NULL,
        from_table_id INT UNSIGNED NULL,
        to_table_id INT UNSIGNED NULL,
        transfer_type ENUM('full_check','items','seats','server','table') NOT NULL,
        from_server_id INT UNSIGNED NULL,
        to_server_id INT UNSIGNED NULL,
        notes TEXT NULL,
        created_by INT UNSIGNED NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_transfer_from(from_restaurant_order_id),
        INDEX idx_rest_transfer_to(to_restaurant_order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_check_transfer_items (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        transfer_id BIGINT UNSIGNED NOT NULL,
        restaurant_order_item_id BIGINT UNSIGNED NOT NULL,
        qty DECIMAL(14,4) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_transfer_items_transfer(transfer_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}


$errors = [];

try{
r_ensure_table_layout_columns($pdo);
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_orders (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, property_id INT UNSIGNED NULL, area_id INT UNSIGNED NULL, table_id INT UNSIGNED NULL, order_no VARCHAR(60) NOT NULL, order_type ENUM('table','room','group','walkin','takeaway') NOT NULL DEFAULT 'table', linked_order_id INT UNSIGNED NULL, linked_folio_id INT UNSIGNED NULL, guest_name VARCHAR(180) NULL, room_no VARCHAR(80) NULL, covers INT UNSIGNED NOT NULL DEFAULT 1, status ENUM('open','sent','closed','void') NOT NULL DEFAULT 'open', subtotal DECIMAL(14,2) NOT NULL DEFAULT 0.00, tax_total DECIMAL(14,2) NOT NULL DEFAULT 0.00, total_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00, payment_status ENUM('unpaid','part_paid','paid','room_charged','void') NOT NULL DEFAULT 'unpaid', opened_by INT UNSIGNED NULL, cashier_name VARCHAR(180) NULL, closed_by INT UNSIGNED NULL, opened_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, closed_at DATETIME NULL, notes TEXT NULL, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uq_rest_order_no(order_no), INDEX idx_rest_order_table_status(table_id,status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_order_items (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, restaurant_order_id BIGINT UNSIGNED NOT NULL, menu_item_id INT UNSIGNED NULL, inventory_item_id INT UNSIGNED NULL, item_name VARCHAR(180) NOT NULL, qty DECIMAL(14,4) NOT NULL DEFAULT 1, unit_price DECIMAL(14,2) NOT NULL DEFAULT 0, tax_rate DECIMAL(7,4) NOT NULL DEFAULT 0, tax_amount DECIMAL(14,2) NOT NULL DEFAULT 0, line_total DECIMAL(14,2) NOT NULL DEFAULT 0, printer_role VARCHAR(60) NULL, print_status ENUM('pending','sent','printed','void') NOT NULL DEFAULT 'pending', notes VARCHAR(255) NULL, status ENUM('active','void') NOT NULL DEFAULT 'active', created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_payments (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, restaurant_order_id BIGINT UNSIGNED NOT NULL, payment_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, payment_method VARCHAR(60) NOT NULL, amount DECIMAL(14,2) NOT NULL DEFAULT 0, bank_account_id INT UNSIGNED NULL, pos_machine_id INT UNSIGNED NULL, reference_no VARCHAR(120) NULL, created_by INT UNSIGNED NULL, status ENUM('posted','void') NOT NULL DEFAULT 'posted', created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_print_jobs (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, restaurant_order_id BIGINT UNSIGNED NOT NULL, printer_role VARCHAR(60) NOT NULL, job_type VARCHAR(40) NOT NULL DEFAULT 'station', payload LONGTEXT NOT NULL, status ENUM('pending','printing','printed','failed') NOT NULL DEFAULT 'pending', error_message TEXT NULL, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, printed_at DATETIME NULL, INDEX idx_rest_print_status(status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_order_tables (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, restaurant_order_id BIGINT UNSIGNED NOT NULL, table_id INT UNSIGNED NOT NULL, joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, joined_by INT UNSIGNED NULL, UNIQUE KEY uq_rest_order_table(restaurant_order_id,table_id), INDEX idx_rest_order_tables_order(restaurant_order_id), INDEX idx_rest_order_tables_table(table_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_bill_splits (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, restaurant_order_id BIGINT UNSIGNED NOT NULL, split_type ENUM('equal','item','custom') NOT NULL, payer_name VARCHAR(180) NULL, payment_method VARCHAR(60) NULL, split_amount DECIMAL(14,2) NOT NULL DEFAULT 0, reference_no VARCHAR(120) NULL, status ENUM('draft','posted','void') NOT NULL DEFAULT 'posted', created_by INT UNSIGNED NULL, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_rest_bill_splits_order(restaurant_order_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_bill_split_items (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, split_id BIGINT UNSIGNED NOT NULL, restaurant_order_item_id BIGINT UNSIGNED NOT NULL, amount DECIMAL(14,2) NOT NULL DEFAULT 0, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_rest_bill_split_items_split(split_id), INDEX idx_rest_bill_split_items_item(restaurant_order_item_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
r_ensure_order_guest_columns($pdo);
r_ensure_menu_hierarchy_schema($pdo);
r_ensure_simphony_style_schema($pdo);
r_ensure_qz_printer_schema($pdo);
r_ensure_print_routing_columns($pdo);
}catch(Throwable $e){$errors[]=$e->getMessage();}

if(($_SERVER['REQUEST_METHOD']??'GET')==='POST'){
 try{ if(function_exists('verify_csrf')) verify_csrf(); $a=(string)($_POST['action']??'');

 if($a==='save_restaurant_printer'){
    r_ensure_qz_printer_schema($pdo);

    $printerId=(int)($_POST['printer_id']??0);
    $printerName=trim((string)($_POST['printer_name']??''));
    $printerRole=strtolower(trim((string)($_POST['printer_role']??'')));
    $printNodePrinterId=(int)($_POST['printnode_printer_id']??0);
    $legacyName=trim((string)($_POST['printnode_printer_id']??''));
    $printerIp=trim((string)($_POST['printer_ip']??''));
    $printerPort=(int)($_POST['printer_port']??9100);
    $sortOrder=(int)($_POST['sort_order']??1);
    $isActive=isset($_POST['is_active'])?1:0;
    $propertyId=(int)($_POST['property_id']??0);

    if($printerName==='') throw new RuntimeException('Printer display name is required.');
    if($printerRole==='') throw new RuntimeException('Printer role is required, for example kitchen, bar, dessert, master or receipt.');
    if(!preg_match('/^[a-z0-9_\-]+$/',$printerRole)) throw new RuntimeException('Printer role may only contain letters, numbers, dash or underscore.');
    if($printNodePrinterId<=0) throw new RuntimeException('PrintNode Printer ID is required. Example: 75588290.');
    if($printerPort<=0) $printerPort=9100;
    if($legacyName==='') $legacyName=$printerName;

    if($printerId>0){
        $st=$pdo->prepare("
            UPDATE restaurant_printers
            SET property_id=:property_id,
                printer_name=:printer_name,
                printer_role=:printer_role,
                printnode_printer_id=:printnode_printer_id,
                qz_printer_name=:qz_printer_name,
                printer_ip=:printer_ip,
                printer_port=:printer_port,
                printer_type='printnode',
                is_active=:is_active,
                sort_order=:sort_order,
                updated_at=NOW()
            WHERE id=:id
        ");
        $st->execute([
            ':property_id'=>$propertyId>0?$propertyId:null,
            ':printer_name'=>$printerName,
            ':printer_role'=>$printerRole,
            ':printnode_printer_id'=>$printNodePrinterId,
            ':qz_printer_name'=>$legacyName,
            ':printer_ip'=>$printerIp!==''?$printerIp:null,
            ':printer_port'=>$printerPort,
            ':is_active'=>$isActive,
            ':sort_order'=>$sortOrder,
            ':id'=>$printerId
        ]);
    }else{
        $st=$pdo->prepare("
            INSERT INTO restaurant_printers
            (property_id,printer_name,printer_role,printnode_printer_id,qz_printer_name,printer_ip,printer_port,printer_type,is_active,sort_order)
            VALUES
            (:property_id,:printer_name,:printer_role,:printnode_printer_id,:qz_printer_name,:printer_ip,:printer_port,'printnode',:is_active,:sort_order)
        ");
        $st->execute([
            ':property_id'=>$propertyId>0?$propertyId:null,
            ':printer_name'=>$printerName,
            ':printer_role'=>$printerRole,
            ':printnode_printer_id'=>$printNodePrinterId,
            ':qz_printer_name'=>$legacyName,
            ':printer_ip'=>$printerIp!==''?$printerIp:null,
            ':printer_port'=>$printerPort,
            ':is_active'=>$isActive,
            ':sort_order'=>$sortOrder
        ]);
    }

    r_flash('success','PrintNode printer setup saved.');
    header('Location: pp.php?printer_setup=1');
    exit;
 }

  if($a==='delete_restaurant_printer'){
    r_ensure_qz_printer_schema($pdo);
    $printerId=(int)($_POST['printer_id']??0);
    if($printerId<=0) throw new RuntimeException('Invalid printer selected.');
    $pdo->prepare("DELETE FROM restaurant_printers WHERE id=:id")->execute([':id'=>$printerId]);
    r_flash('success','Printer deleted.');
    header('Location: pp.php?printer_setup=1');
    exit;
 }

 if($a==='save_cost_center'){
    r_ensure_cost_center_schema($pdo);
    $ccId=(int)($_POST['cost_center_id']??0);
    $ccName=trim((string)($_POST['cost_center_name']??''));
    $ccSort=(int)($_POST['sort_order']??1);
    $ccActive=isset($_POST['is_active'])?1:0;
    $propertyId=(int)($_POST['property_id']??0);

    if($ccName==='') throw new RuntimeException('Department name is required.');

    if($ccId>0){
        $pdo->prepare("UPDATE restaurant_cost_centers SET property_id=:pid, name=:name, sort_order=:sort, is_active=:active WHERE id=:id")->execute([
            ':pid'=>$propertyId>0?$propertyId:null,
            ':name'=>$ccName,
            ':sort'=>$ccSort,
            ':active'=>$ccActive,
            ':id'=>$ccId
        ]);
    }else{
        $pdo->prepare("INSERT INTO restaurant_cost_centers(property_id,name,sort_order,is_active) VALUES(:pid,:name,:sort,:active)")->execute([
            ':pid'=>$propertyId>0?$propertyId:null,
            ':name'=>$ccName,
            ':sort'=>$ccSort,
            ':active'=>$ccActive
        ]);
    }

    r_flash('success','Costing department saved.');
    header('Location: pp.php?cost_centers=1#costCenterSetupCard');
    exit;
 }

 if($a==='delete_cost_center'){
    r_ensure_cost_center_schema($pdo);
    $ccId=(int)($_POST['cost_center_id']??0);
    if($ccId<=0) throw new RuntimeException('Invalid department selected.');
    $pdo->prepare("DELETE FROM restaurant_cost_centers WHERE id=:id")->execute([':id'=>$ccId]);
    r_flash('success','Costing department deleted.');
    header('Location: pp.php?cost_centers=1#costCenterSetupCard');
    exit;
 }

 if($a==='qz_mark_print_job'){
    header('Content-Type: application/json; charset=utf-8');
    r_ensure_qz_printer_schema($pdo);
    $jobId=(int)($_POST['job_id']??0);
    $status=(string)($_POST['status']??'printed');
    $error=trim((string)($_POST['error_message']??''));
    if($jobId<=0) throw new RuntimeException('Invalid print job.');
    if($status==='printed'){
        $pdo->prepare("UPDATE restaurant_print_jobs SET status='printed', printed_at=NOW(), error_message=NULL WHERE id=:id")->execute([':id'=>$jobId]);
    }else{
        $pdo->prepare("UPDATE restaurant_print_jobs SET status='failed', error_message=:err WHERE id=:id")->execute([':id'=>$jobId,':err'=>$error!==''?$error:'QZ Tray print failed']);
    }
    echo json_encode(['ok'=>true]);
    exit;
 }


 if($a==='update_table_layout'){
    $tableId=(int)($_POST['table_id']??0);
    $x=max(0,(int)($_POST['pos_x']??0));
    $y=max(0,(int)($_POST['pos_y']??0));
    $orientation=(string)($_POST['orientation']??'horizontal');
    if(!in_array($orientation,['horizontal','vertical'],true)) $orientation='horizontal';
    if($tableId<=0) throw new RuntimeException('Invalid table selected.');
    r_ensure_table_layout_columns($pdo);
    $stmt=$pdo->prepare("UPDATE restaurant_tables SET pos_x=:x,pos_y=:y,orientation=:o,updated_at=NOW() WHERE id=:id");
    $stmt->execute([':x'=>$x,':y'=>$y,':o'=>$orientation,':id'=>$tableId]);
    header('Content-Type: application/json');
    echo json_encode(['ok'=>true,'table_id'=>$tableId,'pos_x'=>$x,'pos_y'=>$y,'orientation'=>$orientation]);
    exit;
 }
 if($a==='open_direct_sale'){
    /*
       Non-table / counter sale:
       Creates an open restaurant order without occupying any physical table.
       It can be a direct walk-in sale OR linked to an in-house room guest for room posting.
    */
    r_ensure_order_guest_columns($pdo);

    $customerType=strtolower(trim((string)($_POST['customer_type']??'walkin')));
    if(!in_array($customerType,['walkin','room','group'],true)) $customerType='walkin';

    $covers=max(1,(int)($_POST['covers']??1));
    $linkedOrderId=null;
    $guestName=trim((string)($_POST['guest_name']??''));
    $roomNo=trim((string)($_POST['room_no']??''));
    $groupName=trim((string)($_POST['group_name']??''));
    $propertyId=null;

    if($customerType==='room'){
        $linkedOrderId=(int)($_POST['linked_order_id']??0);
        if($linkedOrderId<=0) throw new RuntimeException('Please select the in-house room guest for this non-table sale.');

        $guestRow=r_resolve_inhouse_guest_booking($pdo,$linkedOrderId);
        $propertyId=!empty($guestRow['property_id'])?(int)$guestRow['property_id']:null;
        if($guestName==='') $guestName=trim((string)($guestRow['guest_name']??''));
        $resolvedRoomNo=trim((string)($guestRow['room_no']??''));
        if($resolvedRoomNo!=='') $roomNo=$resolvedRoomNo;

        if($guestName==='') $guestName='Room guest';
        $groupName=null;
    }elseif($customerType==='group'){
        if($groupName==='') throw new RuntimeException('Group name is required.');
        $linkedOrderId=(int)($_POST['linked_order_id']??0);
        $linkedOrderId=$linkedOrderId>0?$linkedOrderId:null;
        $guestName=$groupName;
        $roomNo=null;
    }else{
        if($guestName==='') $guestName='Non-table Sale';
        $linkedOrderId=null;
        $groupName=null;
        $roomNo=null;
    }

    $notes='Non-table sale opened from planner by '.r_current_user_name().' on '.date('Y-m-d H:i:s');
    if($customerType==='room' && $roomNo){ $notes .= ' | Room '.$roomNo; }

    $pdo->prepare("
        INSERT INTO restaurant_orders
        (property_id,area_id,table_id,order_no,order_type,linked_order_id,guest_name,room_no,group_name,covers,status,opened_by,cashier_name,notes)
        VALUES
        (:property_id,NULL,NULL,:no,:type,:linked_order_id,:guest_name,:room_no,:group_name,:covers,'open',:u,:cashier,:notes)
    ")->execute([
        ':property_id'=>$propertyId,
        ':no'=>r_order_no($pdo),
        ':type'=>$customerType,
        ':linked_order_id'=>$linkedOrderId,
        ':guest_name'=>$guestName,
        ':room_no'=>$roomNo ?: null,
        ':group_name'=>$groupName ?: null,
        ':covers'=>$covers,
        ':u'=>r_current_user_id(),
        ':cashier'=>r_current_user_name(),
        ':notes'=>$notes
    ]);

    $id=(int)$pdo->lastInsertId();
    header('Location: pp.php?restaurant_order_id='.$id.'&direct_sale=1');
    exit;
 }
 if($a==='open_table'){
    $table=(int)($_POST['table_id']??0);
    if($table<=0) throw new RuntimeException('Select table.');

    /*
       A table may be the primary order table OR a secondary merged table.
       Always check both restaurant_orders.table_id and restaurant_order_tables
       before opening a new order. This prevents merged tables from appearing free
       and being opened again.
    */
    $s=$pdo->prepare("
        SELECT ro.id
        FROM restaurant_orders ro
        LEFT JOIN restaurant_order_tables rot ON rot.restaurant_order_id = ro.id
        WHERE ro.status IN (".REST_OPEN_ORDER_STATUSES_SQL.")
          AND (ro.table_id = :t_primary OR rot.table_id = :t_joined)
        ORDER BY ro.id ASC
        LIMIT 1
    ");
    $s->execute([':t_primary'=>$table, ':t_joined'=>$table]);
    $id=(int)$s->fetchColumn();

    if($id<=0){
        $tbl=$pdo->prepare("SELECT * FROM restaurant_tables WHERE id=:id LIMIT 1");
        $tbl->execute([':id'=>$table]);
        $tr=$tbl->fetch(PDO::FETCH_ASSOC);
        if(!$tr) throw new RuntimeException('Table not found.');

        r_ensure_order_guest_columns($pdo);

        $customerType=strtolower(trim((string)($_POST['customer_type']??'')));
        if(!in_array($customerType,['walkin','room','group'],true)) throw new RuntimeException('Please choose whether this is a walk-in, group, or room guest.');

        $covers=max(1,(int)($_POST['covers']??1));
        $linkedOrderId=null;
        $guestName=trim((string)($_POST['guest_name']??''));
        $roomNo=trim((string)($_POST['room_no']??''));
        $groupName=trim((string)($_POST['group_name']??''));

        if($customerType==='room'){
            $linkedOrderId=(int)($_POST['linked_order_id']??0);
            if($linkedOrderId<=0) throw new RuntimeException('Please select the in-house room guest.');
            $guestRow=r_resolve_inhouse_guest_booking($pdo,$linkedOrderId);
            if($guestName==='') $guestName=trim((string)($guestRow['guest_name']??''));
            $resolvedRoomNo=trim((string)($guestRow['room_no']??''));
            if($resolvedRoomNo!=='') $roomNo=$resolvedRoomNo;
            if($guestName==='') $guestName='Room guest';
        }elseif($customerType==='group'){
            if($groupName==='') throw new RuntimeException('Group name is required.');
            $linkedOrderId=(int)($_POST['linked_order_id']??0);
            $linkedOrderId=$linkedOrderId>0?$linkedOrderId:null;
            $guestName=$groupName;
            $roomNo=null;
        }else{
            if($guestName==='') {
                $guestName='Direct Sale';
            }
            $groupName=null;
            $roomNo=null;
        }

        $pdo->prepare("INSERT INTO restaurant_orders(property_id,area_id,table_id,order_no,order_type,linked_order_id,guest_name,room_no,group_name,covers,status,opened_by,cashier_name) VALUES(:p,:a,:t,:no,:type,:linked_order_id,:guest_name,:room_no,:group_name,:c,'open',:u,:cashier_name)")->execute([
            ':p'=>$tr['property_id']??null,
            ':a'=>$tr['area_id']??null,
            ':t'=>$table,
            ':no'=>r_order_no($pdo),
            ':type'=>$customerType,
            ':linked_order_id'=>$linkedOrderId,
            ':guest_name'=>$guestName,
            ':room_no'=>$roomNo,
            ':group_name'=>$groupName ?: null,
            ':c'=>$covers,
            ':u'=>r_current_user_id(),
            ':cashier_name'=>r_current_user_name()
        ]);
        $id=(int)$pdo->lastInsertId();
        $pdo->prepare("UPDATE restaurant_tables SET status='occupied' WHERE id=:id")->execute([':id'=>$table]);
        $pdo->prepare("INSERT IGNORE INTO restaurant_order_tables(restaurant_order_id,table_id,joined_by) VALUES(:rid,:tid,:u)")->execute([':rid'=>$id,':tid'=>$table,':u'=>r_current_user_id()]);
    }
    header('Location: pp.php?restaurant_order_id='.$id); exit;
 }
 if($a==='release_table'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    if($rid<=0) throw new RuntimeException('Invalid restaurant order.');

    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.") LIMIT 1");
    $ro->execute([':id'=>$rid]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Only an open table/check can be released.');

    $cnt=$pdo->prepare("SELECT COUNT(*) FROM restaurant_order_items WHERE restaurant_order_id=:rid AND status='active'");
    $cnt->execute([':rid'=>$rid]);
    $activeItems=(int)$cnt->fetchColumn();
    if($activeItems>0){
        throw new RuntimeException('This table already has order items. Void/cancel the check instead of releasing the table.');
    }

    $pay=$pdo->prepare("SELECT COUNT(*) FROM restaurant_payments WHERE restaurant_order_id=:rid AND status='posted'");
    $pay->execute([':rid'=>$rid]);
    if((int)$pay->fetchColumn()>0){
        throw new RuntimeException('This table has payment activity and cannot be released as an empty table.');
    }

    $pdo->beginTransaction();
    r_release_restaurant_tables($pdo,$rid,!empty($ord['table_id'])?(int)$ord['table_id']:null);
    try{ $pdo->prepare("DELETE FROM restaurant_order_tables WHERE restaurant_order_id=:rid")->execute([':rid'=>$rid]); }catch(Throwable $ignore){}
    $note=trim((string)($ord['notes']??''));
    $releaseNote='Released before ordering by '.r_current_user_name().' on '.date('Y-m-d H:i:s');
    $note=$note!=='' ? ($note."
".$releaseNote) : $releaseNote;
    $pdo->prepare("UPDATE restaurant_orders SET status='void', payment_status='void', notes=:notes, closed_by=:u, closed_at=NOW(), updated_at=NOW() WHERE id=:rid")->execute([
        ':notes'=>$note,
        ':u'=>r_current_user_id(),
        ':rid'=>$rid
    ]);
    $pdo->commit();
    r_flash('success','Table released. No order items were posted.');
    header('Location: pp.php'); exit;
 }
 if($a==='close_zero_value_order'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    if($rid<=0) throw new RuntimeException('Invalid restaurant order.');

    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.") LIMIT 1");
    $ro->execute([':id'=>$rid]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Only an open table/check can be closed this way.');

    r_recalc($pdo,$rid);
    $st=$pdo->prepare("SELECT total_amount FROM restaurant_orders WHERE id=:id LIMIT 1");
    $st->execute([':id'=>$rid]);
    $total=round((float)$st->fetchColumn(),2);
    if($total > 0.0001) throw new RuntimeException('This check has a balance due and cannot be closed without payment.');

    $pay=$pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:rid AND status='posted'");
    $pay->execute([':rid'=>$rid]);
    if((float)$pay->fetchColumn()>0){
        throw new RuntimeException('This check already has posted payments; close it from the Payment screen instead.');
    }

    r_close_if_paid($pdo,$rid);
    r_flash('success','Zero-value check closed. Nothing to post.');
    header('Location: pp.php?closed=1'); exit;
 }
 if($a==='add_item'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $mid=(int)($_POST['menu_item_id']??0);
    $qty=(float)($_POST['qty']??1);
    $seatNo=(int)($_POST['seat_no']??0);
    $seatNo=$seatNo>0?$seatNo:null;
    if($rid<=0||$mid<=0||$qty<=0) throw new RuntimeException('Invalid item/quantity.');
    r_ensure_menu_hierarchy_schema($pdo);
    $m=$pdo->prepare("SELECT * FROM restaurant_menu_items WHERE id=:id AND is_active=1 LIMIT 1");
    $m->execute([':id'=>$mid]);
    $item=$m->fetch(PDO::FETCH_ASSOC);
    if(!$item) throw new RuntimeException('Menu item not found.');

    $modifierIds=$_POST['modifier_ids']??[];
    if(!is_array($modifierIds)) $modifierIds=[$modifierIds];
    $modifierIds=array_values(array_unique(array_filter(array_map('intval',$modifierIds))));
    $modifierTotal=0.0;
    $modifierLabels=[];
    if($modifierIds){
        $in=implode(',',array_fill(0,count($modifierIds),'?'));
        $ms=$pdo->prepare("SELECT id, modifier_name, price_delta FROM restaurant_menu_modifiers WHERE id IN ($in) AND is_active=1 ORDER BY modifier_name");
        $ms->execute($modifierIds);
        foreach($ms->fetchAll(PDO::FETCH_ASSOC) as $mr){
            $modifierTotal += (float)$mr['price_delta'];
            $modifierLabels[] = trim((string)$mr['modifier_name']) . ((float)$mr['price_delta']!=0 ? ' (' . money_fmt($mr['price_delta']) . ')' : '');
        }
    }

    $unitPrice=round((float)$item['price']+$modifierTotal,2);
    // POS menu prices are tax-inclusive. A dish 18.00 + modifier 2.00 must total 20.00, not 23.00 when tax_rate is 15%.
    $line=round($qty*$unitPrice,2);
    $taxRate=(float)$item['tax_rate'];
    $tax=$taxRate>0 ? round($line*($taxRate/(100+$taxRate)),2) : 0.00;
    $itemName=(string)$item['item_name'];
    if($modifierLabels){ $itemName .= ' + '.implode(', ',$modifierLabels); }
    $note=trim((string)($_POST['notes']??''));
    $printerRole = r_resolve_menu_item_printer_role($item);
    $defaultPrinterId = !empty($item['default_printer_id']) ? (int)$item['default_printer_id'] : null;
    r_ensure_print_routing_columns($pdo);
    if(r_column_exists($pdo,'restaurant_order_items','printer_id')){
        $pdo->prepare("INSERT INTO restaurant_order_items(restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,seat_no,unit_price,tax_rate,tax_amount,line_total,printer_role,printer_id,print_status,notes,status) VALUES(:rid,:mid,:inv,:name,:q,:seat_no,:p,:tr,:tax,:line,:role,:printer_id,'pending',:notes,'active')")->execute([
            ':rid'=>$rid,':mid'=>$mid,':inv'=>$item['inventory_item_id']?:null,':name'=>$itemName,':q'=>$qty,':seat_no'=>$seatNo,':p'=>$unitPrice,':tr'=>$item['tax_rate'],':tax'=>$tax,':line'=>$line,':role'=>$printerRole,':printer_id'=>$defaultPrinterId,':notes'=>$note?:null
        ]);
    }else{
        $pdo->prepare("INSERT INTO restaurant_order_items(restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,seat_no,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status) VALUES(:rid,:mid,:inv,:name,:q,:seat_no,:p,:tr,:tax,:line,:role,'pending',:notes,'active')")->execute([
            ':rid'=>$rid,':mid'=>$mid,':inv'=>$item['inventory_item_id']?:null,':name'=>$itemName,':q'=>$qty,':seat_no'=>$seatNo,':p'=>$unitPrice,':tr'=>$item['tax_rate'],':tax'=>$tax,':line'=>$line,':role'=>$printerRole,':notes'=>$note?:null
        ]);
    }
    r_recalc($pdo,$rid);
    header('Location: pp.php?restaurant_order_id='.$rid); exit;
}
 if($a==='void_item'){
    $id=(int)($_POST['item_id']??0);
    $rid=(int)($_POST['restaurant_order_id']??0);
    $reason=trim((string)($_POST['void_reason']??''));
    $pin=trim((string)($_POST['supervisor_pin']??''));
    $note=trim((string)($_POST['void_note']??''));
    r_void_restaurant_item($pdo,$rid,$id,$reason,$pin,$note);
    header('Location: pp.php?restaurant_order_id='.$rid); exit;
 }
 if($a==='add_gratuity'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $amount=(float)($_POST['gratuity_amount']??0);
    $note=trim((string)($_POST['gratuity_note']??''));
    r_add_gratuity_line($pdo,$rid,$amount,$note);
    r_flash('success','Gratuity added. It will not attract the 3% POS/Card surcharge.');
    header('Location: pp.php?restaurant_order_id='.$rid); exit;
 }
 if($a==='supervisor_adjust_item'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $itemIds=array_values(array_unique(array_filter(array_map('intval', $_POST['item_ids'] ?? []))));
    $atype=(string)($_POST['adjustment_type']??'');
    $value=(float)($_POST['adjustment_value']??0);
    $reason=trim((string)($_POST['adjustment_reason']??''));
    $pin=trim((string)($_POST['supervisor_pin']??''));
    $note=trim((string)($_POST['adjustment_note']??''));

    if(!$itemIds) throw new RuntimeException('Select at least one item to adjust.');

    $pdo->beginTransaction();
    foreach($itemIds as $itemId){
        r_adjust_restaurant_item($pdo,$rid,$itemId,$atype,$value,$reason,$pin,$note);
    }
    $pdo->commit();

    r_flash('success', count($itemIds).' item(s) adjusted.');
    header('Location: pp.php?restaurant_order_id='.$rid); exit;
 }
 if($a==='print_bill_to_bar'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $jobIds=r_create_bill_preview_bar_job($pdo,$rid,false);
    $pn=r_dispatch_printnode_jobs($pdo,$jobIds);

    if($pn['failed']>0){
        r_flash('warning','Bill print job was created but PrintNode failed: '.implode(' | ',$pn['messages']));
    }else{
        r_flash('success','Bill printed to Bar printer with totals and customer signature slot.');
    }

    header('Location: pp.php?restaurant_order_id='.$rid.'&bill_printed=1');
    exit;
 }
 if($a==='print_cc_bill_to_bar'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $jobIds=r_create_bill_preview_bar_job($pdo,$rid,false,true);
    $pn=r_dispatch_printnode_jobs($pdo,$jobIds);

    if($pn['failed']>0){
        r_flash('warning','CC bill print job was created but PrintNode failed: '.implode(' | ',$pn['messages']));
    }else{
        r_flash('success','CC bill (with 3% card surcharge, excluding gratuity) printed to Bar printer.');
    }

    header('Location: pp.php?restaurant_order_id='.$rid.'&bill_printed=1');
    exit;
 }
 if($a==='send_to_kitchen'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $jobIds=r_create_station_print_jobs($pdo,$rid,false);
    $pn=r_dispatch_printnode_jobs($pdo,$jobIds);

    if($pn['failed']>0){
        r_flash('warning','Send created print jobs but PrintNode failed on '.$pn['failed'].' ticket(s): '.implode(' | ',$pn['messages']));
    }else{
        r_flash('success','Send printed '.$pn['ok'].' ticket(s) through PrintNode.');
    }

    header('Location: pp.php?restaurant_order_id='.$rid.'&sent=1');
    exit;
 }
 if($a==='reprint_station_docket'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $jobIds=r_create_station_print_jobs($pdo,$rid,true);
    $pn=r_dispatch_printnode_jobs($pdo,$jobIds);

    if($pn['failed']>0){
        r_flash('warning','Reprint created print jobs but PrintNode failed on '.$pn['failed'].' ticket(s): '.implode(' | ',$pn['messages']));
    }else{
        r_flash('success','Reprint printed '.$pn['ok'].' ticket(s) through PrintNode.');
    }

    header('Location: pp.php?restaurant_order_id='.$rid.'&reprinted=1');
    exit;
 }
 if($a==='merge_tables'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $tableIds=array_values(array_unique(array_map('intval', $_POST['table_ids']??[])));
    if($rid<=0) throw new RuntimeException('Invalid restaurant order.');
    if(!$tableIds) throw new RuntimeException('Select at least one table to merge.');
    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.") LIMIT 1");
    $ro->execute([':id'=>$rid]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Only open restaurant orders can be merged.');
    $pdo->beginTransaction();

    /*
       Ensure the primary table is also recorded in restaurant_order_tables.
       Older orders may only have restaurant_orders.table_id, which makes merged
       badges/counts miss the main table.
    */
    $primaryTableId=(int)($ord['table_id']??0);
    if($primaryTableId>0){
        $tableIds[]=$primaryTableId;
    }
    $tableIds=array_values(array_unique(array_filter($tableIds)));

    $capacity=0;
    foreach($tableIds as $tid){
        $ts=$pdo->prepare("
            SELECT
                t.*,
                COALESCE(ro_primary.id, ro_joined.id) AS active_order_id
            FROM restaurant_tables t
            LEFT JOIN restaurant_orders ro_primary
                ON ro_primary.table_id = t.id
               AND ro_primary.status IN (".REST_OPEN_ORDER_STATUSES_SQL.")
            LEFT JOIN restaurant_order_tables rot
                ON rot.table_id = t.id
            LEFT JOIN restaurant_orders ro_joined
                ON ro_joined.id = rot.restaurant_order_id
               AND ro_joined.status IN (".REST_OPEN_ORDER_STATUSES_SQL.")
            WHERE t.id=:id
            ORDER BY COALESCE(ro_primary.id, ro_joined.id) DESC
            LIMIT 1
        ");
        $ts->execute([':id'=>$tid]);
        $tableRow=$ts->fetch(PDO::FETCH_ASSOC);
        if(!$tableRow) throw new RuntimeException('Selected table not found.');
        $activeOrderId=(int)($tableRow['active_order_id']??0);
        if($activeOrderId>0 && $activeOrderId!==$rid){
            throw new RuntimeException('Table '.$tableRow['table_no'].' already has another open order.');
        }
        $capacity += (int)($tableRow['seats']??0);
        $pdo->prepare("INSERT IGNORE INTO restaurant_order_tables(restaurant_order_id,table_id,joined_by) VALUES(:rid,:tid,:u)")->execute([':rid'=>$rid,':tid'=>$tid,':u'=>r_current_user_id()]);
        $pdo->prepare("UPDATE restaurant_tables SET status='occupied' WHERE id=:id")->execute([':id'=>$tid]);
    }
    if($capacity>0){
        $pdo->prepare("UPDATE restaurant_orders SET covers=GREATEST(covers,:covers),updated_at=NOW() WHERE id=:rid")->execute([':covers'=>$capacity,':rid'=>$rid]);
    }
    $pdo->commit();
    r_flash('success','Tables merged. Combined seating capacity is now '.$capacity.'.');
    header('Location: pp.php?restaurant_order_id='.$rid); exit;
 }
 if($a==='close_payment_multi'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    if($rid<=0) throw new RuntimeException('Invalid order.');
    $methods=$_POST['payment_method']??[];
    $amounts=$_POST['amount']??[];
    $refs=$_POST['reference_no']??[];
    $posted=0;
    for($i=0;$i<count($methods);$i++){
        $method=trim((string)($methods[$i]??''));
        $amount=(float)($amounts[$i]??0);
        $ref=trim((string)($refs[$i]??''));
        if($method!=='' && $amount>0){ r_add_restaurant_payment($pdo,$rid,$method,$amount,$ref); $posted++; }
    }
    if($posted<=0) throw new RuntimeException('Enter at least one payment line.');
    r_close_if_paid($pdo,$rid);
    $closedAfterPayment = false;
    try{
        $stClosed=$pdo->prepare("SELECT status FROM restaurant_orders WHERE id=:id LIMIT 1");
        $stClosed->execute([':id'=>$rid]);
        $closedAfterPayment = (strtolower((string)$stClosed->fetchColumn()) === 'closed');
    }catch(Throwable $ignore){}
    try{
        $cleanMethods=[];
        foreach($methods as $m){ $m=trim((string)$m); if($m!=='' && !in_array($m,$cleanMethods,true)) $cleanMethods[]=$m; }
        $cm=implode(' + ', $cleanMethods);
        if($cm!==''){
            $hasCard=false;
            foreach($cleanMethods as $m){ if(r_is_card_surcharge_method($m)) { $hasCard=true; break; } }
            if($hasCard) $cm .= ' + 3% surcharge';
            $pdo->prepare("UPDATE restaurant_orders SET close_method=:cm WHERE id=:id AND status='closed'")->execute([':cm'=>$cm,':id'=>$rid]);
        }
    }catch(Throwable $ignore){}
    if(!empty($_POST['print_bill_after_post'])){
        r_print_bill_to_bar_now($pdo,$rid);
    }
    header('Location: pp.php'.($closedAfterPayment ? '?closed=1' : ('?restaurant_order_id='.$rid))); exit;
 }
 if($a==='save_bill_split'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $splitType=(string)($_POST['split_type']??'equal');
    if($rid<=0) throw new RuntimeException('Invalid order.');
    if(!in_array($splitType,['equal','covers','seat','item','custom','mixed','course'],true)) throw new RuntimeException('Invalid split type.');

    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id LIMIT 1");
    $ro->execute([':id'=>$rid]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Restaurant order not found.');

    $totalDue=round((float)$ord['total_amount'],2);
    $splitPrintBills=[];
    $pdo->beginTransaction();

    if($splitType==='equal' || $splitType==='covers'){
        $count = $splitType==='covers'
            ? max(1,(int)($ord['covers']??1))
            : max(1,(int)($_POST['split_count']??1));

        $per=round($totalDue/$count,2);
        $methods=$_POST['equal_method']??[];
        $refs=$_POST['equal_reference']??[];
        $destinations=$_POST['equal_destination_type']??[];
        $folioIds=$_POST['equal_folio_id']??[];

        for($i=0;$i<$count;$i++){
            $payer=($splitType==='covers'?'Cover ':'Guest ').($i+1);
            $amount=($i===$count-1) ? round($totalDue-($per*($count-1)),2) : $per;
            $dest=strtolower(trim((string)($destinations[$i]??'cash'))) ?: 'cash';
            $method=r_rest_payment_method_from_destination($dest);
            $ref=trim((string)($refs[$i]??''));
            $folioId=(int)($folioIds[$i]??0);
            r_record_hotel_split($pdo,$rid,$splitType,$payer,$dest,$method,$amount,$ref,$folioId,$splitType==='covers'?'Split equally by covers':'Equal split');
            $splitPrintBills[]=['payer'=>$payer,'dest'=>$dest,'method'=>$method,'folio_id'=>$folioId,'amount'=>$amount,'ref'=>$ref,'items'=>[],'line_label'=>$splitType==='covers'?'Cover split':'Equal split'];
        }

    }elseif($splitType==='seat'){
        $seatMethods=$_POST['seat_method']??[];
        $seatRefs=$_POST['seat_reference']??[];
        $seatDestinations=$_POST['seat_destination_type']??[];
        $seatFolios=$_POST['seat_folio_id']??[];

        $active=$pdo->prepare("SELECT id,seat_no,item_name,line_total,tax_amount FROM restaurant_order_items WHERE restaurant_order_id=:rid AND status='active'");
        $active->execute([':rid'=>$rid]);
        $seatTotals=[];
        $seatItems=[];
        foreach($active->fetchAll(PDO::FETCH_ASSOC) as $it){
            $seat=(int)($it['seat_no']??0);
            if($seat<=0) $seat=0;
            $amount=round((float)$it['line_total']+(float)$it['tax_amount'],2);
            if(!isset($seatTotals[$seat])){ $seatTotals[$seat]=0; $seatItems[$seat]=[]; }
            $seatTotals[$seat]+=$amount;
            $seatItems[$seat][]=['id'=>(int)$it['id'],'amount'=>$amount];
        }
        if(!$seatTotals) throw new RuntimeException('There are no active items to split by seat.');

        foreach($seatTotals as $seat=>$amount){
            $key=(string)$seat;
            $payer=$seat>0 ? 'Seat '.$seat : 'Unassigned Seat';
            $dest=strtolower(trim((string)($seatDestinations[$key]??'cash'))) ?: 'cash';
            $method=r_rest_payment_method_from_destination($dest);
            $ref=trim((string)($seatRefs[$key]??''));
            $folioId=(int)($seatFolios[$key]??0);
            $splitId=r_record_hotel_split($pdo,$rid,'seat',$payer,$dest,$method,round($amount,2),$ref,$folioId,'Auto split by seat');

            $printItems=[];
            foreach($seatItems[$seat] as $it){
                $pdo->prepare("INSERT INTO restaurant_bill_split_items(split_id,restaurant_order_item_id,amount) VALUES(:sid,:iid,:amount)")->execute([
                    ':sid'=>$splitId,
                    ':iid'=>$it['id'],
                    ':amount'=>$it['amount']
                ]);
                $printItems[]=['id'=>(int)$it['id'],'amount'=>(float)$it['amount'],'qty'=>1];
            }
            $splitPrintBills[]=['payer'=>$payer,'dest'=>$dest,'method'=>$method,'folio_id'=>$folioId,'amount'=>round($amount,2),'ref'=>$ref,'items'=>$printItems,'line_label'=>'Seat split'];
        }

    }elseif($splitType==='custom' || $splitType==='mixed'){
        $payers=$_POST['custom_payer']??[];
        $amounts=$_POST['custom_amount']??[];
        $percents=$_POST['custom_percent']??[];
        $methods=$_POST['custom_method']??[];
        $refs=$_POST['custom_reference']??[];
        $destinations=$_POST['custom_destination_type']??[];
        $folioIds=$_POST['custom_folio_id']??[];
        $posted=0;

        for($i=0;$i<count($payers);$i++){
            $payer=trim((string)($payers[$i]??'')) ?: ('Guest '.($i+1));
            $amount=(float)($amounts[$i]??0);
            $percent=(float)($percents[$i]??0);
            if($amount<=0 && $percent>0) $amount=round($totalDue*($percent/100),2);
            $dest=strtolower(trim((string)($destinations[$i]??'cash'))) ?: 'cash';
            $method=r_rest_payment_method_from_destination($dest);
            $ref=trim((string)($refs[$i]??''));
            $folioId=(int)($folioIds[$i]??0);

            if($amount>0){
                r_record_hotel_split($pdo,$rid,$splitType,$payer,$dest,$method,$amount,$ref,$folioId,'Mixed hotel settlement');
                $splitPrintBills[]=['payer'=>$payer,'dest'=>$dest,'method'=>$method,'folio_id'=>$folioId,'amount'=>$amount,'ref'=>$ref,'items'=>[],'line_label'=>'Mixed settlement'];
                $posted++;
            }
        }
        if($posted<=0) throw new RuntimeException('Enter at least one custom split amount or percent.');

    }else{
        $itemUnitPayers=$_POST['item_unit_payer']??[];
        $itemUnitAmounts=$_POST['item_unit_amount']??[];
        $itemUnitRefs=$_POST['item_unit_reference']??[];
        $itemUnitDestinations=$_POST['item_unit_destination_type']??[];
        $itemUnitFolios=$_POST['item_unit_folio_id']??[];
        $groups=[];

        if(is_array($itemUnitPayers) && $itemUnitPayers){
            $active=$pdo->prepare("SELECT id, item_name, qty, line_total, tax_amount FROM restaurant_order_items WHERE restaurant_order_id=:rid AND status='active'");
            $active->execute([':rid'=>$rid]);
            $activeItems=[];
            foreach($active->fetchAll(PDO::FETCH_ASSOC) as $it){ $activeItems[(int)$it['id']]=$it; }

            foreach($itemUnitPayers as $iidRaw=>$unitRows){
                $iid=(int)$iidRaw;
                if(!isset($activeItems[$iid]) || !is_array($unitRows)) continue;
                foreach($unitRows as $unitNoRaw=>$payerRaw){
                    $unitNo=(int)$unitNoRaw;
                    $payer=trim((string)$payerRaw);
                    if($payer==='') continue;
                    $dest=strtolower(trim((string)($itemUnitDestinations[$iidRaw][$unitNoRaw]??'cash'))) ?: 'cash';
                    $method=r_rest_payment_method_from_destination($dest);
                    $ref=trim((string)($itemUnitRefs[$iidRaw][$unitNoRaw]??''));
                    $folioId=(int)($itemUnitFolios[$iidRaw][$unitNoRaw]??0);
                    $amount=round((float)($itemUnitAmounts[$iidRaw][$unitNoRaw]??0),2);
                    if($amount<=0){
                        $qty=max(1,(int)round((float)($activeItems[$iid]['qty'] ?? 1)));
                        $amount=round(((float)$activeItems[$iid]['line_total']+(float)$activeItems[$iid]['tax_amount'])/$qty,2);
                    }
                    $key=$payer.'|'.$dest.'|'.$method.'|'.$ref.'|'.$folioId;
                    if(!isset($groups[$key])) $groups[$key]=['payer'=>$payer,'method'=>$method,'dest'=>$dest,'ref'=>$ref,'folio_id'=>$folioId,'amount'=>0,'items'=>[]];
                    $groups[$key]['amount']+=$amount;
                    $groups[$key]['items'][]=['id'=>$iid,'amount'=>$amount,'qty'=>1,'unit_no'=>$unitNo];
                }
            }
        }else{
            /* Backward compatibility for older forms that posted one row per order line. */
            $itemPayers=$_POST['item_payer']??[];
            $itemRefs=$_POST['item_reference']??[];
            $itemDestinations=$_POST['item_destination_type']??[];
            $itemFolios=$_POST['item_folio_id']??[];

            $active=$pdo->prepare("SELECT id, item_name, line_total, tax_amount FROM restaurant_order_items WHERE restaurant_order_id=:rid AND status='active'");
            $active->execute([':rid'=>$rid]);
            foreach($active->fetchAll(PDO::FETCH_ASSOC) as $it){
                $iid=(int)$it['id'];
                $payer=trim((string)($itemPayers[$iid]??''));
                if($payer==='') continue;
                $dest=strtolower(trim((string)($itemDestinations[$iid]??'cash'))) ?: 'cash';
                $method=r_rest_payment_method_from_destination($dest);
                $ref=trim((string)($itemRefs[$iid]??''));
                $folioId=(int)($itemFolios[$iid]??0);
                $key=$payer.'|'.$dest.'|'.$method.'|'.$ref.'|'.$folioId;
                if(!isset($groups[$key])) $groups[$key]=['payer'=>$payer,'method'=>$method,'dest'=>$dest,'ref'=>$ref,'folio_id'=>$folioId,'amount'=>0,'items'=>[]];
                $amount=round((float)$it['line_total']+(float)$it['tax_amount'],2);
                $groups[$key]['amount']+=$amount;
                $groups[$key]['items'][]=['id'=>$iid,'amount'=>$amount,'qty'=>(float)($it['qty'] ?? 1)];
            }
        }

        if(!$groups) throw new RuntimeException('Assign at least one item/unit to a payer.');

        foreach($groups as $g){
            $splitId=r_record_hotel_split($pdo,$rid,'item',$g['payer'],$g['dest'],$g['method'],round($g['amount'],2),$g['ref'],(int)$g['folio_id'],'Item split');
            foreach($g['items'] as $it){
                $pdo->prepare("INSERT INTO restaurant_bill_split_items(split_id,restaurant_order_item_id,amount) VALUES(:sid,:iid,:amount)")->execute([
                    ':sid'=>$splitId,
                    ':iid'=>$it['id'],
                    ':amount'=>$it['amount']
                ]);
                r_mark_restaurant_item_split_allocation(
                    $pdo,
                    $rid,
                    (int)$it['id'],
                    $splitId,
                    (int)($it['unit_no'] ?? 0),
                    (string)$g['dest'],
                    (int)$g['folio_id'],
                    (string)$g['payer'],
                    (float)$it['amount']
                );
            }
            $splitPrintBills[]=['payer'=>$g['payer'],'dest'=>$g['dest'],'method'=>$g['method'],'folio_id'=>(int)$g['folio_id'],'amount'=>round($g['amount'],2),'ref'=>$g['ref'],'items'=>$g['items'],'line_label'=>'Item split'];
        }
    }
    r_close_if_paid($pdo,$rid);
    $closedAfterSplit = false;
    try{
        $stClosed=$pdo->prepare("SELECT status FROM restaurant_orders WHERE id=:id LIMIT 1");
        $stClosed->execute([':id'=>$rid]);
        $closedAfterSplit = (strtolower((string)$stClosed->fetchColumn()) === 'closed');
    }catch(Throwable $ignore){}
    $pdo->commit();
    if($splitPrintBills){
        /* Always create/dispatch the separate split bills after posting.
           Previously this depended only on the checkbox being submitted, so a modal/browser quirk could post correctly but not print. */
        r_print_split_group_bills_to_bar_now($pdo,$rid,$splitPrintBills);
    }
    r_flash('success','Split / settlement posted successfully. Items are marked by room/folio on the table and separate room/folio bills were sent to the receipt printer.');
    header('Location: pp.php'.($closedAfterSplit ? '?closed=1' : ('?restaurant_order_id='.$rid))); exit;
 }
  if($a==='transfer_check'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $transferType=(string)($_POST['transfer_type']??'full_check');
    $targetTableId=(int)($_POST['target_table_id']??0);
    $notes=trim((string)($_POST['transfer_notes']??''));
    $itemIds=array_values(array_unique(array_map('intval', $_POST['transfer_item_ids']??[])));
    $seatNos=array_values(array_unique(array_map('intval', $_POST['transfer_seats']??[])));

    if($rid<=0) throw new RuntimeException('Invalid restaurant order.');
    if($targetTableId<=0 && in_array($transferType,['full_check','items','seats','table'],true)) throw new RuntimeException('Select the target table.');

    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.") LIMIT 1");
    $ro->execute([':id'=>$rid]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Only open restaurant checks can be transferred.');

    $pdo->beginTransaction();

    if($transferType==='full_check' || $transferType==='table'){
        $oldTable=(int)($ord['table_id']??0);
        $pdo->prepare("UPDATE restaurant_orders SET table_id=:new_table,updated_at=NOW() WHERE id=:rid")->execute([':new_table'=>$targetTableId,':rid'=>$rid]);
        $pdo->prepare("DELETE FROM restaurant_order_tables WHERE restaurant_order_id=:rid")->execute([':rid'=>$rid]);
        $pdo->prepare("INSERT IGNORE INTO restaurant_order_tables(restaurant_order_id,table_id,joined_by) VALUES(:rid,:tid,:u)")->execute([':rid'=>$rid,':tid'=>$targetTableId,':u'=>r_current_user_id()]);
        if($oldTable>0) $pdo->prepare("UPDATE restaurant_tables SET status='available' WHERE id=:id")->execute([':id'=>$oldTable]);
        $pdo->prepare("UPDATE restaurant_tables SET status='occupied' WHERE id=:id")->execute([':id'=>$targetTableId]);

        $pdo->prepare("INSERT INTO restaurant_check_transfers(from_restaurant_order_id,to_restaurant_order_id,from_table_id,to_table_id,transfer_type,notes,created_by) VALUES(:from_id,:to_id,:from_table,:to_table,'full_check',:notes,:u)")->execute([
            ':from_id'=>$rid,':to_id'=>$rid,':from_table'=>$oldTable ?: null,':to_table'=>$targetTableId,':notes'=>$notes ?: 'Full check/table transfer',':u'=>r_current_user_id()
        ]);

    }else{
        if($transferType==='seats'){
            if(!$seatNos) throw new RuntimeException('Select at least one seat to transfer.');
            $in=implode(',',array_fill(0,count($seatNos),'?'));
            $q=$pdo->prepare("SELECT id FROM restaurant_order_items WHERE restaurant_order_id=? AND status='active' AND COALESCE(seat_no,0) IN ($in)");
            $q->execute(array_merge([$rid],$seatNos));
            $itemIds=array_values(array_unique(array_map('intval',$q->fetchAll(PDO::FETCH_COLUMN)?:[])));
        }
        if(!$itemIds) throw new RuntimeException('Select at least one item to transfer.');

        $toRid=r_get_or_create_transfer_order($pdo,$ord,$targetTableId);

        $pdo->prepare("INSERT INTO restaurant_check_transfers(from_restaurant_order_id,to_restaurant_order_id,from_table_id,to_table_id,transfer_type,notes,created_by) VALUES(:from_id,:to_id,:from_table,:to_table,:type,:notes,:u)")->execute([
            ':from_id'=>$rid,':to_id'=>$toRid,':from_table'=>(int)($ord['table_id']??0) ?: null,':to_table'=>$targetTableId,':type'=>$transferType==='seats'?'seats':'items',':notes'=>$notes ?: 'Item/seat transfer',':u'=>r_current_user_id()
        ]);
        $transferId=(int)$pdo->lastInsertId();

        $in=implode(',',array_fill(0,count($itemIds),'?'));
        $itemStmt=$pdo->prepare("SELECT id,qty FROM restaurant_order_items WHERE restaurant_order_id=? AND status='active' AND id IN ($in)");
        $itemStmt->execute(array_merge([$rid],$itemIds));
        foreach($itemStmt->fetchAll(PDO::FETCH_ASSOC) as $it){
            $pdo->prepare("INSERT INTO restaurant_check_transfer_items(transfer_id,restaurant_order_item_id,qty) VALUES(:tid,:iid,:qty)")->execute([
                ':tid'=>$transferId,':iid'=>(int)$it['id'],':qty'=>(float)$it['qty']
            ]);
        }

        $params=array_merge([$toRid],$itemIds,[$rid]);
        $pdo->prepare("UPDATE restaurant_order_items SET restaurant_order_id=? WHERE id IN ($in) AND restaurant_order_id=?")->execute($params);
        r_recalc($pdo,$rid);
        r_recalc($pdo,$toRid);
    }

    $pdo->commit();
    r_flash('success','Check transfer completed.');
    header('Location: pp.php?restaurant_order_id='.$rid); exit;
 }
 if($a==='close_room_charge'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $orderId=(int)($_POST['linked_order_id']??0);
    $postTo=trim((string)($_POST['post_to']??'master'));
    $selectedPostItemIds=array_values(array_unique(array_filter(array_map('intval', $_POST['post_item_ids'] ?? []))));
    $adminPin=trim((string)($_POST['admin_pin']??''));

    if($rid<=0) throw new RuntimeException('Invalid restaurant order.');
    r_recalc($pdo,$rid);

    $ro=$pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id LIMIT 1");
    $ro->execute([':id'=>$rid]);
    $ord=$ro->fetch(PDO::FETCH_ASSOC);
    if(!$ord) throw new RuntimeException('Restaurant order not found.');
    /* Walk-in / non-table sales can now be posted to a lodge account too - e.g. a
       visitor's bill that an in-house guest later chooses to cover on their room.
       A booking must still be explicitly selected below before this can proceed. */

    if(!empty($ord['linked_order_id']) && (int)$ord['linked_order_id']>0){
        $orderId=(int)$ord['linked_order_id'];
    }
    if($orderId<=0) throw new RuntimeException('Select lodge booking and account type.');

    $isMasterTarget = ($postTo === '' || $postTo === 'master');

    /*
       Guardrail: posting a restaurant charge to the guest's MASTER account is the
       exception, not the norm. Room/Guest posting stays available to waiters as
       before, but Master requires a supervisor/admin PIN so it can't be picked
       casually by a waiter closing a table.
    */
    if($isMasterTarget){
        if($adminPin === '') throw new RuntimeException('Posting to the Master Account requires an Admin PIN. Enter the Admin PIN, or choose the guest\'s Room/Guest Account instead.');
        if(!r_supervisor_pin_valid($pdo,$adminPin)) throw new RuntimeException('Invalid Admin PIN. Posting to the Master Account was not authorised.');
    }

    $masterFolioId = r_find_active_folio($pdo,$orderId,'master',0,1);
    $fid = r_get_payment_style_target_folio($pdo,$orderId,$postTo,$masterFolioId);

    $stmt=$pdo->prepare("SELECT property_id FROM orders WHERE id=:id LIMIT 1");
    $stmt->execute([':id'=>$orderId]);
    $prop=(int)$stmt->fetchColumn();

    $targetLabel = 'Master Account';
    if($postTo !== '' && $postTo !== 'master'){
        $payload=json_decode($postTo,true);
        if(is_array($payload)){
            $tt=strtolower(trim((string)($payload['target_type'] ?? 'room')));
            $targetLabel = $tt==='guest' ? 'Guest Account' : 'Room Account';
        }
    }

    $postingSelectedItems = !empty($selectedPostItemIds);
    $amount = round((float)$ord['total_amount'],2);
    $postedItemNames=[];

    if($postingSelectedItems){
        $in=implode(',',array_fill(0,count($selectedPostItemIds),'?'));
        $params=array_merge([$rid],$selectedPostItemIds);
        $stItems=$pdo->prepare("SELECT id,item_name,qty,line_total FROM restaurant_order_items WHERE restaurant_order_id=? AND id IN ($in) AND status='active' ORDER BY id ASC");
        $stItems->execute($params);
        $selectedRows=$stItems->fetchAll(PDO::FETCH_ASSOC) ?: [];
        if(!$selectedRows) throw new RuntimeException('Select at least one active item to post, or untick all items to post the full bill.');
        $amount=0.00;
        foreach($selectedRows as $sr){
            $amount += round((float)($sr['line_total'] ?? 0),2);
            $postedItemNames[] = rtrim(rtrim(number_format((float)($sr['qty'] ?? 1),2,'.',''),'0'),'.').' x '.trim((string)($sr['item_name'] ?? 'Item'));
        }
        $amount=round($amount,2);
    }

    if($amount<0) throw new RuntimeException('There is no amount to post.');
if ($amount == 0) {
    $pdo->beginTransaction();

    r_close_if_paid($pdo, $rid);

    $pdo->commit();

    r_flash('success', 'Zero bill closed successfully.');
    header('Location: pp.php?closed=1');
    exit;
}
    $desc='Restaurant charge - '.$ord['order_no'].' posted to '.$targetLabel;
    if($postingSelectedItems){
        $desc='Restaurant selected items - '.$ord['order_no'].' posted to '.$targetLabel.' | '.implode('; ',array_slice($postedItemNames,0,12));
        if(count($postedItemNames)>12) $desc.='; +'.(count($postedItemNames)-12).' more';
    }
    if($isMasterTarget){
        $desc .= ' | Admin-authorised master posting';
    }

    $pdo->beginTransaction();
    try{
        $pdo->prepare("INSERT INTO folio_transactions(property_id,folio_id,order_id,tx_date,tx_type,description,debit,credit,source_table,source_id) VALUES(:p,:f,:o,NOW(),'restaurant',:d,:amt,0,'restaurant_orders',:rid)")->execute([
            ':p'=>$prop,':f'=>$fid,':o'=>$orderId,':d'=>$desc,':amt'=>$amount,':rid'=>$rid
        ]);

        if(r_table_exists($pdo,'restaurant_folio_posting_queue')){
            $pdo->prepare("INSERT INTO restaurant_folio_posting_queue(restaurant_order_id,pms_order_id,folio_id,window_no,amount,description,status,posted_at) VALUES(:rid,:oid,:fid,:wn,:amt,:d,'posted',NOW())")->execute([
                ':rid'=>$rid,':oid'=>$orderId,':fid'=>$fid,':wn'=>1,':amt'=>$amount,':d'=>$desc
            ]);
        }
    }catch(Throwable $postError){
        if(r_table_exists($pdo,'restaurant_folio_posting_queue')){
            try{
                $pdo->prepare("INSERT INTO restaurant_folio_posting_queue(restaurant_order_id,pms_order_id,folio_id,window_no,amount,description,status,error_message) VALUES(:rid,:oid,:fid,:wn,:amt,:d,'failed',:err)")->execute([
                    ':rid'=>$rid,':oid'=>$orderId,':fid'=>$fid ?: null,':wn'=>1,':amt'=>$amount,':d'=>$desc,':err'=>$postError->getMessage()
                ]);
            }catch(Throwable $ignore){}
        }
        throw $postError;
    }

    if($postingSelectedItems){
        $noteAdd='Selected room post to '.$targetLabel.' on '.date('Y-m-d H:i:s').' | Amount '.money_fmt($amount).' | '.implode('; ',array_slice($postedItemNames,0,12));
        $pdo->prepare("UPDATE restaurant_orders SET linked_order_id=:oid, linked_folio_id=:fid, payment_status=IF(payment_status='paid','paid','part_room_charged'), close_method=:cm, notes=TRIM(CONCAT(COALESCE(notes,''), :note_add)), updated_at=NOW() WHERE id=:rid")->execute([
            ':oid'=>$orderId,':fid'=>$fid,':cm'=>'Part posted to '.$targetLabel,':note_add'=>"\n".$noteAdd,':rid'=>$rid
        ]);
    }else{
        $pdo->prepare("UPDATE restaurant_orders SET linked_order_id=:oid, linked_folio_id=:fid, status='closed', payment_status='room_charged', close_method=:cm, closed_by=:u, closed_at=NOW() WHERE id=:rid")->execute([
            ':oid'=>$orderId,':fid'=>$fid,':cm'=>'Posted to '.$targetLabel,':u'=>r_current_user_id(),':rid'=>$rid
        ]);
        r_recipe_stock_flash(r_post_recipe_stock_sale_movements($pdo,$rid));
        r_release_restaurant_tables($pdo,$rid,!empty($ord['table_id'])?(int)$ord['table_id']:null);
    }

    r_recalc_folio($pdo,$fid);
    r_sync_order_status($pdo,$orderId);
    $pdo->commit();

    r_flash('success',($postingSelectedItems ? 'Selected items posted to ' : 'Restaurant order posted to ').$targetLabel.($postingSelectedItems ? '.' : ' and table closed.'));
    if(!empty($_POST['print_bill_after_post'])){
        if($postingSelectedItems) r_print_selected_bill_to_bar_now($pdo,$rid,$selectedPostItemIds,'POSTED ROOM BILL');
        else r_print_bill_to_bar_now($pdo,$rid);
    }
    header('Location: pp.php'.($postingSelectedItems ? ('?restaurant_order_id='.$rid) : '?closed=1')); exit;
 }
 if($a==='close_cost_center'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $costCenterId=(int)($_POST['cost_center_id']??0);
    $selectedPostItemIds=array_values(array_unique(array_filter(array_map('intval', $_POST['post_item_ids'] ?? []))));
    $reason=trim((string)($_POST['cost_center_reason']??''));
    $pin=trim((string)($_POST['supervisor_pin']??''));
    $note=trim((string)($_POST['cost_center_note']??''));
    $discountType=trim((string)($_POST['cost_center_discount_type']??'none'));
    $discountValue=(float)($_POST['cost_center_discount_value']??0);

    if($rid<=0) throw new RuntimeException('Invalid restaurant order.');
    if($costCenterId<=0) throw new RuntimeException('Select a costing department.');

    r_ensure_cost_center_schema($pdo);
    $stCC=$pdo->prepare("SELECT name FROM restaurant_cost_centers WHERE id=:id AND is_active=1 LIMIT 1");
    $stCC->execute([':id'=>$costCenterId]);
    $costCenterName=(string)($stCC->fetchColumn() ?: '');
    if($costCenterName==='') throw new RuntimeException('Selected costing department was not found or is inactive.');

    $postingSelectedItems = !empty($selectedPostItemIds);

    $pdo->beginTransaction();
    r_post_items_to_cost_center($pdo,$rid,$costCenterId,$costCenterName,$selectedPostItemIds,$reason,$pin,$note,$discountType,$discountValue);
    $pdo->commit();

    r_flash('success','Posted to '.$costCenterName.' and table closed.');
    if(!empty($_POST['print_bill_after_post'])){
        if($postingSelectedItems) r_print_selected_bill_to_bar_now($pdo,$rid,$selectedPostItemIds,'POSTED '.strtoupper($costCenterName).' BILL');
        else r_print_bill_to_bar_now($pdo,$rid);
    }
    header('Location: pp.php?closed=1'); exit;
 }
 if($a==='close_payment'){
    $rid=(int)($_POST['restaurant_order_id']??0);
    $method=trim((string)($_POST['payment_method']??''));
    $amount=(float)($_POST['amount']??0);
    $ref=trim((string)($_POST['reference_no']??''));
    if($rid<=0||$method===''||$amount<=0) throw new RuntimeException('Invalid payment.');
    r_add_restaurant_payment($pdo,$rid,$method,$amount,$ref);
    r_close_if_paid($pdo,$rid);
    $closedAfterPayment=false;
    try{
        $stClosed=$pdo->prepare("SELECT status FROM restaurant_orders WHERE id=:id LIMIT 1");
        $stClosed->execute([':id'=>$rid]);
        $closedAfterPayment=(strtolower((string)$stClosed->fetchColumn())==='closed');
    }catch(Throwable $ignore){}
    try{
        $pdo->prepare("UPDATE restaurant_orders SET close_method=:cm WHERE id=:id AND status='closed'")->execute([
            ':cm'=>r_is_card_surcharge_method($method) ? ($method.' + 3% surcharge') : $method,
            ':id'=>$rid
        ]);
    }catch(Throwable $ignore){}
    if(!empty($_POST['print_bill_after_post'])){
        r_print_bill_to_bar_now($pdo,$rid);
    }
    header('Location: pp.php'.($closedAfterPayment ? '?closed=1' : ('?restaurant_order_id='.$rid))); exit;
 }
 }catch(Throwable $e){ if($pdo->inTransaction())$pdo->rollBack(); r_flash('danger',$e->getMessage()); header('Location: pp.php'.(!empty($_POST['restaurant_order_id'])?'?restaurant_order_id='.(int)$_POST['restaurant_order_id']:'')); exit; }
}
$selectedRid=(int)($_GET['restaurant_order_id']??0);$flashes=r_get_flash();
$restaurantPrinters=[];
$qzPrintJobs=[];
$qzTestJob=null;
try{
    r_ensure_qz_printer_schema($pdo);
    $restaurantPrinters=r_load_restaurant_printers($pdo);
    $qzIdsRaw=trim((string)($_GET['qz_print_jobs']??''));
    if($qzIdsRaw!==''){
        $qzIds=array_filter(array_map('intval', preg_split('/[^0-9]+/',$qzIdsRaw)));
        try{
            $qzPrintJobs=r_qz_print_jobs($pdo,$qzIds);
            if(!$qzPrintJobs && $qzIds){
                r_flash('warning','Print jobs were created, but no QZ jobs could be loaded for printing. Check restaurant_printers setup and job IDs: '.implode(',', $qzIds));
            }
        }catch(Throwable $e){
            r_flash('danger','Could not load QZ print jobs for Send/Reprint: '.$e->getMessage());
            $qzPrintJobs=[];
        }
    }

    if(isset($_GET['test_printer']) && (int)$_GET['test_printer']>0){
        $testPrinterId=(int)$_GET['test_printer'];
        $st=$pdo->prepare("SELECT * FROM restaurant_printers WHERE id=:id LIMIT 1");
        $st->execute([':id'=>$testPrinterId]);
        $tp=$st->fetch(PDO::FETCH_ASSOC);
        if($tp){
            $payload =
                "Pioneers Lodge\n".
                "------------------------------\n".
                "THERMAL PRINTER TEST\n\n".
                "Printer : ".($tp['printer_name'] ?? '')."\n".
                "Role    : ".($tp['printer_role'] ?? '')."\n".
                "Type    : ".($tp['printer_type'] ?? 'qz_windows')."\n".
                "Date    : ".date('Y-m-d H:i:s')."\n\n".
                "QZ Tray communication OK\n".
                "------------------------------\n".
                "Thank you\n";
            $qzTestJob=[
                'id'=>0,
                'restaurant_order_id'=>0,
                'printer_role'=>(string)($tp['printer_role'] ?? ''),
                'job_type'=>'test',
                'payload'=>$payload,
                'printnode_printer_id'=>(string)($tp['printnode_printer_id'] ?? ''),
                'printer_name'=>(string)($tp['printer_name'] ?? ''),
                'printer_ip'=>(string)($tp['printer_ip'] ?? ''),
                'printer_port'=>(int)($tp['printer_port'] ?? 9100),
                'printer_type'=>(string)($tp['printer_type'] ?? 'qz_windows'),
                'is_test'=>1
            ];
        }else{
            r_flash('danger','Printer not found for test print.');
        }
    }
}catch(Throwable $ignore){}
$lineOrderMode=((string)($_GET['line_order']??'default')==='menu')?'menu':'default';
$orderTables=[];$orderTableSummary='';$orderMergedCapacity=0;$splitRows=[];$paymentRows=[];
$areas=$pdo->query("SELECT * FROM restaurant_areas WHERE is_active=1 ORDER BY sort_order,area_name")->fetchAll(PDO::FETCH_ASSOC);
/*
   Floor plan active order lookup.
   A table is busy if it is either:
   1) restaurant_orders.table_id, or
   2) a joined/merged table in restaurant_order_tables.
   The derived table returns one active order per table, avoiding GROUP BY
   ambiguity and making secondary merged tables show occupied.
*/
$tables=$pdo->query("
    SELECT
        t.*,
        a.area_name,
        ro.id AS open_order_id,
        ro.order_no,
        ro.total_amount,
        ro.payment_status,
        ro.guest_name AS open_guest_name,
        ro.room_no AS open_room_no,
        ro.order_type AS open_order_type,
        ro.cashier_name AS open_cashier_name
    FROM restaurant_tables t
    LEFT JOIN restaurant_areas a ON a.id=t.area_id
    LEFT JOIN (
        SELECT table_id, MIN(restaurant_order_id) AS restaurant_order_id
        FROM (
            SELECT table_id, id AS restaurant_order_id
            FROM restaurant_orders
            WHERE table_id IS NOT NULL
              AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.")
            UNION ALL
            SELECT rot.table_id, rot.restaurant_order_id
            FROM restaurant_order_tables rot
            INNER JOIN restaurant_orders rox
                ON rox.id = rot.restaurant_order_id
               AND rox.status IN (".REST_OPEN_ORDER_STATUSES_SQL.")
        ) active_table_orders
        WHERE table_id IS NOT NULL
        GROUP BY table_id
    ) ato ON ato.table_id = t.id
    LEFT JOIN restaurant_orders ro ON ro.id = ato.restaurant_order_id
    WHERE t.is_active=1
    ORDER BY a.sort_order,t.table_no
")->fetchAll(PDO::FETCH_ASSOC);
$openOrderTableMap=[];
foreach($tables as $idx=>$row){
    $oid=(int)($row['open_order_id']??0);
    if($oid>0){
        if(!isset($openOrderTableMap[$oid])) $openOrderTableMap[$oid]=[];
        $openOrderTableMap[$oid][]=(string)($row['table_no']??'');
    }
}
foreach($tables as $idx=>$row){
    $oid=(int)($row['open_order_id']??0);
    $group=$oid>0?($openOrderTableMap[$oid]??[]):[];
    $tables[$idx]['open_merged_count']=count($group);
    $tables[$idx]['open_merged_tables']=implode(', ',array_filter($group));
}

/* Non-table sales (walk-in / room / group counter sales) have no table_id, so they never
   appear on the floor plan. List the currently open ones so staff can find and keep
   adding items to a non-table sale before it is closed. Non-admin users only see the
   ones they personally opened; admins/supervisors see all of them. */
$isAdminUser = r_current_user_is_admin($pdo);
$nonTableSalesSql = "
    SELECT id,order_no,order_type,guest_name,room_no,group_name,covers,total_amount,payment_status,opened_at,cashier_name,opened_by
    FROM restaurant_orders
    WHERE table_id IS NULL
      AND status IN (".REST_OPEN_ORDER_STATUSES_SQL.")
";
$nonTableSalesParams = [];
if(!$isAdminUser){
    $nonTableSalesSql .= " AND opened_by = :uid";
    $nonTableSalesParams[':uid'] = r_current_user_id();
}
$nonTableSalesSql .= " ORDER BY opened_at DESC";
$stNonTableSales = $pdo->prepare($nonTableSalesSql);
$stNonTableSales->execute($nonTableSalesParams);
$openNonTableSales = $stNonTableSales->fetchAll(PDO::FETCH_ASSOC);

r_ensure_menu_hierarchy_schema($pdo);
$cats=$pdo->query("SELECT id, property_id, parent_id, category_name, COALESCE(button_color,'') AS button_color, COALESCE(depth,0) AS depth, printer_role, sort_order, is_active FROM restaurant_menu_categories WHERE is_active=1 ORDER BY COALESCE(parent_id,0), sort_order, category_name")->fetchAll(PDO::FETCH_ASSOC);
$menu=$pdo->query("SELECT i.*, c.category_name, c.parent_id AS category_parent_id, COALESCE(c.depth,0) AS category_depth FROM restaurant_menu_items i LEFT JOIN restaurant_menu_categories c ON c.id=i.category_id WHERE i.is_active=1 ORDER BY COALESCE(c.parent_id,0), c.sort_order, i.sort_order, i.item_name")->fetchAll(PDO::FETCH_ASSOC);
$menuModifierRows=[];
try{
    $menuModifierRows=$pdo->query("SELECT im.menu_item_id, m.id, m.modifier_name, m.price_delta FROM restaurant_menu_item_modifiers im INNER JOIN restaurant_menu_modifiers m ON m.id=im.modifier_id WHERE m.is_active=1 ORDER BY im.sort_order, m.modifier_name")->fetchAll(PDO::FETCH_ASSOC);
}catch(Throwable $e){ $menuModifierRows=[]; }
$menuModifiersByItem=[];
foreach($menuModifierRows as $mr){ $menuModifiersByItem[(int)$mr['menu_item_id']][]=$mr; }
$allModifierRows=[];
try{ $allModifierRows=$pdo->query("SELECT id, modifier_name, price_delta FROM restaurant_menu_modifiers WHERE is_active=1 ORDER BY modifier_name")->fetchAll(PDO::FETCH_ASSOC); }catch(Throwable $e){ $allModifierRows=[]; }

$menuModifierGroupsByItem=[];
try{
    if(r_table_exists($pdo,'restaurant_menu_item_modifier_groups') && r_table_exists($pdo,'restaurant_modifier_groups')){
        /*
           Robust reusable modifier loading for POS:
           1) item -> modifier group links come from restaurant_menu_item_modifier_groups.
           2) group -> modifier links preferably come from restaurant_modifier_group_items.
           3) fallback still supports older restaurant_menu_modifiers.modifier_group_id.
           4) also supports a few earlier link-table names if they exist.

           This avoids the previous LEFT JOIN/OR query that could return groups
           with zero options, so the modifier panel appeared empty when selecting a dish.
        */
        $hasMin       = r_column_exists($pdo,'restaurant_modifier_groups','min_select');
        $hasMax       = r_column_exists($pdo,'restaurant_modifier_groups','max_select');
        $hasGroupType = r_column_exists($pdo,'restaurant_modifier_groups','group_type');
        $hasGroupSort = r_column_exists($pdo,'restaurant_modifier_groups','sort_order');
        $hasParentMod = r_column_exists($pdo,'restaurant_menu_modifiers','parent_modifier_id');
        $hasPrefix    = r_column_exists($pdo,'restaurant_menu_modifiers','prefix_allowed');
        $hasModSort   = r_column_exists($pdo,'restaurant_menu_modifiers','sort_order');
        $hasOldGroup  = r_column_exists($pdo,'restaurant_menu_modifiers','modifier_group_id');

        $minSql  = $hasMin ? "COALESCE(g.min_select,0)" : "0";
        $maxSql  = $hasMax ? "COALESCE(g.max_select,1)" : "1";
        $typeSql = $hasGroupType ? "COALESCE(g.group_type,'optional')" : "'optional'";
        $sortSql = $hasGroupSort ? "COALESCE(g.sort_order,1)" : "1";

        $itemGroupSql="
            SELECT
                img.menu_item_id,
                g.id AS group_id,
                g.group_name,
                {$typeSql} AS group_type,
                {$minSql} AS min_select,
                {$maxSql} AS max_select,
                {$sortSql} AS group_sort,
                COALESCE(img.sort_order, {$sortSql}) AS item_group_sort
            FROM restaurant_menu_item_modifier_groups img
            INNER JOIN restaurant_modifier_groups g
                ON g.id = img.modifier_group_id
               AND COALESCE(g.is_active,1)=1
            ORDER BY img.menu_item_id, item_group_sort, g.group_name
        ";
        $itemGroupRows=$pdo->query($itemGroupSql)->fetchAll(PDO::FETCH_ASSOC);

        $groupOptionsByGroup=[];
        $optionSelect = "m.id AS modifier_id, m.modifier_name, COALESCE(m.price_delta,0) AS price_delta";
        $optionSelect .= $hasParentMod ? ", COALESCE(m.parent_modifier_id,0) AS parent_modifier_id" : ", 0 AS parent_modifier_id";
        $optionSelect .= $hasPrefix ? ", COALESCE(m.prefix_allowed,1) AS prefix_allowed" : ", 1 AS prefix_allowed";

        $linkTables=[];
        foreach(['restaurant_modifier_group_options','restaurant_modifier_group_items','restaurant_modifier_group_modifiers','restaurant_menu_modifier_group_items'] as $lt){
            if(r_table_exists($pdo,$lt) && r_column_exists($pdo,$lt,'modifier_group_id') && r_column_exists($pdo,$lt,'modifier_id')){
                $linkTables[]=$lt;
            }
        }

        foreach($linkTables as $lt){
            $sortExpr = r_column_exists($pdo,$lt,'sort_order') ? "COALESCE(gi.sort_order,".($hasModSort ? "m.sort_order" : "1").",1)" : ($hasModSort ? "COALESCE(m.sort_order,1)" : "1");
            $sql="
                SELECT
                    gi.modifier_group_id AS group_id,
                    {$optionSelect},
                    {$sortExpr} AS modifier_sort
                FROM {$lt} gi
                INNER JOIN restaurant_menu_modifiers m
                    ON m.id=gi.modifier_id
                   AND COALESCE(m.is_active,1)=1
                ORDER BY gi.modifier_group_id, modifier_sort, m.modifier_name
            ";
            foreach($pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) as $r){
                $gid=(int)$r['group_id'];
                if(!isset($groupOptionsByGroup[$gid])) $groupOptionsByGroup[$gid]=[];
                $mid=(int)$r['modifier_id'];
                $groupOptionsByGroup[$gid][$mid]=[
                    'id'=>$mid,
                    'modifier_name'=>(string)$r['modifier_name'],
                    'price_delta'=>(float)$r['price_delta'],
                    'parent_modifier_id'=>(int)$r['parent_modifier_id'],
                    'prefix_allowed'=>(int)$r['prefix_allowed']
                ];
            }
        }

        if($hasOldGroup){
            $sortExpr = $hasModSort ? "COALESCE(m.sort_order,1)" : "1";
            $parentExpr = $hasParentMod ? "COALESCE(m.parent_modifier_id,0)" : "0";
            $prefixExpr = $hasPrefix ? "COALESCE(m.prefix_allowed,1)" : "1";
            $sql="
                SELECT
                    m.modifier_group_id AS group_id,
                    m.id AS modifier_id,
                    m.modifier_name,
                    COALESCE(m.price_delta,0) AS price_delta,
                    {$parentExpr} AS parent_modifier_id,
                    {$prefixExpr} AS prefix_allowed,
                    {$sortExpr} AS modifier_sort
                FROM restaurant_menu_modifiers m
                WHERE COALESCE(m.is_active,1)=1
                  AND m.modifier_group_id IS NOT NULL
                  AND m.modifier_group_id > 0
                ORDER BY m.modifier_group_id, modifier_sort, m.modifier_name
            ";
            foreach($pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) as $r){
                $gid=(int)$r['group_id'];
                if(!isset($groupOptionsByGroup[$gid])) $groupOptionsByGroup[$gid]=[];
                $mid=(int)$r['modifier_id'];
                if(!isset($groupOptionsByGroup[$gid][$mid])){
                    $groupOptionsByGroup[$gid][$mid]=[
                        'id'=>$mid,
                        'modifier_name'=>(string)$r['modifier_name'],
                        'price_delta'=>(float)$r['price_delta'],
                        'parent_modifier_id'=>(int)$r['parent_modifier_id'],
                        'prefix_allowed'=>(int)$r['prefix_allowed']
                    ];
                }
            }
        }

        foreach($itemGroupRows as $r){
            $itemId=(int)$r['menu_item_id'];
            $gid=(int)$r['group_id'];
            if(!isset($menuModifierGroupsByItem[$itemId])) $menuModifierGroupsByItem[$itemId]=[];

            $gt=strtolower((string)($r['group_type'] ?: 'optional'));
            if($gt==='allowed') $gt='optional';
            if(!in_array($gt,['required','optional','default'],true)) $gt='optional';

            $menuModifierGroupsByItem[$itemId][]=[
                'id'=>$gid,
                'group_name'=>(string)$r['group_name'],
                'group_type'=>$gt,
                'min_select'=>(int)$r['min_select'],
                'max_select'=>(int)$r['max_select'],
                'item_group_sort'=>(int)$r['item_group_sort'],
                'sort_order'=>(int)$r['item_group_sort'],
                'options'=>array_values($groupOptionsByGroup[$gid] ?? [])
            ];
        }
    }
}catch(Throwable $e){
    $menuModifierGroupsByItem=[];
    $errors[]='Could not load menu modifier groups: '.$e->getMessage();
}
$menuJson=[];
foreach($menu as $mi){
    $id=(int)$mi['id'];
    $menuJson[]=[
        'id'=>$id,
        'category_id'=>(int)($mi['category_id']??0),
        'name'=>(string)($mi['item_name']??''),
        'code'=>(string)($mi['item_code']??''),
        'description'=>(string)($mi['description']??''),
        'price'=>(float)($mi['price']??0),
        'tax_rate'=>(float)($mi['tax_rate']??0),
        'printer_role'=>(string)($mi['printer_role']??''),
        'tags'=>(string)($mi['tags']??''),
        'button_color'=>(string)($mi['button_color']??''),
        'modifiers'=>$menuModifiersByItem[$id]??[],
        'modifier_groups'=>$menuModifierGroupsByItem[$id]??[],
    ];
}
$catJson=[];
foreach($cats as $c){ $catJson[]=['id'=>(int)$c['id'],'parent_id'=>($c['parent_id']!==null?(int)$c['parent_id']:0),'name'=>(string)$c['category_name'],'depth'=>(int)($c['depth']??0),'printer_role'=>(string)($c['printer_role']??''),'button_color'=>(string)($c['button_color']??'')]; }
$allTags=[];
foreach($menuJson as $mi){ foreach(preg_split('/\s*,\s*/',(string)$mi['tags']) as $tag){ $tag=trim($tag); if($tag!=='') $allTags[$tag]=$tag; } }
$allTags=array_values($allTags);
$order=null;$orderItems=[];$bookingMatches=[];$inhouseGuests=[];$folios=[];
$cashierName = r_current_user_name();
if($selectedRid>0){$s=$pdo->prepare("SELECT ro.*,t.table_no,t.table_name,a.area_name FROM restaurant_orders ro LEFT JOIN restaurant_tables t ON t.id=ro.table_id LEFT JOIN restaurant_areas a ON a.id=ro.area_id WHERE ro.id=:id");$s->execute([':id'=>$selectedRid]);$order=$s->fetch(PDO::FETCH_ASSOC);if($lineOrderMode==='menu'){
        $i=$pdo->prepare("SELECT oi.*, COALESCE(c.category_name,'') AS line_category_name, COALESCE(c.sort_order,9999) AS line_category_sort, COALESCE(mi.sort_order,9999) AS line_item_sort FROM restaurant_order_items oi LEFT JOIN restaurant_menu_items mi ON mi.id=oi.menu_item_id LEFT JOIN restaurant_menu_categories c ON c.id=mi.category_id WHERE oi.restaurant_order_id=:id ORDER BY COALESCE(c.sort_order,9999), COALESCE(c.category_name,''), COALESCE(oi.seat_no,0), COALESCE(mi.sort_order,9999), COALESCE(mi.item_name,oi.item_name), oi.id ASC");
    }else{
        $i=$pdo->prepare("SELECT oi.*, COALESCE(c.category_name,'') AS line_category_name, COALESCE(c.sort_order,9999) AS line_category_sort, COALESCE(mi.sort_order,9999) AS line_item_sort FROM restaurant_order_items oi LEFT JOIN restaurant_menu_items mi ON mi.id=oi.menu_item_id LEFT JOIN restaurant_menu_categories c ON c.id=mi.category_id WHERE oi.restaurant_order_id=:id ORDER BY oi.id DESC");
    }
    $i->execute([':id'=>$selectedRid]);$orderItems=$i->fetchAll(PDO::FETCH_ASSOC);$orderItems=r_attach_item_allocation_badges($pdo,$selectedRid,$orderItems);
    try{
        $ot=$pdo->prepare("SELECT t.id,t.table_no,t.table_name,t.seats FROM restaurant_order_tables rot INNER JOIN restaurant_tables t ON t.id=rot.table_id WHERE rot.restaurant_order_id=:rid ORDER BY t.table_no");
        $ot->execute([':rid'=>$selectedRid]);
        $orderTables=$ot->fetchAll(PDO::FETCH_ASSOC)?:[];
        if(!$orderTables && $order && !empty($order['table_id'])) $orderTables=[['id'=>(int)$order['table_id'],'table_no'=>$order['table_no']??'','table_name'=>$order['table_name']??'','seats'=>0]];
        $labels=[]; foreach($orderTables as $otRow){ $labels[]=trim((string)$otRow['table_no']); $orderMergedCapacity+=(int)($otRow['seats']??0); }
        $orderTableSummary=implode(', ',array_filter($labels));
    }catch(Throwable $e){ $orderTables=[]; $orderTableSummary=(string)($order['table_no']??''); }
    try{$sp=$pdo->prepare("SELECT * FROM restaurant_bill_splits WHERE restaurant_order_id=:rid AND status='posted' ORDER BY id DESC");$sp->execute([':rid'=>$selectedRid]);$splitRows=$sp->fetchAll(PDO::FETCH_ASSOC)?:[];}catch(Throwable $e){$splitRows=[];}
    try{$pp=$pdo->prepare("SELECT * FROM restaurant_payments WHERE restaurant_order_id=:rid AND status='posted' ORDER BY id DESC");$pp->execute([':rid'=>$selectedRid]);$paymentRows=$pp->fetchAll(PDO::FETCH_ASSOC)?:[];}catch(Throwable $e){$paymentRows=[];}
}
$seatSplitTotals=[];
if($orderItems){
    foreach($orderItems as $sit){
        if(($sit['status']??'')!=='active') continue;
        $seat=(int)($sit['seat_no']??0);
        $amount=round((float)($sit['line_total']??0)+(float)($sit['tax_amount']??0),2);
        if(!isset($seatSplitTotals[$seat])) $seatSplitTotals[$seat]=0;
        $seatSplitTotals[$seat]+=$amount;
    }
    ksort($seatSplitTotals);
}
try{
    $currentLinkedOrderId = ($order && !empty($order['linked_order_id'])) ? (int)$order['linked_order_id'] : 0;
    if($currentLinkedOrderId>0){
        $stBookings=$pdo->prepare("SELECT o.id,o.order_no,TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) guest,o.check_in,o.check_out,p.name property_name FROM orders o LEFT JOIN guests g ON g.id=CAST(o.guest_id AS UNSIGNED) LEFT JOIN properties p ON p.id=o.property_id WHERE o.id=:id LIMIT 1");
        $stBookings->execute([':id'=>$currentLinkedOrderId]);
        $bookingMatches=$stBookings->fetchAll(PDO::FETCH_ASSOC)?:[];
    }else{
        $bookingMatches=$pdo->query("SELECT o.id,o.order_no,TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) guest,o.check_in,o.check_out,p.name property_name FROM orders o LEFT JOIN guests g ON g.id=CAST(o.guest_id AS UNSIGNED) LEFT JOIN properties p ON p.id=o.property_id WHERE COALESCE(o.status,1) IN (1,2,3) ORDER BY o.check_in DESC LIMIT 80")->fetchAll(PDO::FETCH_ASSOC);
    }
}catch(Throwable $e){}
try{
    $roomJoinSql = r_column_exists($pdo,'folios','room_id') && r_table_exists($pdo,'rooms') ? "LEFT JOIN rooms rm ON rm.id=f.room_id" : "";
    $roomSelectSql = r_column_exists($pdo,'folios','room_id') && r_table_exists($pdo,'rooms') ? "COALESCE(rm.room_no,'') AS room_no," : "'' AS room_no,";

    /*
       Folio dropdown fix:
       - First pull ALL active folios for the PMS booking linked to this restaurant order.
       - Include room and guest folios even if window_no/status values differ from master.
       - Normalize folio_type for dropdown filtering.
       This fixes Room Folio / Guest Folio showing empty while Master Folio appears.
    */
    $folioSql = "
        SELECT
            f.id,
            f.order_id,
            f.folio_type,
            COALESCE(f.window_no,1) AS window_no,
            " . (r_column_exists($pdo,'folios','window_name') ? "COALESCE(f.window_name,'')" : "''") . " AS window_name,
            COALESCE(f.folio_no,'') AS folio_no,
            COALESCE(f.folio_name,'') AS folio_name,
            {$roomSelectSql}
            COALESCE(f.balance,0) AS balance
        FROM folios f
        INNER JOIN orders o ON o.id=f.order_id
        {$roomJoinSql}
        WHERE COALESCE(f.status,1)=1
    ";

    $folioParams = [];
    if(!empty($currentLinkedOrderId) && (int)$currentLinkedOrderId > 0){
        $folioSql .= " AND f.order_id = :linked_order_id ";
        $folioParams[':linked_order_id'] = (int)$currentLinkedOrderId;
    }else{
        $folioSql .= "
          AND COALESCE(o.status,1) IN (1,2,3)
          AND (
                (o.check_in IS NULL AND o.check_out IS NULL)
                OR (DATE(o.check_in) <= CURDATE() AND DATE(o.check_out) >= CURDATE())
                OR DATE(o.check_in) = CURDATE()
                OR DATE(o.check_out) = CURDATE()
          )
        ";
    }

    $folioSql .= "
        ORDER BY f.order_id,
            CASE
                WHEN LOWER(CAST(f.folio_type AS CHAR)) LIKE '%master%' THEN 1
                WHEN LOWER(CAST(f.folio_type AS CHAR)) LIKE '%room%' THEN 2
                WHEN LOWER(CAST(f.folio_type AS CHAR)) LIKE '%guest%' THEN 3
                ELSE 9
            END,
            COALESCE(f.window_no,1),
            f.id
        LIMIT 1500
    ";

    $stFolios = $pdo->prepare($folioSql);
    $stFolios->execute($folioParams);
    $folioOptions=$stFolios->fetchAll(PDO::FETCH_ASSOC) ?: [];

    // Fallback: if linked booking had no active folios due to a non-standard status value, still show its folios.
    if(!$folioOptions && !empty($currentLinkedOrderId) && (int)$currentLinkedOrderId > 0){
        $fallbackSql = "
            SELECT
                f.id,
                f.order_id,
                f.folio_type,
                COALESCE(f.window_no,1) AS window_no,
                " . (r_column_exists($pdo,'folios','window_name') ? "COALESCE(f.window_name,'')" : "''") . " AS window_name,
                COALESCE(f.folio_no,'') AS folio_no,
                COALESCE(f.folio_name,'') AS folio_name,
                {$roomSelectSql}
                COALESCE(f.balance,0) AS balance
            FROM folios f
            {$roomJoinSql}
            WHERE f.order_id = :linked_order_id
            ORDER BY
                CASE
                    WHEN LOWER(CAST(f.folio_type AS CHAR)) LIKE '%master%' THEN 1
                    WHEN LOWER(CAST(f.folio_type AS CHAR)) LIKE '%room%' THEN 2
                    WHEN LOWER(CAST(f.folio_type AS CHAR)) LIKE '%guest%' THEN 3
                    ELSE 9
                END,
                COALESCE(f.window_no,1),
                f.id
            LIMIT 500
        ";
        $stFolios=$pdo->prepare($fallbackSql);
        $stFolios->execute([':linked_order_id'=>(int)$currentLinkedOrderId]);
        $folioOptions=$stFolios->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }
}catch(Throwable $e){ $folioOptions=[]; }
try{
    if(r_table_exists($pdo,'order_rooming')){
        /*
           Show in-house guests by allocated room.
           Important: one booking may have multiple rooms, so we keep one dropdown option per rooming row.
        */
        $guestNameSql = "TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,'')))";
        $roomExpr = "COALESCE(NULLIF(TRIM(CAST(rm.room_no AS CHAR)),''), NULLIF(TRIM(CAST(rmg.room_no AS CHAR)),''), '')";
        $sql = "
            SELECT
                o.id AS order_id,
                o.property_id,
                CAST(o.guest_id AS UNSIGNED) AS guest_id,
                o.order_no,
                {$guestNameSql} AS guest_name,
                {$roomExpr} AS room_no,
                rmg.id AS rooming_id,
                COALESCE(rmg.check_in, o.check_in) AS check_in,
                COALESCE(rmg.check_out, o.check_out) AS check_out,
                p.name AS property_name,
                COALESCE(g.loyalty_tier,'') AS loyalty_tier,
                COALESCE(g.dietary_notes,'') AS dietary_notes,
                COALESCE(g.allergy_notes,'') AS allergy_notes,
                COALESCE(g.billing_preference,'') AS billing_preference
            FROM orders o
            LEFT JOIN guests g ON g.id = CAST(o.guest_id AS UNSIGNED)
            INNER JOIN order_rooming rmg ON rmg.order_id = o.id
            LEFT JOIN rooms rm ON rm.id = rmg.room_id
            LEFT JOIN properties p ON p.id = o.property_id
            WHERE COALESCE(o.status,1) NOT IN (0,4)
              AND DATE(COALESCE(rmg.check_in,o.check_in)) <= CURDATE()
              AND DATE(COALESCE(rmg.check_out,o.check_out)) > CURDATE()
            ORDER BY
                CASE WHEN {$roomExpr} = '' THEN 1 ELSE 0 END,
                {$roomExpr} ASC,
                guest_name ASC,
                o.order_no ASC
            LIMIT 500
        ";
        $inhouseGuests=$pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
    }else{
        $inhouseGuests=$pdo->query("SELECT o.id AS order_id,o.property_id,CAST(o.guest_id AS UNSIGNED) AS guest_id,o.order_no,TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) AS guest_name,'' AS room_no,o.check_in,o.check_out,p.name AS property_name FROM orders o LEFT JOIN guests g ON g.id=CAST(o.guest_id AS UNSIGNED) LEFT JOIN properties p ON p.id=o.property_id WHERE COALESCE(o.status,1) NOT IN (0,4) AND DATE(o.check_in) <= CURDATE() AND DATE(o.check_out) > CURDATE() ORDER BY guest_name ASC LIMIT 250")->fetchAll(PDO::FETCH_ASSOC);
    }
}catch(Throwable $e){ $inhouseGuests=[]; }

$roomChargeOrderId = ($order && !empty($order['linked_order_id'])) ? (int)$order['linked_order_id'] : 0;
$roomChargeMasterFolio = null;
$roomChargeRoomFolios = [];
$roomChargeGuestFolios = [];
$roomChargeBookingFolios = [];
$roomChargeRoomBalances = [];
$roomChargeGuestBalances = [];
if($roomChargeOrderId > 0){
    try{
        $roomChargeMasterId = r_find_active_folio($pdo,$roomChargeOrderId,'master',0,1);
        $stMaster = $pdo->prepare("SELECT * FROM folios WHERE id=:id LIMIT 1");
        $stMaster->execute([':id'=>$roomChargeMasterId]);
        $roomChargeMasterFolio = $stMaster->fetch(PDO::FETCH_ASSOC) ?: null;
    }catch(Throwable $ignore){}
    try{ $roomChargeRoomFolios = r_get_room_folios_like_payments($pdo,$roomChargeOrderId); }catch(Throwable $ignore){ $roomChargeRoomFolios=[]; }
    try{ $roomChargeGuestFolios = r_get_guest_folios_like_payments($pdo,$roomChargeOrderId); }catch(Throwable $ignore){ $roomChargeGuestFolios=[]; }
    try{ $roomChargeBookingFolios = r_get_booking_folios_for_room_charge($pdo,$roomChargeOrderId); }catch(Throwable $ignore){ $roomChargeBookingFolios=[]; }

    foreach($roomChargeBookingFolios as $bfBalance){
        if((string)($bfBalance['folio_type'] ?? '') === 'room'){
            $roomChargeRoomBalances[(int)($bfBalance['rooming_id'] ?? 0)] = (float)($bfBalance['balance'] ?? 0);
        }elseif((string)($bfBalance['folio_type'] ?? '') === 'guest'){
            $roomChargeGuestBalances[(int)($bfBalance['id'] ?? 0)] = (float)($bfBalance['balance'] ?? 0);
        }
    }
}

try{ $costCenters = r_load_cost_centers($pdo); }catch(Throwable $ignore){ $costCenters = []; }
try{ r_ensure_cost_center_schema($pdo); $allCostCentersAdmin = $pdo->query("SELECT * FROM restaurant_cost_centers ORDER BY sort_order ASC, name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; }catch(Throwable $ignore){ $allCostCentersAdmin = []; }
try{ $costCenterBalances = r_cost_center_balances($pdo); }catch(Throwable $ignore){ $costCenterBalances = []; }

require_once __DIR__.'/../../includes/header.php';
?>
<div class="restaurant-shell">
<div class="hero-band compact-restaurant-hero">
    <div class="row align-items-center g-2">
        <div class="col-lg-8">
            <div class="page-eyebrow text-white-50"><i class="bi bi-cup-hot"></i> Restaurant Table Planner</div>
            <div class="hero-title mt-1">Restaurant POS</div>
            <div class="hero-subtitle mt-1">Floor plan, order pad, menu buttons, KDS routing and room folio posting.</div>
        </div>
        <div class="col-lg-4 text-lg-end">
            <button class="btn btn-success btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#directSaleModal" title="Open a counter/direct sale without selecting a table">
                <i class="bi bi-lightning-charge me-1"></i>Non-table Sale
            </button>
            <a class="btn btn-outline-light btn-sm" href="restaurant_tables.php"><i class="bi bi-grid-3x3-gap me-1"></i>Tables</a>
            <a class="btn btn-light btn-sm" href="menu.php"><i class="bi bi-journal-text me-1"></i>Menu</a>
            <a class="btn btn-warning btn-sm" href="kitchen_display.php"><i class="bi bi-display me-1"></i>Kitchen</a>
            <a class="btn btn-info btn-sm" href="bar_display.php"><i class="bi bi-cup-straw me-1"></i>Bar</a>
        </div>
    </div>
</div>
<?php foreach($errors as $err): ?><div class="alert alert-danger"><?php echo h($err); ?></div><?php endforeach; ?>

<?php foreach($flashes as $f):?><div class="alert alert-<?php echo h($f['type']);?>"><?php echo h($f['message']);?></div><?php endforeach;?>

<?php if(!empty($openNonTableSales)): ?>
<div class="card panel-card mb-3" id="nonTableSalesCard">
    <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
        <strong><i class="bi bi-lightning-charge me-1 text-success"></i>Open Non-table Sales</strong>
        <span class="text-muted small">Not tied to a table - select one to keep adding items, or close it from the order pad.</span>
    </div>
    <div class="card-body d-flex flex-wrap gap-2">
        <?php foreach($openNonTableSales as $nts): $ntsActive=($order && (int)$order['id']===(int)$nts['id']); ?>
        <a href="pp.php?restaurant_order_id=<?php echo (int)$nts['id']; ?>"
           class="btn btn-sm <?php echo $ntsActive?'btn-success':'btn-outline-success'; ?> text-start"
           title="Opened <?php echo h($nts['opened_at']); ?> by <?php echo h($nts['cashier_name']); ?>">
            <i class="bi bi-lightning-charge me-1"></i><?php echo h($nts['guest_name'] ?: ($nts['group_name'] ?: $nts['order_no'])); ?>
            <?php if(!empty($nts['room_no'])): ?><span class="opacity-75"> - Room <?php echo h($nts['room_no']); ?></span><?php endif; ?>
            <span class="badge bg-light text-dark ms-1"><?php echo money_fmt($nts['total_amount']??0); ?></span>
        </a>
        <?php endforeach; ?>
    </div>
</div>
<?php endif; ?>

<div class="card panel-card mb-3" id="printerSetupCard">
    <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
        <div>
            <strong><i class="bi bi-printer me-1"></i>PrintNode Printer Setup</strong>
            <div class="small text-muted">Cloud PMS prints through PrintNode. Enter the numeric PrintNode Printer ID from the PrintNode dashboard, set the correct role, and tick Active.</div>
        </div>
        <a class="btn btn-sm btn-outline-primary" href="pp.php?printer_setup=1#printerSetupCard">Setup Printers</a>
    </div>
    <div id="printerSetupBody" class="<?php echo isset($_GET['printer_setup']) ? '' : 'd-none'; ?>">
        <div class="card-body">
            <form method="post" class="row g-2 align-items-end mb-3">
                <?php if(function_exists('csrf_field')) echo csrf_field(); ?>
                <input type="hidden" name="action" value="save_restaurant_printer">
                <input type="hidden" name="printer_id" value="0">

                <div class="col-md-2">
                    <label class="form-label">Role</label>
                    <input type="text" name="printer_role" class="form-control form-control-sm" placeholder="bar" required>
                    <div class="form-text">Must match item printer role.</div>
                </div>

                <div class="col-md-2">
                    <label class="form-label">Display Name</label>
                    <input type="text" name="printer_name" class="form-control form-control-sm" placeholder="Bar Printer" required>
                </div>

                <div class="col-md-3">
                    <label class="form-label">PrintNode Printer ID</label>
                    <input type="number" name="printnode_printer_id" class="form-control form-control-sm" placeholder="e.g. 75588290" required>
                    <div class="form-text">Use the numeric ID from PrintNode dashboard.</div>
                </div>

                <div class="col-md-2">
                    <label class="form-label">Printer Type</label>
                    <input type="text" class="form-control form-control-sm" value="PrintNode" readonly>
                </div>

                <div class="col-md-2">
                    <label class="form-label">Printer IP</label>
                    <input type="text" name="printer_ip" class="form-control form-control-sm" placeholder="192.168.1.50">
                </div>

                <div class="col-md-1">
                    <label class="form-label">Port</label>
                    <input type="number" name="printer_port" class="form-control form-control-sm" value="9100">
                </div>

                <div class="col-md-1">
                    <label class="form-label">Order</label>
                    <input type="number" name="sort_order" class="form-control form-control-sm" value="1">
                </div>

                <div class="col-md-1">
                    <label class="form-label d-block">Active</label>
                    <input type="checkbox" name="is_active" value="1" checked class="form-check-input">
                </div>

                <input type="hidden" name="property_id" value="0">

                <div class="col-12">
                    <button class="btn btn-sm btn-primary"><i class="bi bi-save me-1"></i>Add Printer</button>
                    <button type="button" class="btn btn-sm btn-outline-secondary ms-1" id="detectQzPrintersBtn"><i class="bi bi-search me-1"></i>Detect Local Printers</button>
                    <span class="small text-muted ms-2">For QZ Tray, the browser prints to the local printer name, not directly from the cloud server to the IP.</span>
                </div>
            </form>

            <div class="table-responsive">
                <table class="table table-sm align-middle mb-0">
                    <thead>
                        <tr>
                            <th>Role</th>
                            <th>Name</th>
                            <th>QZ / Windows Printer</th>
                            <th>Type</th>
                            <th>IP</th>
                            <th>Port</th>
                            <th>Active</th>
                            <th class="text-end">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if(empty($restaurantPrinters)): ?>
                            <tr><td colspan="8" class="text-muted text-center">No printers configured.</td></tr>
                        <?php endif; ?>
                        <?php foreach($restaurantPrinters as $pr): ?>
                            <tr>
                                <form method="post">
                                    <?php if(function_exists('csrf_field')) echo csrf_field(); ?>
                                    <input type="hidden" name="action" value="save_restaurant_printer">
                                    <input type="hidden" name="printer_id" value="<?php echo (int)$pr['id']; ?>">
                                    <input type="hidden" name="property_id" value="<?php echo (int)($pr['property_id']??0); ?>">
                                    <td><input type="text" name="printer_role" class="form-control form-control-sm" value="<?php echo h($pr['printer_role']); ?>" required></td>
                                    <td><input type="text" name="printer_name" class="form-control form-control-sm" value="<?php echo h($pr['printer_name']); ?>" required></td>
                                    <td><input type="text" name="printnode_printer_id" class="form-control form-control-sm" value="<?php echo h($pr['printnode_printer_id'] ?? ''); ?>"></td>
                                    <td>
                                        <?php $pt=(string)($pr['printer_type'] ?? 'qz_windows'); ?>
                                        <select name="printer_type" class="form-select form-select-sm">
                                            <option value="qz_windows" <?php echo $pt==='qz_windows'?'selected':''; ?>>Windows Raw</option>
                                            <option value="qz_windows_html" <?php echo $pt==='qz_windows_html'?'selected':''; ?>>Windows HTML</option>
                                            <option value="qz_network" <?php echo $pt==='qz_network'?'selected':''; ?>>Network IP</option>
                                        </select>
                                    </td>
                                    <td><input type="text" name="printer_ip" class="form-control form-control-sm" value="<?php echo h($pr['printer_ip'] ?? ''); ?>"></td>
                                    <td><input type="number" name="printer_port" class="form-control form-control-sm" value="<?php echo (int)($pr['printer_port'] ?? 9100); ?>"></td>
                                    <td>
                                        <input type="hidden" name="sort_order" value="<?php echo (int)($pr['sort_order'] ?? 1); ?>">
                                        <input type="checkbox" name="is_active" value="1" class="form-check-input" <?php echo (int)($pr['is_active']??1)===1?'checked':''; ?>>
                                    </td>
                                    <td class="text-end">
                                        <div class="d-inline-flex gap-1">
                                            <button class="btn btn-sm btn-primary">Save</button>
                                            <a class="btn btn-sm btn-outline-success" href="pp.php?printer_setup=1&test_printer=<?php echo (int)$pr['id']; ?>">Test</a>
                                        </div>
                                    </td>
                                </form>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>

            <div class="alert alert-info small mt-3 mb-0">
                Install QZ Tray on each POS computer. For an Epson TM-T88V installed in Windows, start with <strong>Windows Raw</strong>. If QZ says successful but no paper comes out, change that same printer row to <strong>Windows HTML</strong> and test again. Use <strong>Detect Local Printers</strong> to copy the exact name seen by QZ Tray. For a true Ethernet/IP printer, choose <strong>QZ Network IP</strong> and enter the printer IP plus port 9100.
            </div>
        </div>
    </div>
</div>

<div class="card panel-card mb-3" id="costCenterSetupCard">
    <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
        <div>
            <strong><i class="bi bi-diagram-3 me-1"></i>Costing Departments</strong>
            <div class="small text-muted">Internal accounts a meal can be written off to (Staff, Management, Director, Marketing, etc.) instead of a guest payment or room folio.</div>
        </div>
        <a class="btn btn-sm btn-outline-primary" href="pp.php?cost_centers=1#costCenterSetupCard">Manage Departments</a>
    </div>
    <div id="costCenterSetupBody" class="<?php echo isset($_GET['cost_centers']) ? '' : 'd-none'; ?>">
        <div class="card-body">
            <form method="post" class="row g-2 align-items-end mb-3">
                <?php if(function_exists('csrf_field')) echo csrf_field(); ?>
                <input type="hidden" name="action" value="save_cost_center">
                <input type="hidden" name="cost_center_id" value="0">
                <input type="hidden" name="property_id" value="0">

                <div class="col-md-5">
                    <label class="form-label">Department Name</label>
                    <input type="text" name="cost_center_name" class="form-control form-control-sm" placeholder="e.g. Housekeeping Account" required>
                </div>
                <div class="col-md-2">
                    <label class="form-label">Order</label>
                    <input type="number" name="sort_order" class="form-control form-control-sm" value="1">
                </div>
                <div class="col-md-2">
                    <label class="form-label d-block">Active</label>
                    <input type="checkbox" name="is_active" value="1" checked class="form-check-input">
                </div>
                <div class="col-md-3">
                    <button class="btn btn-sm btn-primary w-100"><i class="bi bi-plus-circle me-1"></i>Add Department</button>
                </div>
            </form>

            <div class="table-responsive">
                <table class="table table-sm align-middle mb-0">
                    <thead>
                        <tr>
                            <th>Department</th>
                            <th class="text-end" style="width:140px">Balance (All-Time)</th>
                            <th style="width:110px">Order</th>
                            <th style="width:90px">Active</th>
                            <th class="text-end" style="width:140px">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if(empty($allCostCentersAdmin)): ?>
                            <tr><td colspan="5" class="text-muted text-center">No costing departments configured.</td></tr>
                        <?php endif; ?>
                        <?php foreach($allCostCentersAdmin as $cc): $ccBalance=$costCenterBalances[(int)$cc['id']] ?? ['total'=>0,'count'=>0]; ?>
                            <tr>
                                <form method="post" class="d-contents">
                                    <?php if(function_exists('csrf_field')) echo csrf_field(); ?>
                                    <input type="hidden" name="action" value="save_cost_center">
                                    <input type="hidden" name="cost_center_id" value="<?php echo (int)$cc['id']; ?>">
                                    <input type="hidden" name="property_id" value="<?php echo (int)($cc['property_id']??0); ?>">
                                    <td><input type="text" name="cost_center_name" class="form-control form-control-sm" value="<?php echo h($cc['name']); ?>" required></td>
                                    <td class="text-end align-middle">
                                        <strong><?php echo money_fmt($ccBalance['total']); ?></strong>
                                        <div class="small text-muted"><?php echo (int)$ccBalance['count']; ?> posting<?php echo (int)$ccBalance['count']===1?'':'s'; ?></div>
                                    </td>
                                    <td><input type="number" name="sort_order" class="form-control form-control-sm" value="<?php echo (int)($cc['sort_order'] ?? 1); ?>"></td>
                                    <td><input type="checkbox" name="is_active" value="1" class="form-check-input" <?php echo (int)($cc['is_active']??1)===1?'checked':''; ?>></td>
                                    <td class="text-end">
                                        <div class="d-inline-flex gap-1">
                                            <button class="btn btn-sm btn-primary">Save</button>
                                        </div>
                                    </td>
                                </form>
                            </tr>
                            <tr>
                                <td colspan="5" class="text-end py-1 border-0">
                                    <form method="post" class="d-inline" onsubmit="return confirm('Delete this costing department? This does not affect past charges already posted to it.');">
                                        <?php if(function_exists('csrf_field')) echo csrf_field(); ?>
                                        <input type="hidden" name="action" value="delete_cost_center">
                                        <input type="hidden" name="cost_center_id" value="<?php echo (int)$cc['id']; ?>">
                                        <button class="btn btn-sm btn-outline-danger">Delete</button>
                                    </form>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>

<div class="simphony-workspace <?php echo $order ? 'has-active-order' : ''; ?>" id="restaurantWorkspace">
    <section class="simphony-panel simphony-floor-panel">
        <div class="simphony-panel-head">
            <div><div class="simphony-kicker">Floor plan</div><h5>Tables & Sections</h5></div>
            <span class="simphony-pill"><i class="bi bi-hand-index-thumb me-1"></i>Tap to open / resume</span>
            <?php if($order): ?>
                <button type="button" class="btn btn-sm btn-light active-floor-expand-btn" id="expandFloorPlanBtn" title="Show floor plan to select another table">
                    <i class="bi bi-grid-3x3-gap me-1"></i>Show Tables
                </button>
            <?php endif; ?>
        </div>
        <div class="layout-guide compact mb-3">
            <div class="layout-guide-title"><i class="bi bi-info-circle me-1"></i>Layout control</div>
            <div class="layout-guide-grid"><span><strong>Drag:</strong> move table</span><span><strong>Double-click:</strong> rotate rectangle</span><span><strong>Status:</strong> green free, amber busy, purple merged</span></div>
        </div>
        <div id="layoutCsrf" style="display:none"><?php if(function_exists('csrf_field')) echo csrf_field();?></div>
        <div class="restaurant-floor simphony-floor" id="restaurantFloor"><?php foreach($tables as $t): $busy=!empty($t['open_order_id']); $shape=(string)($t['shape']??'round'); $orientation=(string)($t['orientation']??'horizontal'); if(!in_array($orientation,['horizontal','vertical'],true)) $orientation='horizontal'; ?>
        <form method="post" draggable="true" class="table-tile <?php echo $busy?'is-busy':'is-free';?> <?php echo ((int)($t['open_merged_count'] ?? 0)>1)?'is-merged':'';?> shape-<?php echo h($shape);?> orientation-<?php echo h($orientation);?>" data-table-id="<?php echo (int)$t['id'];?>" data-table-label="<?php echo h($t['table_no']);?>" data-open-order-id="<?php echo (int)($t['open_order_id'] ?? 0);?>" data-shape="<?php echo h($shape);?>" data-orientation="<?php echo h($orientation);?>" style="left:<?php echo (int)$t['pos_x'];?>px;top:<?php echo (int)$t['pos_y'];?>px;">
            <?php if(function_exists('csrf_field')) echo csrf_field();?><input type="hidden" name="action" value="open_table"><input type="hidden" name="table_id" value="<?php echo (int)$t['id'];?>">
            <button type="button" class="table-main-btn"><strong><?php echo h($t['table_no']);?></strong><small><?php echo h($t['area_name']);?> • <?php echo (int)$t['seats'];?> seats</small><?php if($busy):?><span><?php echo h($t['order_no']);?> • <?php echo money_fmt($t['total_amount']);?></span><?php if((int)($t['open_merged_count'] ?? 0)>1):?><span class="table-merged-badge"><i class="bi bi-link-45deg me-1"></i>MERGED <?php echo (int)$t['open_merged_count'];?> tables</span><span class="table-merged-list"><?php echo h($t['open_merged_tables']);?></span><?php endif;?><?php if(!empty($t['open_guest_name'])):?><span class="table-client"><i class="bi bi-person me-1"></i><?php echo h($t['open_guest_name']);?><?php echo (!empty($t['open_room_no']) && !in_array(($t['open_order_type'] ?? ''), ['walkin','group'], true))?' • Room '.h($t['open_room_no']):'';?></span><?php endif;?><span class="table-cashier"><i class="bi bi-cash-register me-1"></i><?php echo h($t['open_cashier_name'] ?: $cashierName);?></span><?php endif;?><em class="table-coords">X <?php echo (int)$t['pos_x'];?> • Y <?php echo (int)$t['pos_y'];?></em></button>
            <?php if($shape==='rect'): ?><button type="button" class="table-rotate-btn" title="Rotate table"><i class="bi bi-arrow-repeat"></i></button><?php endif; ?>
        </form><?php endforeach;?></div>
    </section>
    <section class="simphony-panel simphony-check-panel">
        <div class="simphony-panel-head dark"><div><div class="simphony-kicker">Active check</div><h5><?php echo $order?'Order '.h($order['order_no']):'No table selected';?></h5></div><?php if($order): ?><span class="simphony-total"><?php echo money_fmt($order['total_amount']);?></span><?php endif; ?></div>
        <?php if(!$order):?><div class="simphony-empty"><i class="bi bi-grid-3x3-gap"></i><strong>Select a table to begin</strong><span>Choose a table from the floor plan, or use Non-table Sale for a counter/direct sale.</span></div><?php else:?>
        <div class="simphony-check-meta"><div><span>Table(s)</span><strong><?php echo h($orderTableSummary !== '' ? $orderTableSummary : $order['table_no']);?></strong></div><div><span>Guest</span><strong><?php echo h($order['guest_name'] ?: 'Not captured');?></strong></div><?php if(!empty($order['room_no']) && ($order['order_type'] ?? '') !== 'walkin'):?><div><span>Room</span><strong><?php echo h($order['room_no']);?></strong></div><?php endif;?><div><span>Cashier</span><strong><?php echo h($order['cashier_name'] ?: $cashierName);?></strong></div><div><span>Status</span><strong><?php echo h($order['payment_status']);?></strong></div></div>
        <div class="simphony-action-row active-check-toolbar">
            <form method="post" class="m-0" onsubmit="return confirm('Send pending items to kitchen/bar now?');">
                <?php if(function_exists('csrf_field')) echo csrf_field();?>
                <input type="hidden" name="action" value="send_to_kitchen">
                <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
                <button class="btn btn-warning"><i class="bi bi-printer me-1"></i>Send</button>
            </form>
            <form method="post" class="m-0" onsubmit="return confirm('Reprint all active kitchen/bar docket items for this check?');">
                <?php if(function_exists('csrf_field')) echo csrf_field();?>
                <input type="hidden" name="action" value="reprint_station_docket">
                <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
                <button class="btn btn-outline-warning"><i class="bi bi-arrow-clockwise me-1"></i>Reprint</button>
            </form>
            <button class="btn btn-outline-dark" type="button" data-bs-toggle="modal" data-bs-target="#billPreviewModal"><i class="bi bi-receipt me-1"></i>Preview Bill</button>
            <button class="btn btn-outline-success" type="button" data-bs-toggle="modal" data-bs-target="#gratuityModal"><i class="bi bi-cash-coin me-1"></i>Gratuity</button>
            <button class="btn btn-outline-danger" type="button" data-bs-toggle="modal" data-bs-target="#supervisorAdjustModal"><i class="bi bi-shield-lock me-1"></i>Supervisor Adjust</button>
            <?php if($lineOrderMode==='menu'): ?>
                <a class="btn btn-outline-secondary" href="pp.php?restaurant_order_id=<?php echo (int)$order['id'];?>"><i class="bi bi-clock-history me-1"></i>Default Order</a>
            <?php else: ?>
                <a class="btn btn-outline-primary" href="pp.php?restaurant_order_id=<?php echo (int)$order['id'];?>&line_order=menu"><i class="bi bi-sort-down me-1"></i>Menu Sort</a>
            <?php endif; ?>
        </div>
        <div class="simphony-lines"><?php foreach($orderItems as $it):?><div class="simphony-line <?php echo $it['status']==='void'?'is-void':'';?>"><div class="simphony-line-main"><strong><?php echo h((!empty($it['seat_no'])?'Seat '.$it['seat_no'].' • ':'').$it['qty'].' x '.$it['item_name']);?></strong><span><?php echo h($it['printer_role']);?> • <?php echo h($it['print_status']);?> <?php echo $it['notes']?'• '.h($it['notes']):'';?></span><?php if(!empty($it['_split_allocation_badges']) && is_array($it['_split_allocation_badges'])): ?><div class="split-allocation-badges"><?php foreach($it['_split_allocation_badges'] as $badge): ?><span class="badge text-bg-success me-1 mb-1"><i class="bi bi-building-check me-1"></i><?php echo h($badge); ?></span><?php endforeach; ?></div><?php endif; ?></div><div class="simphony-line-amt"><strong><?php echo money_fmt($it['line_total']+$it['tax_amount']);?></strong><?php if($it['status']==='active'):?>
    <?php $lineSent = !in_array(strtolower((string)($it['print_status'] ?? 'pending')), ['','pending'], true); ?>
    <?php if($lineSent): ?>
        <button type="button"
                class="btn btn-sm btn-outline-danger py-0 px-2 line-void-modal-btn"
                data-bs-toggle="modal"
                data-bs-target="#lineVoidModal"
                data-item-id="<?php echo (int)$it['id'];?>"
                data-item-name="<?php echo h($it['qty'].' x '.$it['item_name']);?>"
                data-item-amount="<?php echo h(money_fmt($it['line_total']+$it['tax_amount']));?>">
            Void
        </button>
    <?php else: ?>
        <form method="post" class="m-0" onsubmit="return confirm('Void this unsent line?');">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="void_item">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <input type="hidden" name="item_id" value="<?php echo (int)$it['id'];?>">
            <button class="btn btn-sm btn-link text-danger p-0">Void</button>
        </form>
    <?php endif; ?>
<?php endif;?></div></div><?php endforeach;?></div>
        <?php $activeOrderItemCount=0; foreach($orderItems as $rit){ if(($rit['status']??'')==='active') $activeOrderItemCount++; } ?>
        <div class="simphony-close-grid">
            <?php if($activeOrderItemCount<=0): ?>
            <form method="post" class="m-0" onsubmit="return confirm('Release this table? This will void the empty check and make the table available again.');">
                <?php if(function_exists('csrf_field')) echo csrf_field();?>
                <input type="hidden" name="action" value="release_table">
                <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
                <button class="btn btn-outline-danger w-100" type="submit"><i class="bi bi-box-arrow-left me-1"></i>Release Table</button>
            </form>
            <?php elseif(round((float)($order['total_amount']??0),2) <= 0): ?>
            <form method="post" class="m-0" onsubmit="return confirm('Close this check? The total is zero, so nothing will be posted.');">
                <?php if(function_exists('csrf_field')) echo csrf_field();?>
                <input type="hidden" name="action" value="close_zero_value_order">
                <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
                <button class="btn btn-outline-success w-100" type="submit"><i class="bi bi-check2-circle me-1"></i>Close (Zero Value)</button>
            </form>
            <?php endif; ?>
            <button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#mergeTablesModal" type="button"><i class="bi bi-union me-1"></i>Merge</button>
            <button class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#splitBillModal" type="button"><i class="bi bi-scissors me-1"></i>Split</button>
            <button class="btn btn-outline-warning" data-bs-toggle="modal" data-bs-target="#transferCheckModal" type="button"><i class="bi bi-arrow-left-right me-1"></i>Transfer</button>
            <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#closePaymentModal" type="button"><i class="bi bi-credit-card me-1"></i>Payment</button>
            <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#roomChargeModal" type="button"><i class="bi bi-building me-1"></i>Room Folio</button>
            <button class="btn btn-outline-dark" data-bs-toggle="modal" data-bs-target="#costCenterModal" type="button"><i class="bi bi-diagram-3 me-1"></i>Cost Center</button>
        </div>
        <?php if($orderTableSummary!==''): ?><div class="small text-muted mt-2 px-3"><strong>Merged tables:</strong> <?php echo h($orderTableSummary); ?><?php echo $orderMergedCapacity>0?' • Capacity '.(int)$orderMergedCapacity:''; ?></div><?php endif; ?><?php endif;?>
    </section>
    <aside class="simphony-panel simphony-menu-panel">
        <div class="simphony-panel-head">
            <div>
                <div class="simphony-kicker">Menu pad</div>
                <h5>Quick order</h5>
            </div>
            <button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#menuPosModal">Full menu</button>
        </div>
        <?php if(!$order): ?>
            <div class="simphony-empty small-empty"><i class="bi bi-cup-hot"></i><span>Select a table first.</span></div>
        <?php else: ?>
            <div class="simphony-seat-strip">
                <label>Seat</label>
                <select class="form-select form-select-sm" id="quickSeatNo">
                    <option value="">Any</option>
                    <?php for($sn=1;$sn<=min(65,max(1,(int)($order['covers']??1)));$sn++): ?>
                        <option value="<?php echo $sn;?>"><?php echo $sn;?></option>
                    <?php endfor; ?>
                </select>
            </div>

            <div class="quick-pad-toolbar">
                <button type="button" class="btn btn-sm btn-light" id="quickPadBack" title="Back"><i class="bi bi-arrow-left"></i></button>
                <button type="button" class="btn btn-sm btn-light" id="quickPadHome" title="Top"><i class="bi bi-house"></i></button>
                <span id="quickPadCrumb">All Categories</span>
            </div>

            <div class="simphony-quick-grid" id="quickPadGrid"></div>
            <div class="small text-muted px-3 pb-3">
                Quick Pad opens categories first. Sale items only appear inside the final category.
            </div>
        <?php endif; ?>
    </aside>
</div>


<?php if($order): ?>
<div class="modal fade" id="gratuityModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-md modal-dialog-centered">
        <form method="post" class="modal-content">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="add_gratuity">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header">
                <h5 class="modal-title"><i class="bi bi-cash-coin me-1"></i>Add Gratuity</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="alert alert-success small">Gratuity is added to the guest bill but is excluded from the 3% POS/Card surcharge.</div>
                <label class="form-label">Gratuity Amount</label>
                <input type="number" step="0.01" min="0.01" name="gratuity_amount" class="form-control form-control-lg" required>
                <label class="form-label mt-3">Note</label>
                <input type="text" name="gratuity_note" class="form-control" placeholder="Optional">
            </div>
            <div class="modal-footer"><button class="btn btn-success">Add Gratuity</button></div>
        </form>
    </div>
</div>

<div class="modal fade" id="supervisorAdjustModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered">
        <form method="post" class="modal-content">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="supervisor_adjust_item">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header">
                <h5 class="modal-title"><i class="bi bi-shield-lock me-1"></i>Supervisor Adjustment</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="alert alert-warning small">Requires supervisor/manager PIN. Use this for discounts, waivers, or price changes with a reason. Select one or more items - the same adjustment is applied to each individually.</div>
                <div class="row g-3">
                    <div class="col-md-12">
                        <label class="form-label d-flex justify-content-between align-items-center">
                            <span>Items</span>
                            <button type="button" class="btn btn-sm btn-outline-secondary" id="toggleAdjustItems">Select / Clear All</button>
                        </label>
                        <div class="card border" style="max-height:220px;overflow:auto;">
                            <div class="card-body p-2">
                                <?php $adjustableItemCount=0; foreach($orderItems as $it): if(($it['status']??'')!=='active' || strtolower(trim((string)$it['item_name']))==='gratuity') continue; $adjustableItemCount++; ?>
                                    <label class="d-flex align-items-center gap-2 border-bottom py-2 px-2 mb-0 supervisor-adjust-item-row">
                                        <input class="form-check-input supervisor-adjust-item-check" type="checkbox" name="item_ids[]" value="<?php echo (int)$it['id']; ?>">
                                        <span class="flex-grow-1"><?php echo h($it['qty'].' x '.$it['item_name']); ?></span>
                                        <strong><?php echo money_fmt($it['line_total']); ?></strong>
                                    </label>
                                <?php endforeach; ?>
                                <?php if($adjustableItemCount<=0): ?>
                                    <div class="text-muted small p-2">No active items to adjust.</div>
                                <?php endif; ?>
                            </div>
                        </div>
                        <div class="form-text">Selected: <strong id="supervisorAdjustSelectedCount">0</strong> item(s)</div>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Adjustment</label>
                        <select name="adjustment_type" class="form-select" required>
                            <option value="discount_amount">Discount Amount</option>
                            <option value="discount_percent">Discount %</option>
                            <option value="waiver">Waiver / Comp</option>
                            <option value="change_price">Change Unit Price</option>
                        </select>
                        <div class="form-text">For multiple items, this applies to each one separately (e.g. $5 off applies per item, not split across them).</div>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Value</label>
                        <input type="number" step="0.01" min="0" name="adjustment_value" class="form-control" value="0">
                        <div class="form-text">For waiver this can stay 0.</div>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Supervisor PIN</label>
                        <input type="password" name="supervisor_pin" class="form-control" required>
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">Reason</label>
                        <select name="adjustment_reason" class="form-select" required>
                            <option value="">Select reason</option>
                            <option>Wrong order</option>
                            <option>Spoilt</option>
                            <option>Training</option>
                            <option>Waiter error</option>
                            <option>Customer Change</option>
                            <option>Out of stock</option>
                            <option>Manager discount</option>
                            <option>Guest recovery</option>
                            <option>Price correction</option>
                        </select>
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">Notes</label>
                        <input type="text" name="adjustment_note" class="form-control" placeholder="Optional detail">
                    </div>
                </div>
            </div>
            <div class="modal-footer"><button class="btn btn-danger">Apply Supervisor Adjustment</button></div>
        </form>
    </div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function(){
    var checks=Array.prototype.slice.call(document.querySelectorAll('.supervisor-adjust-item-check'));
    var countEl=document.getElementById('supervisorAdjustSelectedCount');
    var toggleBtn=document.getElementById('toggleAdjustItems');
    function refreshCount(){
        if(countEl) countEl.textContent=checks.filter(function(c){return c.checked;}).length;
    }
    checks.forEach(function(c){ c.addEventListener('change', refreshCount); });
    if(toggleBtn){
        toggleBtn.addEventListener('click', function(){
            var shouldCheck=checks.some(function(c){ return !c.checked; });
            checks.forEach(function(c){ c.checked=shouldCheck; });
            refreshCount();
        });
    }
    refreshCount();
});
</script>


<div class="modal fade" id="lineVoidModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-md modal-dialog-centered">
        <form method="post" class="modal-content" onsubmit="return confirm('Supervisor void this sent line?');">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="void_item">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <input type="hidden" name="item_id" id="lineVoidItemId">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title"><i class="bi bi-shield-lock me-1"></i>Supervisor Void</h5>
                    <div class="small text-muted">Only required because this line was already sent to Kitchen / Bar.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="alert alert-light border mb-3">
                    <div class="small text-muted">Line item</div>
                    <strong id="lineVoidItemName">Selected item</strong>
                    <span class="float-end fw-bold" id="lineVoidItemAmount"></span>
                </div>

                <label class="form-label fw-semibold">Void Reason</label>
                <select name="void_reason" class="form-select form-select-lg" required>
                    <option value="">Select reason</option>
                    <option>Wrong order</option>
                    <option>Spoilt</option>
                    <option>Training</option>
                    <option>Customer Change</option>
                    <option>Waiter error</option>
                    <option>Out of stock</option>
                </select>

                <label class="form-label fw-semibold mt-3">Supervisor PIN</label>
                <input type="password" name="supervisor_pin" class="form-control form-control-lg" placeholder="Enter supervisor PIN" required>

                <label class="form-label fw-semibold mt-3">Optional Notes</label>
                <textarea name="void_note" class="form-control" rows="2" placeholder="Optional detail"></textarea>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                <button class="btn btn-danger"><i class="bi bi-trash me-1"></i>Confirm Void</button>
            </div>
        </form>
    </div>
</div>

<div class="modal fade" id="billPreviewModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
        <div class="modal-content">
            <div class="modal-header pos-modal-header">
                <div>
                    <h5 class="modal-title"><i class="bi bi-receipt me-1"></i>Bill Preview</h5>
                    <div class="small text-white-50">Review and print the guest bill to the Bar printer.</div>
                </div>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <div class="row g-2 mb-3">
                    <div class="col-md-4"><div class="border rounded-3 p-2"><span class="small text-muted d-block">Order</span><strong><?php echo h($order['order_no']);?></strong></div></div>
                    <div class="col-md-4"><div class="border rounded-3 p-2"><span class="small text-muted d-block">Table(s)</span><strong><?php echo h($orderTableSummary !== '' ? $orderTableSummary : $order['table_no']);?></strong></div></div>
                    <div class="col-md-4"><div class="border rounded-3 p-2"><span class="small text-muted d-block">Guest</span><strong><?php echo h($order['guest_name'] ?: 'Not captured');?></strong></div></div>
                </div>
                <div class="small text-muted mb-2">
                    View: <strong><?php echo $lineOrderMode==='menu'?'Menu category sort + seat number':'Default order entered';?></strong>
                </div>
                <?php if(!$orderItems): ?>
                    <div class="alert alert-light border mb-0">No items on this bill yet.</div>
                <?php else: ?>
                    <?php foreach($orderItems as $it): ?>
                        <div class="bill-preview-line <?php echo $it['status']==='void'?'text-decoration-line-through opacity-50':'';?>">
                            <div><strong><?php echo money_fmt($it['qty']);?> ×</strong><span class="d-block"><?php echo !empty($it['seat_no'])?'Seat '.h($it['seat_no']):'No seat';?></span></div>
                            <div><strong><?php echo h($it['item_name']);?></strong><span class="d-block"><?php echo h(($it['line_category_name']??'') ?: ($it['printer_role']??''));?> <?php echo $it['notes']?'• '.h($it['notes']):'';?></span></div>
                            <div class="text-end"><strong><?php echo money_fmt($it['line_total']+$it['tax_amount']);?></strong><span class="d-block"><?php echo h($it['print_status']);?></span></div>
                        </div>
                    <?php endforeach; ?>
                    <?php $previewGratuity = r_order_gratuity_total($pdo,(int)$order['id']); $previewCharges = max(0, round((float)$order['total_amount'] - $previewGratuity, 2)); $previewCcSurcharge = round($previewCharges * REST_POS_CARD_SURCHARGE_RATE, 2); ?>
                    <div class="bill-preview-total"><span>Total Charges</span><strong><?php echo money_fmt($previewCharges);?></strong></div>
                    <div class="bill-preview-total"><span>Tax/VAT incl.</span><strong><?php echo money_fmt($order['tax_total']);?></strong></div>
                    <div class="bill-preview-total"><span>Gross Total</span><strong><?php echo money_fmt($previewCharges);?></strong></div>
                    <div class="alert alert-light border small mt-2 mb-0">If paying by card, <strong>Print CC Bill to Bar</strong> adds a 3% surcharge (<?php echo money_fmt($previewCcSurcharge);?>) on top of the charges above. Gratuity is never included in the surcharge.</div>
                <?php endif; ?>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close Preview</button>
                <form method="post" class="m-0" onsubmit="return confirm('Print customer bill to the Bar printer now?');">
                    <?php if(function_exists('csrf_field')) echo csrf_field();?>
                    <input type="hidden" name="action" value="print_bill_to_bar">
                    <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
                    <button class="btn btn-warning"><i class="bi bi-printer me-1"></i>Print Bill to Bar</button>
                </form>
                <form method="post" class="m-0" onsubmit="return confirm('Print CC bill to the Bar printer with the 3% card surcharge (gratuity excluded)?');">
                    <?php if(function_exists('csrf_field')) echo csrf_field();?>
                    <input type="hidden" name="action" value="print_cc_bill_to_bar">
                    <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
                    <button class="btn btn-outline-warning"><i class="bi bi-credit-card me-1"></i>Print CC Bill to Bar</button>
                </form>
            </div>
        </div>
    </div>
</div>
<?php endif; ?>

<div class="modal fade" id="openTableModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered">
        <form method="post" class="modal-content" id="openTableForm">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="open_table">
            <input type="hidden" name="table_id" id="openTableId">
            <input type="hidden" name="room_no" id="openRoomNoHidden">
            <input type="hidden" name="guest_name" id="openGuestNameHidden">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title">Open Table <span id="openTableLabel"></span></h5>
                    <div class="small text-muted">Cashier: <strong><?php echo h($cashierName); ?></strong></div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <div class="row g-3">
                    <div class="col-md-4"><label class="form-label">Customer Type</label><select name="customer_type" id="customerType" class="form-select" required><option value="">Choose customer type...</option><option value="room">Room / In-house Guest</option><option value="group">Group / In-house Group</option><option value="walkin">Direct Walk-in</option></select></div>
                    <div class="col-md-2"><label class="form-label">Covers</label><input type="number" name="covers" class="form-control" min="1" value="1" required></div>
                    <div class="col-md-6 room-guest-wrap"><label class="form-label">Room / Guest</label><select name="linked_order_id" id="inhouseGuestSelect" class="form-select"><option value="">Select in-house room guest</option><?php foreach($inhouseGuests as $ig): ?><?php $guest=trim((string)($ig['guest_name'] ?? '')); if($guest==='') $guest='Guest'; $room=trim((string)($ig['room_no'] ?? '')); $property=trim((string)($ig['property_name'] ?? '')); $label=($room!==''?'Room '.$room.' • ':'Unallocated Room • ').$guest.' • '.(($ig['order_no'] ?? '') ?: '#'.$ig['order_id']).($property!==''?' • '.$property:''); ?><option value="<?php echo (int)$ig['order_id'];?>" data-guest="<?php echo h($guest);?>" data-room="<?php echo h($room);?>" data-property="<?php echo h($property);?>"><?php echo h($label); ?></option><?php endforeach; ?></select><div class="form-text">Only current in-house guests are shown. For a group, this booking is optional and can help with later posting.</div></div>
                    <div class="col-md-6 group-wrap" style="display:none;"><label class="form-label">Group Name</label><input type="text" name="group_name" id="groupNameInput" class="form-control" placeholder="e.g. Smith Wedding Group / Conference Group"><div class="form-text">Use this when combined tables belong to an in-house group and posting may be split to different rooms later.</div></div>
                    <div class="col-md-6 walkin-wrap" style="display:none;"><label class="form-label">Walk-in Customer Name</label><input type="text" id="walkinGuestName" class="form-control" placeholder="e.g. Mr Dube / Walk-in guest"></div>
                    <div class="col-md-6"><label class="form-label">Client Name</label><input type="text" id="displayClientName" class="form-control" readonly></div>
                    <div class="col-md-3 modal-room-display"><label class="form-label">Room</label><input type="text" id="displayRoomNo" class="form-control" readonly></div>
                    <div class="col-md-3"><label class="form-label">Cashier</label><input type="text" class="form-control" value="<?php echo h($cashierName); ?>" readonly></div>
                </div>
                <div class="alert alert-info mt-3 mb-0 small">For lodge guests, choose the in-house room/guest. For groups, enter the group name; you may optionally link a lodge booking and later split/post to different rooms. For walk-ins, enter the customer name and close by cash/card/POS.</div>
            </div>
            <div class="modal-footer"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button><button class="btn btn-primary"><i class="bi bi-box-arrow-in-right me-1"></i>Open Table</button></div>
        </form>
    </div>
</div>

<div class="modal fade" id="directSaleModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered">
        <form method="post" class="modal-content" id="directSaleForm">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="open_direct_sale">
            <input type="hidden" name="room_no" id="directRoomNoHidden">
            <input type="hidden" name="guest_name" id="directGuestNameHidden">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title"><i class="bi bi-lightning-charge me-1 text-success"></i>Non-table Sale</h5>
                    <div class="small text-muted">Open a counter/direct sale without occupying a table. You can still link it to an in-house room.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <div class="row g-3">
                    <div class="col-md-4">
                        <label class="form-label">Sale Type</label>
                        <select name="customer_type" id="directCustomerType" class="form-select" required>
                            <option value="walkin">Direct Walk-in / Counter Sale</option>
                            <option value="room">Room / In-house Guest</option>
                            <option value="group">Group / In-house Group</option>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">Covers / Pax</label>
                        <input type="number" name="covers" class="form-control" min="1" value="1" required>
                    </div>
                    <div class="col-md-6 direct-room-wrap" style="display:none;">
                        <label class="form-label">Room / Guest</label>
                        <select name="linked_order_id" id="directInhouseGuestSelect" class="form-select">
                            <option value="">Select in-house room guest</option>
                            <?php foreach($inhouseGuests as $ig): ?>
                                <?php
                                    $guest=trim((string)($ig['guest_name'] ?? ''));
                                    if($guest==='') $guest='Guest';
                                    $room=trim((string)($ig['room_no'] ?? ''));
                                    $property=trim((string)($ig['property_name'] ?? ''));
                                    $label=($room!==''?'Room '.$room.' • ':'Unallocated Room • ').$guest.' • '.(($ig['order_no'] ?? '') ?: '#'.$ig['order_id']).($property!==''?' • '.$property:'');
                                ?>
                                <option value="<?php echo (int)$ig['order_id'];?>" data-guest="<?php echo h($guest);?>" data-room="<?php echo h($room);?>" data-property="<?php echo h($property);?>"><?php echo h($label); ?></option>
                            <?php endforeach; ?>
                        </select>
                        <div class="form-text">Use this when the guest does not sit at a table but wants the charge linked to their room.</div>
                    </div>
                    <div class="col-md-6 direct-group-wrap" style="display:none;">
                        <label class="form-label">Group Name</label>
                        <input type="text" name="group_name" id="directGroupNameInput" class="form-control" placeholder="e.g. Conference Group">
                    </div>
                    <div class="col-md-6 direct-walkin-wrap">
                        <label class="form-label">Customer Name</label>
                        <input type="text" id="directWalkinGuestName" class="form-control" placeholder="Non-table Sale / Counter Sale" value="Non-table Sale">
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">Client Name</label>
                        <input type="text" id="directDisplayClientName" class="form-control" readonly>
                    </div>
                    <div class="col-md-3 direct-room-display" style="display:none;">
                        <label class="form-label">Room</label>
                        <input type="text" id="directDisplayRoomNo" class="form-control" readonly>
                    </div>
                    <div class="col-md-3">
                        <label class="form-label">Cashier</label>
                        <input type="text" class="form-control" value="<?php echo h($cashierName); ?>" readonly>
                    </div>
                </div>
                <div class="alert alert-info mt-3 mb-0 small">
                    Direct walk-ins close by cash/card/POS. Room guest non-table sales can still be posted to the room folio later from the active check.
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                <button class="btn btn-success"><i class="bi bi-box-arrow-in-right me-1"></i>Open Non-table Sale</button>
            </div>
        </form>
    </div>
</div>
<?php if($order):?>
<script>
window.REST_MENU_CATEGORIES = <?php echo json_encode($catJson, JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT); ?>;
window.REST_MENU_ITEMS = <?php echo json_encode($menuJson, JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT); ?>;
window.REST_MENU_TAGS = <?php echo json_encode($allTags, JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT); ?>;
window.REST_ALL_MODIFIERS = <?php echo json_encode($allModifierRows, JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT); ?>;
window.REST_ACTIVE_RESTAURANT_ORDER_ID = <?php echo (int)($order['id'] ?? 0); ?>;
</script>
<div class="modal fade menu-pos-modal" id="menuPosModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-fullscreen">
        <form method="post" class="modal-content" id="menuPosForm">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="add_item">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <input type="hidden" name="menu_item_id" id="menuPosItemId">
            <div class="modal-header pos-modal-header">
                <div>
                    <div class="small text-white-50 text-uppercase fw-bold">Restaurant POS Menu</div>
                    <h4 class="modal-title mb-0">Add Item to <?php echo h($order['order_no']); ?></h4>
                </div>
                <div class="text-end text-white">
                    <div class="fw-bold">Table(s): <?php echo h($orderTableSummary !== '' ? $orderTableSummary : $order['table_no']); ?></div>
                    <div class="small text-white-50">Client: <?php echo h($order['guest_name'] ?: 'Walk-in / Guest'); ?></div>
                </div>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body p-0">
                <div class="pos-menu-shell">
                    <aside class="pos-menu-sidebar">
                        <div class="pos-search-box">
                            <i class="bi bi-search"></i>
                            <input type="text" id="menuPosSearch" placeholder="Search item, code or tag...">
                        </div>
                        <div class="pos-breadcrumb" id="menuPosBreadcrumb">All Categories</div>
                        <div class="pos-tag-strip" id="menuPosTags"></div>
                        <div class="pos-category-list" id="menuPosCategories"></div>
                    </aside>
                    <main class="pos-menu-main">
                        <div class="pos-main-toolbar">
                            <button type="button" class="btn btn-outline-secondary btn-sm" id="menuPosBack"><i class="bi bi-arrow-left"></i> Back</button>
                            <button type="button" class="btn btn-outline-dark btn-sm" id="menuPosHome"><i class="bi bi-house"></i> Top</button>
                            <div class="ms-auto small text-muted" id="menuPosCount">0 items</div>
                        </div>
                        <div class="pos-item-grid" id="menuPosItems"></div>
                    </main>
                    <aside class="pos-menu-order-panel">
                        <div class="selected-item-card">
                            <div class="small text-muted text-uppercase fw-bold">Selected Item</div>
                            <h5 id="selectedItemName">Tap an item</h5>
                            <div id="selectedItemMeta" class="text-muted small">Choose a category and select an item button.</div>
                            <div class="selected-price" id="selectedItemPrice">0.00</div>
                        </div>
                        <div class="mt-3">
                            <label class="form-label fw-bold">Quantity</label>
                            <input type="number" step="0.01" min="0.01" name="qty" id="menuPosQty" value="1" class="form-control form-control-lg">
                        </div>
                        <div class="mt-3">
                            <label class="form-label fw-bold">Seat No</label>
                            <input type="number" min="1" max="65" name="seat_no" id="menuPosSeatNo" class="form-control form-control-lg" placeholder="Optional">
                            <div class="small text-muted">Used for split-by-seat billing.</div>
                        </div>
                        <div class="mt-3">
                            <label class="form-label fw-bold">Modifiers / Options</label>
                            <div id="menuPosModifiers" class="modifier-list"><div class="text-muted small">No item selected.</div></div>
                        </div>
                        <div class="mt-3">
                            <label class="form-label fw-bold">Kitchen / Bar Notes</label>
                            <textarea name="notes" class="form-control" rows="3" placeholder="No onion, birthday plate, serve later..."></textarea>
                        </div>
                        <div class="pos-total-box mt-3">
                            <span>Line Total</span>
                            <strong id="menuPosLineTotal">0.00</strong>
                        </div>
                        <button class="btn btn-success btn-lg w-100 mt-3" id="menuPosSubmit" disabled>
                            <i class="bi bi-plus-circle me-1"></i> Add to Tab
                        </button>
                    </aside>
                </div>
            </div>
        </form>
    </div>
</div>
<div class="modal fade" id="closePaymentModal" tabindex="-1">
    <div class="modal-dialog modal-lg">
        <form method="post" class="modal-content">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="close_payment_multi">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header"><h5 class="modal-title">Close With Multiple Payments</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
            <div class="modal-body">
                <div class="alert alert-light border small mb-3">Total due: <strong><?php echo money_fmt($order['total_amount']); ?></strong>. Add one or more payment methods. <strong>POS/Card automatically adds a 3% surcharge</strong> and posts it as a surcharge line on the restaurant bill.</div>
                <div id="multiPaymentRows">
                    <div class="row g-2 payment-row mb-2">
                        <div class="col-md-4"><select name="payment_method[]" class="form-select"><option>Cash</option><option>Card</option><option>POS</option><option>Mobile Money</option><option>Bank Transfer</option></select></div>
                        <div class="col-md-3"><input type="number" step="0.01" name="amount[]" class="form-control" value="<?php echo h($order['total_amount']);?>"></div>
                        <div class="col-md-4"><input name="reference_no[]" class="form-control" placeholder="Reference"></div>
                        <div class="col-md-1"><button type="button" class="btn btn-outline-danger remove-pay-row">×</button></div>
                    </div>
                </div>
                <button type="button" class="btn btn-sm btn-outline-primary" id="addPaymentRow"><i class="bi bi-plus-circle me-1"></i>Add Payment Method</button>
                <div class="form-check mt-3">
                    <input class="form-check-input" type="checkbox" name="print_bill_after_post" value="1" id="printBillAfterPayment" checked>
                    <label class="form-check-label fw-semibold" for="printBillAfterPayment">Print customer bill to Bar printer after posting payment</label>
                </div>
                <?php if($paymentRows): ?><hr><div class="small text-muted mb-1">Payments already posted</div><?php foreach($paymentRows as $pr): ?><div class="d-flex justify-content-between small border-bottom py-1"><span><?php echo h($pr['payment_method']); ?> <?php echo $pr['reference_no']?'• '.h($pr['reference_no']):''; ?></span><strong><?php echo money_fmt($pr['amount']); ?></strong></div><?php endforeach; ?><?php endif; ?>
            </div>
            <div class="modal-footer"><button class="btn btn-success">Post Payment(s)</button></div>
        </form>
    </div>
</div>

<div class="modal fade" id="splitBillModal" tabindex="-1">
    <div class="modal-dialog modal-xl">
        <form method="post" class="modal-content">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="save_bill_split">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title">Bill Split & Settlement</h5>
                    <div class="small text-muted">split by amount, covers, seat, item, or mixed PMS settlement.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="alert alert-light border small mb-3">
                    Total due: <strong><?php echo money_fmt($order['total_amount']); ?></strong>.
                    Folio destinations post directly to the selected PMS billing window. Cash/card destinations post as restaurant payments.
                </div>

                <ul class="nav nav-pills mb-3" role="tablist">
                    <li class="nav-item"><button class="nav-link active" data-bs-toggle="pill" data-bs-target="#splitEqual" type="button">Equal Amount</button></li>
                    <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#splitCovers" type="button">By Covers</button></li>
                    <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#splitSeats" type="button">By Seat</button></li>
                    <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#splitItems" type="button">By Item</button></li>
                    <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#splitMixed" type="button">Mixed Settlement</button></li>
                </ul>

                <?php
                $folioSelectHtml = '<option value="">-- select folio --</option>';
                foreach($folioOptions as $fo){
                    $rawFt = (string)($fo['folio_type'] ?? '');
                    $ft = r_normalize_folio_type_value($rawFt);
                    $label = 'Order '.$fo['order_id'].' • '.ucfirst($ft ?: $rawFt).' W'.$fo['window_no'].' '.$fo['folio_no'].' '.$fo['folio_name'].' '.($fo['room_no']?'Room '.$fo['room_no']:'').' Bal '.money_fmt($fo['balance']);
                    $folioSelectHtml .= '<option value="'.(int)$fo['id'].'" data-folio-type="'.h($ft).'" data-raw-folio-type="'.h($rawFt).'" data-order-id="'.(int)$fo['order_id'].'" data-room-no="'.h($fo['room_no'] ?? '').'">'.h($label).'</option>';
                }
                ?>

                <div class="tab-content">
                    <div class="tab-pane fade show active" id="splitEqual">
                        <input type="radio" class="btn-check" name="split_type" id="splitTypeEqual" value="equal" checked>
                        <label class="btn btn-sm btn-outline-primary mb-2" for="splitTypeEqual">Use Equal Split</label>
                        <label class="form-label d-block">Number of guests</label>
                        <input type="number" min="1" max="65" value="2" id="equalSplitCount" name="split_count" class="form-control" style="max-width:160px">
                        <div class="small text-muted mt-2">Each guest can settle by cash/card/POS or post to a selected room/guest/group/company folio.</div>
                        <div id="equalPaymentRows" class="mt-3"></div>
                    </div>

                    <div class="tab-pane fade" id="splitCovers">
                        <input type="radio" class="btn-check" name="split_type" id="splitTypeCovers" value="covers">
                        <label class="btn btn-sm btn-outline-primary mb-2" for="splitTypeCovers">Use Covers Split</label>
                        <div class="alert alert-light border small">
                            Covers on this check: <strong id="coversCountLabel"><?php echo (int)($order['covers']??1); ?></strong>.
                            The system creates one split bill per cover and shows each cover below before posting.
                            POS/Card destinations show the 3% surcharge in the preview.
                        </div>
                        <div id="coverPaymentRows" class="mt-3"></div>
                    </div>

                    <div class="tab-pane fade" id="splitSeats">
                        <input type="radio" class="btn-check" name="split_type" id="splitTypeSeat" value="seat">
                        <label class="btn btn-sm btn-outline-primary mb-2" for="splitTypeSeat">Use Seat Split</label>
                        <div class="table-responsive">
                            <table class="table table-sm align-middle">
                                <thead><tr><th>Seat</th><th class="text-end">Amount</th><th>Settlement</th><th>Folio / Account</th><th>Reference</th></tr></thead>
                                <tbody>
                                <?php foreach($seatSplitTotals as $seat=>$amount): $seatKey=(string)$seat; ?>
                                    <tr>
                                        <td><strong><?php echo $seat>0?'Seat '.(int)$seat:'Unassigned'; ?></strong></td>
                                        <td class="text-end"><?php echo money_fmt($amount); ?></td>
                                        <td>
                                            <select name="seat_destination_type[<?php echo h($seatKey); ?>]" class="form-select form-select-sm split-destination">
                                                <option value="cash">Cash</option><option value="card">Card</option><option value="pos">POS</option><option value="mobile_money">Mobile Money</option><option value="bank_transfer">Bank Transfer</option>
                                                <option value="room_folio">Room Folio</option><option value="guest_folio">Guest Folio</option><option value="master_folio">Master Folio</option><option value="group_master">Group Master</option><option value="company_account">Company Account</option>
                                                <option value="comp">Comp</option><option value="house_account">House Account</option><option value="dispute">Dispute Hold</option><option value="package">Package / Allowance</option>
                                            </select>
                                        </td>
                                        <td><select name="seat_folio_id[<?php echo h($seatKey); ?>]" class="form-select form-select-sm split-folio-select"><?php echo $folioSelectHtml; ?></select></td>
                                        <td><input name="seat_reference[<?php echo h($seatKey); ?>]" class="form-control form-control-sm" placeholder="Reference"></td>
                                    </tr>
                                <?php endforeach; ?>
                                <?php if(!$seatSplitTotals): ?><tr><td colspan="5" class="text-muted">No active items found for seat split.</td></tr><?php endif; ?>
                                </tbody>
                            </table>
                        </div>
                    </div>

                    <div class="tab-pane fade" id="splitItems">
                        <input type="radio" class="btn-check" name="split_type" id="splitTypeItem" value="item">
                        <label class="btn btn-sm btn-outline-primary mb-2" for="splitTypeItem">Use Item Split</label>

                        <div class="alert alert-info border small mb-3">
                            <strong>Fast room posting:</strong> tick several items or item units, choose one Room Folio or Guest Folio once, then click <strong>Apply To Selected</strong>.
                            This fills the selected rows automatically so you do not need to choose the room line by line.
                        </div>

                        <div class="card border-0 shadow-sm mb-3">
                            <div class="card-header bg-light d-flex justify-content-between align-items-center">
                                <div>
                                    <strong>Bulk apply selected item units</strong>
                                    <div class="small text-muted">Use this when one room/guest is taking several items from the split bill.</div>
                                </div>
                                <button type="button" class="btn btn-sm btn-outline-secondary" id="toggleItemSplitUnits">Select / Clear All</button>
                            </div>
                            <div class="card-body">
                                <div class="row g-2 align-items-end">
                                    <div class="col-lg-2 col-md-4">
                                        <label class="form-label small fw-bold">Payer label</label>
                                        <input type="text" class="form-control form-control-sm" id="bulkItemPayer" placeholder="Room / Guest">
                                    </div>
                                    <div class="col-lg-2 col-md-4">
                                        <label class="form-label small fw-bold">Settlement</label>
                                        <select class="form-select form-select-sm" id="bulkItemDestination">
                                            <option value="room_folio">Room Folio</option>
                                            <option value="guest_folio">Guest Folio</option>
                                            <option value="master_folio">Master Folio</option>
                                            <option value="cash">Cash</option>
                                            <option value="card">Card</option>
                                            <option value="pos">POS</option>
                                            <option value="mobile_money">Mobile Money</option>
                                            <option value="bank_transfer">Bank Transfer</option>
                                            <option value="comp">Comp</option>
                                            <option value="dispute">Dispute</option>
                                        </select>
                                    </div>
                                    <div class="col-lg-4 col-md-8">
                                        <label class="form-label small fw-bold">Room / Guest / Master Account</label>
                                        <select class="form-select form-select-sm" id="bulkItemFolio"><?php echo $folioSelectHtml; ?></select>
                                        <div class="small text-muted" id="bulkItemFolioHelp">Select the room or guest once, then apply to all ticked items.</div>
                                    </div>
                                    <div class="col-lg-2 col-md-4">
                                        <label class="form-label small fw-bold">Reference</label>
                                        <input type="text" class="form-control form-control-sm" id="bulkItemReference" placeholder="Optional">
                                    </div>
                                    <div class="col-lg-2 col-md-4 d-grid">
                                        <button type="button" class="btn btn-sm btn-primary" id="applyBulkItemSplit"><i class="bi bi-check2-square me-1"></i>Apply To Selected</button>
                                    </div>
                                </div>
                                <div class="d-flex justify-content-between mt-2 small">
                                    <span class="text-muted">Selected item/unit total</span>
                                    <strong id="itemSplitSelectedTotal">0.00</strong>
                                </div>
                            </div>
                        </div>

                        <div class="table-responsive">
                            <table class="table table-sm align-middle">
                                <thead><tr><th style="width:36px"></th><th>Item</th><th class="text-end">Amount</th><th>Payer</th><th>Settlement</th><th>Folio / Account</th><th>Reference</th></tr></thead>
                                <tbody>
                                <?php foreach($orderItems as $it): if($it['status']!=='active') continue; ?>
                                    <?php
                                        $splitQty = max(1, (int)round((float)($it['qty'] ?? 1)));
                                        $lineAmount = (float)$it['line_total'] + (float)$it['tax_amount'];
                                        $unitSplitAmount = $splitQty > 0 ? round($lineAmount / $splitQty, 2) : round($lineAmount, 2);
                                        for($unitNo = 1; $unitNo <= $splitQty; $unitNo++):
                                            $thisUnitAmount = ($unitNo === $splitQty) ? round($lineAmount - ($unitSplitAmount * ($splitQty - 1)), 2) : $unitSplitAmount;
                                    ?>
                                    <tr class="item-unit-row" data-amount="<?php echo h((string)$thisUnitAmount); ?>">
                                        <td><input type="checkbox" class="form-check-input item-unit-check" data-amount="<?php echo h((string)$thisUnitAmount); ?>"></td>
                                        <td>
                                            <?php echo h(($it['seat_no']?'S'.$it['seat_no'].' • ':'').$it['item_name']); ?>
                                            <?php if($splitQty > 1): ?><span class="badge text-bg-light border ms-1">Unit <?php echo (int)$unitNo; ?> of <?php echo (int)$splitQty; ?></span><?php endif; ?>
                                            <input type="hidden" name="item_unit_amount[<?php echo (int)$it['id']; ?>][<?php echo (int)$unitNo; ?>]" value="<?php echo h((string)$thisUnitAmount); ?>">
                                        </td>
                                        <td class="text-end"><?php echo money_fmt($thisUnitAmount); ?></td>
                                        <td><input name="item_unit_payer[<?php echo (int)$it['id']; ?>][<?php echo (int)$unitNo; ?>]" class="form-control form-control-sm item-unit-payer" placeholder="Guest / payer"></td>
                                        <td>
                                            <select name="item_unit_destination_type[<?php echo (int)$it['id']; ?>][<?php echo (int)$unitNo; ?>]" class="form-select form-select-sm split-destination item-unit-destination">
                                                <option value="cash">Cash</option><option value="card">Card</option><option value="pos">POS</option><option value="mobile_money">Mobile Money</option><option value="bank_transfer">Bank Transfer</option><option value="room_folio">Room Folio</option><option value="guest_folio">Guest Folio</option><option value="master_folio">Master Folio</option><option value="group_master">Group Master</option><option value="company_account">Company Account</option><option value="comp">Comp</option><option value="dispute">Dispute</option>
                                            </select>
                                        </td>
                                        <td><select name="item_unit_folio_id[<?php echo (int)$it['id']; ?>][<?php echo (int)$unitNo; ?>]" class="form-select form-select-sm split-folio-select item-unit-folio"><?php echo $folioSelectHtml; ?></select></td>
                                        <td><input name="item_unit_reference[<?php echo (int)$it['id']; ?>][<?php echo (int)$unitNo; ?>]" class="form-control form-control-sm item-unit-reference"></td>
                                    </tr>
                                    <?php endfor; ?>
                                <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>
                    </div>

                    <div class="tab-pane fade" id="splitMixed">
                        <input type="radio" class="btn-check" name="split_type" id="splitTypeMixed" value="mixed">
                        <label class="btn btn-sm btn-outline-primary mb-2" for="splitTypeMixed">Use Mixed Settlement</label>
                        <div class="small text-muted mb-2">Real-world examples: part to Group Master, part to Room 201, remainder cash/card; or company pays 80%, guest pays 20%.</div>
                        <div id="customSplitRows">
                            <div class="row g-2 mb-2 custom-row">
                                <div class="col-md-2"><input name="custom_payer[]" class="form-control" placeholder="Payer / name"></div>
                                <div class="col-md-1"><input name="custom_percent[]" type="number" step="0.01" class="form-control custom-percent" placeholder="%"></div>
                                <div class="col-md-2"><input name="custom_amount[]" type="number" step="0.01" class="form-control custom-amount" placeholder="Amount"></div>
                                <div class="col-md-2">
                                    <select name="custom_destination_type[]" class="form-select split-destination">
                                        <option value="cash">Cash</option><option value="card">Card</option><option value="pos">POS</option><option value="mobile_money">Mobile Money</option><option value="bank_transfer">Bank Transfer</option>
                                        <option value="room_folio">Room Folio</option><option value="guest_folio">Guest Folio</option><option value="master_folio">Master Folio</option><option value="group_master">Group Master</option><option value="company_account">Company Account</option>
                                        <option value="comp">Comp</option><option value="house_account">House Account</option><option value="dispute">Dispute Hold</option><option value="package">Package / Allowance</option>
                                    </select>
                                </div>
                                <div class="col-md-2"><select name="custom_folio_id[]" class="form-select split-folio-select"><?php echo $folioSelectHtml; ?></select></div>
                                <div class="col-md-2"><input name="custom_reference[]" class="form-control" placeholder="Reference"></div>
                                <div class="col-md-1"><button type="button" class="btn btn-outline-danger remove-custom-row">×</button></div>
                            </div>
                        </div>
                        <button type="button" class="btn btn-sm btn-outline-primary" id="addCustomSplitRow">Add Settlement Line</button>
                    </div>
                </div>

                <hr>
                <div class="settlement-preview-panel">
                    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
                        <div>
                            <h6 class="mb-0"><i class="bi bi-printer me-1"></i>Thermal Preview Before Posting</h6>
                            <div class="small text-muted">Preview the master bill and individual split bills before final posting/printing. Designed for 80mm thermal printers.</div>
                        </div>
                        <div class="d-flex flex-wrap gap-2">
                            <button type="button" class="btn btn-sm btn-outline-dark" id="refreshSettlementPreview"><i class="bi bi-arrow-clockwise me-1"></i>Refresh Preview</button>
                            <button type="button" class="btn btn-sm btn-outline-primary" id="printMasterPreview"><i class="bi bi-receipt me-1"></i>Preview Master Bill</button>
                            <button type="button" class="btn btn-sm btn-outline-success" id="printSplitPreview"><i class="bi bi-receipt-cutoff me-1"></i>Preview Split Bills</button>
                        </div>
                    </div>
                    <div class="row g-3">
                        <div class="col-lg-5">
                            <div class="thermal-preview-card">
                                <div class="thermal-preview-title">MASTER BILL</div>
                                <pre id="thermalMasterPreview" class="thermal-preview-pre"></pre>
                            </div>
                        </div>
                        <div class="col-lg-7">
                            <div class="thermal-preview-card">
                                <div class="thermal-preview-title">INDIVIDUAL SPLIT BILLS</div>
                                <div id="thermalSplitPreview" class="thermal-split-list"></div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="form-check mt-3">
                    <input class="form-check-input" type="checkbox" name="print_split_bills_after_post" value="1" id="printSplitBillsAfterPost" checked>
                    <label class="form-check-label fw-semibold" for="printSplitBillsAfterPost">
                        Post Print: print separate room/folio bills after posting split
                    </label>
                    <div class="form-text">For split by item, each room/folio receives only the items assigned to that room/folio.</div>
                </div>

                <?php if($splitRows): ?>
                    <hr><h6>Existing split records</h6>
                    <?php foreach($splitRows as $sr): ?>
                        <div class="d-flex justify-content-between small border-bottom py-1">
                            <span><?php echo h(ucfirst($sr['split_type']).' • '.($sr['payer_name'] ?: 'Payer').' • '.($sr['payment_method'] ?: '')); ?></span>
                            <strong><?php echo money_fmt($sr['split_amount']); ?></strong>
                        </div>
                    <?php endforeach; ?>
                <?php endif; ?>
            </div>
            <div class="modal-footer"><button class="btn btn-success">Post Split / Settlement</button></div>
        </form>
    </div>
</div>

<div class="modal fade" id="transferCheckModal" tabindex="-1">
    <div class="modal-dialog modal-xl">
        <form method="post" class="modal-content">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="transfer_check">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title">Check / Item Transfer</h5>
                    <div class="small text-muted">Move a full check, selected items, or selected seats to another table. An audit record is kept.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="row g-3 mb-3">
                    <div class="col-md-4">
                        <label class="form-label">Transfer Type</label>
                        <select name="transfer_type" class="form-select">
                            <option value="full_check">Full check to another table</option>
                            <option value="items">Selected items to another/new check</option>
                            <option value="seats">Selected seats to another/new check</option>
                        </select>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Target Table</label>
                        <select name="target_table_id" class="form-select" required>
                            <option value="">Select table...</option>
                            <?php foreach($tables as $t): ?>
                                <option value="<?php echo (int)$t['id']; ?>">
                                    <?php echo h($t['table_no'].' • '.$t['area_name'].' • '.(int)$t['seats'].' seats'.(!empty($t['open_order_id'])?' • has open check':'')); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Transfer Notes</label>
                        <input name="transfer_notes" class="form-control" placeholder="Guest moved to bar, server shift end...">
                    </div>
                </div>

                <div class="row g-3">
                    <div class="col-lg-7">
                        <h6>Transfer selected items</h6>
                        <div class="table-responsive" style="max-height:300px;overflow:auto">
                            <table class="table table-sm align-middle">
                                <thead><tr><th></th><th>Item</th><th>Seat</th><th class="text-end">Amount</th></tr></thead>
                                <tbody>
                                <?php foreach($orderItems as $it): if($it['status']!=='active') continue; ?>
                                    <tr>
                                        <td><input type="checkbox" name="transfer_item_ids[]" value="<?php echo (int)$it['id']; ?>"></td>
                                        <td><?php echo h($it['qty'].' x '.$it['item_name']); ?></td>
                                        <td><?php echo !empty($it['seat_no'])?'Seat '.(int)$it['seat_no']:'-'; ?></td>
                                        <td class="text-end"><?php echo money_fmt((float)$it['line_total']+(float)$it['tax_amount']); ?></td>
                                    </tr>
                                <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>
                    </div>
                    <div class="col-lg-5">
                        <h6>Or transfer selected seats</h6>
                        <div class="row g-2">
                            <?php for($sn=1;$sn<=min(65,max(1,(int)($order['covers']??1)));$sn++): ?>
                                <div class="col-4">
                                    <label class="merge-table-option">
                                        <input type="checkbox" name="transfer_seats[]" value="<?php echo $sn; ?>">
                                        <strong>Seat <?php echo $sn; ?></strong>
                                    </label>
                                </div>
                            <?php endfor; ?>
                        </div>
                        <div class="alert alert-light border small mt-3">
                            For <strong>Full check</strong>, item selections are ignored. For <strong>Selected seats</strong>, all active items assigned to those seats are moved.
                        </div>
                    </div>
                </div>
            </div>
            <div class="modal-footer"><button class="btn btn-warning">Transfer Check / Items</button></div>
        </form>
    </div>
</div>

<div class="modal fade" id="mergeTablesModal" tabindex="-1">
    <div class="modal-dialog modal-lg">
        <form method="post" class="modal-content">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="merge_tables">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header"><h5 class="modal-title">Merge / Join Tables</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
            <div class="modal-body">
                <div class="alert alert-light border small">Current table group: <strong><?php echo h($orderTableSummary !== '' ? $orderTableSummary : $order['table_no']); ?></strong><?php echo $orderMergedCapacity>0?' • Current capacity '.(int)$orderMergedCapacity:''; ?>. Select additional tables for group seating.</div>
                <div class="row g-2">
                <?php foreach($tables as $t): $openId=(int)($t['open_order_id']??0); $belongs=false; foreach($orderTables as $ot){ if((int)$ot['id']===(int)$t['id']){$belongs=true; break;} } if($openId>0 && $openId!==(int)$order['id'] && !$belongs) continue; ?>
                    <div class="col-md-4"><label class="merge-table-option"><input type="checkbox" name="table_ids[]" value="<?php echo (int)$t['id']; ?>" <?php echo ($belongs || (int)$t['id']===(int)$order['table_id'])?'checked':''; ?>> <strong><?php echo h($t['table_no']); ?></strong> <span><?php echo (int)$t['seats']; ?> seats</span></label></div>
                <?php endforeach; ?>
                </div>
            </div>
            <div class="modal-footer"><button class="btn btn-primary">Merge Selected Tables</button></div>
        </form>
    </div>
</div>
<div class="modal fade" id="roomChargeModal" tabindex="-1">
    <div class="modal-dialog modal-xl modal-dialog-centered">
        <form method="post" class="modal-content" id="roomChargeForm">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="close_room_charge">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title">Post Restaurant Bill To Lodge Account</h5>
                    <div class="small text-muted">Choose exactly where this restaurant charge must go. Room/Guest accounts are the normal choice - Master requires Admin PIN.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="row g-3">
                    <div class="col-md-12">
                        <label class="form-label">Booking / Lodge Guest</label>
                        <select name="linked_order_id" class="form-select" id="linkedOrder" required <?php echo ($order && !empty($order['linked_order_id'])) ? 'readonly data-locked-linked-booking="1"' : ''; ?>>
                            <?php if(!($order && !empty($order['linked_order_id']))): ?><option value="">Select booking</option><?php endif; ?>
                            <?php foreach($bookingMatches as $b):?>
                                <?php $bookingSelected = $order && (int)($order['linked_order_id'] ?? 0) === (int)$b['id']; ?>
                                <option value="<?php echo (int)$b['id'];?>" <?php echo $bookingSelected ? 'selected' : ''; ?>>
                                    <?php echo h(($b['order_no']?:'#'.$b['id']).' • '.$b['guest'].' • '.$b['property_name'].' • '.$b['check_in'].' to '.$b['check_out']);?>
                                </option>
                            <?php endforeach;?>
                        </select>
                    </div>

                    <div class="col-md-12">
                        <label class="form-label">Post To</label>
                        <select name="post_to" id="restaurant_post_to" class="form-select form-select-lg">
                            <?php if(!empty($roomChargeRoomFolios)): ?>
                                <optgroup label="Room Folios (recommended)">
                                    <?php foreach($roomChargeRoomFolios as $r): ?>
                                        <?php $roomPayload = $r; $roomPayload['target_type'] = 'room'; ?>
                                        <option value='<?php echo h(json_encode($roomPayload, JSON_HEX_APOS | JSON_HEX_QUOT)); ?>'
                                                data-target-type="room"
                                                data-balance-usd="<?php echo h((string)round((float)($roomChargeRoomBalances[(int)($r['rooming_id'] ?? 0)] ?? 0), 2)); ?>">
                                            Room <?php echo h($r['room_no'] ?: 'N/A'); ?>
                                            <?php if(!empty($r['room_type_title'])): ?> • <?php echo h($r['room_type_title']); ?><?php endif; ?>
                                            - <?php echo h(trim((string)$r['guest']) !== '' ? $r['guest'] : 'Guest'); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </optgroup>
                            <?php endif; ?>

                            <?php if(!empty($roomChargeGuestFolios)): ?>
                                <optgroup label="Individual Guest Folios (recommended)">
                                    <?php foreach($roomChargeGuestFolios as $gf): ?>
                                        <?php
                                            $guestPayload = [
                                                'target_type' => 'guest',
                                                'folio_id' => (int)$gf['folio_id'],
                                                'rooming_id' => (int)($gf['rooming_id'] ?? 0),
                                                'occupant_id' => (int)($gf['occupant_id'] ?? 0),
                                            ];
                                            $guestNameOpt = trim((string)($gf['occupant_name'] ?? ''));
                                            if($guestNameOpt === '') $guestNameOpt = trim((string)($gf['folio_name'] ?? 'Guest Folio'));
                                        ?>
                                        <option value='<?php echo h(json_encode($guestPayload, JSON_HEX_APOS | JSON_HEX_QUOT)); ?>'
                                                data-target-type="guest"
                                                data-balance-usd="<?php echo h((string)round((float)($roomChargeGuestBalances[(int)$gf['folio_id']] ?? (float)($gf['balance'] ?? 0)), 2)); ?>">
                                            Guest: <?php echo h($guestNameOpt); ?>
                                            <?php if(!empty($gf['room_no'])): ?> • Room <?php echo h($gf['room_no']); ?><?php endif; ?>
                                            <?php if(!empty($gf['room_type_title'])): ?> • <?php echo h($gf['room_type_title']); ?><?php endif; ?>
                                        </option>
                                    <?php endforeach; ?>
                                </optgroup>
                            <?php endif; ?>

                            <optgroup label="Master Account (requires Admin PIN)">
                                <option value="master" data-target-type="master" data-balance-usd="<?php echo h((string)round((float)($roomChargeMasterFolio['balance'] ?? 0), 2)); ?>">
                                    ⚠ Master Folio (Agent / Master Account)
                                </option>
                            </optgroup>
                        </select>
                        <div class="form-text">
                            Room/Guest accounts are the normal way to post a restaurant charge and can be selected by any waiter/cashier. <strong>Master Folio is the exception</strong> - it bills the whole group/company, not this individual guest, so it requires Admin PIN authorisation below.
                        </div>
                    </div>

                    <div class="col-md-12" id="restaurantPostToAdminPinWrap" style="display:none;">
                        <div class="alert alert-danger py-2 px-3 mb-2">
                            <i class="bi bi-exclamation-triangle-fill me-1"></i>
                            You selected the <strong>Master Account</strong>. This posts the charge to the whole group/company, not this individual guest. Enter the <strong>Admin PIN</strong> to confirm this is intended.
                        </div>
                        <label class="form-label fw-semibold">Admin PIN</label>
                        <input type="password" name="admin_pin" id="restaurantPostToAdminPin" class="form-control form-control-lg" autocomplete="off" placeholder="Enter Admin PIN to post to Master Account">
                    </div>
                </div>

                <div class="card border-0 shadow-sm mb-3 mt-3">
                    <div class="card-header bg-light d-flex justify-content-between align-items-center">
                        <div>
                            <strong>Items to Post</strong>
                            <div class="small text-muted">Tick one or more items to post only those items to the selected room/account. Leave all unticked to post the full bill and close the table.</div>
                        </div>
                        <button type="button" class="btn btn-sm btn-outline-secondary" id="toggleRoomPostItems">Select / Clear All</button>
                    </div>
                    <div class="card-body p-2" style="max-height:260px;overflow:auto;">
                        <?php foreach($orderItems as $postIt): if(($postIt['status'] ?? '') !== 'active') continue; ?>
                            <label class="d-flex align-items-center gap-2 border-bottom py-2 px-2 mb-0 room-post-item-row">
                                <input class="form-check-input room-post-item-check" type="checkbox" name="post_item_ids[]" value="<?php echo (int)$postIt['id']; ?>" data-amount="<?php echo h((string)round((float)($postIt['line_total'] ?? 0),2)); ?>">
                                <span class="flex-grow-1">
                                    <strong><?php echo h($postIt['item_name']); ?></strong>
                                    <span class="small text-muted d-block"><?php echo h(rtrim(rtrim(number_format((float)($postIt['qty'] ?? 1),2,'.',''),'0'),'.')); ?> x <?php echo money_fmt((float)($postIt['unit_price'] ?? 0)); ?></span>
                                </span>
                                <strong><?php echo money_fmt((float)($postIt['line_total'] ?? 0)); ?></strong>
                            </label>
                        <?php endforeach; ?>
                    </div>
                    <div class="card-footer bg-white d-flex justify-content-between small">
                        <span class="text-muted">Selected posting total</span>
                        <strong id="roomPostSelectedTotal">0.00</strong>
                    </div>
                </div>

                <div class="form-check">
                    <input class="form-check-input" type="checkbox" name="print_bill_after_post" value="1" id="printBillAfterRoomPost" checked>
                    <label class="form-check-label fw-semibold" for="printBillAfterRoomPost">Post Print: print bill for selected items after posting to folio</label>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                <button class="btn btn-primary"><i class="bi bi-printer me-1"></i>Post Print To Selected Account</button>
            </div>
        </form>
    </div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function(){
    var checks = Array.prototype.slice.call(document.querySelectorAll('.room-post-item-check'));
    var totalEl = document.getElementById('roomPostSelectedTotal');
    var toggleBtn = document.getElementById('toggleRoomPostItems');
    function money(v){ return (parseFloat(v || 0) || 0).toFixed(2); }
    function refreshRoomPostTotal(){
        var total = 0;
        checks.forEach(function(ch){ if(ch.checked) total += parseFloat(ch.dataset.amount || '0') || 0; });
        if(totalEl) totalEl.textContent = money(total);
    }
    checks.forEach(function(ch){ ch.addEventListener('change', refreshRoomPostTotal); });
    if(toggleBtn){
        toggleBtn.addEventListener('click', function(){
            var shouldCheck = checks.some(function(ch){ return !ch.checked; });
            checks.forEach(function(ch){ ch.checked = shouldCheck; });
            refreshRoomPostTotal();
        });
    }
    refreshRoomPostTotal();

    var postToSelect = document.getElementById('restaurant_post_to');
    var adminPinWrap = document.getElementById('restaurantPostToAdminPinWrap');
    var adminPinInput = document.getElementById('restaurantPostToAdminPin');
    var roomChargeFormEl = document.getElementById('roomChargeForm');

    function isMasterSelected(){
        if(!postToSelect) return false;
        var opt = postToSelect.options[postToSelect.selectedIndex];
        return !!(opt && opt.getAttribute('data-target-type') === 'master');
    }

    function refreshAdminPinRequirement(){
        var masterSelected = isMasterSelected();
        if(adminPinWrap) adminPinWrap.style.display = masterSelected ? '' : 'none';
        if(adminPinInput) adminPinInput.required = masterSelected;
        if(!masterSelected && adminPinInput) adminPinInput.value = '';
    }

    if(postToSelect){
        postToSelect.addEventListener('change', refreshAdminPinRequirement);
        refreshAdminPinRequirement();
    }

    if(roomChargeFormEl){
        roomChargeFormEl.addEventListener('submit', function(e){
            if(isMasterSelected()){
                if(!adminPinInput || !adminPinInput.value.trim()){
                    e.preventDefault();
                    alert('Posting to the Master Account requires the Admin PIN. Enter the PIN, or choose the guest\'s Room/Guest Account instead.');
                    if(adminPinInput) adminPinInput.focus();
                    return false;
                }
                if(!confirm('This will post the charge to the MASTER account, billing the whole group/company rather than this individual guest. Continue?')){
                    e.preventDefault();
                    return false;
                }
            }
        });
    }

    var itemChecks = Array.prototype.slice.call(document.querySelectorAll('.item-unit-check'));
    var itemTotalEl = document.getElementById('itemSplitSelectedTotal');
    var toggleItemBtn = document.getElementById('toggleItemSplitUnits');
    var bulkApplyBtn = document.getElementById('applyBulkItemSplit');
    var bulkDest = document.getElementById('bulkItemDestination');
    var bulkFolio = document.getElementById('bulkItemFolio');

    function wantedFolioType(dest){
        dest = String(dest || '').toLowerCase();
        if(dest === 'room_folio') return 'room';
        if(dest === 'guest_folio') return 'guest';
        if(['master_folio','group_master','company_account','folio'].indexOf(dest) >= 0) return 'master';
        return '';
    }

    function refreshBulkFolioOptions(){
        if(!bulkDest || !bulkFolio) return;
        var wanted = wantedFolioType(bulkDest.value);
        var visible = 0;
        Array.prototype.slice.call(bulkFolio.options).forEach(function(opt){
            if(!opt.value){ opt.hidden = false; return; }
            var ok = !wanted || String(opt.dataset.folioType || '').toLowerCase() === wanted;
            opt.hidden = !ok;
            if(ok) visible++;
            if(!ok && opt.selected) bulkFolio.value = '';
        });
        var help = document.getElementById('bulkItemFolioHelp');
        if(help){
            help.textContent = wanted ? (visible ? 'Showing '+wanted+' accounts only. Apply once to all ticked items.' : 'No '+wanted+' accounts found for the available bookings.') : 'No folio is required for this settlement type.';
        }
    }

    function refreshItemSplitSelectedTotal(){
        var total = 0;
        itemChecks.forEach(function(ch){ if(ch.checked) total += parseFloat(ch.dataset.amount || '0') || 0; });
        if(itemTotalEl) itemTotalEl.textContent = money(total);
    }

    itemChecks.forEach(function(ch){ ch.addEventListener('change', refreshItemSplitSelectedTotal); });

    if(toggleItemBtn){
        toggleItemBtn.addEventListener('click', function(){
            var shouldCheck = itemChecks.some(function(ch){ return !ch.checked; });
            itemChecks.forEach(function(ch){ ch.checked = shouldCheck; });
            refreshItemSplitSelectedTotal();
        });
    }

    if(bulkDest){
        bulkDest.addEventListener('change', refreshBulkFolioOptions);
        refreshBulkFolioOptions();
    }

    if(bulkApplyBtn){
        bulkApplyBtn.addEventListener('click', function(){
            var selected = itemChecks.filter(function(ch){ return ch.checked; });
            if(!selected.length){ alert('Tick one or more item units first.'); return; }

            var payer = (document.getElementById('bulkItemPayer')?.value || '').trim();
            var dest = bulkDest ? bulkDest.value : 'room_folio';
            var folioId = bulkFolio ? bulkFolio.value : '';
            var ref = (document.getElementById('bulkItemReference')?.value || '').trim();
            var needsFolio = wantedFolioType(dest) !== '';

            if(needsFolio && !folioId){ alert('Select the Room / Guest / Master account to post to.'); return; }
            if(!payer){
                if(bulkFolio && bulkFolio.selectedIndex >= 0){
                    payer = String(bulkFolio.options[bulkFolio.selectedIndex].text || '').replace(/\s+/g,' ').trim();
                }
                if(!payer) payer = dest.replace(/_/g,' ').replace(/\b\w/g,function(c){ return c.toUpperCase(); });
            }

            selected.forEach(function(ch){
                var row = ch.closest('.item-unit-row');
                if(!row) return;
                var payerEl = row.querySelector('.item-unit-payer');
                var destEl = row.querySelector('.item-unit-destination');
                var folioEl = row.querySelector('.item-unit-folio');
                var refEl = row.querySelector('.item-unit-reference');
                if(payerEl) payerEl.value = payer;
                if(destEl){ destEl.value = dest; destEl.dispatchEvent(new Event('change', {bubbles:true})); }
                if(folioEl && folioId) folioEl.value = folioId;
                if(refEl && ref !== '') refEl.value = ref;
            });

            try{ if(window.restaurantSettlementPreviewRefresh) window.restaurantSettlementPreviewRefresh(); }catch(e){}
            var refresh=document.getElementById('refreshSettlementPreview');
            if(refresh) refresh.dispatchEvent(new Event('click',{bubbles:true}));
            alert('Applied the selected account to '+selected.length+' item unit(s). You can now Post Split / Settlement.');
        });
    }
    refreshItemSplitSelectedTotal();
});
</script>

<div class="modal fade" id="costCenterModal" tabindex="-1">
    <div class="modal-dialog modal-xl modal-dialog-centered">
        <form method="post" class="modal-content" id="costCenterForm">
            <?php if(function_exists('csrf_field')) echo csrf_field();?>
            <input type="hidden" name="action" value="close_cost_center">
            <input type="hidden" name="restaurant_order_id" value="<?php echo (int)$order['id'];?>">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title">Post To Costing Department</h5>
                    <div class="small text-muted">Write off this meal (or selected items) to an internal department instead of a guest account. The table always closes after posting. Requires supervisor PIN.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="row g-3">
                    <div class="col-md-6">
                        <label class="form-label">Costing Department</label>
                        <select name="cost_center_id" class="form-select form-select-lg" required>
                            <option value="">Select department</option>
                            <?php foreach($costCenters as $cc): ?>
                                <option value="<?php echo (int)$cc['id']; ?>"><?php echo h($cc['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                        <div class="form-text"><a href="pp.php?cost_centers=1#costCenterSetupCard" target="_blank">Manage departments</a> (add/remove from the setup card below the printer settings).</div>
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">Supervisor PIN</label>
                        <input type="password" name="supervisor_pin" class="form-control form-control-lg" autocomplete="off" required>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Discount</label>
                        <select name="cost_center_discount_type" id="costCenterDiscountType" class="form-select">
                            <option value="none">No discount</option>
                            <option value="amount">Amount off</option>
                            <option value="percent">Percentage off</option>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">Value</label>
                        <input type="number" step="0.01" min="0" name="cost_center_discount_value" id="costCenterDiscountValue" class="form-control" value="0" disabled>
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">Reason</label>
                        <select name="cost_center_reason" class="form-select" required>
                            <option value="">Select reason</option>
                            <option>Staff meal</option>
                            <option>Management taste test / tasting</option>
                            <option>Director's request</option>
                            <option>Marketing / promotional shoot</option>
                            <option>Guest recovery goodwill</option>
                            <option>Training</option>
                            <option>Other internal use</option>
                        </select>
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">Notes</label>
                        <input type="text" name="cost_center_note" class="form-control" placeholder="Optional detail">
                    </div>
                </div>

                <div class="card border-0 shadow-sm mb-3 mt-3">
                    <div class="card-header bg-light d-flex justify-content-between align-items-center">
                        <div>
                            <strong>Items to Post</strong>
                            <div class="small text-muted">Tick specific items to base the amount/discount on just those - the table still closes fully either way. Leave all unticked to use the full bill.</div>
                        </div>
                        <button type="button" class="btn btn-sm btn-outline-secondary" id="toggleCostCenterItems">Select / Clear All</button>
                    </div>
                    <div class="card-body p-2" style="max-height:260px;overflow:auto;">
                        <?php foreach($orderItems as $ccIt): if(($ccIt['status'] ?? '') !== 'active') continue; ?>
                            <label class="d-flex align-items-center gap-2 border-bottom py-2 px-2 mb-0 cost-center-item-row">
                                <input class="form-check-input cost-center-item-check" type="checkbox" name="post_item_ids[]" value="<?php echo (int)$ccIt['id']; ?>" data-amount="<?php echo h((string)round((float)($ccIt['line_total'] ?? 0),2)); ?>">
                                <span class="flex-grow-1">
                                    <strong><?php echo h($ccIt['item_name']); ?></strong>
                                    <span class="small text-muted d-block"><?php echo h(rtrim(rtrim(number_format((float)($ccIt['qty'] ?? 1),2,'.',''),'0'),'.')); ?> x <?php echo money_fmt((float)($ccIt['unit_price'] ?? 0)); ?></span>
                                </span>
                                <strong><?php echo money_fmt((float)($ccIt['line_total'] ?? 0)); ?></strong>
                            </label>
                        <?php endforeach; ?>
                    </div>
                    <div class="card-footer bg-white">
                        <div class="d-flex justify-content-between small">
                            <span class="text-muted">Selected/full posting total</span>
                            <strong id="costCenterSelectedTotal">0.00</strong>
                        </div>
                        <div class="d-flex justify-content-between small text-danger" id="costCenterDiscountRow" style="display:none!important;">
                            <span>Discount</span>
                            <strong id="costCenterDiscountAmount">0.00</strong>
                        </div>
                        <div class="d-flex justify-content-between mt-1">
                            <span class="fw-semibold">Amount charged to department</span>
                            <strong id="costCenterFinalTotal">0.00</strong>
                        </div>
                    </div>
                </div>

                <div class="form-check">
                    <input class="form-check-input" type="checkbox" name="print_bill_after_post" value="1" id="printBillAfterCostCenter" checked>
                    <label class="form-check-label fw-semibold" for="printBillAfterCostCenter">Post Print: print bill for selected items after posting to department</label>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                <button class="btn btn-dark"><i class="bi bi-diagram-3 me-1"></i>Post To Costing Department (Closes Table)</button>
            </div>
        </form>
    </div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function(){
    var ccChecks = Array.prototype.slice.call(document.querySelectorAll('.cost-center-item-check'));
    var ccTotalEl = document.getElementById('costCenterSelectedTotal');
    var ccToggleBtn = document.getElementById('toggleCostCenterItems');
    function ccMoney(v){ return (parseFloat(v || 0) || 0).toFixed(2); }
    function refreshCostCenterTotal(){
        var total = 0;
        var anyChecked = ccChecks.some(function(ch){ return ch.checked; });
        if(anyChecked){
            ccChecks.forEach(function(ch){ if(ch.checked) total += parseFloat(ch.dataset.amount || '0') || 0; });
        }else{
            total = <?php echo json_encode(round((float)($order['total_amount'] ?? 0), 2)); ?>;
        }
        if(ccTotalEl) ccTotalEl.textContent = ccMoney(total);

        var discountTypeEl = document.getElementById('costCenterDiscountType');
        var discountValueEl = document.getElementById('costCenterDiscountValue');
        var discountRow = document.getElementById('costCenterDiscountRow');
        var discountAmountEl = document.getElementById('costCenterDiscountAmount');
        var finalTotalEl = document.getElementById('costCenterFinalTotal');

        var discountType = discountTypeEl ? discountTypeEl.value : 'none';
        var discountValue = discountValueEl ? (parseFloat(discountValueEl.value || '0') || 0) : 0;
        var discountAmount = 0;
        if(discountType === 'amount'){
            discountAmount = Math.min(total, discountValue);
        }else if(discountType === 'percent'){
            discountAmount = total * (Math.min(100, discountValue) / 100);
        }
        var finalTotal = Math.max(0, total - discountAmount);

        if(discountRow) discountRow.style.setProperty('display', discountAmount > 0 ? 'flex' : 'none', 'important');
        if(discountAmountEl) discountAmountEl.textContent = '-' + ccMoney(discountAmount);
        if(finalTotalEl) finalTotalEl.textContent = ccMoney(finalTotal);
    }
    ccChecks.forEach(function(ch){ ch.addEventListener('change', refreshCostCenterTotal); });
    if(ccToggleBtn){
        ccToggleBtn.addEventListener('click', function(){
            var shouldCheck = ccChecks.some(function(ch){ return !ch.checked; });
            ccChecks.forEach(function(ch){ ch.checked = shouldCheck; });
            refreshCostCenterTotal();
        });
    }

    var ccDiscountType = document.getElementById('costCenterDiscountType');
    var ccDiscountValue = document.getElementById('costCenterDiscountValue');
    if(ccDiscountType){
        ccDiscountType.addEventListener('change', function(){
            if(ccDiscountValue) ccDiscountValue.disabled = (ccDiscountType.value === 'none');
            if(ccDiscountType.value === 'none' && ccDiscountValue) ccDiscountValue.value = 0;
            refreshCostCenterTotal();
        });
    }
    if(ccDiscountValue){
        ccDiscountValue.addEventListener('input', refreshCostCenterTotal);
    }

    refreshCostCenterTotal();
});
</script>

<?php endif;?>
<style>
.simphony-workspace{display:grid;grid-template-columns:minmax(420px,1.2fr) minmax(360px,.9fr) minmax(320px,.75fr);gap:14px;align-items:stretch}.simphony-panel{background:#fff;border:1px solid #e2e8f0;border-radius:22px;box-shadow:0 14px 36px rgba(15,23,42,.08);overflow:hidden}.simphony-panel-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid #e2e8f0;background:linear-gradient(135deg,#ffffff,#f8fafc)}.simphony-panel-head.dark{background:linear-gradient(135deg,#0f172a,#1e3a8a);color:#fff;border-bottom:0}.simphony-panel-head h5{margin:0;font-weight:900}.simphony-kicker{text-transform:uppercase;letter-spacing:.12em;font-size:.67rem;font-weight:900;color:#64748b}.simphony-panel-head.dark .simphony-kicker{color:#bfdbfe}.simphony-pill{font-size:.75rem;font-weight:800;background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:999px;padding:6px 9px}.simphony-total{font-size:1.45rem;font-weight:950}.simphony-floor-panel{min-height:710px}.simphony-floor{height:650px;border:0;border-radius:0;background:radial-gradient(circle at 24px 24px,#dbeafe 2px,transparent 3px),linear-gradient(135deg,#f8fafc,#eef2ff);background-size:34px 34px,100% 100%;margin:0}.layout-guide.compact{margin:12px 14px 0;padding:10px;border-radius:16px;background:#f8fafc;border:1px solid #e2e8f0}.simphony-check-panel{min-height:710px;display:flex;flex-direction:column}.simphony-check-meta{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;padding:14px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.simphony-check-meta div{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:9px}.simphony-check-meta span{display:block;font-size:.68rem;text-transform:uppercase;letter-spacing:.08em;color:#64748b;font-weight:900}.simphony-check-meta strong{display:block;color:#0f172a;font-size:.9rem}.simphony-action-row{display:grid;grid-template-columns:1fr auto;gap:8px;padding:12px 14px;border-bottom:1px solid #e2e8f0}.simphony-lines{padding:10px 14px;overflow:auto;max-height:350px;flex:1}.simphony-line{display:flex;justify-content:space-between;gap:10px;padding:10px 0;border-bottom:1px dashed #e2e8f0}.simphony-line.is-void{opacity:.45;text-decoration:line-through}.simphony-line-main strong{display:block;color:#0f172a}.simphony-line-main span{display:block;font-size:.75rem;color:#64748b}.split-allocation-badges{margin-top:5px}.split-allocation-badges .badge{font-size:.68rem;border-radius:999px}.simphony-line-amt{text-align:right;min-width:90px}.line-void-modal-btn{font-size:.72rem;border-radius:999px;font-weight:800}.simphony-close-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:14px;border-top:1px solid #e2e8f0;background:#f8fafc}.simphony-walkin-note{grid-column:1/-1}.simphony-empty{min-height:360px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:#64748b;padding:24px;gap:8px}.simphony-empty i{font-size:2.4rem;color:#2563eb}.simphony-empty strong{font-size:1.05rem;color:#0f172a}.small-empty{min-height:180px}.small-empty i{font-size:1.7rem}.simphony-menu-panel{min-height:710px}.simphony-seat-strip{display:flex;align-items:center;gap:8px;padding:12px 14px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.simphony-seat-strip label{font-weight:900;color:#0f172a}.simphony-quick-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:14px;max-height:615px;overflow:auto}.quick-pad-toolbar{display:flex;align-items:center;gap:6px;padding:10px 14px 0}.quick-pad-toolbar span{font-size:.76rem;font-weight:900;color:#334155;background:#f8fafc;border:1px solid #e2e8f0;border-radius:999px;padding:5px 9px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.quick-item-form{margin:0}.quick-item-btn{width:100%;min-height:82px;border:1px solid #dbeafe;background:linear-gradient(135deg,#eff6ff,#fff);border-radius:16px;padding:10px;text-align:left;box-shadow:0 8px 18px rgba(15,23,42,.05);transition:.14s}.quick-item-btn:hover{transform:translateY(-1px);box-shadow:0 14px 24px rgba(37,99,235,.14);border-color:#93c5fd}.quick-item-btn strong{display:block;font-size:.82rem;color:#0f172a;line-height:1.15;min-height:34px}.quick-item-btn span{display:block;margin-top:8px;color:#16a34a;font-weight:950}.quick-cat-btn strong{color:#fff}.quick-cat-btn span{color:rgba(255,255,255,.92)!important}@media(max-width:1500px){.simphony-workspace{grid-template-columns:1fr 420px}.simphony-menu-panel{grid-column:1/-1;min-height:auto}.simphony-quick-grid{grid-template-columns:repeat(4,minmax(0,1fr));max-height:none}}@media(max-width:992px){.simphony-workspace{grid-template-columns:1fr}.simphony-floor{height:560px}.simphony-floor-panel,.simphony-check-panel,.simphony-menu-panel{min-height:auto}.simphony-quick-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* Active order layout: collapse the floor plan fully left and give working space to Active Check + Menu Pad */
.simphony-workspace.has-active-order{
    grid-template-columns:54px minmax(400px,.85fr) minmax(560px,1.25fr);
    align-items:start;
}
.simphony-workspace.has-active-order .simphony-floor-panel{
    position:sticky;
    top:76px;
    min-height:calc(100vh - 190px);
    align-self:start;
    overflow:hidden;
    border-radius:18px;
}
.simphony-workspace.has-active-order .simphony-floor-panel .simphony-panel-head{
    height:100%;
    min-height:calc(100vh - 190px);
    padding:10px 6px;
    display:flex;
    justify-content:center;
    align-items:flex-start;
    background:linear-gradient(180deg,#0f172a,#1d4ed8);
    color:#fff;
    writing-mode:vertical-rl;
    text-orientation:mixed;
}
.simphony-workspace.has-active-order .simphony-floor-panel .simphony-panel-head h5{
    font-size:.82rem;
    white-space:nowrap;
    margin:0;
}
.simphony-workspace.has-active-order .simphony-floor-panel .simphony-kicker{
    color:#bfdbfe;
    font-size:.58rem;
}
.simphony-workspace.has-active-order .simphony-floor-panel .simphony-pill,
.simphony-workspace.has-active-order .layout-guide,
.simphony-workspace.has-active-order .simphony-floor{
    display:none!important;
}
.simphony-workspace.has-active-order .simphony-floor-panel::after{
    content:'Table selected';
    position:absolute;
    bottom:12px;
    left:50%;
    transform:translateX(-50%) rotate(180deg);
    writing-mode:vertical-rl;
    color:#dbeafe;
    font-size:.68rem;
    font-weight:800;
    white-space:nowrap;
}
.simphony-workspace.has-active-order .simphony-check-panel,
.simphony-workspace.has-active-order .simphony-menu-panel{
    min-height:calc(100vh - 190px);
}
@media(max-width:1500px){
    .simphony-workspace.has-active-order{
        grid-template-columns:50px minmax(360px,.9fr) minmax(460px,1.1fr);
    }
    .simphony-workspace.has-active-order .simphony-menu-panel{
        grid-column:auto;
    }
}
@media(max-width:1180px){
    .simphony-workspace.has-active-order{
        grid-template-columns:1fr;
    }
    .simphony-workspace.has-active-order .simphony-floor-panel{
        display:none;
    }
}

/* Let cashier reopen the floor after a table/order is selected, then it collapses again after selecting another table. */
.simphony-workspace.has-active-order.floor-expanded{
    grid-template-columns:minmax(420px,1.15fr) minmax(360px,.9fr) minmax(420px,1fr);
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel{
    min-height:710px;
    overflow:visible;
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel .simphony-panel-head{
    height:auto;
    min-height:auto;
    padding:14px 16px;
    writing-mode:horizontal-tb;
    text-orientation:mixed;
    justify-content:space-between;
    align-items:center;
    background:linear-gradient(135deg,#ffffff,#f8fafc);
    color:#0f172a;
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel .simphony-panel-head h5{
    font-size:1.05rem;
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel .simphony-kicker{
    color:#64748b;
    font-size:.7rem;
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel .simphony-pill,
.simphony-workspace.has-active-order.floor-expanded .layout-guide,
.simphony-workspace.has-active-order.floor-expanded .simphony-floor{
    display:block!important;
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel::after{
    display:none;
}
.simphony-workspace.has-active-order.floor-expanded .active-floor-expand-btn{
    display:none;
}
.active-floor-expand-btn{
    writing-mode:horizontal-tb;
    white-space:nowrap;
    margin-top:10px;
    font-size:.72rem;
    font-weight:800;
}
.simphony-workspace.has-active-order .simphony-floor-panel{
    cursor:pointer;
}
.simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel{
    cursor:default;
}
@media(max-width:1500px){
    .simphony-workspace.has-active-order.floor-expanded{
        grid-template-columns:1fr 420px;
    }
    .simphony-workspace.has-active-order.floor-expanded .simphony-menu-panel{
        grid-column:1/-1;
    }
}
@media(max-width:1180px){
    .simphony-workspace.has-active-order.floor-expanded .simphony-floor-panel{
        display:block;
    }
}

.compact-restaurant-hero{padding:12px 16px!important;margin-bottom:10px!important}.compact-restaurant-hero .btn{margin-left:3px;margin-bottom:3px}.panel-card{border-radius:16px;box-shadow:0 12px 30px rgba(15,23,42,.07)}.restaurant-floor{height:560px;position:relative;background:linear-gradient(135deg,#f8fafc,#eef2ff);border:1px solid #e2e8f0;border-radius:18px;overflow:auto;background-size:150px 120px;background-image:linear-gradient(to right,rgba(15,23,42,.035) 1px,transparent 1px),linear-gradient(to bottom,rgba(15,23,42,.035) 1px,transparent 1px),linear-gradient(135deg,#f8fafc,#eef2ff)}.layout-guide{border:1px solid #bfdbfe;background:linear-gradient(135deg,#eff6ff,#ffffff);border-radius:14px;padding:10px 12px;color:#1e3a8a}.layout-guide-title{font-size:.82rem;font-weight:900;text-transform:uppercase;letter-spacing:.04em;margin-bottom:4px}.layout-guide-text{font-size:.78rem;color:#334155}.layout-guide-grid{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.layout-guide-grid span{background:#fff;border:1px solid #dbeafe;border-radius:999px;padding:4px 8px;font-size:.72rem;color:#0f172a}.table-tile{position:absolute;width:132px;height:96px;cursor:grab;user-select:none}.table-tile.is-dragging{opacity:.74;z-index:9999;cursor:grabbing}.table-tile .table-main-btn{width:100%;height:100%;border:0;border-radius:24px;background:#fff;box-shadow:0 12px 25px rgba(15,23,42,.12);display:flex;flex-direction:column;align-items:center;justify-content:center;color:#0f172a;padding:10px;position:relative}.table-tile.is-saving .table-main-btn{outline:3px solid rgba(59,130,246,.22)}.table-tile.is-busy .table-main-btn{background:linear-gradient(135deg,#fee2e2,#fff7ed);border:2px solid #ef4444}.table-tile.is-free .table-main-btn{border:2px solid #22c55e}.table-tile.shape-square .table-main-btn{border-radius:14px}.table-tile.shape-rect.orientation-horizontal{width:170px;height:96px}.table-tile.shape-rect.orientation-vertical{width:96px;height:170px}.table-tile.shape-rect .table-main-btn{border-radius:14px}.table-tile strong{font-size:1.15rem}.table-tile small,.table-tile span{font-size:.72rem}.table-client{font-weight:800;color:#334155;max-width:94%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-cashier{font-size:.62rem!important;color:#475569;background:#f1f5f9;border-radius:999px;padding:1px 6px;margin-top:2px;max-width:94%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-coords{font-style:normal;font-size:.58rem;color:#64748b;margin-top:3px}.table-rotate-btn{position:absolute;right:-7px;top:-7px;width:28px;height:28px;border-radius:999px;border:1px solid #cbd5e1;background:#fff;color:#0f172a;box-shadow:0 8px 16px rgba(15,23,42,.18);display:flex;align-items:center;justify-content:center;z-index:5}.table-rotate-btn:hover{background:#eff6ff;color:#1d4ed8}.layout-toast{position:fixed;right:18px;bottom:18px;z-index:99999;background:#0f172a;color:#fff;border-radius:12px;padding:10px 14px;font-size:.82rem;box-shadow:0 18px 45px rgba(15,23,42,.28);display:none}.layout-toast.is-show{display:block}.order-lines{max-height:330px;overflow:auto}.order-line{display:flex;justify-content:space-between;gap:10px;padding:10px;border-bottom:1px solid #eef2f7}.order-line.is-void{opacity:.45;text-decoration:line-through}.order-card{position:sticky;top:80px}
.merge-table-option{display:flex;align-items:center;gap:8px;border:1px solid #e2e8f0;border-radius:12px;padding:10px;background:#fff}.merge-table-option span{margin-left:auto;color:#64748b;font-size:.78rem}.payment-row,.custom-row{align-items:center}
.quick-modifier-header{background:linear-gradient(135deg,#0f172a,#1e40af);color:#fff}.quick-wizard-summary{border:1px solid #dbeafe;background:#eff6ff;border-radius:16px;padding:10px 12px;margin-bottom:12px}.quick-wizard-summary strong{display:block;color:#0f172a}.quick-wizard-summary span{font-size:.8rem;color:#475569;font-weight:800}.modifier-group-heading{display:flex;justify-content:space-between;align-items:flex-end;gap:12px;border-bottom:1px solid #e2e8f0;padding-bottom:8px;margin-bottom:10px}.modifier-group-heading h5{margin:0;font-weight:950;color:#0f172a}.quick-modifier-options{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:9px}.quick-modifier-option{display:flex;align-items:center;gap:10px;border:1px solid #e2e8f0;background:#fff;border-radius:14px;padding:10px;cursor:pointer;box-shadow:0 8px 18px rgba(15,23,42,.04)}.quick-modifier-option:hover{border-color:#93c5fd;background:#eff6ff}.quick-modifier-option input{transform:scale(1.15)}.quick-modifier-option div{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.quick-modifier-option strong{font-size:.9rem;color:#0f172a}.quick-modifier-option span{font-weight:950;color:#16a34a}.quick-modifier-option.child-option{margin-left:18px;background:#f8fafc}.quick-wizard-footer{background:#f8fafc;border-top:1px solid #e2e8f0}
.pos-action-strip{border:1px solid #dbeafe;background:linear-gradient(135deg,#eff6ff,#fff);border-radius:14px;padding:12px}.pos-modal-header{background:linear-gradient(135deg,#0f172a,#334155);color:#fff;border-bottom:0}.pos-menu-shell{display:grid;grid-template-columns:300px minmax(0,1fr) 340px;min-height:calc(100vh - 72px);background:#f8fafc}.pos-menu-sidebar,.pos-menu-order-panel{background:#fff;border-right:1px solid #e2e8f0;padding:16px;overflow:auto}.pos-menu-order-panel{border-right:0;border-left:1px solid #e2e8f0}.pos-search-box{display:flex;align-items:center;gap:8px;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:14px;padding:10px 12px}.pos-search-box input{border:0;background:transparent;outline:0;width:100%;font-weight:700}.pos-breadcrumb{font-size:.78rem;font-weight:900;color:#334155;background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:9px 10px;margin:12px 0}.pos-tag-strip{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}.pos-tag-pill{border:1px solid #cbd5e1;background:#fff;border-radius:999px;padding:5px 9px;font-size:.72rem;font-weight:800;color:#475569}.pos-tag-pill.active{background:#0f172a;color:#fff;border-color:#0f172a}.pos-category-btn{width:100%;border:1px solid #e2e8f0;background:#fff;border-radius:14px;padding:12px;margin-bottom:8px;text-align:left;display:flex;align-items:center;justify-content:space-between;box-shadow:0 8px 18px rgba(15,23,42,.04)}.pos-category-btn:hover,.pos-category-btn.active{border-color:#2563eb;background:#eff6ff}.pos-category-btn-colored strong{color:inherit}.pos-category-btn-colored:hover{filter:brightness(1.03);transform:translateY(-1px);box-shadow:0 12px 22px rgba(15,23,42,.12)}.pos-menu-main{padding:16px;overflow:auto}.pos-main-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:12px}.pos-item-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(175px,1fr));gap:12px}.pos-item-btn{min-height:118px;border:1px solid #dbeafe;background:#fff;border-radius:18px;padding:13px;text-align:left;box-shadow:0 12px 24px rgba(15,23,42,.06);display:flex;flex-direction:column;justify-content:space-between;transition:transform .14s ease,box-shadow .14s ease,border-color .14s ease}.pos-item-btn:hover{transform:translateY(-1px);box-shadow:0 16px 30px rgba(15,23,42,.11)}.pos-item-btn.active{outline:3px solid rgba(37,99,235,.30);border-color:#2563eb}.pos-item-btn.is-colored{color:#fff;border-color:rgba(15,23,42,.08);background:linear-gradient(135deg,var(--item-color),var(--item-color-dark))}.pos-item-btn.is-colored .pos-item-name,.pos-item-btn.is-colored .pos-item-desc,.pos-item-btn.is-colored .pos-price,.pos-item-btn.is-colored .pos-tags{color:#fff}.pos-item-btn.is-light-color{color:#0f172a}.pos-item-btn.is-light-color .pos-item-name,.pos-item-btn.is-light-color .pos-item-desc,.pos-item-btn.is-light-color .pos-tags{color:#0f172a}.pos-item-btn.is-light-color .pos-price{color:#14532d}.pos-item-name{font-size:.96rem;font-weight:900;color:#0f172a}.pos-item-desc{font-size:.72rem;color:#64748b;line-height:1.25;max-height:36px;overflow:hidden}.pos-item-footer{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:8px}.pos-price{font-weight:950;color:#16a34a}.pos-tags{font-size:.68rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.selected-item-card{border:1px solid #e2e8f0;border-radius:18px;padding:14px;background:linear-gradient(135deg,#ffffff,#f8fafc);box-shadow:0 10px 24px rgba(15,23,42,.05)}.selected-price{font-size:1.8rem;font-weight:950;color:#0f172a;margin-top:8px}.modifier-group-box{border:1px solid #dbeafe;border-radius:14px;background:#fff;padding:8px}.modifier-group-mini-head{display:flex;justify-content:space-between;align-items:center;background:#eff6ff;border-radius:10px;padding:6px 8px;margin-bottom:7px}.modifier-group-mini-head strong{color:#0f172a}.modifier-group-mini-head span{font-size:.72rem;font-weight:800;color:#2563eb}.modifier-list{max-height:240px;overflow:auto;border:1px solid #e2e8f0;border-radius:14px;padding:8px;background:#f8fafc}.modifier-check{display:flex;align-items:center;gap:8px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:8px 10px;margin-bottom:7px}.modifier-check span{margin-left:auto;font-weight:800;color:#475569}.pos-total-box{display:flex;justify-content:space-between;align-items:center;border-radius:16px;background:#0f172a;color:#fff;padding:13px 14px}.pos-total-box strong{font-size:1.25rem}@media(max-width:1100px){.pos-menu-shell{grid-template-columns:1fr}.pos-menu-sidebar,.pos-menu-order-panel{border:0;border-bottom:1px solid #e2e8f0}.pos-item-grid{grid-template-columns:repeat(auto-fill,minmax(145px,1fr))}}

.table-tile.is-merged .table-main-btn{border:3px solid #7c3aed!important;box-shadow:0 0 0 4px rgba(124,58,237,.12),0 14px 30px rgba(15,23,42,.18)}
.table-merged-badge{background:#7c3aed!important;color:#fff!important;border-radius:999px;padding:2px 8px;font-size:.62rem!important;font-weight:900;margin-top:2px;letter-spacing:.04em}
.table-merged-list{font-size:.60rem!important;color:#5b21b6!important;max-width:94%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.surcharge-preview{grid-column:1/-1;background:#fff7ed;border:1px solid #fed7aa;border-radius:10px;padding:5px 8px}


.active-check-toolbar{grid-template-columns:auto auto auto auto auto!important;align-items:center;justify-content:start;gap:8px;flex-wrap:wrap}.bill-preview-line{display:grid;grid-template-columns:70px minmax(0,1fr) 95px;gap:10px;padding:8px 0;border-bottom:1px dashed #e2e8f0}.bill-preview-line strong{color:#0f172a}.bill-preview-line span{color:#64748b;font-size:.78rem}.bill-preview-total{display:flex;justify-content:space-between;align-items:center;background:#0f172a;color:#fff;border-radius:14px;padding:12px 14px;margin-top:12px}.bill-preview-total strong{font-size:1.2rem}.settlement-preview-panel{border:1px solid #dbeafe;background:linear-gradient(135deg,#eff6ff,#fff);border-radius:16px;padding:12px}.thermal-preview-card{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:12px;height:100%;box-shadow:0 8px 18px rgba(15,23,42,.04)}.thermal-preview-title{font-size:.72rem;font-weight:950;color:#334155;letter-spacing:.08em;text-transform:uppercase;margin-bottom:8px}.thermal-preview-pre{background:#0f172a;color:#e5e7eb;border-radius:12px;padding:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:1.25;white-space:pre-wrap;margin:0;max-height:420px;overflow:auto}.thermal-split-list{max-height:520px;overflow:auto}.split-surcharge-note{font-weight:800}@media(max-width:768px){.active-check-toolbar{grid-template-columns:1fr!important}.active-check-toolbar .btn{width:100%}.bill-preview-line{grid-template-columns:52px minmax(0,1fr) 78px}}

.account-choice-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.75rem}.account-choice-btn{border:1px solid #e5e7eb;border-radius:16px;padding:.9rem;background:#f8fafc;color:#334155;min-height:104px;text-align:left;display:flex;flex-direction:column;gap:.28rem;transition:.15s;box-shadow:0 3px 10px rgba(15,23,42,.04)}.account-choice-btn i{font-size:1.35rem;color:#2563eb}.account-choice-btn strong{display:block;color:#0f172a;font-size:.95rem}.account-choice-btn span{font-size:.78rem;line-height:1.25;display:block}.account-choice-btn:hover{transform:translateY(-1px);border-color:#93c5fd;background:#eff6ff}.account-choice-btn.is-active{border-color:#2563eb;background:linear-gradient(135deg,#eff6ff,#ffffff);box-shadow:0 8px 22px rgba(37,99,235,.15)}@media(max-width:768px){.account-choice-grid{grid-template-columns:1fr}}
</style>
<script>
window.money = window.money || function(v){ return (parseFloat(v || 0) || 0).toFixed(2); };
window.escapeHtml = window.escapeHtml || function(s){ return String(s ?? '').replace(/[&<>'"]/g, function(ch){ return ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'})[ch]; }); };
document.addEventListener('DOMContentLoaded', function(){
    const floor = document.getElementById('restaurantFloor');
    const csrfBox = document.getElementById('layoutCsrf');
    let dragged = null;
    let offsetX = 0;
    let offsetY = 0;
    let moved = false;
    let toastTimer = null;
    const openTableModalEl = document.getElementById('openTableModal');

    function showOpenTableModal(){
        if(!openTableModalEl) return false;

        // Bootstrap 5
        if(window.bootstrap && bootstrap.Modal){
            try{
                bootstrap.Modal.getOrCreateInstance(openTableModalEl).show();
                return true;
            }catch(ignore){}
        }

        // Bootstrap 3/4 with jQuery
        if(window.jQuery && typeof jQuery.fn.modal === 'function'){
            try{
                jQuery(openTableModalEl).modal('show');
                return true;
            }catch(ignore){}
        }

        // Last-resort manual display, so user still gets the choice window.
        openTableModalEl.style.display = 'block';
        openTableModalEl.removeAttribute('aria-hidden');
        openTableModalEl.setAttribute('aria-modal', 'true');
        openTableModalEl.classList.add('show');

        var backdrop = document.createElement('div');
        backdrop.className = 'modal-backdrop fade show open-table-manual-backdrop';
        document.body.appendChild(backdrop);
        document.body.classList.add('modal-open');

        return true;
    }

    function hideOpenTableModal(){
        if(!openTableModalEl) return;

        if(window.bootstrap && bootstrap.Modal){
            try{
                bootstrap.Modal.getOrCreateInstance(openTableModalEl).hide();
                return;
            }catch(ignore){}
        }

        if(window.jQuery && typeof jQuery.fn.modal === 'function'){
            try{
                jQuery(openTableModalEl).modal('hide');
                return;
            }catch(ignore){}
        }

        openTableModalEl.style.display = 'none';
        openTableModalEl.setAttribute('aria-hidden', 'true');
        openTableModalEl.removeAttribute('aria-modal');
        openTableModalEl.classList.remove('show');
        document.querySelectorAll('.open-table-manual-backdrop').forEach(function(el){ el.remove(); });
        document.body.classList.remove('modal-open');
    }

    if(openTableModalEl){
        openTableModalEl.querySelectorAll('[data-bs-dismiss="modal"], [data-dismiss="modal"], .btn-close').forEach(function(btn){
            btn.addEventListener('click', function(ev){
                // Let Bootstrap handle it when available; otherwise manual close.
                if(!(window.bootstrap && bootstrap.Modal) && !(window.jQuery && typeof jQuery.fn.modal === 'function')){
                    ev.preventDefault();
                    hideOpenTableModal();
                }
            });
        });
    }
    const openTableId = document.getElementById('openTableId');
    const openTableLabel = document.getElementById('openTableLabel');
    const customerType = document.getElementById('customerType');
    const inhouseGuestSelect = document.getElementById('inhouseGuestSelect');
    const walkinGuestName = document.getElementById('walkinGuestName');
    const groupNameInput = document.getElementById('groupNameInput');
    const displayClientName = document.getElementById('displayClientName');
    const displayRoomNo = document.getElementById('displayRoomNo');
    const openRoomNoHidden = document.getElementById('openRoomNoHidden');
    const openGuestNameHidden = document.getElementById('openGuestNameHidden');
    const REST_ORDER_TOTAL = <?php echo $order ? json_encode((float)$order['total_amount']) : '0'; ?>;
    const REST_SURCHARGE_RATE = 0.03;

    function toast(message){
        let el = document.getElementById('layoutToast');
        if(!el){
            el = document.createElement('div');
            el.id = 'layoutToast';
            el.className = 'layout-toast';
            document.body.appendChild(el);
        }
        el.textContent = message;
        el.classList.add('is-show');
        clearTimeout(toastTimer);
        toastTimer = setTimeout(()=>el.classList.remove('is-show'), 2200);
    }

    function csrfFields(){
        const fields = [];
        if(!csrfBox) return fields;
        csrfBox.querySelectorAll('input').forEach(input => fields.push([input.name, input.value]));
        return fields;
    }

    function saveLayout(tile, x, y, orientation){
        const fd = new FormData();
        fd.append('action','update_table_layout');
        fd.append('table_id', tile.dataset.tableId || '0');
        fd.append('pos_x', String(x));
        fd.append('pos_y', String(y));
        fd.append('orientation', orientation || tile.dataset.orientation || 'horizontal');
        csrfFields().forEach(([k,v]) => fd.append(k,v));
        tile.classList.add('is-saving');
        return fetch('pp.php', {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'})
            .then(r => r.json())
            .then(data => {
                if(!data || !data.ok) throw new Error((data && data.message) ? data.message : 'Save failed');
                tile.style.left = parseInt(data.pos_x || x, 10) + 'px';
                tile.style.top = parseInt(data.pos_y || y, 10) + 'px';
                tile.dataset.orientation = data.orientation || orientation || 'horizontal';
                const coords = tile.querySelector('.table-coords');
                if(coords) coords.textContent = 'X ' + parseInt(data.pos_x || x, 10) + ' • Y ' + parseInt(data.pos_y || y, 10);
                toast('Table layout saved');
            })
            .catch(err => toast('Could not save layout: ' + err.message))
            .finally(() => tile.classList.remove('is-saving'));
    }

    function syncOpenTableCustomer(){
        if(!customerType) return;
        const type = customerType.value || '';
        const roomWrap = document.querySelector('.room-guest-wrap');
        const walkinWrap = document.querySelector('.walkin-wrap');
        const groupWrap = document.querySelector('.group-wrap');

        if(roomWrap) roomWrap.style.display = (type === 'room' || type === 'group') ? '' : 'none';
        if(walkinWrap) walkinWrap.style.display = type === 'walkin' ? '' : 'none';
        if(groupWrap) groupWrap.style.display = type === 'group' ? '' : 'none';

        const modalRoomDisplay = document.querySelector('.modal-room-display');
        if(modalRoomDisplay) modalRoomDisplay.style.display = type === 'room' ? '' : 'none';

        let guest = '';
        let room = '';

        if(type === 'walkin' && walkinGuestName && walkinGuestName.value.trim() === ''){
            walkinGuestName.value = 'Direct Sale';
        }

        if(type === 'room'){
            const opt = inhouseGuestSelect ? inhouseGuestSelect.options[inhouseGuestSelect.selectedIndex] : null;
            guest = opt ? (opt.dataset.guest || '') : '';
            room = opt ? (opt.dataset.room || '') : '';
        }else if(type === 'group'){
            guest = groupNameInput ? groupNameInput.value.trim() : '';
            room = '';
        }else{
            guest = walkinGuestName ? walkinGuestName.value : '';
            room = '';
        }

        if(displayClientName) displayClientName.value = guest;
        if(displayRoomNo) displayRoomNo.value = room;
        if(openGuestNameHidden) openGuestNameHidden.value = guest;
        if(openRoomNoHidden) openRoomNoHidden.value = room;
    }

    function syncDirectSaleCustomer(){
        const typeEl=document.getElementById('directCustomerType');
        if(!typeEl) return;
        const type=typeEl.value || 'walkin';
        const roomWrap=document.querySelector('.direct-room-wrap');
        const walkinWrap=document.querySelector('.direct-walkin-wrap');
        const groupWrap=document.querySelector('.direct-group-wrap');
        const roomDisplay=document.querySelector('.direct-room-display');
        const roomSelect=document.getElementById('directInhouseGuestSelect');
        const walkinName=document.getElementById('directWalkinGuestName');
        const groupName=document.getElementById('directGroupNameInput');
        const displayClient=document.getElementById('directDisplayClientName');
        const displayRoom=document.getElementById('directDisplayRoomNo');
        const hiddenGuest=document.getElementById('directGuestNameHidden');
        const hiddenRoom=document.getElementById('directRoomNoHidden');

        if(roomWrap) roomWrap.style.display = (type === 'room' || type === 'group') ? '' : 'none';
        if(walkinWrap) walkinWrap.style.display = type === 'walkin' ? '' : 'none';
        if(groupWrap) groupWrap.style.display = type === 'group' ? '' : 'none';
        if(roomDisplay) roomDisplay.style.display = type === 'room' ? '' : 'none';

        let guest='';
        let room='';

        if(type === 'room' && roomSelect){
            const opt=roomSelect.options[roomSelect.selectedIndex];
            if(opt){
                guest=opt.dataset.guest || '';
                room=opt.dataset.room || '';
            }
            if(!guest) guest='Room guest';
        }else if(type === 'group'){
            guest=groupName && groupName.value.trim() ? groupName.value.trim() : '';
        }else{
            guest=walkinName && walkinName.value.trim() ? walkinName.value.trim() : 'Non-table Sale';
        }

        if(displayClient) displayClient.value=guest;
        if(displayRoom) displayRoom.value=room;
        if(hiddenGuest) hiddenGuest.value=guest;
        if(hiddenRoom) hiddenRoom.value=room;
    }

    const directSaleForm=document.getElementById('directSaleForm');
    const directCustomerType=document.getElementById('directCustomerType');
    const directInhouseGuestSelect=document.getElementById('directInhouseGuestSelect');
    const directWalkinGuestName=document.getElementById('directWalkinGuestName');
    const directGroupNameInput=document.getElementById('directGroupNameInput');

    if(directCustomerType) directCustomerType.addEventListener('change', syncDirectSaleCustomer);
    if(directInhouseGuestSelect) directInhouseGuestSelect.addEventListener('change', syncDirectSaleCustomer);
    if(directWalkinGuestName) directWalkinGuestName.addEventListener('input', syncDirectSaleCustomer);
    if(directGroupNameInput) directGroupNameInput.addEventListener('input', syncDirectSaleCustomer);
    syncDirectSaleCustomer();

    if(directSaleForm){
        directSaleForm.addEventListener('submit', function(e){
            syncDirectSaleCustomer();
            const type=directCustomerType ? directCustomerType.value : 'walkin';
            if(type === 'room' && (!directInhouseGuestSelect || !directInhouseGuestSelect.value)){
                e.preventDefault();
                toast('Please select the in-house room guest.');
                return false;
            }
            if(type === 'group' && (!directGroupNameInput || directGroupNameInput.value.trim() === '')){
                e.preventDefault();
                toast('Please enter the group name.');
                return false;
            }
            if(type === 'walkin' && directWalkinGuestName && directWalkinGuestName.value.trim() === ''){
                directWalkinGuestName.value='Non-table Sale';
                syncDirectSaleCustomer();
            }
        });
    }

    function openVacantTable(tile){
        if(!tile || !openTableId) return;
        openTableId.value = tile.dataset.tableId || '';
        if(openTableLabel) openTableLabel.textContent = tile.dataset.tableLabel ? (' - ' + tile.dataset.tableLabel) : '';

        // Old required flow: do not default to walk-in/room.
        // Always ask first: Room / In-house Guest, Group, or Direct Walk-in.
        if(customerType) customerType.value = '';
        if(inhouseGuestSelect) inhouseGuestSelect.value = '';
        if(walkinGuestName) walkinGuestName.value = '';
        if(groupNameInput) groupNameInput.value = '';
        syncOpenTableCustomer();

        if(showOpenTableModal()){
            setTimeout(function(){ if(customerType) customerType.focus(); }, 180);
        }else{
            toast('Could not open the customer type window. Please refresh and try again.');
        }
    }

    if(customerType) customerType.addEventListener('change', syncOpenTableCustomer);
    if(inhouseGuestSelect) inhouseGuestSelect.addEventListener('change', syncOpenTableCustomer);
    if(walkinGuestName) walkinGuestName.addEventListener('input', syncOpenTableCustomer);
    if(groupNameInput) groupNameInput.addEventListener('input', syncOpenTableCustomer);
    syncOpenTableCustomer();

    if(floor){
        floor.querySelectorAll('.table-tile').forEach(tile => {
            const mainBtn = tile.querySelector('.table-main-btn');
            if(mainBtn){
                mainBtn.addEventListener('click', function(e){
                    e.preventDefault();
                    if(moved){ moved = false; return; }
                    const existingOrderId = parseInt(tile.dataset.openOrderId || '0', 10) || 0;
                    if(existingOrderId > 0){
                        window.location.href = 'pp.php?restaurant_order_id=' + existingOrderId;
                        return;
                    }
                    openVacantTable(tile);
                });
            }
            tile.addEventListener('dragstart', function(e){
                if(e.target && e.target.closest('.table-rotate-btn')) return false;
                dragged = tile;
                moved = false;
                const rect = tile.getBoundingClientRect();
                offsetX = e.clientX - rect.left;
                offsetY = e.clientY - rect.top;
                tile.classList.add('is-dragging');
                e.dataTransfer.effectAllowed = 'move';
                e.dataTransfer.setData('text/plain', tile.dataset.tableId || '');
            });
            tile.addEventListener('dragend', function(){
                tile.classList.remove('is-dragging');
                setTimeout(()=>{ dragged = null; }, 50);
            });
            tile.addEventListener('dblclick', function(e){
                if(tile.dataset.shape === 'rect'){
                    e.preventDefault();
                    rotateTile(tile);
                }
            });
        });

        floor.addEventListener('dragover', function(e){
            if(!dragged) return;
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
        });

        floor.addEventListener('drop', function(e){
            if(!dragged) return;
            e.preventDefault();
            const floorRect = floor.getBoundingClientRect();
            let x = Math.round(e.clientX - floorRect.left + floor.scrollLeft - offsetX);
            let y = Math.round(e.clientY - floorRect.top + floor.scrollTop - offsetY);
            x = Math.max(0, x);
            y = Math.max(0, y);
            dragged.style.left = x + 'px';
            dragged.style.top = y + 'px';
            moved = true;
            saveLayout(dragged, x, y, dragged.dataset.orientation || 'horizontal');
        });
    }

    function rotateTile(tile){
        const current = tile.dataset.orientation || 'horizontal';
        const next = current === 'vertical' ? 'horizontal' : 'vertical';
        tile.classList.remove('orientation-' + current);
        tile.classList.add('orientation-' + next);
        tile.dataset.orientation = next;
        const x = parseInt(tile.style.left || '0', 10) || 0;
        const y = parseInt(tile.style.top || '0', 10) || 0;
        saveLayout(tile, x, y, next);
    }

    const openTableForm = document.getElementById('openTableForm');
    if(openTableForm){
        openTableForm.addEventListener('submit', function(e){
            syncOpenTableCustomer();
            const type = customerType ? customerType.value : '';
            if(!openTableId || !openTableId.value){
                e.preventDefault();
                toast('Please click a table first.');
                return false;
            }
            if(!type){
                e.preventDefault();
                toast('Please choose whether this is a walk-in, group, or room guest.');
                if(customerType) customerType.focus();
                return false;
            }
            if(type === 'room' && (!inhouseGuestSelect || !inhouseGuestSelect.value)){
                e.preventDefault();
                toast('Please select the in-house room guest.');
                return false;
            }
            if(type === 'group' && (!groupNameInput || groupNameInput.value.trim() === '')){
                e.preventDefault();
                toast('Please enter the group name.');
                return false;
            }
            if(type === 'walkin' && walkinGuestName && walkinGuestName.value.trim() === ''){
                walkinGuestName.value = 'Direct Sale';
                syncOpenTableCustomer();
            }
        });
    }

    function wireRemoveButtons(){
        document.querySelectorAll('.remove-pay-row,.remove-custom-row').forEach(btn=>{
            btn.onclick=function(){ const row=this.closest('.payment-row,.custom-row'); if(row && row.parentElement.children.length>1){ row.remove(); updateCustomSplitAmounts(); refreshSurchargePreviews(); } };
        });
    }

    function isSurchargeMethod(method){
        const m=String(method||'').trim().toLowerCase();
        return ['pos','card','credit card','debit card','visa','mastercard','card payment','pos/card'].includes(m);
    }
    function formatMoney(v){ return (parseFloat(v||0)||0).toFixed(2); }
    function ensurePreview(row){
        if(!row) return null;
        let preview=row.querySelector('.surcharge-preview');
        if(!preview){
            preview=document.createElement('div');
            preview.className='surcharge-preview small text-muted mt-1';
            row.appendChild(preview);
        }
        return preview;
    }
    function refreshSurchargePreviews(){
        document.querySelectorAll('.payment-row,.custom-row').forEach(row=>{
            const methodEl=row.querySelector('select[name="payment_method[]"],select[name="custom_method[]"],select[name="equal_method[]"],select[name^="item_method"]');
            const amountEl=row.querySelector('input[name="amount[]"],input[name="custom_amount[]"]');
            if(!methodEl || !amountEl) return;
            const preview=ensurePreview(row);
            const base=parseFloat(amountEl.value||'0')||0;
            if(isSurchargeMethod(methodEl.value) && base>0){
                const fee=base*REST_SURCHARGE_RATE;
                preview.innerHTML='<span class="text-warning fw-bold">3% POS/Card surcharge:</span> '+formatMoney(fee)+' • Customer debit: '+formatMoney(base+fee);
            }else{
                preview.textContent='';
            }
        });
    }
    function updateCustomSplitAmounts(){
        document.querySelectorAll('#customSplitRows .custom-row').forEach(row=>{
            const percent=row.querySelector('input[name="custom_percent[]"]');
            const amount=row.querySelector('input[name="custom_amount[]"]');
            if(percent && amount){
                const pct=parseFloat(percent.value||'0')||0;
                if(pct>0){
                    amount.value=formatMoney(REST_ORDER_TOTAL*(pct/100));
                }
            }
        });
        refreshSurchargePreviews();
    }

    const addPay=document.getElementById('addPaymentRow');
    if(addPay){ addPay.addEventListener('click', function(){
        const wrap=document.getElementById('multiPaymentRows'); if(!wrap) return;
        const first=wrap.querySelector('.payment-row'); if(!first) return;
        const row=first.cloneNode(true); row.querySelectorAll('input').forEach(i=>i.value=''); row.querySelectorAll('select').forEach(s=>{ if(s.classList.contains('split-folio-select')) s.value=''; }); wrap.appendChild(row); wireRemoveButtons(); refreshFolioDropdowns(); updateCustomSplitAmounts(); refreshSurchargePreviews(); refreshSurchargePreviews();
    }); }
    window.REST_FOLIO_OPTIONS_HTML = <?php echo json_encode($folioSelectHtml, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;

    function folioTypeForDestination(dest){
        dest = String(dest || '').toLowerCase().trim();
        if(dest === 'room_folio') return 'room';
        if(dest === 'guest_folio') return 'guest';
        if(dest === 'master_folio' || dest === 'group_master' || dest === 'company_account' || dest === 'folio') return 'master';
        return '';
    }

    function refreshFolioDropdowns(){
        document.querySelectorAll('.split-folio-select').forEach(function(sel){
            const row = sel.closest('tr') || sel.closest('.row') || sel.closest('.custom-row') || sel.parentElement;
            const destEl = row ? row.querySelector('.split-destination') : null;
            const needed = folioTypeForDestination(destEl ? destEl.value : '');
            const current = sel.value;

            Array.from(sel.options).forEach(function(opt, idx){
                if(idx === 0){
                    opt.hidden = false;
                    opt.disabled = false;
                    return;
                }
                const ft = String(opt.getAttribute('data-folio-type') || '').toLowerCase();
                const show = !needed || ft === needed || (needed === 'master' && (ft === 'master' || ft === 'group' || ft === 'company'));
                opt.hidden = !show;
                opt.disabled = !show;
            });

            if(current){
                const chosen = Array.from(sel.options).find(o => o.value === current);
                if(chosen && !chosen.disabled) sel.value = current;
                else sel.value = '';
            }

            sel.closest('td,div')?.classList.toggle('d-none', needed === '');
        });
    }

    const equalCount=document.getElementById('equalSplitCount');
    const equalRows=document.getElementById('equalPaymentRows');
    function renderEqualRows(){
        if(!equalCount||!equalRows) return;
        const n=Math.max(1,parseInt(equalCount.value||'1',10));
        equalRows.innerHTML='';
        for(let i=0;i<n;i++){
            const div=document.createElement('div');
            div.className='row g-2 mb-2 align-items-end';
            div.innerHTML=
                '<div class="col-md-2"><label class="form-label small mb-1">Payer</label><input class="form-control form-control-sm" value="Guest '+(i+1)+'" readonly></div>'+
                '<div class="col-md-3"><label class="form-label small mb-1">Settlement</label><select name="equal_destination_type[]" class="form-select form-select-sm split-destination">'+
                    '<option value="cash">Cash</option><option value="card">Card</option><option value="pos">POS</option><option value="mobile_money">Mobile Money</option><option value="bank_transfer">Bank Transfer</option>'+
                    '<option value="room_folio">Room Folio</option><option value="guest_folio">Guest Folio</option><option value="master_folio">Master Folio</option><option value="group_master">Group Master</option><option value="company_account">Company Account</option>'+
                '</select></div>'+
                '<div class="col-md-4"><label class="form-label small mb-1">Folio / Account</label><select name="equal_folio_id[]" class="form-select form-select-sm split-folio-select">'+window.REST_FOLIO_OPTIONS_HTML+'</select></div>'+
                '<div class="col-md-3"><label class="form-label small mb-1">Reference</label><input name="equal_reference[]" class="form-control form-control-sm" placeholder="Reference"></div>';
            equalRows.appendChild(div);
        }
        refreshFolioDropdowns();
        refreshSurchargePreviews();
    }
    if(equalCount){ equalCount.addEventListener('input', renderEqualRows); renderEqualRows(); }
    const addCustom=document.getElementById('addCustomSplitRow');
    if(addCustom){ addCustom.addEventListener('click', function(){
        const wrap=document.getElementById('customSplitRows'); if(!wrap) return;
        const first=wrap.querySelector('.custom-row'); if(!first) return;
        const row=first.cloneNode(true); row.querySelectorAll('input').forEach(i=>i.value=''); wrap.appendChild(row); wireRemoveButtons();
    }); }
    document.querySelectorAll('[data-bs-target="#splitEqual"]').forEach(b=>b.addEventListener('click',()=>{const r=document.getElementById('splitTypeEqual'); if(r) r.checked=true;}));
    document.querySelectorAll('[data-bs-target="#splitItems"]').forEach(b=>b.addEventListener('click',()=>{const r=document.getElementById('splitTypeItem'); if(r) r.checked=true;}));
    document.querySelectorAll('[data-bs-target="#splitCustom"]').forEach(b=>b.addEventListener('click',()=>{const r=document.getElementById('splitTypeCustom'); if(r) r.checked=true;}));
    document.addEventListener('input', function(e){
        if(e.target && e.target.matches('input[name="custom_percent[]"]')) updateCustomSplitAmounts();
        if(e.target && e.target.matches('input[name="custom_amount[]"],input[name="amount[]"]')) refreshSurchargePreviews();
    });
    document.addEventListener('change', function(e){
        if(e.target && e.target.matches('.split-destination')) refreshFolioDropdowns();
        if(e.target && e.target.matches('select[name="custom_method[]"],select[name="payment_method[]"],select[name="equal_destination_type[]"],select[name^="item_destination_type"],select[name^="seat_destination_type"],select[name="custom_destination_type[]"]')) refreshSurchargePreviews();
    });
    wireRemoveButtons();
    updateCustomSplitAmounts();
    refreshFolioDropdowns();
    refreshSurchargePreviews();


    const menuCategories = window.REST_MENU_CATEGORIES || [];
    const menuItems = window.REST_MENU_ITEMS || [];
    const menuTags = window.REST_MENU_TAGS || [];
    let currentCat = 0;
    let catStack = [];
    const menuPosStateKey='REST_MENU_POS_STATE_'+(window.REST_ACTIVE_RESTAURANT_ORDER_ID || '0');
    try{
        const savedMenu=JSON.parse(sessionStorage.getItem(menuPosStateKey) || '{}');
        if(savedMenu && Number.isFinite(parseInt(savedMenu.currentCat,10))){
            currentCat=parseInt(savedMenu.currentCat,10)||0;
            catStack=Array.isArray(savedMenu.catStack) ? savedMenu.catStack.map(x=>parseInt(x,10)||0) : [];
        }
    }catch(e){}
    function saveMenuPosState(){
        try{ sessionStorage.setItem(menuPosStateKey, JSON.stringify({currentCat:currentCat,catStack:catStack})); }catch(e){}
    }
    let selectedTag = '';
    let selectedItem = null;
    const catWrap = document.getElementById('menuPosCategories');
    const itemWrap = document.getElementById('menuPosItems');
    const breadcrumb = document.getElementById('menuPosBreadcrumb');
    const searchInput = document.getElementById('menuPosSearch');
    const tagWrap = document.getElementById('menuPosTags');
    const itemIdInput = document.getElementById('menuPosItemId');
    const selectedName = document.getElementById('selectedItemName');
    const selectedMeta = document.getElementById('selectedItemMeta');
    const selectedPrice = document.getElementById('selectedItemPrice');
    const modWrap = document.getElementById('menuPosModifiers');
    const qtyInput = document.getElementById('menuPosQty');
    const lineTotal = document.getElementById('menuPosLineTotal');
    const submitBtn = document.getElementById('menuPosSubmit');

    const money = (typeof window.money === 'function')
        ? window.money
        : function(v){ return (parseFloat(v || 0) || 0).toFixed(2); };
    const escapeHtml = (typeof window.escapeHtml === 'function')
        ? window.escapeHtml
        : function(s){ return String(s ?? '').replace(/[&<>'"]/g, function(ch){ return ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'})[ch]; }); };
    function namedItemColor(value){
        const v=String(value||'').trim().toLowerCase();
        const map={blue:'#2563eb',navy:'#0f172a',green:'#16a34a',emerald:'#059669',red:'#dc2626',orange:'#ea580c',amber:'#d97706',yellow:'#f59e0b',purple:'#7c3aed',pink:'#db2777',rose:'#e11d48',teal:'#0d9488',cyan:'#0891b2',slate:'#475569',grey:'#64748b',gray:'#64748b',black:'#111827',brown:'#92400e'};
        return map[v] || value || '';
    }
    function normalizeItemColor(value){
        let c=String(value||'').trim();
        if(c==='') return '';
        c=namedItemColor(c);
        if(/^#[0-9a-f]{3}$/i.test(c)){ c='#'+c[1]+c[1]+c[2]+c[2]+c[3]+c[3]; }
        if(/^#[0-9a-f]{6}$/i.test(c)) return c;
        return '';
    }
    function darkenHex(hex, percent){
        const n=parseInt(hex.slice(1),16);
        let r=(n>>16)&255,g=(n>>8)&255,b=n&255;
        const f=(100-percent)/100;
        r=Math.max(0,Math.min(255,Math.round(r*f)));
        g=Math.max(0,Math.min(255,Math.round(g*f)));
        b=Math.max(0,Math.min(255,Math.round(b*f)));
        return '#'+((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1);
    }
    function isLightHex(hex){
        const n=parseInt(hex.slice(1),16), r=(n>>16)&255, g=(n>>8)&255, b=n&255;
        return ((r*299+g*587+b*114)/1000) > 170;
    }
    function catById(id){ return menuCategories.find(c => parseInt(c.id,10) === parseInt(id,10)); }
    function catPath(id){
        const parts=[]; let guard=0; let c=catById(id);
        while(c && guard<30){ parts.unshift(c.name); c=catById(c.parent_id); guard++; }
        return parts.length ? parts.join(' > ') : 'All Categories';
    }
    function childCats(parentId){ return menuCategories.filter(c => parseInt(c.parent_id||0,10) === parseInt(parentId||0,10)); }
    function descendantCatIds(parentId){
        const ids=[parseInt(parentId||0,10)];
        childCats(parentId).forEach(c => descendantCatIds(c.id).forEach(id => ids.push(id)));
        return [...new Set(ids.filter(id=>id>0))];
    }
    function renderTags(){
        if(!tagWrap) return;
        tagWrap.innerHTML='';
        if(!menuTags.length) return;
        const all=document.createElement('button'); all.type='button'; all.className='pos-tag-pill'+(selectedTag===''?' active':''); all.textContent='All'; all.onclick=()=>{selectedTag=''; renderMenu();}; tagWrap.appendChild(all);
        menuTags.forEach(t=>{ const b=document.createElement('button'); b.type='button'; b.className='pos-tag-pill'+(selectedTag===t?' active':''); b.textContent=t; b.onclick=()=>{selectedTag=selectedTag===t?'':t; renderMenu();}; tagWrap.appendChild(b); });
    }
    function renderCategories(){
        if(!catWrap) return;
        catWrap.innerHTML='';
        childCats(currentCat).forEach(c=>{
            const b=document.createElement('button'); b.type='button'; b.className='pos-category-btn pos-category-btn-colored';
            const color=normalizeItemColor(c.button_color || c.printer_role || '') || '#475569';
            b.style.setProperty('--cat-color', color);
            b.style.setProperty('--cat-color-dark', darkenHex(color,18));
            b.style.background='linear-gradient(135deg,'+color+','+darkenHex(color,18)+')';
            b.style.borderColor=color;
            b.style.color=isLightHex(color)?'#0f172a':'#fff';
            const subColor=isLightHex(color)?'rgba(15,23,42,.72)':'rgba(255,255,255,.82)';
            b.innerHTML='<span><strong>'+escapeHtml(c.name)+'</strong><div class="small" style="color:'+subColor+'">'+escapeHtml(catPath(c.id))+'</div></span><i class="bi bi-chevron-right"></i>';
            b.onclick=()=>{ catStack.push(currentCat); currentCat=parseInt(c.id,10); saveMenuPosState(); renderMenu(); };
            catWrap.appendChild(b);
        });
        if(!catWrap.children.length){ catWrap.innerHTML='<div class="text-muted small p-2">No subcategories. Select an item or go back.</div>'; }
    }
    function visibleItems(){
        const q=(searchInput ? searchInput.value.trim().toLowerCase() : '');
        let cats=currentCat>0 ? descendantCatIds(currentCat) : menuCategories.map(c=>parseInt(c.id,10));
        return menuItems.filter(it=>{
            const text=(it.name+' '+(it.code||'')+' '+(it.description||'')+' '+(it.tags||'')).toLowerCase();
            const inCat=currentCat>0 ? cats.includes(parseInt(it.category_id,10)) : true;
            const tagOk=selectedTag==='' || String(it.tags||'').toLowerCase().split(',').map(x=>x.trim()).includes(selectedTag.toLowerCase());
            const qOk=q==='' || text.includes(q);
            return inCat && tagOk && qOk;
        });
    }
    function renderItems(){
        if(!itemWrap) return;
        const rows=visibleItems();
        const countEl=document.getElementById('menuPosCount'); if(countEl) countEl.textContent=rows.length+' item'+(rows.length===1?'':'s');
        itemWrap.innerHTML='';
        rows.forEach(it=>{
            const b=document.createElement('button');
            b.type='button';
            const itemColor=normalizeItemColor(it.button_color || '');
            b.className='pos-item-btn'+(itemColor?' is-colored':'')+(itemColor && isLightHex(itemColor)?' is-light-color':'')+(selectedItem&&selectedItem.id==it.id?' active':'');
            if(itemColor){
                b.style.setProperty('--item-color', itemColor);
                b.style.setProperty('--item-color-dark', darkenHex(itemColor, 18));
                b.title='Button color: '+itemColor;
            }
            b.innerHTML='<div><div class="pos-item-name">'+escapeHtml(it.name)+'</div><div class="pos-item-desc">'+escapeHtml(it.description||catPath(it.category_id))+'</div></div><div class="pos-item-footer"><span class="pos-price">'+money(it.price)+'</span><span class="pos-tags">'+escapeHtml(it.tags||it.printer_role||'')+'</span></div>';
            b.setAttribute('data-menu-id', String(it.id || ''));
            b.addEventListener('click', function(ev){
                ev.preventDefault();
                ev.stopPropagation();

                // Keep side panel updated, then open the quantity/modifier wizard.
                selectItem(it);

                const seatInput = document.getElementById('menuPosSeatNo');
                const seatNo = seatInput ? (seatInput.value || '') : '';
                startQuickModifierWizard(it, seatNo);
            });
            itemWrap.appendChild(b);
        });
        if(!rows.length){ itemWrap.innerHTML='<div class="alert alert-light border">No items found for this category/search.</div>'; }
    }
    function selectItem(it){
        selectedItem=it;
        if(itemIdInput) itemIdInput.value=it.id;
        if(selectedName) selectedName.textContent=it.name;
        if(selectedMeta) selectedMeta.textContent=catPath(it.category_id)+(it.tags?' • '+it.tags:'');
        if(selectedPrice) selectedPrice.textContent=money(it.price);
        if(submitBtn) submitBtn.disabled=false;
        renderModifiers(); updateLineTotal(); renderItems();
    }
    function renderModifiers(){
        if(!modWrap) return;
        modWrap.innerHTML='';
        if(!selectedItem){ modWrap.innerHTML='<div class="text-muted small">No item selected.</div>'; return; }

        const rawGroups = Array.isArray(selectedItem.modifier_groups) ? selectedItem.modifier_groups : [];
        const groups = rawGroups.filter(g => g && Array.isArray(g.options) && g.options.length);

        if(rawGroups.length && !groups.length){
            modWrap.innerHTML='<div class="alert alert-warning py-2 small mb-2"><strong>Modifier group linked, but no options are linked to that group.</strong><br>Open Menu Setup and link reusable modifiers to the selected group.</div>';
        }

        if(groups.length){
            groups.forEach(g=>{
                const box=document.createElement('div');
                box.className='modifier-group-box mb-2';
                const max=parseInt(g.max_select||0,10)||0;
                const min=parseInt(g.min_select||0,10)||0;
                const inputType='checkbox';
                let html='<div class="modifier-group-mini-head"><strong>'+escapeHtml(g.group_name||'Options')+'</strong><span>'+(min>0?'Required':'Optional')+(max>0?' • Max '+max:'')+'</span></div>';
                (g.options||[]).filter(o=>!parseInt(o.parent_modifier_id||0,10)).forEach(o=>{
                    html+='<label class="modifier-check"><input type="'+inputType+'" name="modifier_ids[]" value="'+o.id+'" data-group="'+g.id+'" data-max="'+max+'" data-price="'+(o.price_delta||0)+'"> <strong>'+escapeHtml(o.modifier_name)+'</strong><span>'+money(o.price_delta)+'</span></label>';
                    (g.options||[]).filter(ch=>parseInt(ch.parent_modifier_id||0,10)===parseInt(o.id,10)).forEach(ch=>{
                        html+='<label class="modifier-check ms-3"><input type="checkbox" name="modifier_ids[]" value="'+ch.id+'" data-group="'+g.id+'" data-max="'+max+'" data-price="'+(ch.price_delta||0)+'"> <strong>↳ '+escapeHtml(ch.modifier_name)+'</strong><span>'+money(ch.price_delta)+'</span></label>';
                    });
                });
                box.innerHTML=html;
                modWrap.appendChild(box);
            });
            modWrap.querySelectorAll('input[name="modifier_ids[]"]').forEach(inp=>inp.addEventListener('change', function(){
                const max=parseInt(this.dataset.max||0,10)||0;
                const group=this.dataset.group||'';
                if(max>0 && this.type==='checkbox'){
                    const checked=modWrap.querySelectorAll('input[name="modifier_ids[]"][data-group="'+group+'"]:checked');
                    if(checked.length>max){ this.checked=false; toast('Maximum '+max+' option(s) allowed.'); return; }
                }
                updateLineTotal();
            }));
            updateLineTotal();
            return;
        }

        const mods=(selectedItem.modifiers&&selectedItem.modifiers.length) ? selectedItem.modifiers : [];
        if(!mods.length){ modWrap.innerHTML='<div class="text-muted small">No modifiers configured for this item.</div>'; return; }
        mods.forEach(m=>{
            const label=document.createElement('label'); label.className='modifier-check';
            label.innerHTML='<input type="checkbox" name="modifier_ids[]" value="'+m.id+'" data-price="'+(m.price_delta||0)+'"> <strong>'+escapeHtml(m.modifier_name)+'</strong><span>'+money(m.price_delta)+'</span>';
            label.querySelector('input').addEventListener('change', updateLineTotal);
            modWrap.appendChild(label);
        });
    }
    function updateLineTotal(){
        const base=selectedItem ? parseFloat(selectedItem.price||0) : 0;
        let mods=0;
        if(modWrap) modWrap.querySelectorAll('input[name="modifier_ids[]"]:checked').forEach(cb=>mods += parseFloat(cb.dataset.price||0)||0);
        const qty=qtyInput ? (parseFloat(qtyInput.value||1)||1) : 1;
        if(lineTotal) lineTotal.textContent=money((base+mods)*qty);
    }
    function renderMenu(){
        if(breadcrumb) breadcrumb.textContent=catPath(currentCat);
        renderTags(); renderCategories(); renderItems();
    }
    if(itemWrap){
        itemWrap.addEventListener('click', function(ev){
            const btn = ev.target && ev.target.closest ? ev.target.closest('.pos-item-btn[data-menu-id]') : null;
            if(!btn) return;

            ev.preventDefault();
            ev.stopPropagation();

            const id = parseInt(btn.getAttribute('data-menu-id') || '0', 10) || 0;
            const item = menuItems.find(function(x){ return parseInt(x.id || 0, 10) === id; });
            if(!item) return;

            selectItem(item);

            const seatInput = document.getElementById('menuPosSeatNo');
            const seatNo = seatInput ? (seatInput.value || '') : '';
            startQuickModifierWizard(item, seatNo);
        }, true);
    }

    if(searchInput) searchInput.addEventListener('input', renderMenu);
    if(qtyInput) qtyInput.addEventListener('input', updateLineTotal);
    document.getElementById('menuPosBack')?.addEventListener('click',()=>{ currentCat=catStack.length?catStack.pop():0; saveMenuPosState(); renderMenu(); });
    document.getElementById('menuPosHome')?.addEventListener('click',()=>{ currentCat=0; catStack=[]; selectedTag=''; if(searchInput) searchInput.value=''; saveMenuPosState(); renderMenu(); });
    document.getElementById('menuPosForm')?.addEventListener('submit',function(e){ if(!selectedItem){ e.preventDefault(); toast('Select a menu item first.'); return false; } saveMenuPosState(); });
    function quickCategoryButton(c){
        const btn=document.createElement('button');
        btn.type='button';
        btn.className='quick-item-btn quick-cat-btn';
        const color=normalizeItemColor(c.button_color || c.printer_role || 'slate') || '#475569';
        btn.style.background='linear-gradient(135deg,'+color+','+darkenHex(color,18)+')';
        btn.style.color='#fff';
        btn.innerHTML='<strong><i class="bi bi-folder2-open me-1"></i>'+escapeHtml(c.name)+'</strong><span style="color:rgba(255,255,255,.92)">Open Category</span>';
        return btn;
    }

    function quickSaleItemButton(it){
        const btn=document.createElement('button');
        btn.type='button';
        const itemColor=normalizeItemColor(it.button_color || '');
        btn.className='quick-item-btn quick-sale-item-btn'+(itemColor?' is-colored':'')+(itemColor && isLightHex(itemColor)?' is-light-color':'');
        if(itemColor){
            btn.style.setProperty('--item-color', itemColor);
            btn.style.setProperty('--item-color-dark', darkenHex(itemColor,18));
            btn.style.background='linear-gradient(135deg,'+itemColor+','+darkenHex(itemColor,18)+')';
            btn.title='Button color: '+itemColor;
            if(isLightHex(itemColor)){
                btn.style.color='#0f172a';
            }else{
                btn.style.color='#fff';
            }
        }
        btn.innerHTML='<strong>'+escapeHtml(it.name)+'</strong><span>'+money(it.price)+'</span>';
        return btn;
    }


    let quickWizardState=null;

    function ensureQuickWizardModal(){
        let modal=document.getElementById('quickModifierWizardModal');
        if(modal) return modal;
        const div=document.createElement('div');
        div.innerHTML=`
<div class="modal fade" id="quickModifierWizardModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content quick-modifier-modal">
      <div class="modal-header quick-modifier-header">
        <div><div class="small text-white-50 fw-bold text-uppercase" id="quickWizardStepLabel">Modifier Selection</div><h5 class="modal-title" id="quickWizardTitle">Select modifiers</h5></div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body"><div class="quick-wizard-summary" id="quickWizardSummary"></div><div id="quickWizardBody"></div></div>
      <div class="modal-footer quick-wizard-footer">
        <button type="button" class="btn btn-outline-secondary" id="quickWizardBackBtn">Back</button>
        <button type="button" class="btn btn-outline-primary" id="quickWizardSkipBtn">Skip</button>
        <button type="button" class="btn btn-primary" id="quickWizardNextBtn">Next</button>
        <button type="button" class="btn btn-success" id="quickWizardSaveBtn" style="display:none">Add To Active Check</button>
      </div>
    </div>
  </div>
</div>`;
        document.body.appendChild(div.firstElementChild);
        modal=document.getElementById('quickModifierWizardModal');
        document.getElementById('quickWizardBackBtn')?.addEventListener('click', quickWizardBack);
        document.getElementById('quickWizardSkipBtn')?.addEventListener('click', quickWizardSkip);
        document.getElementById('quickWizardNextBtn')?.addEventListener('click', quickWizardNext);
        document.getElementById('quickWizardSaveBtn')?.addEventListener('click', quickWizardSave);

        modal.querySelectorAll('[data-bs-dismiss="modal"], [data-dismiss="modal"], .btn-close').forEach(function(btn){
            btn.addEventListener('click', function(ev){
                if(!(window.bootstrap && bootstrap.Modal) && !(window.jQuery && typeof jQuery.fn.modal === 'function')){
                    ev.preventDefault();
                    hideQuickWizardModal();
                }
            });
        });
        return modal;
    }
    function quickWizardGroupsForItem(item){
        let groups=Array.isArray(item.modifier_groups) ? item.modifier_groups.filter(g => g && Array.isArray(g.options) && g.options.length) : [];
        if(groups.length) return groups
            .map(g=>({id:parseInt(g.id||0,10)||0, group_name:g.group_name||'Options', group_type:(String(g.group_type||'optional').toLowerCase()==='allowed'?'optional':String(g.group_type||'optional').toLowerCase()), min_select:parseInt(g.min_select||0,10)||0, max_select:parseInt(g.max_select||0,10)||0, item_group_sort:parseInt(g.item_group_sort||g.sort_order||0,10)||0, options:g.options||[]}))
            .sort((a,b)=>(a.item_group_sort-b.item_group_sort)||String(a.group_name).localeCompare(String(b.group_name)));
        const mods=Array.isArray(item.modifiers) ? item.modifiers : [];
        return mods.length ? [{id:0, group_name:'Options', group_type:'optional', min_select:0, max_select:0, item_group_sort:0, options:mods}] : [];
    }
    function showQuickWizardModal(){
        const modal = document.getElementById('quickModifierWizardModal');
        if(!modal) return false;

        // Bootstrap 5
        if(window.bootstrap && bootstrap.Modal){
            try{
                bootstrap.Modal.getOrCreateInstance(modal).show();
                return true;
            }catch(ignore){}
        }

        // Bootstrap 3/4 with jQuery
        if(window.jQuery && typeof jQuery.fn.modal === 'function'){
            try{
                jQuery(modal).modal('show');
                return true;
            }catch(ignore){}
        }

        // Manual fallback
        modal.style.display = 'block';
        modal.classList.add('show');
        modal.removeAttribute('aria-hidden');
        modal.setAttribute('aria-modal', 'true');
        document.body.classList.add('modal-open');

        if(!document.querySelector('.quick-wizard-manual-backdrop')){
            const backdrop = document.createElement('div');
            backdrop.className = 'modal-backdrop fade show quick-wizard-manual-backdrop';
            document.body.appendChild(backdrop);
        }

        return true;
    }

    function hideQuickWizardModal(){
        const modal = document.getElementById('quickModifierWizardModal');
        if(!modal) return;

        if(window.bootstrap && bootstrap.Modal){
            try{
                bootstrap.Modal.getOrCreateInstance(modal).hide();
                return;
            }catch(ignore){}
        }

        if(window.jQuery && typeof jQuery.fn.modal === 'function'){
            try{
                jQuery(modal).modal('hide');
                return;
            }catch(ignore){}
        }

        modal.style.display = 'none';
        modal.classList.remove('show');
        modal.setAttribute('aria-hidden', 'true');
        modal.removeAttribute('aria-modal');
        document.querySelectorAll('.quick-wizard-manual-backdrop').forEach(function(el){ el.remove(); });
        document.body.classList.remove('modal-open');
    }

    function startQuickModifierWizard(item, seatNo){
        const rid=parseInt(window.REST_ACTIVE_RESTAURANT_ORDER_ID||0,10)||0;
        if(!rid){ toast('Open a table/order first.'); return; }
        quickWizardState={item:item, seatNo:seatNo||'', qty:1, groups:quickWizardGroupsForItem(item), step:-1, selections:{}, notes:''};
        ensureQuickWizardModal();
        quickWizardRender();

        if(!showQuickWizardModal()){
            toast('Could not open modifier selection. Please refresh and try again.');
        }
    }
    function quickWizardCurrentGroup(){ return quickWizardState ? (quickWizardState.groups[quickWizardState.step]||null) : null; }
    function quickWizardSelectedIds(groupId){ return quickWizardState.selections[String(groupId||0)] || []; }
    function quickWizardRender(){
        if(!quickWizardState) return;
        const item=quickWizardState.item, groups=quickWizardState.groups, isQtyStep=quickWizardState.step<0, isNotesStep=quickWizardState.step>=groups.length;
        const title=document.getElementById('quickWizardTitle'), stepLabel=document.getElementById('quickWizardStepLabel'), summary=document.getElementById('quickWizardSummary'), body=document.getElementById('quickWizardBody');
        const backBtn=document.getElementById('quickWizardBackBtn'), skipBtn=document.getElementById('quickWizardSkipBtn'), nextBtn=document.getElementById('quickWizardNextBtn'), saveBtn=document.getElementById('quickWizardSaveBtn');
        if(title) title.textContent=item.name||'Menu Item';
        if(stepLabel) stepLabel.textContent=isQtyStep?'Quantity':'Modifier Group '+(quickWizardState.step+1)+' of '+Math.max(groups.length,1); if(isNotesStep) stepLabel.textContent='Final Notes';
        if(summary){
            const selected=[];
            Object.values(quickWizardState.selections).flat().forEach(id=>{ groups.forEach(g=>{ const opt=(g.options||[]).find(o=>parseInt(o.id,10)===parseInt(id,10)); if(opt) selected.push(opt.modifier_name+((parseFloat(opt.price_delta||0)||0)?' +'+money(opt.price_delta):'')); }); });
            summary.innerHTML='<strong>'+escapeHtml(item.name)+'</strong><span> Qty '+escapeHtml(quickWizardState.qty||1)+' • Seat '+escapeHtml(quickWizardState.seatNo||'-')+' • Unit '+money(item.price)+'</span>'+(selected.length?'<div class="small mt-1"><strong>Selected:</strong> '+escapeHtml(selected.join(', '))+'</div>':'<div class="small mt-1 text-muted">No modifiers selected yet.</div>');
        }
        if(backBtn) backBtn.style.display=quickWizardState.step>0 || isNotesStep ? '' : 'none';
        if(isQtyStep){
            if(skipBtn) skipBtn.style.display='none'; 
            if(nextBtn) { nextBtn.style.display=''; nextBtn.textContent='Next'; }
            if(saveBtn) saveBtn.style.display='none';
            const q=parseFloat(quickWizardState.qty||1)||1;
            if(body) body.innerHTML=`<div class="modifier-group-heading"><h5>Quantity</h5><div class="small text-muted">quantity entry before modifiers</div></div>
                <div class="row g-3 align-items-end">
                    <div class="col-md-5">
                        <label class="form-label fw-bold">How many?</label>
                        <input type="number" min="0.001" step="0.001" class="form-control form-control-lg" id="quickWizardQty" value="${escapeHtml(q)}">
                    </div>
                    <div class="col-md-7">
                        <div class="quick-wizard-summary mb-0">
                            <strong>${escapeHtml(item.name||'Menu Item')}</strong>
                            <span>Example: Beef Rump 300g × 3. Modifiers selected next will apply to the full quantity.</span>
                        </div>
                    </div>
                    <div class="col-12">
                        <div class="d-flex flex-wrap gap-2">
                            <button type="button" class="btn btn-outline-primary quick-qty-btn" data-qty="1">1</button>
                            <button type="button" class="btn btn-outline-primary quick-qty-btn" data-qty="2">2</button>
                            <button type="button" class="btn btn-outline-primary quick-qty-btn" data-qty="3">3</button>
                            <button type="button" class="btn btn-outline-primary quick-qty-btn" data-qty="4">4</button>
                            <button type="button" class="btn btn-outline-primary quick-qty-btn" data-qty="6">6</button>
                        </div>
                    </div>
                </div>`;
            const qtyEl=document.getElementById('quickWizardQty');
            if(qtyEl){
                qtyEl.addEventListener('input',()=>{ const v=parseFloat(qtyEl.value||'1'); quickWizardState.qty=(isFinite(v)&&v>0)?v:1; });
                qtyEl.focus(); qtyEl.select();
            }
            body?.querySelectorAll('.quick-qty-btn').forEach(btn=>btn.addEventListener('click',()=>{ quickWizardState.qty=parseFloat(btn.dataset.qty||'1')||1; const q=document.getElementById('quickWizardQty'); if(q) q.value=quickWizardState.qty; quickWizardRender(); }));
            return;
        }
        if(isNotesStep){
            if(skipBtn) skipBtn.style.display='none'; if(nextBtn) nextBtn.style.display='none'; if(saveBtn) saveBtn.style.display='';
            if(body) body.innerHTML='<label class="form-label fw-bold">Final Notes / Kitchen Instructions</label><textarea class="form-control" id="quickWizardNotes" rows="4" placeholder="Example: no salt, sauce on the side, guest allergy...">'+escapeHtml(quickWizardState.notes||'')+'</textarea>';
            return;
        }
        const g=quickWizardCurrentGroup(); const min=parseInt(g.min_select||0,10)||0, max=parseInt(g.max_select||0,10)||0, required=(g.group_type==='required'||min>0);
        if(skipBtn) skipBtn.style.display=required?'none':''; if(nextBtn) nextBtn.style.display=''; if(saveBtn) saveBtn.style.display='none';
        if(body){
            const selected=quickWizardSelectedIds(g.id).map(String); const inputType=(max===1?'radio':'checkbox');
            let html='<div class="modifier-group-heading"><h5>'+escapeHtml(g.group_name||'Options')+'</h5><div class="small text-muted">'+(required?'Required':'Optional')+' • '+(min>0?'Choose at least '+min:'Can skip')+(max>0?' • Max '+max:'')+'</div></div><div class="quick-modifier-options">';
            (g.options||[]).filter(o=>!parseInt(o.parent_modifier_id||0,10)).forEach(o=>{
                const checked=selected.includes(String(o.id))?' checked':'';
                html+='<label class="quick-modifier-option"><input type="'+inputType+'" name="quick_mod_'+g.id+'" value="'+o.id+'" data-price="'+(o.price_delta||0)+'"'+checked+'><div><strong>'+escapeHtml(o.modifier_name)+'</strong><span>'+money(o.price_delta)+'</span></div></label>';
                (g.options||[]).filter(ch=>parseInt(ch.parent_modifier_id||0,10)===parseInt(o.id,10)).forEach(ch=>{ const cchecked=selected.includes(String(ch.id))?' checked':''; html+='<label class="quick-modifier-option child-option"><input type="checkbox" name="quick_mod_'+g.id+'" value="'+ch.id+'" data-price="'+(ch.price_delta||0)+'"'+cchecked+'><div><strong>↳ '+escapeHtml(ch.modifier_name)+'</strong><span>'+money(ch.price_delta)+'</span></div></label>'; });
            });
            body.innerHTML=html+'</div>';
            body.querySelectorAll('input[name="quick_mod_'+g.id+'"]').forEach(inp=>inp.addEventListener('change',()=>{ const vals=Array.from(body.querySelectorAll('input[name="quick_mod_'+g.id+'"]:checked')).map(x=>parseInt(x.value,10)); if(max>0&&vals.length>max){ inp.checked=false; toast('Maximum '+max+' option(s) allowed for '+(g.group_name||'this group')+'.'); return; } quickWizardState.selections[String(g.id||0)]=Array.from(body.querySelectorAll('input[name="quick_mod_'+g.id+'"]:checked')).map(x=>parseInt(x.value,10)); quickWizardRender(); }));
        }
    }
    function quickWizardValidateCurrent(){ if(!quickWizardState) return false; if(quickWizardState.step<0){ const qEl=document.getElementById('quickWizardQty'); const q=parseFloat(qEl?qEl.value:quickWizardState.qty); if(!isFinite(q)||q<=0){ toast('Enter a valid quantity.'); return false; } quickWizardState.qty=q; return true; } const g=quickWizardCurrentGroup(); if(!g) return true; const selected=quickWizardSelectedIds(g.id); const min=parseInt(g.min_select||0,10)||0, max=parseInt(g.max_select||0,10)||0, required=(g.group_type==='required'||min>0); if(required&&selected.length<Math.max(1,min)){ toast('Please select '+(min>0?min:1)+' option(s) for '+(g.group_name||'this group')+'.'); return false; } if(max>0&&selected.length>max){ toast('Maximum '+max+' option(s) allowed.'); return false; } return true; }
    function quickWizardNext(){ if(!quickWizardState) return; if(!quickWizardValidateCurrent()) return; quickWizardState.step++; quickWizardRender(); }
    function quickWizardSkip(){ if(!quickWizardState) return; const g=quickWizardCurrentGroup(); if(g) quickWizardState.selections[String(g.id||0)]=[]; quickWizardState.step++; quickWizardRender(); }
    function quickWizardBack(){ if(!quickWizardState) return; if(quickWizardState.step>0) quickWizardState.step--; else if(quickWizardState.step===0) quickWizardState.step=-1; quickWizardRender(); }
    function quickWizardSave(){
        if(!quickWizardState) return;
        const notesEl=document.getElementById('quickWizardNotes'); quickWizardState.notes=notesEl?notesEl.value:'';
        const rid=parseInt(window.REST_ACTIVE_RESTAURANT_ORDER_ID||0,10)||0; if(!rid){ toast('Open a table/order first.'); return; }
        const form=document.createElement('form'); form.method='post'; form.style.display='none';
        const csrfBox=document.getElementById('layoutCsrf'); if(csrfBox){ csrfBox.querySelectorAll('input').forEach(inp=>form.appendChild(inp.cloneNode(true))); }
        function add(name,value){ const i=document.createElement('input'); i.type='hidden'; i.name=name; i.value=value==null?'':String(value); form.appendChild(i); }
        add('action','add_item'); add('restaurant_order_id',rid); add('menu_item_id',quickWizardState.item.id); add('qty',quickWizardState.qty||1); add('seat_no',quickWizardState.seatNo||''); add('notes',quickWizardState.notes||'');
        Object.values(quickWizardState.selections).flat().forEach(id=>add('modifier_ids[]',id));
        try{ if(window.REST_SAVE_QUICKPAD_STATE) window.REST_SAVE_QUICKPAD_STATE(); }catch(e){}
        try{ hideQuickWizardModal(); }catch(e){}
        document.body.appendChild(form); form.submit();
    }

    function initQuickPad(){
        const grid=document.getElementById('quickPadGrid');
        if(!grid) return;

        const crumb=document.getElementById('quickPadCrumb');
        const back=document.getElementById('quickPadBack');
        const home=document.getElementById('quickPadHome');
        const seat=document.getElementById('quickSeatNo');

        let qCat=0;
        let qStack=[];
        const quickPadStateKey='REST_QUICK_PAD_STATE_'+(window.REST_ACTIVE_RESTAURANT_ORDER_ID || '0');
        try{
            const saved=JSON.parse(sessionStorage.getItem(quickPadStateKey) || '{}');
            if(saved && Number.isFinite(parseInt(saved.qCat,10))){
                qCat=parseInt(saved.qCat,10)||0;
                qStack=Array.isArray(saved.qStack) ? saved.qStack.map(x=>parseInt(x,10)||0) : [];
            }
        }catch(e){}
        function saveQuickPadState(){
            try{ sessionStorage.setItem(quickPadStateKey, JSON.stringify({qCat:qCat,qStack:qStack})); }catch(e){}
        }
        window.REST_SAVE_QUICKPAD_STATE = saveQuickPadState;

        function itemsInCategoryOnly(catId){
            if(!catId || parseInt(catId,10)<=0) return [];
            return menuItems.filter(it => parseInt(it.category_id||0,10) === parseInt(catId||0,10));
        }

        function renderQuickPad(){
            if(crumb) crumb.textContent=catPath(qCat);
            grid.innerHTML='';

            const cats=childCats(qCat);

            // Important rule:
            // Below Seat Selection, Quick Pad shows CATEGORIES first.
            // If a level has subcategories, item buttons are deliberately hidden.
            if(cats.length){
                cats.forEach(c=>{
                    const btn=quickCategoryButton(c);
                    btn.onclick=()=>{
                        qStack.push(qCat);
                        qCat=parseInt(c.id,10)||0;
                        saveQuickPadState();
                        renderQuickPad();
                    };
                    grid.appendChild(btn);
                });
                return;
            }

            // Items only appear after drilling into a final/leaf category.
            const rows=itemsInCategoryOnly(qCat);
            rows.forEach(it=>{
                const btn=quickSaleItemButton(it);
                btn.onclick=()=>{
                    startQuickModifierWizard(it, seat ? (seat.value || '') : '');
                };
                grid.appendChild(btn);
            });

            if(!grid.children.length){
                grid.innerHTML = qCat>0
                    ? '<div class="text-muted small p-2">No sale items in this final category. Use Back or Home.</div>'
                    : '<div class="alert alert-warning small mb-0">No root categories found. Create root categories in Menu Setup first.</div>';
            }
        }

        if(back){
            back.onclick=()=>{
                qCat=qStack.length ? qStack.pop() : 0;
                saveQuickPadState();
                renderQuickPad();
            };
        }

        if(home){
            home.onclick=()=>{
                qCat=0;
                qStack=[];
                saveQuickPadState();
                renderQuickPad();
            };
        }

        renderQuickPad();
    }

    initQuickPad();

    renderMenu();

    document.querySelectorAll('.table-rotate-btn').forEach(btn => {
        btn.addEventListener('click', function(e){
            e.preventDefault();
            e.stopPropagation();
            const tile = this.closest('.table-tile');
            if(tile) rotateTile(tile);
        });
    });
});
</script>
<script>
document.addEventListener('DOMContentLoaded',function(){
    const folioTarget=document.getElementById('folioTarget');
    const linkedOrder=document.getElementById('linkedOrder');
    const folioId=document.getElementById('folioId');
    const hint=document.getElementById('folioFilterHint');
    const accountButtons=document.querySelectorAll('[data-account-target]');
    const labels={master:'Master Account',room:'Room Account',guest:'Guest Account'};
    const allFolioOptions=folioId ? Array.from(folioId.querySelectorAll('option')).map(o=>({
        value:o.value,
        text:o.textContent,
        orderId:o.dataset.orderId || '',
        type:(o.dataset.folioType || '').toLowerCase(),
        roomNo:(o.dataset.roomNo || '').trim(),
        folioName:(o.dataset.folioName || '').trim()
    })) : [];

    function rebuildFolioOptions(){
        if(!folioId) return;
        const current=folioId.value;
        const bookingId=linkedOrder ? linkedOrder.value : '';
        const target=folioTarget ? folioTarget.value : 'master';
        if(linkedOrder && linkedOrder.dataset.lockedLinkedBooking === '1'){
            linkedOrder.classList.add('bg-light');
        }
        folioId.innerHTML='';
        const activeRoom = <?php echo json_encode(trim((string)($order['room_no'] ?? '')), JSON_UNESCAPED_UNICODE); ?>;
        const activeGuest = <?php echo json_encode(trim((string)($order['guest_name'] ?? '')), JSON_UNESCAPED_UNICODE); ?>;
        allFolioOptions.forEach(opt=>{
            if(opt.value!=='' && bookingId!=='' && String(opt.orderId)!==String(bookingId)) return;
            if(opt.value!=='' && opt.type!==target) return;
            if(opt.value!=='' && target==='room' && activeRoom && opt.roomNo && String(opt.roomNo).trim()!==String(activeRoom).trim()) return;
            if(opt.value!=='' && target==='guest' && activeGuest && opt.folioName && !String(opt.folioName).toLowerCase().includes(String(activeGuest).toLowerCase())) {
                // keep guest folios with no name because some systems store the guest name only on transactions/window labels
            }
            const o=document.createElement('option');
            o.value=opt.value;
            o.textContent=opt.text;
            if(opt.orderId) o.dataset.orderId=opt.orderId;
            if(opt.type) o.dataset.folioType=opt.type;
            if(opt.roomNo) o.dataset.roomNo=opt.roomNo;
            if(opt.folioName) o.dataset.folioName=opt.folioName;
            folioId.appendChild(o);
        });
        if(Array.from(folioId.options).some(o=>o.value===current)) folioId.value=current;
        else folioId.value='';
        if(hint){
            const count=Math.max(0, folioId.options.length - 1);
            hint.textContent='Showing '+(labels[target] || target)+' folios for '+(bookingId ? 'the selected in-house booking only' : 'all current bookings')+'. '+count+' matching folio(s).';
        }
    }

    function setAccountTarget(target){
        target=(target || 'master').toLowerCase();
        if(!['master','room','guest'].includes(target)) target='master';
        if(folioTarget) folioTarget.value=target;
        accountButtons.forEach(btn=>btn.classList.toggle('is-active',btn.getAttribute('data-account-target')===target));
        rebuildFolioOptions();
    }

    const quickSeatNo = document.getElementById('quickSeatNo');
    document.querySelectorAll('.quick-item-form').forEach(form => {
        form.addEventListener('submit', function(){
            const seatInput = form.querySelector('input[name="seat_no"]');
            if(seatInput && quickSeatNo) seatInput.value = quickSeatNo.value || '';
        });
    });


    const splitFolioOptionsHtml = <?php echo json_encode($folioSelectHtml ?? '<option value="">-- no folio --</option>'); ?>;
    const totalDue = <?php echo json_encode(isset($order['total_amount'])?(float)$order['total_amount']:0); ?>;
    const coversCount = <?php echo json_encode(max(1,(int)($order['covers']??1))); ?>;
    const cardSurchargeRate = <?php echo json_encode(REST_POS_CARD_SURCHARGE_RATE); ?>;
    const thermalOrderHeader = <?php echo json_encode(' RESTAURANT\n'.($order ? ('Order '.$order['order_no'].' • Table '.($orderTableSummary !== '' ? $orderTableSummary : ($order['table_no'] ?? '')).'\nGuest: '.($order['guest_name'] ?: 'Guest')) : ' RESTAURANT')); ?>;
    const orderLineData = <?php echo json_encode(array_values(array_map(function($it){ return [
        'id'=>(int)($it['id']??0),
        'seat_no'=>(int)($it['seat_no']??0),
        'qty'=>(float)($it['qty']??0),
        'name'=>(string)($it['item_name']??''),
        'amount'=>round((float)($it['line_total']??0)+(float)($it['tax_amount']??0),2),
        'status'=>(string)($it['status']??''),
        'printer_role'=>(string)($it['printer_role']??''),
        'notes'=>(string)($it['notes']??'')
    ]; }, $orderItems ?? [])), JSON_UNESCAPED_UNICODE); ?>;

    function destinationSelect(name){
        return '<select name="'+name+'" class="form-select form-select-sm split-destination">'
            + '<option value="cash">Cash</option><option value="card">Card</option><option value="pos">POS</option><option value="mobile_money">Mobile Money</option><option value="bank_transfer">Bank Transfer</option>'
            + '<option value="room_folio">Room Folio</option><option value="guest_folio">Guest Folio</option><option value="master_folio">Master Folio</option><option value="group_master">Group Master</option><option value="company_account">Company Account</option>'
            + '<option value="comp">Comp</option><option value="house_account">House Account</option><option value="dispute">Dispute Hold</option><option value="package">Package / Allowance</option>'
            + '</select>';
    }

    function isCardDestination(dest){
        const d=String(dest||'').toLowerCase();
        return ['card','pos'].includes(d);
    }
    function surchargeFor(amount, dest){
        amount = parseFloat(amount||0)||0;
        return isCardDestination(dest) ? Math.round(amount * cardSurchargeRate * 100) / 100 : 0;
    }
    function splitMethodLabel(dest){
        const d=String(dest||'cash').replace(/_/g,' ');
        return d.toUpperCase();
    }
    function paymentRowHtml(prefix, label, amount, methodName, destName, folioName, refName){
        const safeAmount=Number(amount||0).toFixed(2);
        return '<div class="row g-2 mb-2 align-items-center split-preview-source" data-split-label="'+String(label).replace(/"/g,'&quot;')+'" data-split-amount="'+safeAmount+'">'
            + '<div class="col-md-2"><strong>'+label+'</strong><div class="small text-muted split-base-amount">'+safeAmount+'</div><div class="small text-warning split-surcharge-note"></div></div>'
            + '<div class="col-md-3">'+destinationSelect(destName)+'</div>'
            + '<div class="col-md-4"><select name="'+folioName+'" class="form-select form-select-sm split-folio-select">'+splitFolioOptionsHtml+'</select><div class="small text-muted folio-help"></div></div>'
            + '<div class="col-md-3"><input name="'+refName+'" class="form-control form-control-sm" placeholder="Reference"></div>'
            + '</div>';
    }
    function folioTypeForDestination(dest){
        dest=String(dest||'').toLowerCase();
        if(dest==='room_folio') return 'room';
        if(dest==='guest_folio') return 'guest';
        if(['master_folio','group_master','company_account','folio'].includes(dest)) return 'master';
        return '';
    }
    function refreshSplitFolioOptions(){
        document.querySelectorAll('.split-folio-select').forEach(sel=>{
            const row=sel.closest('.split-preview-source') || sel.closest('tr') || sel.closest('.custom-row') || sel.parentElement;
            const dest=row ? (row.querySelector('.split-destination')?.value || '') : '';
            const wanted=folioTypeForDestination(dest);
            const holder=sel.closest('td') || sel.parentElement;
            const help=holder ? holder.querySelector('.folio-help') : null;
            if(!wanted){
                sel.value='';
                sel.disabled=true;
                sel.style.display='none';
                if(help) help.textContent='Not required for this settlement type.';
                return;
            }
            sel.disabled=false;
            sel.style.display='';
            let visible=0;
            Array.from(sel.options).forEach(opt=>{
                if(!opt.value){
                    opt.hidden=false;
                    return;
                }
                const ok=(String(opt.dataset.folioType||'').toLowerCase()===wanted);
                opt.hidden=!ok;
                if(ok) visible++;
                if(!ok && opt.selected) sel.value='';
            });
            if(help) help.textContent=visible ? 'Showing '+wanted+' folios for current in-house, arriving-today, or departing-today bookings only.' : 'No matching '+wanted+' folios available for current in-house/arrival/departure bookings.';
        });
    }

    function updateSplitRowSurchargeNotes(){
        document.querySelectorAll('.split-preview-source').forEach(row=>{
            const amount=parseFloat(row.dataset.splitAmount||'0')||0;
            const dest=row.querySelector('select[name*="destination_type"]')?.value || 'cash';
            const fee=surchargeFor(amount,dest);
            const note=row.querySelector('.split-surcharge-note');
            if(note) note.textContent = fee>0 ? '3% POS/Card surcharge: '+money(fee)+' • Pay '+money(amount+fee) : '';
        });
        refreshSplitFolioOptions();
    }
    function rebuildEqualRows(){
        const wrap=document.getElementById('equalPaymentRows');
        const countEl=document.getElementById('equalSplitCount');
        if(!wrap || !countEl) return;
        const count=Math.max(1,Math.min(65,parseInt(countEl.value||'1',10)));
        const per = count ? (totalDue / count) : totalDue;
        wrap.innerHTML='';
        for(let i=0;i<count;i++){
            const amount = (i===count-1) ? (totalDue - (Math.round(per*100)/100)*(count-1)) : per;
            wrap.insertAdjacentHTML('beforeend', paymentRowHtml('equal','Guest '+(i+1),amount,'equal_method[]','equal_destination_type[]','equal_folio_id[]','equal_reference[]'));
        }
        updateSplitRowSurchargeNotes();
        refreshSplitFolioOptions();
        rebuildSettlementPreview();
    }
    function rebuildCoverRows(){
        const wrap=document.getElementById('coverPaymentRows');
        if(!wrap) return;
        const count=Math.max(1,Math.min(65,parseInt(coversCount||1,10)));
        const per = count ? (totalDue / count) : totalDue;
        wrap.innerHTML='';
        for(let i=0;i<count;i++){
            const amount = (i===count-1) ? (totalDue - (Math.round(per*100)/100)*(count-1)) : per;
            wrap.insertAdjacentHTML('beforeend', paymentRowHtml('cover','Cover '+(i+1),amount,'equal_method[]','equal_destination_type[]','equal_folio_id[]','equal_reference[]'));
        }
        updateSplitRowSurchargeNotes();
        refreshSplitFolioOptions();
        rebuildSettlementPreview();
    }
    const eqCount=document.getElementById('equalSplitCount');
    if(eqCount){ eqCount.addEventListener('input',rebuildEqualRows); rebuildEqualRows(); }
    rebuildCoverRows();

    function activeSplitType(){
        return document.querySelector('input[name="split_type"]:checked')?.value || 'equal';
    }
    function thermalLine(left,right,width=42){
        left=String(left||''); right=String(right||'');
        const space=Math.max(1,width-left.length-right.length);
        return left + ' '.repeat(space) + right;
    }
    function receiptText(title, lines, footerLines){
        const width=42;
        let out=[];
        out.push('='.repeat(width));
        out.push(thermalOrderHeader);
        out.push('-'.repeat(width));
        out.push(title);
        out.push('-'.repeat(width));
        let sub=0;
        (lines||[]).forEach(l=>{
            const qty=(parseFloat(l.qty||1)||1);
            const name=(qty!==1 ? qty+' x ' : '') + (l.name||'Item');
            sub += parseFloat(l.amount||0)||0;
            out.push(thermalLine(name.substring(0,28), money(l.amount||0), width));
            if(l.notes) out.push('  * '+String(l.notes).substring(0,36));
        });
        out.push('-'.repeat(width));
        out.push(thermalLine('BILL TOTAL', money(sub), width));
        (footerLines||[]).forEach(f=>out.push(thermalLine(f.label, money(f.amount), width)));
        const add=(footerLines||[]).reduce((a,f)=>a+(parseFloat(f.amount)||0),0);
        if(add>0) out.push(thermalLine('AMOUNT PAYABLE', money(sub+add), width));
        out.push('='.repeat(width));
        return out.join('\n');
    }
    function masterReceiptText(){
        return receiptText('MASTER BILL', orderLineData.filter(l=>l.status==='active'), []);
    }
    function currentSettlementBills(){
        const type=activeSplitType();
        const bills=[];
        if(type==='equal' || type==='covers'){
            const container = type==='covers' ? document.getElementById('coverPaymentRows') : document.getElementById('equalPaymentRows');
            (container ? container.querySelectorAll('.split-preview-source') : []).forEach(row=>{
                const amount=parseFloat(row.dataset.splitAmount||'0')||0;
                const dest=row.querySelector('select[name*="destination_type"]')?.value || 'cash';
                const fee=surchargeFor(amount,dest);
                bills.push({
                    title:(row.dataset.splitLabel||'Split Bill')+' • '+splitMethodLabel(dest),
                    lines:[{qty:1,name:(type==='covers'?'Cover share':'Equal share'),amount:amount}],
                    surcharge:fee
                });
            });
        }else if(type==='seat'){
            const seatMap={};
            orderLineData.filter(l=>l.status==='active').forEach(l=>{ const k=String(l.seat_no||0); (seatMap[k]=seatMap[k]||[]).push(l); });
            Object.keys(seatMap).sort((a,b)=>parseInt(a)-parseInt(b)).forEach(k=>{
                const row=document.querySelector('select[name="seat_destination_type['+k+']"]')?.closest('tr');
                const dest=row?.querySelector('select[name*="destination_type"]')?.value || 'cash';
                const total=seatMap[k].reduce((a,l)=>a+(parseFloat(l.amount)||0),0);
                bills.push({title:(parseInt(k)>0?'Seat '+k:'Unassigned Seat')+' • '+splitMethodLabel(dest),lines:seatMap[k],surcharge:surchargeFor(total,dest)});
            });
        }else if(type==='item'){
            const groups={};
            orderLineData.filter(l=>l.status==='active').forEach(l=>{
                const payer=document.querySelector('input[name="item_unit_payer['+l.id+']['+l.unit_no+']"]')?.value.trim() || 'Unassigned Item Split';
                const dest=document.querySelector('select[name="item_unit_destination_type['+l.id+']['+l.unit_no+']"]')?.value || 'cash';
                const key=payer+'|'+dest;
                (groups[key]=groups[key]||{title:payer+' • '+splitMethodLabel(dest),lines:[],dest}).lines.push(l);
            });
            Object.values(groups).forEach(g=>{ const total=g.lines.reduce((a,l)=>a+(parseFloat(l.amount)||0),0); bills.push({title:g.title,lines:g.lines,surcharge:surchargeFor(total,g.dest)}); });
        }else{
            document.querySelectorAll('#customSplitRows .custom-row').forEach((row,idx)=>{
                const payer=row.querySelector('input[name="custom_payer[]"]')?.value.trim() || ('Settlement Line '+(idx+1));
                const percent=parseFloat(row.querySelector('input[name="custom_percent[]"]')?.value||0)||0;
                let amount=parseFloat(row.querySelector('input[name="custom_amount[]"]')?.value||0)||0;
                if(amount<=0 && percent>0) amount=Math.round(totalDue*(percent/100)*100)/100;
                if(amount<=0) return;
                const dest=row.querySelector('select[name="custom_destination_type[]"]')?.value || 'cash';
                bills.push({title:payer+' • '+splitMethodLabel(dest),lines:[{qty:1,name:'Mixed settlement allocation',amount}],surcharge:surchargeFor(amount,dest)});
            });
        }
        return bills;
    }
    function rebuildSettlementPreview(){
        updateSplitRowSurchargeNotes();
        const master=document.getElementById('thermalMasterPreview');
        const split=document.getElementById('thermalSplitPreview');
        if(master) master.textContent=masterReceiptText();
        if(split){
            const bills=currentSettlementBills();
            if(!bills.length){ split.innerHTML='<div class="text-muted small p-3">No split bills to preview yet.</div>'; return; }
            split.innerHTML='';
            bills.forEach((b,idx)=>{
                const footer=b.surcharge>0?[{label:'POS/CARD SURCHARGE 3%',amount:b.surcharge}]:[];
                const pre=document.createElement('pre');
                pre.className='thermal-preview-pre mb-3';
                pre.textContent=receiptText(b.title,b.lines,footer);
                split.appendChild(pre);
            });
        }
    }
    function printPreviewText(text){
        const safeText = String(text || '');
        const html = '<!doctype html><html><head><meta charset="utf-8"><title>Thermal Preview</title><style>body{font-family:monospace;font-size:12px;margin:10px;}pre{white-space:pre-wrap;}@media print{@page{size:80mm auto;margin:4mm;}body{margin:0}}</style></head><body><pre></pre></body></html>';
        const w=window.open('','_blank','width=420,height=680');
        if(!w){
            const modal=document.createElement('div');
            modal.className='modal fade';
            modal.tabIndex=-1;
            modal.innerHTML='<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Thermal Print Preview</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><pre class="thermal-preview-pre"></pre></div><div class="modal-footer"><button type="button" class="btn btn-primary" id="fallbackPrintBtn">Print</button><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button></div></div></div>';
            modal.querySelector('pre').textContent=safeText;
            document.body.appendChild(modal);
            const printBtn=modal.querySelector('#fallbackPrintBtn');
            printBtn.addEventListener('click',function(){
                const fw=window.open('','_blank','width=420,height=680');
                if(fw){
                    fw.document.open();
                    fw.document.write(html);
                    fw.document.close();
                    const pre=fw.document.querySelector('pre');
                    if(pre) pre.textContent=safeText;
                    fw.focus();
                    setTimeout(function(){ fw.print(); }, 250);
                } else {
                    window.print();
                }
            });
            if(window.bootstrap && bootstrap.Modal) bootstrap.Modal.getOrCreateInstance(modal).show();
            else { modal.style.display='block'; modal.classList.add('show'); }
            modal.addEventListener('hidden.bs.modal',()=>modal.remove());
            return;
        }
        w.document.open();
        w.document.write(html);
        w.document.close();
        const pre=w.document.querySelector('pre');
        if(pre) pre.textContent=safeText;
        w.focus();
        setTimeout(function(){ w.print(); }, 250);
    }
    document.getElementById('refreshSettlementPreview')?.addEventListener('click',rebuildSettlementPreview);
    document.getElementById('printMasterPreview')?.addEventListener('click',()=>printPreviewText(masterReceiptText()));
    document.getElementById('printSplitPreview')?.addEventListener('click',()=>{
        const txt=currentSettlementBills().map(b=>receiptText(b.title,b.lines,b.surcharge>0?[{label:'POS/CARD SURCHARGE 3%',amount:b.surcharge}]:[])).join('\n\n');
        printPreviewText(txt || 'No split bills to print.');
    });
    document.getElementById('splitBillModal')?.addEventListener('input',rebuildSettlementPreview);
    document.getElementById('splitBillModal')?.addEventListener('change',rebuildSettlementPreview);
    document.getElementById('splitBillModal')?.addEventListener('shown.bs.modal',rebuildSettlementPreview);

    document.querySelectorAll('[data-bs-toggle="pill"][data-bs-target^="#split"]').forEach(btn=>{
        btn.addEventListener('shown.bs.tab', function(){
            const pane=document.querySelector(this.getAttribute('data-bs-target'));
            const radio=pane ? pane.querySelector('input[name="split_type"]') : null;
            if(radio) radio.checked=true;
            rebuildSettlementPreview();
        });
    });

    const addCustom=document.getElementById('addCustomSplitRow');
    if(addCustom){
        addCustom.addEventListener('click',function(){
            const wrap=document.getElementById('customSplitRows');
            const first=wrap ? wrap.querySelector('.custom-row') : null;
            if(!wrap || !first) return;
            const clone=first.cloneNode(true);
            clone.querySelectorAll('input').forEach(inp=>{ inp.value=''; });
            clone.querySelectorAll('select').forEach(sel=>{ sel.selectedIndex=0; });
            wrap.appendChild(clone);
        });
    }
    document.addEventListener('click',function(e){
        if(e.target && e.target.classList.contains('remove-custom-row')){
            const rows=document.querySelectorAll('#customSplitRows .custom-row');
            if(rows.length>1) e.target.closest('.custom-row').remove();
        }
        if(e.target && e.target.classList.contains('remove-pay-row')){
            const rows=document.querySelectorAll('#multiPaymentRows .payment-row');
            if(rows.length>1) e.target.closest('.payment-row').remove();
        }
    });
    const addPayment=document.getElementById('addPaymentRow');
    if(addPayment){
        addPayment.addEventListener('click',function(){
            const wrap=document.getElementById('multiPaymentRows');
            const first=wrap ? wrap.querySelector('.payment-row') : null;
            if(!wrap || !first) return;
            const clone=first.cloneNode(true);
            clone.querySelectorAll('input').forEach(inp=>{ inp.value=''; });
            wrap.appendChild(clone);
        });
    }

    accountButtons.forEach(btn=>{
        btn.addEventListener('click',function(){
            setAccountTarget(this.getAttribute('data-account-target'));
        });
    });
    if(linkedOrder) linkedOrder.addEventListener('change',rebuildFolioOptions);
    setAccountTarget(folioTarget && folioTarget.value ? folioTarget.value : 'master');
});
</script>
<?php if($order): ?>
<script>
(function(){
    "use strict";

    function showModalById(id){
        var el=document.getElementById(id);
        if(!el) return false;
        try{
            if(window.bootstrap && bootstrap.Modal){
                bootstrap.Modal.getOrCreateInstance(el).show();
                return true;
            }
        }catch(e){}
        el.style.display='block';
        el.removeAttribute('aria-hidden');
        el.classList.add('show');
        document.body.classList.add('modal-open');
        var backdrop=document.createElement('div');
        backdrop.className='modal-backdrop fade show';
        backdrop.setAttribute('data-fallback-backdrop-for',id);
        document.body.appendChild(backdrop);
        el.querySelectorAll('[data-bs-dismiss="modal"],.btn-close').forEach(function(btn){
            btn.addEventListener('click',function(){
                el.classList.remove('show');
                el.style.display='none';
                el.setAttribute('aria-hidden','true');
                document.body.classList.remove('modal-open');
                document.querySelectorAll('[data-fallback-backdrop-for="'+id+'"]').forEach(function(b){b.remove();});
            },{once:true});
        });
        return true;
    }

    function textOf(selector){
        var el=document.querySelector(selector);
        return el ? (el.textContent || '') : '';
    }

    function bindBillPreviewButtons(){
        document.querySelectorAll('[data-bs-target="#billPreviewModal"],[data-bs-target="#splitBillModal"]').forEach(function(btn){
            if(btn.dataset.fallbackModalBound==='1') return;
            btn.dataset.fallbackModalBound='1';
            btn.addEventListener('click',function(e){
                var target=this.getAttribute('data-bs-target');
                if(target && target.charAt(0)==='#'){
                    setTimeout(function(){
                        var el=document.querySelector(target);
                        if(el && !el.classList.contains('show')) showModalById(target.substring(1));
                        if(target==='#splitBillModal' && window.restaurantSettlementPreviewRefresh){
                            try{ window.restaurantSettlementPreviewRefresh(); }catch(err){}
                        }
                    },60);
                }
            });
        });
    }

    function fallbackRefreshSettlementPreview(){
        if(window.restaurantSettlementPreviewRefresh){
            try{ window.restaurantSettlementPreviewRefresh(); return; }catch(e){}
        }
        var master=document.getElementById('thermalMasterPreview');
        var split=document.getElementById('thermalSplitPreview');
        if(master && !master.textContent.trim()){
            master.textContent='==========================================\n RESTAURANT\nMASTER BILL\n------------------------------------------\nPreview could not auto-build from JavaScript.\nUse the bill lines shown on the active check.\n==========================================';
        }
        if(split && !split.textContent.trim()){
            split.innerHTML='<div class="text-muted small p-3">Choose a split method/payment details, then click Refresh Preview.</div>';
        }
    }

    function bindSettlementPreviewButtons(){
        var refresh=document.getElementById('refreshSettlementPreview');
        var printMaster=document.getElementById('printMasterPreview');
        var printSplit=document.getElementById('printSplitPreview');

        if(refresh && refresh.dataset.fallbackBound!=='1'){
            refresh.dataset.fallbackBound='1';
            refresh.addEventListener('click',function(e){
                e.preventDefault();
                fallbackRefreshSettlementPreview();
            });
        }

        if(printMaster && printMaster.dataset.fallbackBound!=='1'){
            printMaster.dataset.fallbackBound='1';
            printMaster.addEventListener('click',function(e){
                e.preventDefault();
                fallbackRefreshSettlementPreview();
                var t=textOf('#thermalMasterPreview') || 'No master bill preview available.';
                var w=window.open('','_blank','width=420,height=680');
                if(w){
                    w.document.write('<html><head><title>Master Bill</title><style>body{font-family:monospace;font-size:12px;margin:10px;}pre{white-space:pre-wrap;}@media print{@page{size:80mm auto;margin:4mm;}body{margin:0}}</style></head><body><pre>'+t.replace(/[&<>]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;"}[c];})+'</pre><script>window.onload=function(){window.print();}<\/script></body></html>');
                    w.document.close();
                }else{
                    alert('Popup blocked. Please allow popups, or use the preview on screen.');
                }
            });
        }

        if(printSplit && printSplit.dataset.fallbackBound!=='1'){
            printSplit.dataset.fallbackBound='1';
            printSplit.addEventListener('click',function(e){
                e.preventDefault();
                fallbackRefreshSettlementPreview();
                var t=textOf('#thermalSplitPreview') || 'No split bill preview available.';
                var w=window.open('','_blank','width=420,height=680');
                if(w){
                    w.document.write('<html><head><title>Split Bills</title><style>body{font-family:monospace;font-size:12px;margin:10px;}pre{white-space:pre-wrap;}@media print{@page{size:80mm auto;margin:4mm;}body{margin:0}}</style></head><body><pre>'+t.replace(/[&<>]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;"}[c];})+'</pre><script>window.onload=function(){window.print();}<\/script></body></html>');
                    w.document.close();
                }else{
                    alert('Popup blocked. Please allow popups, or use the preview on screen.');
                }
            });
        }
    }

    function boot(){
        bindBillPreviewButtons();
        bindSettlementPreviewButtons();

        var splitModal=document.getElementById('splitBillModal');
        if(splitModal){
            splitModal.addEventListener('shown.bs.modal',function(){ setTimeout(fallbackRefreshSettlementPreview,80); });
            splitModal.addEventListener('input',function(){ setTimeout(fallbackRefreshSettlementPreview,0); });
            splitModal.addEventListener('change',function(){ setTimeout(fallbackRefreshSettlementPreview,0); });
        }

        setTimeout(fallbackRefreshSettlementPreview,150);
    }

    if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',boot);
    else boot();
})();
</script>
<?php endif; ?>


<?php if($order): ?>
<script>
/*
  Final override: split/master preview buttons must PREVIEW first, not print directly.
  This capture-phase handler runs before older click handlers that used window.open/print.
*/
(function(){
    "use strict";

    function esc(s){
        return String(s == null ? '' : s).replace(/[&<>"]/g,function(c){
            return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];
        });
    }

    function ensureThermalPreviewModal(){
        var modal=document.getElementById('thermalPreviewOnlyModal');
        if(modal) return modal;

        var div=document.createElement('div');
        div.innerHTML =
        '<div class="modal fade" id="thermalPreviewOnlyModal" tabindex="-1" aria-hidden="true">'+
          '<div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable">'+
            '<div class="modal-content">'+
              '<div class="modal-header">'+
                '<div>'+
                  '<h5 class="modal-title" id="thermalPreviewOnlyTitle">Bill Preview</h5>'+
                  '<div class="small text-muted">Review this thermal bill before printing. Nothing is posted or printed until you click Print.</div>'+
                '</div>'+
                '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>'+
              '</div>'+
              '<div class="modal-body bg-light">'+
                '<pre id="thermalPreviewOnlyBody" class="thermal-preview-pre" style="max-width:360px;margin:0 auto;background:#fff;border:1px dashed #94a3b8;min-height:280px;white-space:pre-wrap;"></pre>'+
              '</div>'+
              '<div class="modal-footer">'+
                '<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>'+
                '<button type="button" class="btn btn-primary" id="thermalPreviewOnlyPrintBtn"><i class="bi bi-printer me-1"></i>Print This Preview</button>'+
              '</div>'+
            '</div>'+
          '</div>'+
        '</div>';
        document.body.appendChild(div.firstElementChild);
        modal=document.getElementById('thermalPreviewOnlyModal');

        modal.querySelector('#thermalPreviewOnlyPrintBtn').addEventListener('click',function(){
            var text=(document.getElementById('thermalPreviewOnlyBody')?.textContent || '').trim();
            if(!text){ alert('Nothing to print.'); return; }

            var html='<html><head><title>Thermal Print</title>'+
                '<style>body{font-family:monospace;font-size:12px;margin:10px;}pre{white-space:pre-wrap;}@media print{@page{size:80mm auto;margin:4mm;}body{margin:0}}</style>'+
                '</head><body><pre>'+esc(text)+'</pre><script>window.onload=function(){window.print();}<\/script></body></html>';

            var w=window.open('','_blank','width=420,height=680');
            if(w){
                w.document.write(html);
                w.document.close();
            }else{
                alert('Popup blocked. Allow popups to print, or print from the browser menu.');
            }
        });

        return modal;
    }

    function showThermalPreview(title,text){
        var modal=ensureThermalPreviewModal();
        document.getElementById('thermalPreviewOnlyTitle').textContent=title || 'Bill Preview';
        document.getElementById('thermalPreviewOnlyBody').textContent=(text || '').trim() || 'No preview available yet. Click Refresh Preview, choose a split method, or add items to the check.';
        if(window.bootstrap && bootstrap.Modal){
            bootstrap.Modal.getOrCreateInstance(modal).show();
        }else{
            modal.style.display='block';
            modal.classList.add('show');
            modal.removeAttribute('aria-hidden');
        }
    }

    function collectPreviewText(which){
        try{
            if(window.restaurantSettlementPreviewRefresh) window.restaurantSettlementPreviewRefresh();
        }catch(e){}
        try{
            var refresh=document.getElementById('refreshSettlementPreview');
            if(refresh) refresh.dispatchEvent(new Event('click',{bubbles:true}));
        }catch(e){}

        if(which==='master'){
            var m=document.getElementById('thermalMasterPreview');
            return m ? m.textContent : '';
        }

        var split=document.getElementById('thermalSplitPreview');
        if(!split) return '';
        var parts=[];
        split.querySelectorAll('pre').forEach(function(pre){
            var t=(pre.textContent || '').trim();
            if(t) parts.push(t);
        });
        if(parts.length) return parts.join('\n\n');
        return split.textContent || '';
    }

    // Capture before older click handlers that directly call printPreviewText/window.open.
    document.addEventListener('click',function(e){
        var btn=e.target && e.target.closest ? e.target.closest('#printMasterPreview,#printSplitPreview') : null;
        if(!btn) return;

        e.preventDefault();
        e.stopPropagation();
        if(e.stopImmediatePropagation) e.stopImmediatePropagation();

        var isMaster=btn.id==='printMasterPreview';
        var text=collectPreviewText(isMaster ? 'master' : 'split');
        showThermalPreview(isMaster ? 'Master Bill Preview' : 'Split Bills Preview', text);
    },true);

    // Make split modal opening also refresh previews after it appears.
    document.addEventListener('shown.bs.modal',function(e){
        if(e.target && e.target.id==='splitBillModal'){
            setTimeout(function(){
                try{
                    if(window.restaurantSettlementPreviewRefresh) window.restaurantSettlementPreviewRefresh();
                    var refresh=document.getElementById('refreshSettlementPreview');
                    if(refresh) refresh.dispatchEvent(new Event('click',{bubbles:true}));
                }catch(err){}
            },120);
        }
    });
})();
</script>

<script>
(function(){
    "use strict";

    const data = {
        header: <?php echo json_encode(' RESTAURANT\n'.($order ? ('Order '.$order['order_no'].' • Table '.($orderTableSummary !== '' ? $orderTableSummary : ($order['table_no'] ?? '')).'\nGuest: '.($order['guest_name'] ?: 'Guest')) : ' RESTAURANT'), JSON_UNESCAPED_UNICODE); ?>,
        total: <?php echo json_encode(isset($order['total_amount']) ? round((float)$order['total_amount'],2) : 0); ?>,
        covers: <?php echo json_encode(max(1,(int)($order['covers'] ?? 1))); ?>,
        surchargeRate: <?php echo json_encode(REST_POS_CARD_SURCHARGE_RATE); ?>,
        lines: <?php
            $previewLines = [];
            foreach(($orderItems ?? []) as $it){
                if(($it['status'] ?? '') !== 'active') continue;
                $qty = max(1, (int)round((float)($it['qty'] ?? 1)));
                $lineAmount = round((float)($it['line_total'] ?? 0) + (float)($it['tax_amount'] ?? 0), 2);
                $unitAmount = $qty > 0 ? round($lineAmount / $qty, 2) : $lineAmount;
                for($u=1; $u <= $qty; $u++){
                    $thisAmount = ($u === $qty) ? round($lineAmount - ($unitAmount * ($qty - 1)), 2) : $unitAmount;
                    $previewLines[] = [
                        'id'=>(int)($it['id'] ?? 0),
                        'unit_no'=>$u,
                        'split_key'=>(int)($it['id'] ?? 0).'_'.$u,
                        'seat_no'=>(int)($it['seat_no'] ?? 0),
                        'qty'=>1,
                        'name'=>(string)($it['item_name'] ?? 'Item') . ($qty > 1 ? ' (Unit '.$u.' of '.$qty.')' : ''),
                        'amount'=>$thisAmount,
                        'status'=>(string)($it['status'] ?? ''),
                        'notes'=>(string)($it['notes'] ?? '')
                    ];
                }
            }
            echo json_encode($previewLines, JSON_UNESCAPED_UNICODE);
        ?>
    };

    function money(v){ return (parseFloat(v || 0) || 0).toFixed(2); }
    function safeText(s){ return String(s || '').replace(/[&<>"']/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[ch])); }
    function isCardLike(dest, method){
        const d=String(dest || '').toLowerCase();
        const m=String(method || '').toLowerCase();
        return ['card','pos'].includes(d) || ['pos','card','credit card','debit card','visa','mastercard','card payment','pos/card'].includes(m);
    }
    function surcharge(amount,dest){
        amount=parseFloat(amount || 0) || 0;
        return isCardLike(dest,'') ? Math.round(amount * data.surchargeRate * 100) / 100 : 0;
    }
    function labelMethod(dest){
        const d=String(dest || 'cash').replace(/_/g,' ');
        return d.toUpperCase();
    }
    function line(left,right,width){
        width=width || 42;
        left=String(left || '');
        right=String(right || '');
        const space=Math.max(1,width-left.length-right.length);
        return left + ' '.repeat(space) + right;
    }
    function receipt(title, lines, extraLines){
        const width=42;
        let out=[];
        out.push('='.repeat(width));
        out.push(data.header);
        out.push('-'.repeat(width));
        out.push(title);
        out.push('-'.repeat(width));
        let total=0;
        (lines || []).forEach(l=>{
            const qty=parseFloat(l.qty || 1) || 1;
            const amount=parseFloat(l.amount || 0) || 0;
            total += amount;
            const name=(qty!==1 ? qty+' x ' : '') + (l.name || 'Item');
            out.push(line(name.substring(0,28), money(amount), width));
            if(l.notes) out.push('  * '+String(l.notes).substring(0,36));
        });
        out.push('-'.repeat(width));
        out.push(line('BILL TOTAL', money(total), width));
        let add=0;
        (extraLines || []).forEach(x=>{
            const a=parseFloat(x.amount || 0) || 0;
            add += a;
            out.push(line(x.label, money(a), width));
        });
        if(add>0) out.push(line('AMOUNT PAYABLE', money(total + add), width));
        out.push('='.repeat(width));
        return out.join('\n');
    }
    function selectedSplitType(){
        const r=document.querySelector('#splitBillModal input[name="split_type"]:checked');
        return r ? r.value : 'equal';
    }
    function ensureGeneratedRows(){
        if(typeof window.rebuildEqualRows === 'function') { try{ window.rebuildEqualRows(); }catch(e){} }
        if(typeof window.rebuildCoverRows === 'function') { try{ window.rebuildCoverRows(); }catch(e){} }

        const eq=document.getElementById('equalPaymentRows');
        if(eq && !eq.querySelector('.split-preview-source')){
            const countEl=document.getElementById('equalSplitCount');
            const count=Math.max(1,Math.min(65,parseInt((countEl && countEl.value) || '2',10)));
            const each=data.total / count;
            eq.innerHTML='';
            for(let i=0;i<count;i++){
                const amount=(i===count-1) ? data.total - (Math.round(each*100)/100)*(count-1) : each;
                eq.insertAdjacentHTML('beforeend', generatedSplitRow('Guest '+(i+1), amount, 'equal_method[]','equal_destination_type[]','equal_folio_id[]','equal_reference[]'));
            }
        }
        const cov=document.getElementById('coverPaymentRows');
        if(cov && !cov.querySelector('.split-preview-source')){
            const count=Math.max(1,Math.min(65,parseInt(data.covers || 1,10)));
            const each=data.total / count;
            cov.innerHTML='';
            for(let i=0;i<count;i++){
                const amount=(i===count-1) ? data.total - (Math.round(each*100)/100)*(count-1) : each;
                cov.insertAdjacentHTML('beforeend', generatedSplitRow('Cover '+(i+1), amount, 'equal_method[]','equal_destination_type[]','equal_folio_id[]','equal_reference[]'));
            }
        }
    }
    function generatedSplitRow(label, amount, methodName, destName, folioName, refName){
        const folios = <?php echo json_encode($folioSelectHtml ?? '<option value="">-- select folio --</option>', JSON_UNESCAPED_UNICODE); ?>;
        return '<div class="row g-2 mb-2 align-items-center split-preview-source" data-split-label="'+safeText(label)+'" data-split-amount="'+money(amount)+'">'
            + '<div class="col-md-2"><strong>'+safeText(label)+'</strong><div class="small text-muted">'+money(amount)+'</div><div class="small text-warning split-surcharge-note"></div></div>'
            + '<div class="col-md-3"><select name="'+destName+'" class="form-select form-select-sm split-destination"><option value="cash">Cash</option><option value="card">Card</option><option value="pos">POS</option><option value="mobile_money">Mobile Money</option><option value="bank_transfer">Bank Transfer</option><option value="room_folio">Room Folio</option><option value="guest_folio">Guest Folio</option><option value="master_folio">Master Folio</option><option value="group_master">Group Master</option><option value="company_account">Company Account</option><option value="comp">Comp</option><option value="dispute">Dispute</option></select></div>'
            + '<div class="col-md-4"><select name="'+folioName+'" class="form-select form-select-sm split-folio-select">'+folios+'</select><div class="small text-muted folio-help"></div></div>'
            + '<div class="col-md-3"><input name="'+refName+'" class="form-control form-control-sm" placeholder="Reference"></div>'
            + '</div>';
    }
    function getRowDestMethod(row){
        const dest=row.querySelector('select[name*="destination_type"]')?.value || 'cash';
        return {dest:dest, method:dest};
    }
    function getBills(){
        ensureGeneratedRows();
        const type=selectedSplitType();
        let bills=[];
        if(type==='equal' || type==='covers'){
            const wrap=document.getElementById(type==='covers' ? 'coverPaymentRows' : 'equalPaymentRows');
            (wrap ? wrap.querySelectorAll('.split-preview-source') : []).forEach(row=>{
                const amount=parseFloat(row.dataset.splitAmount || '0') || 0;
                const dm=getRowDestMethod(row);
                const fee=surcharge(amount, dm.dest);
                const note=row.querySelector('.split-surcharge-note');
                if(note) note.textContent = fee>0 ? '3% POS/Card surcharge: '+money(fee)+' • Pay '+money(amount+fee) : '';
                bills.push({
                    title:(row.dataset.splitLabel || 'Split')+' • '+labelMethod(dm.dest),
                    lines:[{qty:1,name:type==='covers'?'Cover share':'Equal bill share',amount:amount}],
                    surcharge:fee
                });
            });
        }else if(type==='seat'){
            const bySeat={};
            data.lines.forEach(l=>{
                const key=String(l.seat_no || 0);
                (bySeat[key]=bySeat[key] || []).push(l);
            });
            Object.keys(bySeat).sort((a,b)=>parseInt(a,10)-parseInt(b,10)).forEach(key=>{
                const row=document.querySelector('select[name="seat_destination_type['+key+']"]')?.closest('tr');
                const dm=row ? getRowDestMethod(row) : {dest:'cash',method:'cash'};
                const amount=bySeat[key].reduce((a,l)=>a+(parseFloat(l.amount)||0),0);
                bills.push({
                    title:(parseInt(key,10)>0?'Seat '+key:'Unassigned Seat')+' • '+labelMethod(dm.dest),
                    lines:bySeat[key],
                    surcharge:surcharge(amount,dm.dest)
                });
            });
        }else if(type==='item'){
            const groups={};
            data.lines.forEach(l=>{
                const payer=document.querySelector('input[name="item_unit_payer['+l.id+']['+l.unit_no+']"]')?.value.trim() || 'Unassigned Item Split';
                const dest=document.querySelector('select[name="item_unit_destination_type['+l.id+']['+l.unit_no+']"]')?.value || 'cash';
                const key=payer+'|'+dest;
                if(!groups[key]) groups[key]={title:payer+' • '+labelMethod(dest),dest,lines:[]};
                groups[key].lines.push(l);
            });
            Object.keys(groups).forEach(k=>{
                const g=groups[k], amount=g.lines.reduce((a,l)=>a+(parseFloat(l.amount)||0),0);
                bills.push({title:g.title,lines:g.lines,surcharge:surcharge(amount,g.dest)});
            });
        }else{
            document.querySelectorAll('#customSplitRows .custom-row').forEach((row,idx)=>{
                const payer=row.querySelector('input[name="custom_payer[]"]')?.value.trim() || ('Settlement Line '+(idx+1));
                const pct=parseFloat(row.querySelector('input[name="custom_percent[]"]')?.value || '0') || 0;
                let amount=parseFloat(row.querySelector('input[name="custom_amount[]"]')?.value || '0') || 0;
                if(amount<=0 && pct>0) amount=Math.round(data.total*(pct/100)*100)/100;
                if(amount<=0) return;
                const dest=row.querySelector('select[name="custom_destination_type[]"]')?.value || 'cash';
                bills.push({
                    title:payer+' • '+labelMethod(dest),
                    lines:[{qty:1,name:'Mixed settlement allocation',amount}],
                    surcharge:surcharge(amount,dest)
                });
            });
        }
        return bills;
    }
    function rebuildPreview(){
        const master=document.getElementById('thermalMasterPreview');
        const split=document.getElementById('thermalSplitPreview');
        if(master) master.textContent=receipt('MASTER BILL', data.lines, []);
        if(split){
            const bills=getBills();
            split.innerHTML='';
            if(!bills.length){
                split.innerHTML='<div class="text-muted small p-3">No split bills to preview yet.</div>';
                return;
            }
            bills.forEach(b=>{
                const pre=document.createElement('pre');
                pre.className='thermal-preview-pre mb-3';
                pre.textContent=receipt(b.title,b.lines,b.surcharge>0?[{label:'POS/CARD SURCHARGE 3%',amount:b.surcharge}]:[]);
                split.appendChild(pre);
            });
        }
    }
    function showSplitModal(){
        const el=document.getElementById('splitBillModal');
        if(!el) return;
        try{
            if(window.bootstrap && bootstrap.Modal) bootstrap.Modal.getOrCreateInstance(el).show();
            else { el.style.display='block'; el.classList.add('show'); }
        }catch(e){ el.style.display='block'; el.classList.add('show'); }
        setTimeout(function(){ if(typeof refreshSplitFolioOptions==='function') refreshSplitFolioOptions(); rebuildPreview(); },80);
    }

    window.restaurantSettlementPreviewRefresh = rebuildPreview;

    function bind(){
        document.querySelectorAll('[data-bs-target="#splitBillModal"]').forEach(btn=>{
            btn.addEventListener('click',function(e){
                e.preventDefault();
                e.stopImmediatePropagation();
                showSplitModal();
            },true);
        });
        const refresh=document.getElementById('refreshSettlementPreview');
        if(refresh) refresh.addEventListener('click',function(e){ e.preventDefault(); rebuildPreview(); },true);
        const modal=document.getElementById('splitBillModal');
        if(modal){
            modal.addEventListener('input',()=>setTimeout(rebuildPreview,0));
            modal.addEventListener('change',()=>setTimeout(rebuildPreview,0));
            modal.addEventListener('shown.bs.modal',()=>setTimeout(rebuildPreview,80));
        }
        document.querySelectorAll('[data-bs-toggle="pill"][data-bs-target^="#split"]').forEach(btn=>{
            btn.addEventListener('shown.bs.tab',function(){
                const pane=document.querySelector(this.getAttribute('data-bs-target'));
                const radio=pane ? pane.querySelector('input[name="split_type"]') : null;
                if(radio) radio.checked=true;
                setTimeout(rebuildPreview,50);
            });
        });
        setTimeout(rebuildPreview,150);
    }
    if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',bind);
    else bind();
})();
</script>

<?php endif; ?>


<script>
(function(){
    function folioTypeForDestination(dest){
        dest=String(dest||'').toLowerCase();
        if(dest==='room_folio') return 'room';
        if(dest==='guest_folio') return 'guest';
        if(['master_folio','group_master','company_account','folio'].includes(dest)) return 'master';
        return '';
    }
    window.refreshSplitFolioOptions = function(){
        document.querySelectorAll('.split-folio-select').forEach(function(sel){
            var row=sel.closest('.split-preview-source') || sel.closest('tr') || sel.closest('.custom-row') || sel.parentElement;
            var dest=row ? ((row.querySelector('.split-destination') || row.querySelector('select[name*="destination_type"]') || {}).value || '') : '';
            var wanted=folioTypeForDestination(dest);
            var holder=sel.closest('td') || sel.parentElement;
            var help=holder ? holder.querySelector('.folio-help') : null;
            if(!wanted){
                sel.value='';
                sel.disabled=true;
                sel.style.display='none';
                if(help) help.textContent='No folio needed.';
                return;
            }
            sel.disabled=false;
            sel.style.display='';
            var visible=0;
            Array.prototype.forEach.call(sel.options,function(opt){
                if(!opt.value){ opt.hidden=false; return; }
                var ok=String(opt.getAttribute('data-folio-type')||'').toLowerCase()===wanted;
                opt.hidden=!ok;
                if(ok) visible++;
                if(!ok && opt.selected) sel.value='';
            });
            if(help) help.textContent = visible ? 'Showing '+wanted+' folios only.' : 'No current '+wanted+' folios found.';
        });
    };
    function boot(){
        document.addEventListener('change',function(e){
            if(e.target && (e.target.classList.contains('split-destination') || (e.target.name||'').indexOf('destination_type')>-1)){
                window.refreshSplitFolioOptions();
            }
        });
        setTimeout(window.refreshSplitFolioOptions,100);
        var modal=document.getElementById('splitBillModal');
        if(modal){
            modal.addEventListener('shown.bs.modal',function(){ setTimeout(window.refreshSplitFolioOptions,80); });
        }
    }
    if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',boot); else boot();
})();
</script>


<script>
(function(){
    function bootFloorCollapseFix(){
        var workspace = document.getElementById('restaurantWorkspace');
        var floorPanel = workspace ? workspace.querySelector('.simphony-floor-panel') : null;
        var expandBtn = document.getElementById('expandFloorPlanBtn');

        if(!workspace || !floorPanel || !workspace.classList.contains('has-active-order')) return;

        function expandFloor(ev){
            if(ev){
                var tag = String((ev.target && ev.target.tagName) || '').toLowerCase();
                if(['a','button','input','select','textarea','label'].indexOf(tag) !== -1 && ev.target !== expandBtn) return;
                ev.preventDefault();
                ev.stopPropagation();
            }
            workspace.classList.add('floor-expanded');
            var firstTable = floorPanel.querySelector('.restaurant-table-btn, .table-open-btn, button[data-table-id], form button');
            setTimeout(function(){
                if(firstTable && typeof firstTable.focus === 'function') firstTable.focus({preventScroll:true});
            }, 80);
        }

        if(expandBtn) expandBtn.addEventListener('click', expandFloor);
        floorPanel.addEventListener('click', function(ev){
            if(!workspace.classList.contains('floor-expanded')) expandFloor(ev);
        });
    }

    if(document.readyState === 'loading'){
        document.addEventListener('DOMContentLoaded', bootFloorCollapseFix);
    } else {
        bootFloorCollapseFix();
    }
})();
</script>




<?php require_once __DIR__ . '/planner_printnode_patch.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function(){
    const lineVoidModal = document.getElementById('lineVoidModal');
    if(!lineVoidModal) return;

    lineVoidModal.addEventListener('show.bs.modal', function(event){
        const btn = event.relatedTarget;
        if(!btn) return;

        const itemId = btn.getAttribute('data-item-id') || '';
        const itemName = btn.getAttribute('data-item-name') || 'Selected item';
        const itemAmount = btn.getAttribute('data-item-amount') || '';

        const idInput = lineVoidModal.querySelector('#lineVoidItemId');
        const nameBox = lineVoidModal.querySelector('#lineVoidItemName');
        const amountBox = lineVoidModal.querySelector('#lineVoidItemAmount');

        if(idInput) idInput.value = itemId;
        if(nameBox) nameBox.textContent = itemName;
        if(amountBox) amountBox.textContent = itemAmount;
    });
});
</script>


<?php require_once __DIR__.'/../../includes/footer.php'; ?>
