Initial commit: Vertical Industry packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:52 +02:00
commit d5567a0017
766 changed files with 733028 additions and 0 deletions

View file

@ -0,0 +1,179 @@
/** @odoo-module */
import { useBus, useService } from "@web/core/utils/hooks";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
const { Component, useState, onWillStart, markup, xml } = owl;
export class LunchCurrency extends Component {
get amount() {
return parseFloat(this.props.amount).toFixed(2);
}
}
LunchCurrency.template = 'lunch.LunchCurrency';
LunchCurrency.props = ["currency", "amount"];
export class LunchOrderLine extends Component {
setup() {
super.setup();
this.orm = useService('orm');
this.state = useState({ mobileOpen: false });
}
get line() {
return this.props.line;
}
get canEdit() {
return !['sent', 'confirmed'].includes(this.line.raw_state);
}
get badgeClass() {
const mapping = {'new': 'warning', 'confirmed': 'success', 'sent': 'info', 'ordered': 'danger'};
return mapping[this.line.raw_state];
}
get hasToppings() {
return this.line.toppings.length !== 0;
}
async updateQuantity(increment) {
await this.orm.call('lunch.order', 'update_quantity', [
this.props.line.id,
increment
]);
await this.props.onUpdateQuantity();
}
}
LunchOrderLine.template = 'lunch.LunchOrderLine';
LunchOrderLine.props = ["line", "currency", "onUpdateQuantity", "openOrderLine"];
LunchOrderLine.components = {
LunchCurrency,
};
export class LunchAlert extends Component {
get message() {
return markup(this.props.message);
}
}
LunchAlert.props = ["message"];
LunchAlert.template = xml`<t t-out="message"/>`
export class LunchAlerts extends Component {}
LunchAlerts.components = {
LunchAlert,
}
LunchAlerts.props = ["alerts"];
LunchAlerts.template = 'lunch.LunchAlerts';
export class LunchUser extends Component {
getDomain() {
return [['share', '=', false]];
}
}
LunchUser.components = {
Many2XAutocomplete,
}
LunchUser.props = ["username", "isManager", "onUpdateUser"];
LunchUser.template = "lunch.LunchUser";
export class LunchLocation extends Component {
getDomain() {
return [];
}
}
LunchLocation.components = {
Many2XAutocomplete,
}
LunchLocation.props = ["location", "onUpdateLunchLocation"];
LunchLocation.template = "lunch.LunchLocation";
export class LunchDashboard extends Component {
setup() {
super.setup();
this.rpc = useService("rpc");
this.user = useService("user");
this.state = useState({
infos: {},
});
useBus(this.env.bus, 'lunch_update_dashboard', () => this._fetchLunchInfos());
onWillStart(async () => {
await this._fetchLunchInfos()
this.env.searchModel.updateLocationId(this.state.infos.user_location[0]);
});
}
async lunchRpc(route, args = {}) {
return await this.rpc(route, {
...args,
context: this.user.context,
user_id: this.env.searchModel.lunchState.userId,
})
}
async _fetchLunchInfos() {
this.state.infos = await this.lunchRpc('/lunch/infos');
}
async emptyCart() {
await this.lunchRpc('/lunch/trash');
await this._fetchLunchInfos();
}
get hasLines() {
return this.state.infos.lines && this.state.infos.lines.length !== 0;
}
get canOrder() {
return this.state.infos.raw_state === 'new';
}
get location() {
return this.state.infos.user_location && this.state.infos.user_location[1];
}
async orderNow() {
if (!this.canOrder) {
return;
}
await this.lunchRpc('/lunch/pay');
await this._fetchLunchInfos();
}
async onUpdateQuantity() {
await this._fetchLunchInfos();
}
async onUpdateUser(value) {
if (!value) {
return;
}
this.env.searchModel.updateUserId(value[0].id);
await this._fetchLunchInfos();
}
async onUpdateLunchLocation(value) {
if (!value) {
return;
}
await this.lunchRpc('/lunch/user_location_set', {
location_id: value[0].id,
});
await this._fetchLunchInfos();
this.env.searchModel.updateLocationId(value[0].id);
}
}
LunchDashboard.components = {
LunchAlerts,
LunchCurrency,
LunchLocation,
LunchOrderLine,
LunchUser,
Many2XAutocomplete,
};
LunchDashboard.props = ["openOrderLine"];
LunchDashboard.template = 'lunch.LunchDashboard';

View file

@ -0,0 +1,9 @@
.lunch_topping:before {
content: '+ ';
}
.o_lunch_content {
.o-autocomplete--input {
cursor: pointer;
}
}

View file

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="lunch.LunchCurrency" owl="1">
<span>
<t t-if="props.currency.position == 'before'" t-esc="props.currency.symbol"/>
<t t-esc="amount"/>
<t t-if="props.currency.position == 'after'" t-esc="props.currency.symbol"/>
</span>
</t>
<t t-name="lunch.LunchOrderLine" owl="1">
<div class="d-flex align-items-center pe-3">
<div class="btn-group">
<button class="btn btn-sm btn-icon btn-link fa fa-minus-circle p-0" t-if="canEdit" t-on-click="() => this.updateQuantity(-1)"/>
<span t-esc="line.quantity" class="px-2 py-1"/>
<button class="btn btn-sm btn-icon btn-link fa fa-plus-circle p-0" t-if="canEdit" t-on-click="() => this.updateQuantity(1)"/>
</div>
<span class="flex-grow-1 ps-2 text-700">
<a t-on-click="() => props.openOrderLine(line.product[0], line.id)" t-if="canEdit" role="button" title="Edit order">
<t t-esc="line.product[1]"/>
</a>
<t t-else="" t-esc="line.product[1]"/>
<span t-esc="line.state" t-attf-class="badge ms-2 rounded-pill text-bg-#{badgeClass} border-#{badgeClass} "/>
</span>
<LunchCurrency currency="props.currency" amount="line.product[2]"/>
</div>
<ul t-if="hasToppings" class="list-unstyled ps-4">
<li t-foreach="line.toppings" t-as="topping" t-key="topping" class="d-flex pe-3">
<span class="flex-grow-1 lunch_topping" t-esc="topping[0]"/>
<LunchCurrency currency="props.currency" amount="topping[1]"/>
</li>
</ul>
<div t-if="line.note" t-esc="line.note" class="text-muted ps-4"/>
</t>
<t t-name="lunch.LunchAlerts" owl="1">
<div class="alert alert-warning mb-0" t-if="props.alerts.length !== 0" role="alert">
<t t-foreach="props.alerts" t-as="alert" t-key="alert.id">
<LunchAlert message="alert.message" />
</t>
</div>
</t>
<t t-name="lunch.LunchUser" owl="1">
<div class="lunch_user pb-1">
<span t-if="!props.isManager" t-esc="props.username"/>
<Many2XAutocomplete
t-else=""
value="props.username"
resModel="'res.users'"
getDomain="getDomain"
activeActions="{}"
update.bind="props.onUpdateUser"
/>
</div>
</t>
<t t-name="lunch.LunchLocation" owl="1">
<div class="lunch_location pb-1">
<t t-if="props.location">
<Many2XAutocomplete
value="props.location"
resModel="'lunch.location'"
getDomain="getDomain"
activeActions="{}"
update.bind="props.onUpdateLunchLocation"
/>
</t>
<t t-else="">
<p>No lunch location available.</p>
</t>
</div>
</t>
<t t-name="lunch.LunchDashboardOrder" owl="1">
<LunchAlerts alerts="state.infos.alerts"/>
<div class="o_lunch_banner container-fluid p-4 border-bottom bg-view">
<div class="row h-100">
<div class="col-12 col-md-4">
<div class="row h-100 align-content-center">
<div class="col-3">
<img class="o_image_64_cover rounded-circle" t-att-src="state.infos.userimage"/>
</div>
<div class="col-9">
<LunchUser
isManager="state.infos.is_manager"
username="state.infos.username"
onUpdateUser.bind="onUpdateUser"/>
<LunchLocation
location="location"
onUpdateLunchLocation.bind="onUpdateLunchLocation"/>
<div class="d-flex pb-1">
<span class="flex-grow-1">Your Account</span>
<LunchCurrency currency="currency" amount="state.infos.wallet"/>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-5" t-if="hasLines">
<h4 class="mb-0">
Your Order
<button t-if="state.infos.raw_state != 'confirmed'" class="btn btn-sm btn-icon btn-link fa fa-trash" t-on-click.prevent="emptyCart"/>
</h4>
<ul class="o_lunch_widget_lines overflow-auto list-unstyled">
<li t-foreach="state.infos.lines" t-as="line" t-key="line.id">
<LunchOrderLine line="line" currency="currency" onUpdateQuantity.bind="onUpdateQuantity" openOrderLine.bind="props.openOrderLine"/>
</li>
</ul>
</div>
<div class="col-12 col-md-3 d-flex flex-column justify-content-between" t-if="hasLines">
<h4 class="d-flex flex-row mt-1">
<span class="flex-grow-1">
Total
</span>
<LunchCurrency currency="currency" amount="state.infos.total"/>
</h4>
<button class="btn btn-primary" t-if="canOrder" t-on-click="orderNow">Order Now</button>
</div>
</div>
</div>
</t>
<t t-name="lunch.LunchDashboard" owl="1">
<t t-set="currency" t-value="state.infos.currency"/>
<t t-if="!env.isSmall">
<t t-call="lunch.LunchDashboardOrder"/>
</t>
<t t-else="">
<details class="fixed-bottom bg-view p-2" t-att-open="state.mobileOpen">
<summary class="btn btn-primary w-100" t-on-click="() => state.mobileOpen = !state.mobileOpen">
<i class="fa fa-fw fa-shopping-cart"/>
Your Cart (<LunchCurrency currency="currency" amount="state.infos.total || 0"/>)
</summary>
<t t-call="lunch.LunchDashboardOrder"/>
</details>
</t>
</t>
</templates>