forked from alaguraj/odoo-testing-addons
implemented the equal spilt option
This commit is contained in:
parent
79ba021f55
commit
d073defbda
@ -23,6 +23,13 @@
|
|||||||
'views/res_users_views.xml',
|
'views/res_users_views.xml',
|
||||||
'views/product_template_views.xml',
|
'views/product_template_views.xml',
|
||||||
],
|
],
|
||||||
|
'assets': {
|
||||||
|
'point_of_sale._assets_pos': [
|
||||||
|
'dine360_restaurant/static/src/css/equal_split.css',
|
||||||
|
'dine360_restaurant/static/src/js/equal_split.js',
|
||||||
|
'dine360_restaurant/static/src/xml/equal_split.xml',
|
||||||
|
],
|
||||||
|
},
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': True,
|
'application': True,
|
||||||
'license': 'LGPL-3',
|
'license': 'LGPL-3',
|
||||||
|
|||||||
51
addons/dine360_restaurant/static/src/css/equal_split.css
Normal file
51
addons/dine360_restaurant/static/src/css/equal_split.css
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/* Equal Split Panel — dine360_restaurant */
|
||||||
|
|
||||||
|
.equal-split-toggle-bar {
|
||||||
|
min-height: 44px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-toggle-btn {
|
||||||
|
min-width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-panel {
|
||||||
|
background: #fff;
|
||||||
|
animation: slideDown 0.18s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from { opacity: 0; transform: translateY(-6px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-counter-btn {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-count-display {
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-count-number {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-amount-box {
|
||||||
|
background: #e8f4fd;
|
||||||
|
border: 2px solid #3498db;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-split-amount {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #2980b9;
|
||||||
|
}
|
||||||
131
addons/dine360_restaurant/static/src/js/equal_split.js
Normal file
131
addons/dine360_restaurant/static/src/js/equal_split.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { SplitBillScreen } from "@pos_restaurant/app/split_bill_screen/split_bill_screen";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import { useState } from "@odoo/owl";
|
||||||
|
|
||||||
|
patch(SplitBillScreen.prototype, {
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments);
|
||||||
|
this.equalSplit = useState({
|
||||||
|
active: false,
|
||||||
|
numPeople: 2,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleEqualSplit() {
|
||||||
|
this.equalSplit.active = !this.equalSplit.active;
|
||||||
|
},
|
||||||
|
|
||||||
|
decreasePeople() {
|
||||||
|
if (this.equalSplit.numPeople > 2) {
|
||||||
|
this.equalSplit.numPeople--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
increasePeople() {
|
||||||
|
if (this.equalSplit.numPeople < 20) {
|
||||||
|
this.equalSplit.numPeople++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get equalAmountPerPerson() {
|
||||||
|
const order = this.pos.get_order();
|
||||||
|
if (!order || this.equalSplit.numPeople < 1) return 0;
|
||||||
|
return order.getTotalWithTax() / this.equalSplit.numPeople;
|
||||||
|
},
|
||||||
|
|
||||||
|
get formattedEqualAmount() {
|
||||||
|
return this.env.utils.formatCurrency(this.equalAmountPerPerson);
|
||||||
|
},
|
||||||
|
|
||||||
|
get formattedOrderTotal() {
|
||||||
|
const order = this.pos.get_order();
|
||||||
|
if (!order) return this.env.utils.formatCurrency(0);
|
||||||
|
return this.env.utils.formatCurrency(order.getTotalWithTax());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Intercept pay() to auto-assign proportional lines when equal split is active
|
||||||
|
async pay() {
|
||||||
|
if (this.equalSplit.active) {
|
||||||
|
await this._payEqualSplit();
|
||||||
|
} else {
|
||||||
|
await super.pay(...arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _payEqualSplit() {
|
||||||
|
const order = this.pos.get_order();
|
||||||
|
if (!order) return;
|
||||||
|
|
||||||
|
const lines = order.get_orderlines();
|
||||||
|
if (lines.length === 0) return;
|
||||||
|
|
||||||
|
const N = this.equalSplit.numPeople;
|
||||||
|
const total = order.getTotalWithTax();
|
||||||
|
const target = Math.round((total / N) * 100) / 100;
|
||||||
|
|
||||||
|
let accumulated = 0;
|
||||||
|
const splitData = {};
|
||||||
|
|
||||||
|
// Greedy fill: assign lines until we reach the per-person target amount
|
||||||
|
for (const line of lines) {
|
||||||
|
if (accumulated >= target - 0.005) break;
|
||||||
|
|
||||||
|
const lineTotal = this._getLineTotal(line);
|
||||||
|
const perUnit = line.qty > 0 ? lineTotal / line.qty : 0;
|
||||||
|
if (perUnit <= 0) continue;
|
||||||
|
|
||||||
|
const needed = target - accumulated;
|
||||||
|
|
||||||
|
if (lineTotal <= needed + 0.005) {
|
||||||
|
// Take the full line
|
||||||
|
splitData[line.uid] = line.qty;
|
||||||
|
accumulated += lineTotal;
|
||||||
|
} else {
|
||||||
|
// Take a partial quantity to fill up to target
|
||||||
|
const qtyToTake = Math.round((needed / perUnit) * 1000) / 1000;
|
||||||
|
if (qtyToTake > 0.001) {
|
||||||
|
splitData[line.uid] = qtyToTake;
|
||||||
|
accumulated += qtyToTake * perUnit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write into whichever splitlines object the screen uses
|
||||||
|
this._setSplitlines(splitData);
|
||||||
|
|
||||||
|
await super.pay(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getLineTotal(line) {
|
||||||
|
// Try the standard Odoo 17 method, fall back to price * qty
|
||||||
|
if (typeof line.get_price_with_tax === "function") {
|
||||||
|
return line.get_price_with_tax();
|
||||||
|
}
|
||||||
|
if (typeof line.getDisplayData === "function") {
|
||||||
|
const d = line.getDisplayData();
|
||||||
|
return d.totalPrice || line.price * line.qty;
|
||||||
|
}
|
||||||
|
return (line.price || 0) * (line.qty || 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setSplitlines(newLines) {
|
||||||
|
// Odoo 17 may store splitlines on this.splitlines or this.state.splitlines
|
||||||
|
const target =
|
||||||
|
this.splitlines !== undefined
|
||||||
|
? this.splitlines
|
||||||
|
: this.state && this.state.splitlines !== undefined
|
||||||
|
? this.state.splitlines
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
for (const key of Object.keys(target)) {
|
||||||
|
delete target[key];
|
||||||
|
}
|
||||||
|
for (const [uid, qty] of Object.entries(newLines)) {
|
||||||
|
target[uid] = qty;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
79
addons/dine360_restaurant/static/src/xml/equal_split.xml
Normal file
79
addons/dine360_restaurant/static/src/xml/equal_split.xml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Extends the pos_restaurant SplitBillScreen to add an "Equal Split" panel.
|
||||||
|
The panel is injected at the top of the screen via xpath on the root div.
|
||||||
|
Cashier picks the number of people → presses "Charge" → the system
|
||||||
|
auto-assigns proportional items to the split order and navigates to payment.
|
||||||
|
-->
|
||||||
|
<t t-name="dine360_restaurant.EqualSplitPatch"
|
||||||
|
t-inherit="pos_restaurant.SplitBillScreen"
|
||||||
|
t-inherit-mode="extension"
|
||||||
|
owl="1">
|
||||||
|
|
||||||
|
<xpath expr="//div[hasclass('split-bill-screen')]" position="prepend">
|
||||||
|
|
||||||
|
<!-- Toggle bar — always visible at the top of the split screen -->
|
||||||
|
<div class="equal-split-toggle-bar d-flex align-items-center justify-content-between px-3 py-2 border-bottom bg-light">
|
||||||
|
<span class="fw-semibold text-muted small">
|
||||||
|
<i class="fa fa-cut me-1"/>By Item
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
t-attf-class="btn btn-sm equal-split-toggle-btn {{ equalSplit.active ? 'btn-primary' : 'btn-outline-primary' }}"
|
||||||
|
t-on-click="toggleEqualSplit">
|
||||||
|
<i class="fa fa-users me-1"/>
|
||||||
|
Equal Split
|
||||||
|
<i t-attf-class="fa ms-1 {{ equalSplit.active ? 'fa-chevron-up' : 'fa-chevron-down' }}"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Equal Split panel — shown only when toggled on -->
|
||||||
|
<div t-if="equalSplit.active" class="equal-split-panel d-flex flex-column align-items-center gap-3 p-4 border-bottom bg-white shadow-sm">
|
||||||
|
|
||||||
|
<!-- Order total reference -->
|
||||||
|
<div class="text-muted small">
|
||||||
|
Order Total: <strong t-esc="formattedOrderTotal"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- People counter -->
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary btn-lg equal-split-counter-btn"
|
||||||
|
t-on-click="decreasePeople"
|
||||||
|
t-att-disabled="equalSplit.numPeople <= 2">
|
||||||
|
<i class="fa fa-minus"/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="text-center equal-split-count-display">
|
||||||
|
<div class="equal-split-count-number fw-bold" t-esc="equalSplit.numPeople"/>
|
||||||
|
<div class="text-muted small">people</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary btn-lg equal-split-counter-btn"
|
||||||
|
t-on-click="increasePeople"
|
||||||
|
t-att-disabled="equalSplit.numPeople >= 20">
|
||||||
|
<i class="fa fa-plus"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Per-person amount -->
|
||||||
|
<div class="equal-split-amount-box text-center p-3 rounded">
|
||||||
|
<div class="text-muted small mb-1">Each person pays</div>
|
||||||
|
<div class="equal-split-amount fw-bold" t-esc="formattedEqualAmount"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-muted small text-center">
|
||||||
|
<i class="fa fa-info-circle me-1"/>
|
||||||
|
Press <strong>Charge</strong> to collect from the first person.
|
||||||
|
Repeat for each person.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</templates>
|
||||||
Loading…
x
Reference in New Issue
Block a user