diff --git a/addons/dine360_order_channels/static/src/js/channel_panel.js b/addons/dine360_order_channels/static/src/js/channel_panel.js
index e470f5a..cf8d4ed 100644
--- a/addons/dine360_order_channels/static/src/js/channel_panel.js
+++ b/addons/dine360_order_channels/static/src/js/channel_panel.js
@@ -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,
diff --git a/addons/dine360_order_channels/static/src/xml/channel_panel.xml b/addons/dine360_order_channels/static/src/xml/channel_panel.xml
index 823d41e..5df98ab 100644
--- a/addons/dine360_order_channels/static/src/xml/channel_panel.xml
+++ b/addons/dine360_order_channels/static/src/xml/channel_panel.xml
@@ -4,11 +4,20 @@
-
+
+
+
+ Order Channels
+
+
+
+
-
📋 ORDER SOURCE
+
ORDER SOURCE
diff --git a/addons/dine360_pos_navbar/static/src/css/pos_navbar.css b/addons/dine360_pos_navbar/static/src/css/pos_navbar.css
index a517bfd..7d2368a 100644
--- a/addons/dine360_pos_navbar/static/src/css/pos_navbar.css
+++ b/addons/dine360_pos_navbar/static/src/css/pos_navbar.css
@@ -1,30 +1,11 @@
-/* Custom POS Navbar styling */
-.pos .pos-topheader {
- background: inherit;
-}
-
-.btn-dashboard:hover {
- background: linear-gradient(135deg, #a00d0e 0%, #d61112 100%) !important;
- transform: translateY(-1px);
- 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;
-}
\ No newline at end of file
+/* Custom POS Navbar styling */
+.pos .pos-topheader {
+ background: inherit;
+}
+
+.btn-dashboard:hover {
+ background: linear-gradient(135deg, #a00d0e 0%, #d61112 100%) !important;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3) !important;
+}
+
\ No newline at end of file
diff --git a/addons/dine360_qz_printer/static/src/js/qz_wrapper.js b/addons/dine360_qz_printer/static/src/js/qz_wrapper.js
index d47416f..4e63627 100644
--- a/addons/dine360_qz_printer/static/src/js/qz_wrapper.js
+++ b/addons/dine360_qz_printer/static/src/js/qz_wrapper.js
@@ -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));
+ }
+
+ // Show unit price if qty > 1
+ if (qty > 1) {
+ lines.push(` @ ${money(priceUnit, currency)}`);
}
- lines.push(leftRight(` ${qty} x`, money(total, currency)));
}
- lines.push("-".repeat(RECEIPT_COLUMNS));
- lines.push(leftRight("TOTAL", money(order?.get_total_with_tax?.(), 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(leftRight("TAX", money(tax, currency)));
}
+
+ lines.push("-".repeat(RECEIPT_COLUMNS));
+ lines.push(leftRight("TOTAL", money(order?.get_total_with_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.");