lib/order-app.mjs:100:  /* t309: factual tour consult blurb — not paid catalog / official price / booking channel */
lib/hotel-ops.mjs:23:import { simPayEnabled, isSimulatedPayment, dispatchAllowedForFood, dispatchEligible, dispatchAllowed } from './pay-sim.mjs';
lib/hotel-ops.mjs:32:import { storeSigned, loadSigned, markSigned, isSigned, NOT_SIGNED } from './phieu-sign.mjs';
lib/hotel-ops.mjs:135:    if (pay !== 'da-tt' && pay !== 'da' && pay !== 'paid') return false;
lib/hotel-ops.mjs:138:    const paidDay = vnDay(st.paidAt || st.settleDate);
lib/hotel-ops.mjs:140:    return paidDay === d || arr === d;
lib/hotel-ops.mjs:147:  /* t331: current state holds every booking of this hotel → no paid booking that day is a TRUE zero (not invented).
lib/hotel-ops.mjs:167:/** t314: one request = one department. Legacy undispatched amenity orders carrying paid drink lines (a7 → f24) are split
lib/hotel-ops.mjs:169: *  Parent becomes status 'tach' (hidden from queues). Staff-claimed parent payment is NOT carried to the paid child (fail closed). Idempotent. */
lib/hotel-ops.mjs:170:/** t314 policy (user 06:44): HK delivers ONLY free items; priced food/drink → nha-hang only (and fully paid). Precedence over legacy config. */
lib/hotel-ops.mjs:175:  if (o && o.group === 'laundry') return its.every(([i]) => (CAT_ITEM[i] || {}).g === 'laundry') ? ['buong-phong'] : []; /* t329 narrow paid-HK exception */
lib/hotel-ops.mjs:200:      add.push({ id: fid, room: o.room, booking: o.booking, group: 'food', items: lines, status: 'cho', dept: 'nha-hang', step: null, priority: o.priority || 'tb', total, px, pay: total > 0 ? 'phong' : 'free', note: 'Tách từ ' + o.id + ' · thanh toán cần xác minh', splitFrom: o.id, paymentQuestion: o.payment ? { parentClaim: { via: o.payment.via || null, amount: o.total }, expected: total } : null, log: [{ at: Date.now(), by: 'system', msg: 'Tách từ ' + o.id + ' · nước uống → nhà hàng (đơn giá danh mục ' + unit + ')' }] });
lib/hotel-ops.mjs:338:  return { key: k, code: b.code, name: hold.tqid || 'Khách', tqid: hold.tqid || null, src: (b.settlement && b.settlement.src) || b.src || null, payStatus: (b.settlement && b.settlement.payStatus) || null, paidDay: (b.settlement && (vnDayOf(b.settlement.paidAt) || vnDayOf(b.settlement.settleDate))) || null, simulated: !!(b.settlement && b.settlement.simulated), out: !!(s.out && s.out[k]), nights: b.nights || (b.settlement && b.settlement.nights) || null, roomType: b.roomType, qty: b.qty, guests: b.guests, arrive: b.arrive, eta: (s.eta && s.eta[k]) || b.eta || b.arrive,
lib/hotel-ops.mjs:353: if(!['da-tt','da','paid'].includes(p)||st.simulated)return {ok:false,code:'not_paid',message:'Booking chưa có thanh toán hợp lệ — không nhận phòng / cấp phiên lưu trú.'};
lib/hotel-ops.mjs:379:  const paid=settlementGate(s,k);if(!paid.ok)return paid;
lib/hotel-ops.mjs:419:  if((manualRead||manualPost)&&!manualLegalEnabled)return E(403,'manual_legal_test_only','Nhập tay pháp lý chỉ mở trong TEST tổng hợp có D1 riêng/marker và host TEST hợp lệ; không nhận dữ liệu thật.');
lib/hotel-ops.mjs:471:      const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:548:    const body = dayLedger(sDay, { hotelId: hid, date, hvCap: DEFAULT_HV_CAP, taCap: DEFAULT_TA_CAP, locks, dailyStateBlob: false /* t336: always filter by paid day (daily blob held all bookings → today showed HV3 TA2) */ });
lib/hotel-ops.mjs:583:      paymentCodeTtlMs: PAYMENT_CODE_TTL_MS, noHold: true,
lib/hotel-ops.mjs:585:        hv: 'absolute_noncancel_nonrefund',
lib/hotel-ops.mjs:593:  /* t302/t303: sold-room qty HV|TA by tuần/tháng/năm — payment-day basis; ops/quy/money read */
lib/hotel-ops.mjs:623:        note: 'payment-day sold rooms · ≠ room-nights stay chart',
lib/hotel-ops.mjs:628:  /* t292/t293: TA cancel — TravelQ highest admin only; HV absolute deny; inventory ≠ refund; no gateway */
lib/hotel-ops.mjs:643:    await HA('ks.booking-cancel', bk.code, null, { by: ss.s.sid, reason: b.reason, inventoryReleased: !!out.inventoryReleased, refund: out.refund || 'not_requested' });
lib/hotel-ops.mjs:646:  if (r0 === 'bookings' && r1 && r2 === 'refund-request' && M === 'POST') {
lib/hotel-ops.mjs:652:      const st = (auth.code === 'hv_absolute_nonrefund' || auth.code === 'forbidden_role' || auth.code === 'forbidden_hotel_admin' || auth.code === 'highest_admin_unresolved' || auth.code === 'hotel_scope' || auth.code === 'auth_required') ? 403 : 422;
lib/hotel-ops.mjs:656:    if (!out.ok) return E(out.code === 'already_refunded' ? 409 : 422, out.code || 'refund_failed', out.message || 'Ghi yêu cầu hoàn thất bại.');
lib/hotel-ops.mjs:657:    await HA('ks.booking-refund-request', bk.code, null, { by: ss.s.sid, reason: b.reason, gateway: 'not_issued', refundStatus: out.refundStatus });
lib/hotel-ops.mjs:708:  if (r0 === 'payments' && M === 'GET') { const x = await hotelSide(env || {}, hid, staff, 'bookings', { date: url.searchParams.get('date') }); return J(x.body, x.status); }
lib/hotel-ops.mjs:740:    /* t298: domain validateAssign — qty+type lines, catalog, dirty/maint, stay overlap, paid-only; never sets checkedIn */
lib/hotel-ops.mjs:797:    const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:815:    const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:833:      const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:863:    if (rid && pIdx < 0 && op !== 'mark-later') return E(404, 'pending_not_found', 'Không còn dòng khai báo sau này (đã xác minh hoặc đã bỏ).');
lib/hotel-ops.mjs:890:    } else if (op === 'mark-later') {
lib/hotel-ops.mjs:891:      /* pending declared marker — NOT verified, no fake TQID */
lib/hotel-ops.mjs:907:      return E(422, 'validation', 'op phải là add-adult | add-child | mark-later | remove-pending');
lib/hotel-ops.mjs:922:    /* pending pay fail-closed — payment ≠ check-in but unpaid HV/pending cannot check in */
lib/hotel-ops.mjs:924:    if (stPay === 'cho-tt' || stPay === 'cho' || stPay === 'pending') return E(409, 'payment_pending', 'Chưa thanh toán xong — không nhận phòng.');
lib/hotel-ops.mjs:926:    if (!['da-tt', 'da', 'paid'].includes(stPay)) return E(409, 'not_paid', 'Booking chưa ở trạng thái đã thanh toán — không nhận phòng.');
lib/hotel-ops.mjs:945:    const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:980:    const paid=settlementGate(s,bk.key); if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:985:    const at = m2hm(nowMin(env)); markSigned(s, bk.key, r.row, at);
lib/hotel-ops.mjs:1001:    const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/hotel-ops.mjs:1032:      ...(({ daThu, choThu, simTest, unverified }) => ({ daThu, choThu, simTest, unverified }))(payKpi(today)) /* t352c shared classification */,
lib/hotel-ops.mjs:1035:    const view = () => ({ hotel: { id: hid, name: ss.hotelName }, role, roleName: HROLES[role], can: { edit: isMgr, notify: can.ops, markPaid: false /* t353: không thu tiền mặt/ghi đã thu */, audit: isMgr || can.money, foodPrice: !!can.foodPrice, foodCatalog: !!can.foodPrice, hotelInfo: !!can.ops, deptWork: !!myDept },
lib/hotel-ops.mjs:1041:    if (M === 'GET' && r1 === 'payments') {
lib/hotel-ops.mjs:1043:        items: o.items.map(([i, q]) => ({ id: i, name: itemName(o, i), qty: q, price: o.px && Number.isInteger(o.px[i]) ? o.px[i] : null })), payment: o.payment || null }));
lib/hotel-ops.mjs:1044:      return J({ items: L.map((o) => { const c = payClass(o); return { ...o, payClass: c, ...(c === 'test' ? { revenueExcluded: true, paymentSimulated: true } : c === 'unverified' ? { revenueExcluded: true, paymentUnverified: true } : {}) }; }), kpi: payKpi(L) });
lib/hotel-ops.mjs:1130:        const forbid = ['prices', 'roles', 'staff', 'payment', 'payDest', 'gateway', 'features', 'noiQuy', 'qee', 'tours', 'items', 'spaSlots', 'templates', 'rental'];
lib/hotel-ops.mjs:1157:      // t230/t231: cho-tt (chờ khách trả) KHÔNG vào hàng LT / badge — giữ guest/payment domain.
lib/hotel-ops.mjs:1160:      else if (!st) items = items.filter((o) => o.status !== 'cho-tt' && (o.status !== 'cho' || dispatchEligible(o))); /* t314: unpaid chargeable never in LT queue */
lib/hotel-ops.mjs:1278:    if (M === 'POST' && r2 === 'mark-paid') { if (DEPT_ONLY) return deny('ops');
lib/hotel-ops.mjs:1279:      /* t314 P1: staff cannot claim a chargeable order paid (no verified provider proof). Fail closed; no cash permission invented. */
lib/hotel-ops.mjs:1284:      const before = o.pay; o.pay = 'da'; o.payment = { status: 'da', ref, paidAt: nowMs(env), at: m2hm(nowMin(env)), by: ss.s.sid, via: 'quay' };
lib/hotel-ops.mjs:1285:      await HA('ks.order-da-thu', o.id, { pay: before }, { pay: 'da', ref, total: o.total }); return J({ ok: true, order: o }); }
lib/hotel-ops.mjs:1287:      o.status = 'huy'; if (o.pay === 'da') o.pay = 'hoan'; await HA('ks.order-cancel', o.id, null, { by: ss.s.sid }); return J({ ok: true, order: o }); }
lib/he-thong.mjs:223:  if (op === 'bookings') return { status: 200, body: { date, items: bookingsOf(s, h, date, env).map((b) => ({ ...b, createdAt: new Date(b.createdAt).toISOString(), paidAt: b.paidAt ? new Date(b.paidAt).toISOString() : null, expiresAt: b.expiresAt ? new Date(b.expiresAt).toISOString() : null })) } };
lib/he-thong.mjs:274:    const L = list(); const group = (key, name) => ({ code: key, name, hotels: 0, active: 0, rooms: 0, hv: 0, hvSold: 0, hvHeld: 0, hvAvail: 0, reached: 0, locked: 0, ta: 0, taSold: 0, paid: 0, amount: 0, holds: 0 });
lib/he-thong.mjs:282:        bk.forEach((b) => { if (b.status === 'da-tt') { g.paid++; g.amount += b.amount; } if (b.status === 'cho-tt') g.holds++; });
lib/he-thong.mjs:412:      .map((b) => ({ ...b, amount: canMoney(u) || u.role === 'ho-tro' ? b.amount : undefined, createdAt: new Date(b.createdAt).toISOString(), paidAt: b.paidAt ? new Date(b.paidAt).toISOString() : null, expiresAt: b.expiresAt ? new Date(b.expiresAt).toISOString() : null }));
lib/app-guest.mjs:11:import { storeSigned, markSigned, isSigned, NOT_SIGNED } from './phieu-sign.mjs';
lib/app-guest.mjs:20:import { simPayEnabled, simPayPublicMeta, isSimulatedPayment, orderNeedsPrepay } from './pay-sim.mjs';
lib/app-guest.mjs:43:  /* t230/t231: unpaid chargeable stays guest/payment domain — not LT queue */
lib/app-guest.mjs:54:const guestOrder = (o) => ({ id: o.id, room: o.room, group: o.group, items: o.items.map(([i, q]) => ({ id: i, name: itemName(o, i), qty: q, ...(o.un && o.un[i] ? { unit: o.un[i] } : {}), ...(o.px && Number.isInteger(o.px[i]) ? { price: o.px[i] } : {}) })), total: o.total, pay: o.pay, note: o.note, slot: o.slot || '', slotConfirmed: o.slotConfirmed || '', at: o.at || null, dept: DEPTN[o.dept], status: guestStatus(o), ...(Array.isArray(o.log) ? { log: o.log.slice(-20) } : {}), ...(o.tour ? { tour: o.tour } : {}), ...(isSimulatedPayment(o) ? { paymentSimulated: true, paymentNotice: 'SIMULATION ONLY — no funds moved' } : {}) });
lib/app-guest.mjs:117:    const paid=settlementGate(s,bk.key);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:147:    const paid=settlementGate(s,t.k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:160:    const paid=settlementGate(s,t.k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:180:    const paid=settlementGate(s,t.k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:186:    const at = m2hm(nowMin(env)); markSigned(s, t.k, r.row, at);
lib/app-guest.mjs:194:    const paid=settlementGate(s,tick.k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:218:    const paid=settlementGate(s,t.k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:261:      const paid=settlementGate(s,k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:268:    const paid=settlementGate(s,k);if(!paid.ok)return E(409,paid.code,paid.message);
lib/app-guest.mjs:333:      menu: menu(), laundryPriceVersion: Number.isInteger(cfg.laundryVer) ? cfg.laundryVer : 0, chat: featOn(s, 'chat'), payment: 'room_folio', paySim: simPayPublicMeta(env), rental: { show: !(cfg.rental && cfg.rental.show === false), ...EVT },
