qz printer error handling and format updated
This commit is contained in:
parent
1cccf85702
commit
2115f20e95
@ -4,7 +4,7 @@
|
||||
height: 100vh !important;
|
||||
width: 100vw !important;
|
||||
overflow: hidden;
|
||||
background: url('/dine360_theme_chennora/static/src/img/chen-banner-2.webp') !important;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/dine360_theme_chennora/static/src/img/chen-banner-2.webp') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center center !important;
|
||||
background-size: cover !important;
|
||||
@ -111,8 +111,8 @@
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
border-radius: 12px !important;
|
||||
padding: 12px 15px !important;
|
||||
height: auto !important;
|
||||
@ -120,7 +120,14 @@
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: rgba(255, 255, 255, 0.3) !important;
|
||||
color: rgba(255, 255, 255, 0.6) !important;
|
||||
}
|
||||
|
||||
/* Accessibility contrast for error messages */
|
||||
.alert-danger {
|
||||
background-color: rgba(220, 53, 69, 0.9) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Login Button with Gradient */
|
||||
@ -157,7 +164,16 @@
|
||||
|
||||
.o_login_right_side {
|
||||
background: #0f172a !important;
|
||||
/* Keep dark background on mobile */
|
||||
}
|
||||
|
||||
.o_login_card_wrapper {
|
||||
width: 90% !important;
|
||||
}
|
||||
|
||||
.oe_website_login_container .oe_login_form,
|
||||
.oe_website_login_container .oe_signup_form,
|
||||
.oe_website_login_container .oe_reset_password_form {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -106,17 +106,7 @@
|
||||
object-fit: contain !important;
|
||||
}
|
||||
|
||||
/* "Rush Mode" Label next to logo */
|
||||
.pos .pos-logo::after {
|
||||
content: "RUSH MODE";
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
color: #ef4444;
|
||||
letter-spacing: 2px;
|
||||
border-left: 2px solid #e5e7eb;
|
||||
padding-left: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
/* "Rush Mode" Label removed for production */
|
||||
|
||||
/* 3. Search Bar - Teal Theme */
|
||||
.pos .search-bar {
|
||||
@ -365,7 +355,8 @@
|
||||
|
||||
.pos-receipt .pos-receipt-order-data {
|
||||
color: #94a3b8 !important;
|
||||
font-size: 11px !important;
|
||||
font-size: 12px !important;
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
|
||||
.pos-receipt .receipt-orderlines {
|
||||
@ -375,11 +366,27 @@
|
||||
|
||||
.pos-receipt .orderline {
|
||||
border: none !important;
|
||||
margin: 5px 0 !important;
|
||||
padding: 10px 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 8px 0 !important;
|
||||
border-bottom: 1px dashed #e2e8f0 !important;
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: baseline !important;
|
||||
}
|
||||
|
||||
.pos-receipt .orderline .product-name {
|
||||
flex: 1 1 65% !important;
|
||||
font-weight: 700 !important;
|
||||
white-space: normal !important;
|
||||
word-break: break-word !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
|
||||
.pos-receipt .orderline .pos-receipt-right-align {
|
||||
flex: 0 0 auto !important;
|
||||
text-align: right !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
.pos-receipt .pos-receipt-total {
|
||||
@ -393,3 +400,47 @@
|
||||
.pos-receipt-amount {
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
RESPONSIVE MEDIA QUERIES
|
||||
======================================== */
|
||||
@media (max-width: 1024px) {
|
||||
.pos .leftpane {
|
||||
width: 380px !important;
|
||||
}
|
||||
.pos .product {
|
||||
width: 160px !important;
|
||||
height: 180px !important;
|
||||
}
|
||||
.pos .product .product-img {
|
||||
height: 110px !important;
|
||||
}
|
||||
.pos .search-bar {
|
||||
width: 280px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.product-screen {
|
||||
flex-direction: column !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
.pos .leftpane {
|
||||
width: 100% !important;
|
||||
height: 50vh !important;
|
||||
border-left: none !important;
|
||||
border-top: 2px solid #f1f5f9 !important;
|
||||
}
|
||||
.pos .rightpane {
|
||||
height: 50vh !important;
|
||||
}
|
||||
.pos .pos-topheader {
|
||||
height: auto !important;
|
||||
flex-wrap: wrap !important;
|
||||
padding: 10px !important;
|
||||
}
|
||||
.pos .search-bar {
|
||||
width: 100% !important;
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
}
|
||||
@ -19,14 +19,6 @@
|
||||
<i class="fa fa-globe"/>
|
||||
</a>
|
||||
|
||||
<a href="#" class="o_top_item" title="AI Assistant">
|
||||
<span class="o_ai_icon">AI</span>
|
||||
</a>
|
||||
|
||||
<a href="#" class="o_top_item" title="Search">
|
||||
<i class="fa fa-search"/>
|
||||
</a>
|
||||
|
||||
<a href="/web#action=mail.action_discuss" class="o_top_item" title="Messages">
|
||||
<i class="fa fa-comments-o"/>
|
||||
<span class="badge_dot"/>
|
||||
|
||||
@ -3,14 +3,14 @@
|
||||
/* ============================================ */
|
||||
|
||||
.online-orders-screen {
|
||||
background: #1a1a2e;
|
||||
background: #171422;
|
||||
color: #eee;
|
||||
font-family: 'Inter', 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.online-orders-header {
|
||||
background: linear-gradient(135deg, #16213e 0%, #0f3460 100%);
|
||||
background: #171422;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||
min-height: 60px;
|
||||
}
|
||||
@ -58,7 +58,7 @@
|
||||
|
||||
/* Body */
|
||||
.online-orders-body {
|
||||
background: #1a1a2e;
|
||||
background: #171422;
|
||||
}
|
||||
|
||||
/* Left Panel - Orders List */
|
||||
@ -87,9 +87,9 @@
|
||||
}
|
||||
|
||||
.order-card.selected {
|
||||
background: rgba(15, 52, 96, 0.5);
|
||||
border-color: #e94560;
|
||||
box-shadow: 0 0 15px rgba(233, 69, 96, 0.2);
|
||||
background: rgba(214, 17, 30, 0.1);
|
||||
border-color: #d6111e;
|
||||
box-shadow: 0 0 15px rgba(214, 17, 30, 0.2);
|
||||
}
|
||||
|
||||
.order-ref {
|
||||
@ -164,7 +164,7 @@
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #e94560, #0f3460);
|
||||
background: linear-gradient(135deg, #d6111e, #1a1d23);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -229,8 +229,8 @@
|
||||
}
|
||||
|
||||
.kitchen-badge.active {
|
||||
background: rgba(233, 69, 96, 0.2);
|
||||
color: #e94560;
|
||||
background: rgba(214, 17, 30, 0.2);
|
||||
color: #d6111e;
|
||||
}
|
||||
|
||||
/* Detail Action Buttons */
|
||||
@ -265,9 +265,9 @@
|
||||
|
||||
/* Navbar Button */
|
||||
.online-orders-nav-btn {
|
||||
background: rgba(233, 69, 96, 0.15);
|
||||
border: 1px solid rgba(233, 69, 96, 0.3);
|
||||
color: #e94560;
|
||||
background: rgba(214, 17, 30, 0.15);
|
||||
border: 1px solid rgba(214, 17, 30, 0.3);
|
||||
color: #d6111e;
|
||||
border-radius: 10px;
|
||||
padding: 6px 14px;
|
||||
font-weight: 600;
|
||||
@ -276,9 +276,9 @@
|
||||
}
|
||||
|
||||
.online-orders-nav-btn:hover {
|
||||
background: #e94560;
|
||||
background: #d6111e;
|
||||
color: #fff;
|
||||
border-color: #e94560;
|
||||
border-color: #d6111e;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
@ -300,7 +300,7 @@
|
||||
|
||||
/* Spinner */
|
||||
.spinner-border {
|
||||
color: #e94560 !important;
|
||||
color: #d6111e !important;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
|
||||
@ -25,6 +25,7 @@ export class OnlineOrdersScreen extends Component {
|
||||
loading: true,
|
||||
selectedOrder: null,
|
||||
confirmingId: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
console.log("[OnlineOrders] Services obtained:", {
|
||||
@ -83,6 +84,7 @@ export class OnlineOrdersScreen extends Component {
|
||||
async loadOnlineOrders() {
|
||||
try {
|
||||
this.state.loading = true;
|
||||
this.state.error = null;
|
||||
const orders = await this.orm.call(
|
||||
"pos.order",
|
||||
"get_online_orders",
|
||||
@ -94,6 +96,7 @@ export class OnlineOrdersScreen extends Component {
|
||||
} catch (error) {
|
||||
console.error("[OnlineOrders] Error loading orders:", error);
|
||||
this.state.loading = false;
|
||||
this.state.error = "Failed to load orders. Please check your connection.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +107,7 @@ export class OnlineOrdersScreen extends Component {
|
||||
async confirmOrder(orderId) {
|
||||
try {
|
||||
this.state.confirmingId = orderId;
|
||||
this.state.error = null;
|
||||
await this.orm.call(
|
||||
"pos.order",
|
||||
"action_confirm_online_order",
|
||||
@ -119,12 +123,14 @@ export class OnlineOrdersScreen extends Component {
|
||||
} catch (error) {
|
||||
console.error("[OnlineOrders] Confirm error:", error);
|
||||
this.notification.add("Failed to confirm order");
|
||||
this.state.error = "Failed to confirm order. It might have been modified.";
|
||||
this.state.confirmingId = null;
|
||||
}
|
||||
}
|
||||
|
||||
async rejectOrder(orderId) {
|
||||
try {
|
||||
this.state.error = null;
|
||||
await this.orm.call(
|
||||
"pos.order",
|
||||
"action_reject_online_order",
|
||||
@ -138,6 +144,7 @@ export class OnlineOrdersScreen extends Component {
|
||||
} catch (error) {
|
||||
console.error("[OnlineOrders] Reject error:", error);
|
||||
this.notification.add("Failed to reject order");
|
||||
this.state.error = "Failed to reject order.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="online-orders-body flex-grow-1 d-flex overflow-hidden">
|
||||
<div class="online-orders-body flex-grow-1 d-flex overflow-hidden position-relative">
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div t-if="state.error" class="position-absolute w-100 p-3" style="z-index: 1050; top: 0;">
|
||||
<div class="alert alert-danger d-flex justify-content-between align-items-center m-0 shadow">
|
||||
<div>
|
||||
<i class="fa fa-exclamation-triangle me-2"/>
|
||||
<span t-esc="state.error"/>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-danger border-0" t-on-click="() => this.state.error = null">
|
||||
<i class="fa fa-times"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div t-if="state.loading" class="d-flex align-items-center justify-content-center w-100">
|
||||
<div class="text-center">
|
||||
|
||||
@ -20,25 +20,26 @@ export class ChannelPanel extends Component {
|
||||
this.dialog = useService("dialog");
|
||||
|
||||
this.SOURCE_LABELS = {
|
||||
walk_in: '🚶 Walk-In',
|
||||
phone: '📞 Phone',
|
||||
whatsapp: '💬 WhatsApp',
|
||||
social_media: '📱 Social',
|
||||
online: '🌐 Online',
|
||||
kiosk: '📟 Kiosk',
|
||||
qr: '📷 QR Code',
|
||||
platform: '🛒 Platform',
|
||||
walk_in: 'Walk-In',
|
||||
phone: 'Phone',
|
||||
whatsapp: 'WhatsApp',
|
||||
social_media: 'Social',
|
||||
online: 'Online',
|
||||
kiosk: 'Kiosk',
|
||||
qr: 'QR Code',
|
||||
platform: 'Platform',
|
||||
};
|
||||
|
||||
this.FULFILMENT_LABELS = {
|
||||
dine_in: '🍽️ Dine-In',
|
||||
pickup: '🛍️ Pickup',
|
||||
delivery: '🚚 Delivery',
|
||||
dine_in: 'Dine-In',
|
||||
pickup: 'Pickup',
|
||||
delivery: 'Delivery',
|
||||
};
|
||||
|
||||
this.state = useState({
|
||||
showDelivery: false,
|
||||
showDetails: false, // For manual fields toggle
|
||||
isCollapsed: false, // Panel collapse state
|
||||
searchQuery: '',
|
||||
searchResults: [],
|
||||
searching: false,
|
||||
|
||||
@ -4,11 +4,20 @@
|
||||
<!-- ChannelPanel Component -->
|
||||
<t t-name="dine360_order_channels.ChannelPanel" owl="1">
|
||||
<t t-if="showPanel and currentOrder">
|
||||
<div class="channel-panel d-flex flex-column gap-2 p-2 border-bottom">
|
||||
<div class="channel-panel p-2 border-bottom">
|
||||
<!-- Header with Collapse Toggle -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-1" style="cursor: pointer;" t-on-click="() => this.state.isCollapsed = !this.state.isCollapsed">
|
||||
<span class="fw-bold small text-muted"><i class="fa fa-cogs me-1"/> Order Channels</span>
|
||||
<button class="btn btn-sm btn-link p-0 text-decoration-none shadow-none text-muted">
|
||||
<i t-attf-class="fa #{this.state.isCollapsed ? 'fa-chevron-down' : 'fa-chevron-up'}"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div t-if="!this.state.isCollapsed" class="d-flex flex-column gap-2 mt-2">
|
||||
|
||||
<!-- Order Source Row -->
|
||||
<div class="channel-section">
|
||||
<div class="channel-label mb-1">📋 ORDER SOURCE</div>
|
||||
<div class="channel-label mb-1"><i class="fa fa-list-alt me-1"></i> ORDER SOURCE</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<t t-foreach="Object.entries(SOURCE_LABELS)" t-as="entry" t-key="entry[0]">
|
||||
<button
|
||||
@ -23,7 +32,7 @@
|
||||
<!-- Phone number field -->
|
||||
<div t-if="orderSource === 'phone'" class="channel-extra">
|
||||
<input type="text" class="form-control form-control-sm"
|
||||
placeholder="📞 Phone Number"
|
||||
placeholder="Phone Number"
|
||||
t-att-value="currentOrder.telephone_number"
|
||||
t-on-change="(ev) => { currentOrder.telephone_number = ev.target.value; }"/>
|
||||
</div>
|
||||
@ -31,7 +40,7 @@
|
||||
<!-- WhatsApp number field -->
|
||||
<div t-if="orderSource === 'whatsapp'" class="channel-extra">
|
||||
<input type="text" class="form-control form-control-sm"
|
||||
placeholder="📱 WhatsApp Number"
|
||||
placeholder="WhatsApp Number"
|
||||
t-att-value="currentOrder.whatsapp_number"
|
||||
t-on-change="(ev) => { currentOrder.whatsapp_number = ev.target.value; }"/>
|
||||
</div>
|
||||
@ -39,14 +48,14 @@
|
||||
<!-- Social media ref field -->
|
||||
<div t-if="orderSource === 'social_media'" class="channel-extra">
|
||||
<input type="text" class="form-control form-control-sm"
|
||||
placeholder="📲 Post / Message Reference"
|
||||
placeholder="Post / Message Reference"
|
||||
t-att-value="currentOrder.social_ref"
|
||||
t-on-change="(ev) => { currentOrder.social_ref = ev.target.value; }"/>
|
||||
</div>
|
||||
|
||||
<!-- Fulfilment Row -->
|
||||
<div class="channel-section mt-1">
|
||||
<div class="channel-label mb-1">🚀 FULFILMENT TYPE</div>
|
||||
<div class="channel-label mb-1"><i class="fa fa-rocket me-1"></i> FULFILMENT TYPE</div>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<t t-foreach="Object.entries(FULFILMENT_LABELS)" t-as="entry" t-key="entry[0]">
|
||||
<button
|
||||
@ -125,6 +134,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@ -9,22 +9,3 @@
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
/* Category Sidebar Red Background */
|
||||
.pos .category-button {
|
||||
background: #d61112 !important;
|
||||
color: white !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pos .category-button:hover {
|
||||
background: #a00d0e !important;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.pos .category-button.active {
|
||||
background: #111 !important;
|
||||
/* Contrast active category */
|
||||
box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5) !important;
|
||||
border: 1px solid #FECD4F !important;
|
||||
}
|
||||
@ -52,48 +52,95 @@ function buildReceiptLinesFromOrder(order) {
|
||||
const lines = [];
|
||||
const company = order?.pos?.company;
|
||||
const client = order?.get_partner?.();
|
||||
const table = order?.table;
|
||||
const cashier = order?.employee || order?.pos?.get_cashier?.();
|
||||
|
||||
// 1. HEADER (Centered-ish)
|
||||
if (company?.name) {
|
||||
lines.push(company.name);
|
||||
lines.push(company.name.toUpperCase());
|
||||
}
|
||||
if (company?.phone) {
|
||||
lines.push(company.phone);
|
||||
if (company?.street) lines.push(company.street);
|
||||
if (company?.city) lines.push(company.city);
|
||||
if (company?.phone) lines.push(`Tel: ${company.phone}`);
|
||||
|
||||
// Custom Odoo Header
|
||||
if (order?.pos?.config?.receipt_header) {
|
||||
lines.push(order.pos.config.receipt_header);
|
||||
}
|
||||
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||
|
||||
const receiptNumber = order?.name || order?.uid || "";
|
||||
if (receiptNumber) {
|
||||
lines.push(`Receipt: ${receiptNumber}`);
|
||||
// 2. ORDER INFO
|
||||
const receiptNumber = order?.name || "";
|
||||
if (receiptNumber) lines.push(`Order: ${receiptNumber}`);
|
||||
|
||||
if (table) {
|
||||
lines.push(`TABLE: ${table.name}`.padEnd(20) + `GUESTS: ${order.customer_count || 1}`);
|
||||
}
|
||||
lines.push(new Date().toLocaleString());
|
||||
if (client?.name) {
|
||||
lines.push(`Customer: ${client.name}`);
|
||||
if (cashier) {
|
||||
lines.push(`SERVER: ${cashier.name}`);
|
||||
}
|
||||
lines.push(`DATE: ${new Date().toLocaleString()}`);
|
||||
if (client) {
|
||||
lines.push(`CUSTOMER: ${client.name}`);
|
||||
}
|
||||
|
||||
lines.push("=".repeat(RECEIPT_COLUMNS));
|
||||
lines.push(leftRight("ITEM", "PRICE"));
|
||||
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||
|
||||
// 3. ORDER LINES
|
||||
for (const orderline of order?.get_orderlines?.() || []) {
|
||||
const product = orderline.get_product?.();
|
||||
const name = product?.display_name || product?.name || "";
|
||||
const qty = orderline.get_quantity?.() || 0;
|
||||
const total = orderline.get_price_with_tax?.() ?? orderline.get_display_price?.() ?? 0;
|
||||
for (const wrapped of wrapLine(name, RECEIPT_COLUMNS)) {
|
||||
lines.push(wrapped);
|
||||
const priceUnit = orderline.get_unit_display_price?.() || 0;
|
||||
const total = orderline.get_price_with_tax?.() || 0;
|
||||
|
||||
// "Qty x Name" on left, "Total" on right
|
||||
const itemLabel = `${qty} x ${name}`;
|
||||
const itemPrice = money(total, currency);
|
||||
|
||||
if (itemLabel.length + itemPrice.length + 1 > RECEIPT_COLUMNS) {
|
||||
lines.push(itemLabel);
|
||||
lines.push(itemPrice.padStart(RECEIPT_COLUMNS));
|
||||
} else {
|
||||
lines.push(leftRight(itemLabel, itemPrice));
|
||||
}
|
||||
lines.push(leftRight(` ${qty} x`, money(total, currency)));
|
||||
|
||||
// Show unit price if qty > 1
|
||||
if (qty > 1) {
|
||||
lines.push(` @ ${money(priceUnit, currency)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. TOTALS
|
||||
lines.push("=".repeat(RECEIPT_COLUMNS));
|
||||
lines.push(leftRight("SUBTOTAL", money(order?.get_total_without_tax?.(), currency)));
|
||||
|
||||
const tax = order?.get_total_tax?.();
|
||||
if (tax) {
|
||||
lines.push(leftRight("TAX", money(tax, currency)));
|
||||
}
|
||||
|
||||
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||
lines.push(leftRight("TOTAL", money(order?.get_total_with_tax?.(), currency)));
|
||||
const tax = order?.get_total_tax?.();
|
||||
if (tax) {
|
||||
lines.push(leftRight("Tax", money(tax, currency)));
|
||||
}
|
||||
|
||||
const change = order?.get_change?.();
|
||||
if (change) {
|
||||
lines.push(leftRight("Change", money(change, currency)));
|
||||
lines.push(leftRight("CHANGE", money(change, currency)));
|
||||
}
|
||||
|
||||
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||
lines.push("Thank you");
|
||||
|
||||
// Custom Odoo Footer
|
||||
if (order?.pos?.config?.receipt_footer) {
|
||||
lines.push(order.pos.config.receipt_footer);
|
||||
}
|
||||
|
||||
lines.push("THANK YOU FOR DINING WITH US!");
|
||||
lines.push("PLEASE VISIT AGAIN");
|
||||
lines.push(NEWLINE);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
@ -108,7 +155,16 @@ function buildReceiptLines(receiptElement, order) {
|
||||
function buildEscPosReceipt(receiptElement, order) {
|
||||
const ESC = "\x1B";
|
||||
const GS = "\x1D";
|
||||
const lines = buildReceiptLines(receiptElement, order);
|
||||
let lines = buildReceiptLines(receiptElement, order);
|
||||
|
||||
// Safety mechanism: limit receipt length to prevent runaway printing
|
||||
if (lines.length > 150) {
|
||||
console.warn("Receipt is suspiciously long, truncating to 150 lines to prevent runaway printing.");
|
||||
lines.length = 150;
|
||||
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||
lines.push("TRUNCATED FOR SAFETY");
|
||||
}
|
||||
|
||||
const body = lines.flatMap((line) => wrapLine(line)).join(NEWLINE);
|
||||
|
||||
return [
|
||||
@ -123,14 +179,6 @@ function buildEscPosReceipt(receiptElement, order) {
|
||||
patch(ReceiptScreen.prototype, {
|
||||
async printReceipt() {
|
||||
if (this.pos.config.use_qz_printer && this.pos.config.qz_printer_name) {
|
||||
const rawPrintingEnabled = window.localStorage?.getItem("dine360_qz_raw_enabled") === "1";
|
||||
if (!rawPrintingEnabled) {
|
||||
console.warn(
|
||||
"QZ raw receipt printing is disabled. Set localStorage.dine360_qz_raw_enabled = '1' only while testing a raw ESC/POS printer queue."
|
||||
);
|
||||
return super.printReceipt(...arguments);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!window.qz) {
|
||||
console.error("QZ Tray library not loaded.");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user