qz printer error handling and format updated
This commit is contained in:
parent
1cccf85702
commit
2115f20e95
@ -4,7 +4,7 @@
|
|||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
width: 100vw !important;
|
width: 100vw !important;
|
||||||
overflow: hidden;
|
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-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-position: center center !important;
|
||||||
background-size: cover !important;
|
background-size: cover !important;
|
||||||
@ -111,8 +111,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
background: rgba(255, 255, 255, 0.05) !important;
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||||
border-radius: 12px !important;
|
border-radius: 12px !important;
|
||||||
padding: 12px 15px !important;
|
padding: 12px 15px !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
@ -120,7 +120,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-control::placeholder {
|
.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 */
|
/* Login Button with Gradient */
|
||||||
@ -157,7 +164,16 @@
|
|||||||
|
|
||||||
.o_login_right_side {
|
.o_login_right_side {
|
||||||
background: #0f172a !important;
|
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;
|
object-fit: contain !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "Rush Mode" Label next to logo */
|
/* "Rush Mode" Label removed for production */
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. Search Bar - Teal Theme */
|
/* 3. Search Bar - Teal Theme */
|
||||||
.pos .search-bar {
|
.pos .search-bar {
|
||||||
@ -365,7 +355,8 @@
|
|||||||
|
|
||||||
.pos-receipt .pos-receipt-order-data {
|
.pos-receipt .pos-receipt-order-data {
|
||||||
color: #94a3b8 !important;
|
color: #94a3b8 !important;
|
||||||
font-size: 11px !important;
|
font-size: 12px !important;
|
||||||
|
margin-top: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pos-receipt .receipt-orderlines {
|
.pos-receipt .receipt-orderlines {
|
||||||
@ -375,11 +366,27 @@
|
|||||||
|
|
||||||
.pos-receipt .orderline {
|
.pos-receipt .orderline {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
margin: 5px 0 !important;
|
margin: 0 !important;
|
||||||
padding: 10px 0 !important;
|
padding: 8px 0 !important;
|
||||||
border-bottom: 1px dashed #e2e8f0 !important;
|
border-bottom: 1px dashed #e2e8f0 !important;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
justify-content: space-between !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 {
|
.pos-receipt .pos-receipt-total {
|
||||||
@ -392,4 +399,48 @@
|
|||||||
|
|
||||||
.pos-receipt-amount {
|
.pos-receipt-amount {
|
||||||
font-weight: 800 !important;
|
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"/>
|
<i class="fa fa-globe"/>
|
||||||
</a>
|
</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">
|
<a href="/web#action=mail.action_discuss" class="o_top_item" title="Messages">
|
||||||
<i class="fa fa-comments-o"/>
|
<i class="fa fa-comments-o"/>
|
||||||
<span class="badge_dot"/>
|
<span class="badge_dot"/>
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
||||||
.online-orders-screen {
|
.online-orders-screen {
|
||||||
background: #1a1a2e;
|
background: #171422;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
font-family: 'Inter', 'Segoe UI', sans-serif;
|
font-family: 'Inter', 'Segoe UI', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
.online-orders-header {
|
.online-orders-header {
|
||||||
background: linear-gradient(135deg, #16213e 0%, #0f3460 100%);
|
background: #171422;
|
||||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||||
min-height: 60px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
.online-orders-body {
|
.online-orders-body {
|
||||||
background: #1a1a2e;
|
background: #171422;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Left Panel - Orders List */
|
/* Left Panel - Orders List */
|
||||||
@ -87,9 +87,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-card.selected {
|
.order-card.selected {
|
||||||
background: rgba(15, 52, 96, 0.5);
|
background: rgba(214, 17, 30, 0.1);
|
||||||
border-color: #e94560;
|
border-color: #d6111e;
|
||||||
box-shadow: 0 0 15px rgba(233, 69, 96, 0.2);
|
box-shadow: 0 0 15px rgba(214, 17, 30, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-ref {
|
.order-ref {
|
||||||
@ -164,7 +164,7 @@
|
|||||||
width: 44px;
|
width: 44px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(135deg, #e94560, #0f3460);
|
background: linear-gradient(135deg, #d6111e, #1a1d23);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -229,8 +229,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.kitchen-badge.active {
|
.kitchen-badge.active {
|
||||||
background: rgba(233, 69, 96, 0.2);
|
background: rgba(214, 17, 30, 0.2);
|
||||||
color: #e94560;
|
color: #d6111e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Detail Action Buttons */
|
/* Detail Action Buttons */
|
||||||
@ -265,9 +265,9 @@
|
|||||||
|
|
||||||
/* Navbar Button */
|
/* Navbar Button */
|
||||||
.online-orders-nav-btn {
|
.online-orders-nav-btn {
|
||||||
background: rgba(233, 69, 96, 0.15);
|
background: rgba(214, 17, 30, 0.15);
|
||||||
border: 1px solid rgba(233, 69, 96, 0.3);
|
border: 1px solid rgba(214, 17, 30, 0.3);
|
||||||
color: #e94560;
|
color: #d6111e;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 6px 14px;
|
padding: 6px 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -276,9 +276,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.online-orders-nav-btn:hover {
|
.online-orders-nav-btn:hover {
|
||||||
background: #e94560;
|
background: #d6111e;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #e94560;
|
border-color: #d6111e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar */
|
/* Scrollbar */
|
||||||
@ -300,7 +300,7 @@
|
|||||||
|
|
||||||
/* Spinner */
|
/* Spinner */
|
||||||
.spinner-border {
|
.spinner-border {
|
||||||
color: #e94560 !important;
|
color: #d6111e !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export class OnlineOrdersScreen extends Component {
|
|||||||
loading: true,
|
loading: true,
|
||||||
selectedOrder: null,
|
selectedOrder: null,
|
||||||
confirmingId: null,
|
confirmingId: null,
|
||||||
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("[OnlineOrders] Services obtained:", {
|
console.log("[OnlineOrders] Services obtained:", {
|
||||||
@ -83,6 +84,7 @@ export class OnlineOrdersScreen extends Component {
|
|||||||
async loadOnlineOrders() {
|
async loadOnlineOrders() {
|
||||||
try {
|
try {
|
||||||
this.state.loading = true;
|
this.state.loading = true;
|
||||||
|
this.state.error = null;
|
||||||
const orders = await this.orm.call(
|
const orders = await this.orm.call(
|
||||||
"pos.order",
|
"pos.order",
|
||||||
"get_online_orders",
|
"get_online_orders",
|
||||||
@ -94,6 +96,7 @@ export class OnlineOrdersScreen extends Component {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[OnlineOrders] Error loading orders:", error);
|
console.error("[OnlineOrders] Error loading orders:", error);
|
||||||
this.state.loading = false;
|
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) {
|
async confirmOrder(orderId) {
|
||||||
try {
|
try {
|
||||||
this.state.confirmingId = orderId;
|
this.state.confirmingId = orderId;
|
||||||
|
this.state.error = null;
|
||||||
await this.orm.call(
|
await this.orm.call(
|
||||||
"pos.order",
|
"pos.order",
|
||||||
"action_confirm_online_order",
|
"action_confirm_online_order",
|
||||||
@ -119,12 +123,14 @@ export class OnlineOrdersScreen extends Component {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[OnlineOrders] Confirm error:", error);
|
console.error("[OnlineOrders] Confirm error:", error);
|
||||||
this.notification.add("Failed to confirm order");
|
this.notification.add("Failed to confirm order");
|
||||||
|
this.state.error = "Failed to confirm order. It might have been modified.";
|
||||||
this.state.confirmingId = null;
|
this.state.confirmingId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async rejectOrder(orderId) {
|
async rejectOrder(orderId) {
|
||||||
try {
|
try {
|
||||||
|
this.state.error = null;
|
||||||
await this.orm.call(
|
await this.orm.call(
|
||||||
"pos.order",
|
"pos.order",
|
||||||
"action_reject_online_order",
|
"action_reject_online_order",
|
||||||
@ -138,6 +144,7 @@ export class OnlineOrdersScreen extends Component {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[OnlineOrders] Reject error:", error);
|
console.error("[OnlineOrders] Reject error:", error);
|
||||||
this.notification.add("Failed to reject order");
|
this.notification.add("Failed to reject order");
|
||||||
|
this.state.error = "Failed to reject order.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- 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 -->
|
<!-- Loading State -->
|
||||||
<div t-if="state.loading" class="d-flex align-items-center justify-content-center w-100">
|
<div t-if="state.loading" class="d-flex align-items-center justify-content-center w-100">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|||||||
@ -20,25 +20,26 @@ export class ChannelPanel extends Component {
|
|||||||
this.dialog = useService("dialog");
|
this.dialog = useService("dialog");
|
||||||
|
|
||||||
this.SOURCE_LABELS = {
|
this.SOURCE_LABELS = {
|
||||||
walk_in: '🚶 Walk-In',
|
walk_in: 'Walk-In',
|
||||||
phone: '📞 Phone',
|
phone: 'Phone',
|
||||||
whatsapp: '💬 WhatsApp',
|
whatsapp: 'WhatsApp',
|
||||||
social_media: '📱 Social',
|
social_media: 'Social',
|
||||||
online: '🌐 Online',
|
online: 'Online',
|
||||||
kiosk: '📟 Kiosk',
|
kiosk: 'Kiosk',
|
||||||
qr: '📷 QR Code',
|
qr: 'QR Code',
|
||||||
platform: '🛒 Platform',
|
platform: 'Platform',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.FULFILMENT_LABELS = {
|
this.FULFILMENT_LABELS = {
|
||||||
dine_in: '🍽️ Dine-In',
|
dine_in: 'Dine-In',
|
||||||
pickup: '🛍️ Pickup',
|
pickup: 'Pickup',
|
||||||
delivery: '🚚 Delivery',
|
delivery: 'Delivery',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state = useState({
|
this.state = useState({
|
||||||
showDelivery: false,
|
showDelivery: false,
|
||||||
showDetails: false, // For manual fields toggle
|
showDetails: false, // For manual fields toggle
|
||||||
|
isCollapsed: false, // Panel collapse state
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
searching: false,
|
searching: false,
|
||||||
|
|||||||
@ -4,11 +4,20 @@
|
|||||||
<!-- ChannelPanel Component -->
|
<!-- ChannelPanel Component -->
|
||||||
<t t-name="dine360_order_channels.ChannelPanel" owl="1">
|
<t t-name="dine360_order_channels.ChannelPanel" owl="1">
|
||||||
<t t-if="showPanel and currentOrder">
|
<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 -->
|
<!-- Order Source Row -->
|
||||||
<div class="channel-section">
|
<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">
|
<div class="d-flex flex-wrap gap-1">
|
||||||
<t t-foreach="Object.entries(SOURCE_LABELS)" t-as="entry" t-key="entry[0]">
|
<t t-foreach="Object.entries(SOURCE_LABELS)" t-as="entry" t-key="entry[0]">
|
||||||
<button
|
<button
|
||||||
@ -23,7 +32,7 @@
|
|||||||
<!-- Phone number field -->
|
<!-- Phone number field -->
|
||||||
<div t-if="orderSource === 'phone'" class="channel-extra">
|
<div t-if="orderSource === 'phone'" class="channel-extra">
|
||||||
<input type="text" class="form-control form-control-sm"
|
<input type="text" class="form-control form-control-sm"
|
||||||
placeholder="📞 Phone Number"
|
placeholder="Phone Number"
|
||||||
t-att-value="currentOrder.telephone_number"
|
t-att-value="currentOrder.telephone_number"
|
||||||
t-on-change="(ev) => { currentOrder.telephone_number = ev.target.value; }"/>
|
t-on-change="(ev) => { currentOrder.telephone_number = ev.target.value; }"/>
|
||||||
</div>
|
</div>
|
||||||
@ -31,7 +40,7 @@
|
|||||||
<!-- WhatsApp number field -->
|
<!-- WhatsApp number field -->
|
||||||
<div t-if="orderSource === 'whatsapp'" class="channel-extra">
|
<div t-if="orderSource === 'whatsapp'" class="channel-extra">
|
||||||
<input type="text" class="form-control form-control-sm"
|
<input type="text" class="form-control form-control-sm"
|
||||||
placeholder="📱 WhatsApp Number"
|
placeholder="WhatsApp Number"
|
||||||
t-att-value="currentOrder.whatsapp_number"
|
t-att-value="currentOrder.whatsapp_number"
|
||||||
t-on-change="(ev) => { currentOrder.whatsapp_number = ev.target.value; }"/>
|
t-on-change="(ev) => { currentOrder.whatsapp_number = ev.target.value; }"/>
|
||||||
</div>
|
</div>
|
||||||
@ -39,14 +48,14 @@
|
|||||||
<!-- Social media ref field -->
|
<!-- Social media ref field -->
|
||||||
<div t-if="orderSource === 'social_media'" class="channel-extra">
|
<div t-if="orderSource === 'social_media'" class="channel-extra">
|
||||||
<input type="text" class="form-control form-control-sm"
|
<input type="text" class="form-control form-control-sm"
|
||||||
placeholder="📲 Post / Message Reference"
|
placeholder="Post / Message Reference"
|
||||||
t-att-value="currentOrder.social_ref"
|
t-att-value="currentOrder.social_ref"
|
||||||
t-on-change="(ev) => { currentOrder.social_ref = ev.target.value; }"/>
|
t-on-change="(ev) => { currentOrder.social_ref = ev.target.value; }"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fulfilment Row -->
|
<!-- Fulfilment Row -->
|
||||||
<div class="channel-section mt-1">
|
<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">
|
<div class="btn-group w-100" role="group">
|
||||||
<t t-foreach="Object.entries(FULFILMENT_LABELS)" t-as="entry" t-key="entry[0]">
|
<t t-foreach="Object.entries(FULFILMENT_LABELS)" t-as="entry" t-key="entry[0]">
|
||||||
<button
|
<button
|
||||||
@ -125,6 +134,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
@ -1,30 +1,11 @@
|
|||||||
/* Custom POS Navbar styling */
|
/* Custom POS Navbar styling */
|
||||||
.pos .pos-topheader {
|
.pos .pos-topheader {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-dashboard:hover {
|
.btn-dashboard:hover {
|
||||||
background: linear-gradient(135deg, #a00d0e 0%, #d61112 100%) !important;
|
background: linear-gradient(135deg, #a00d0e 0%, #d61112 100%) !important;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3) !important;
|
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 lines = [];
|
||||||
const company = order?.pos?.company;
|
const company = order?.pos?.company;
|
||||||
const client = order?.get_partner?.();
|
const client = order?.get_partner?.();
|
||||||
|
const table = order?.table;
|
||||||
|
const cashier = order?.employee || order?.pos?.get_cashier?.();
|
||||||
|
|
||||||
|
// 1. HEADER (Centered-ish)
|
||||||
if (company?.name) {
|
if (company?.name) {
|
||||||
lines.push(company.name);
|
lines.push(company.name.toUpperCase());
|
||||||
}
|
}
|
||||||
if (company?.phone) {
|
if (company?.street) lines.push(company.street);
|
||||||
lines.push(company.phone);
|
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));
|
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||||
|
|
||||||
const receiptNumber = order?.name || order?.uid || "";
|
// 2. ORDER INFO
|
||||||
if (receiptNumber) {
|
const receiptNumber = order?.name || "";
|
||||||
lines.push(`Receipt: ${receiptNumber}`);
|
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 (cashier) {
|
||||||
if (client?.name) {
|
lines.push(`SERVER: ${cashier.name}`);
|
||||||
lines.push(`Customer: ${client.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));
|
lines.push("-".repeat(RECEIPT_COLUMNS));
|
||||||
|
|
||||||
|
// 3. ORDER LINES
|
||||||
for (const orderline of order?.get_orderlines?.() || []) {
|
for (const orderline of order?.get_orderlines?.() || []) {
|
||||||
const product = orderline.get_product?.();
|
const product = orderline.get_product?.();
|
||||||
const name = product?.display_name || product?.name || "";
|
const name = product?.display_name || product?.name || "";
|
||||||
const qty = orderline.get_quantity?.() || 0;
|
const qty = orderline.get_quantity?.() || 0;
|
||||||
const total = orderline.get_price_with_tax?.() ?? orderline.get_display_price?.() ?? 0;
|
const priceUnit = orderline.get_unit_display_price?.() || 0;
|
||||||
for (const wrapped of wrapLine(name, RECEIPT_COLUMNS)) {
|
const total = orderline.get_price_with_tax?.() || 0;
|
||||||
lines.push(wrapped);
|
|
||||||
|
// "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));
|
// 4. TOTALS
|
||||||
lines.push(leftRight("TOTAL", money(order?.get_total_with_tax?.(), currency)));
|
lines.push("=".repeat(RECEIPT_COLUMNS));
|
||||||
|
lines.push(leftRight("SUBTOTAL", money(order?.get_total_without_tax?.(), currency)));
|
||||||
|
|
||||||
const tax = order?.get_total_tax?.();
|
const tax = order?.get_total_tax?.();
|
||||||
if (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?.();
|
const change = order?.get_change?.();
|
||||||
if (change) {
|
if (change) {
|
||||||
lines.push(leftRight("Change", money(change, currency)));
|
lines.push(leftRight("CHANGE", money(change, currency)));
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push("-".repeat(RECEIPT_COLUMNS));
|
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;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +155,16 @@ function buildReceiptLines(receiptElement, order) {
|
|||||||
function buildEscPosReceipt(receiptElement, order) {
|
function buildEscPosReceipt(receiptElement, order) {
|
||||||
const ESC = "\x1B";
|
const ESC = "\x1B";
|
||||||
const GS = "\x1D";
|
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);
|
const body = lines.flatMap((line) => wrapLine(line)).join(NEWLINE);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -123,14 +179,6 @@ function buildEscPosReceipt(receiptElement, order) {
|
|||||||
patch(ReceiptScreen.prototype, {
|
patch(ReceiptScreen.prototype, {
|
||||||
async printReceipt() {
|
async printReceipt() {
|
||||||
if (this.pos.config.use_qz_printer && this.pos.config.qz_printer_name) {
|
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 {
|
try {
|
||||||
if (!window.qz) {
|
if (!window.qz) {
|
||||||
console.error("QZ Tray library not loaded.");
|
console.error("QZ Tray library not loaded.");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user