implemented the equal spilt option

This commit is contained in:
metatroncubeswdev 2026-06-12 19:49:53 -04:00
parent 79ba021f55
commit d073defbda
4 changed files with 268 additions and 0 deletions

View File

@ -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',

View 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;
}

View 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;
}
},
});

View 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 &lt;= 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>