[IMP] lunch: migrate to Owl
Migrate lunch to Owl. task-2916338 Part-of: odoo/odoo#99021
This commit is contained in:
committed by
Yannick Tivisse
parent
727fd4f208
commit
503c0d6f8f
@@ -42,36 +42,23 @@ If you want to save your employees' time and avoid them to always have coins in
|
||||
'application': True,
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'lunch/static/src/components/*.js',
|
||||
'lunch/static/src/components/*.scss',
|
||||
'lunch/static/src/mixins/*.js',
|
||||
'lunch/static/src/views/*.js',
|
||||
'lunch/static/src/scss/lunch_view.scss',
|
||||
'lunch/static/src/scss/lunch_kanban.scss',
|
||||
'lunch/static/src/scss/lunch_list.scss',
|
||||
'lunch/static/src/js/lunch_controller_common.js',
|
||||
'lunch/static/src/js/lunch_widget.js',
|
||||
'lunch/static/src/js/lunch_mobile.js',
|
||||
'lunch/static/src/js/lunch_payment_dialog.js',
|
||||
'lunch/static/src/js/lunch_kanban_view.js',
|
||||
'lunch/static/src/js/lunch_kanban_controller.js',
|
||||
'lunch/static/src/js/lunch_kanban_renderer.js',
|
||||
'lunch/static/src/js/lunch_kanban_record.js',
|
||||
'lunch/static/src/js/lunch_model_extension.js',
|
||||
'lunch/static/src/js/lunch_list_view.js',
|
||||
'lunch/static/src/js/lunch_list_controller.js',
|
||||
'lunch/static/src/js/lunch_list_renderer.js',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'lunch/static/tests/tours/*.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'lunch/static/tests/lunch_test_utils.js',
|
||||
'lunch/static/tests/lunch_kanban_tests.js',
|
||||
'lunch/static/tests/lunch_list_tests.js',
|
||||
],
|
||||
'web.qunit_mobile_suite_tests': [
|
||||
'lunch/static/tests/lunch_test_utils.js',
|
||||
'lunch/static/tests/lunch_kanban_mobile_tests.js',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'lunch/static/src/xml/lunch_templates.xml',
|
||||
'lunch/static/src/components/*.xml',
|
||||
'lunch/static/src/views/*.xml',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<field name="name">Coca Cola</field>
|
||||
<field name="category_id" ref="categ_drinks"/>
|
||||
<field name="price">2.9</field>
|
||||
<field name="description"></field>
|
||||
<field name="supplier_id" ref="supplier_hungry_dog"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
</record>
|
||||
@@ -214,6 +215,7 @@
|
||||
<field name="category_id" ref="categ_sandwich"/>
|
||||
<field name="price">3.0</field>
|
||||
<field name="supplier_id" ref="supplier_corner"/>
|
||||
<field name="description"></field>
|
||||
<field name="image_1920" type="base64" file="lunch/static/img/chicken_curry.png"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
</record>
|
||||
|
||||
@@ -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': 'bg-warning', 'confirmed': 'bg-success', 'sent': 'bg-info', 'ordered': 'bg-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';
|
||||
@@ -0,0 +1,9 @@
|
||||
.lunch_topping:before {
|
||||
content: '+ ';
|
||||
}
|
||||
|
||||
.o_lunch_content {
|
||||
.o-autocomplete--input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?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">
|
||||
<div>
|
||||
<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-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">
|
||||
<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 #{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">
|
||||
<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">
|
||||
<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">
|
||||
<Many2XAutocomplete
|
||||
value="props.location"
|
||||
resModel="'lunch.location'"
|
||||
getDomain="getDomain"
|
||||
activeActions="{}"
|
||||
update.bind="props.onUpdateLunchLocation"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="lunch.LunchDashboardOrder" owl="1">
|
||||
<LunchAlerts alerts="state.infos.alerts"/>
|
||||
|
||||
<div class="o_lunch_banner container-fluid py-2">
|
||||
<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 flex-row">
|
||||
<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-4" t-if="hasLines">
|
||||
<h4>
|
||||
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="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-4 d-flex flex-column justify-content-between" t-if="hasLines">
|
||||
<h4 class="d-flex flex-row">
|
||||
<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-white 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>
|
||||
@@ -1,219 +0,0 @@
|
||||
odoo.define('lunch.LunchControllerCommon', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the common events and functions used by Controllers for the Lunch view.
|
||||
*/
|
||||
|
||||
var session = require('web.session');
|
||||
var core = require('web.core');
|
||||
const {Markup} = require('web.utils');
|
||||
var LunchWidget = require('lunch.LunchWidget');
|
||||
var LunchPaymentDialog = require('lunch.LunchPaymentDialog');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
var LunchControllerCommon = {
|
||||
custom_events: {
|
||||
add_product: '_onAddProduct',
|
||||
change_location: '_onLocationChanged',
|
||||
change_user: '_onUserChanged',
|
||||
open_wizard: '_onOpenWizard',
|
||||
order_now: '_onOrderNow',
|
||||
remove_product: '_onRemoveProduct',
|
||||
unlink_order: '_onUnlinkOrder',
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.editMode = false;
|
||||
this.updated = false;
|
||||
this.widgetData = null;
|
||||
this.context = session.user_context;
|
||||
this.archiveEnabled = false;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
// create a div inside o_content that will be used to wrap the lunch
|
||||
// banner and renderer (this is required to get the desired
|
||||
// layout with the searchPanel to the left)
|
||||
var self = this;
|
||||
this.$('.o_content').append($('<div>').addClass('o_lunch_content'));
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self.$('.o_lunch_content').append(self.$('.o_lunch_view'));
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_fetchPaymentInfo: function () {
|
||||
return this._rpc({
|
||||
route: '/lunch/payment_message',
|
||||
params: {
|
||||
context: this.context,
|
||||
},
|
||||
});
|
||||
},
|
||||
async _fetchWidgetData() {
|
||||
const widgetData = await this._rpc({
|
||||
route: '/lunch/infos',
|
||||
params: {
|
||||
user_id: this.searchModel.get('userId'),
|
||||
context: this.context,
|
||||
},
|
||||
});
|
||||
widgetData.wallet = parseFloat(widgetData.wallet).toFixed(2);
|
||||
(widgetData.alerts || []).forEach(alert => { alert.message = Markup(alert.message); });
|
||||
this.widgetData = widgetData;
|
||||
},
|
||||
/**
|
||||
* Renders and appends the lunch banner widget.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_renderLunchWidget: function () {
|
||||
var oldWidget = this.widget;
|
||||
this.widget = new LunchWidget(this, Object.assign(this.widgetData, {edit: this.editMode}));
|
||||
return this.widget.appendTo(document.createDocumentFragment()).then(() => {
|
||||
this.$('.o_lunch_content').prepend(this.widget.$el);
|
||||
if (oldWidget) {
|
||||
oldWidget.destroy();
|
||||
}
|
||||
});
|
||||
},
|
||||
_showPaymentDialog: function (title) {
|
||||
var self = this;
|
||||
|
||||
title = title || '';
|
||||
|
||||
this._fetchPaymentInfo().then(function (data) {
|
||||
var paymentDialog = new LunchPaymentDialog(self, _.extend(data, {title: title}));
|
||||
paymentDialog.open();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Override to fetch and display the lunch data. Because of the presence of
|
||||
* the searchPanel, also wrap the lunch widget and the renderer into
|
||||
* a div, to get the desired layout.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_update: function () {
|
||||
var def = this._fetchWidgetData().then(this._renderLunchWidget.bind(this));
|
||||
return Promise.all([def, this._super.apply(this, arguments)]);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_onAddProduct: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
|
||||
this._rpc({
|
||||
model: 'lunch.order',
|
||||
method: 'update_quantity',
|
||||
args: [[ev.data.lineId], 1],
|
||||
}).then(function () {
|
||||
self.reload();
|
||||
});
|
||||
},
|
||||
_onLocationChanged: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.searchModel.dispatch('setLocationId', ev.data.locationId);
|
||||
},
|
||||
_onOpenWizard: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
|
||||
var ctx = this.searchModel.get('userId') ? {default_user_id: this.searchModel.get('userId')} : {};
|
||||
|
||||
var options = {
|
||||
on_close: function () {
|
||||
self.reload();
|
||||
},
|
||||
};
|
||||
|
||||
var action = {
|
||||
res_model: 'lunch.order',
|
||||
name: _t('Configure Your Order'),
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: _.extend(ctx, {default_product_id: ev.data.productId}),
|
||||
};
|
||||
|
||||
if (ev.data.lineId) {
|
||||
action = _.extend(action, {
|
||||
res_id: ev.data.lineId,
|
||||
context: _.extend(action.context, {
|
||||
active_id: ev.data.lineId,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
this.do_action(action, options);
|
||||
},
|
||||
_onOrderNow: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
|
||||
this._rpc({
|
||||
route: '/lunch/pay',
|
||||
params: {
|
||||
user_id: this.searchModel.get('userId'),
|
||||
context: this.context,
|
||||
},
|
||||
}).then(function (isPaid) {
|
||||
if (isPaid) {
|
||||
// TODO: feedback?
|
||||
self.reload();
|
||||
} else {
|
||||
self._showPaymentDialog(_t("Not enough money in your wallet"));
|
||||
self.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
_onRemoveProduct: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
|
||||
this._rpc({
|
||||
model: 'lunch.order',
|
||||
method: 'update_quantity',
|
||||
args: [[ev.data.lineId], -1],
|
||||
}).then(function () {
|
||||
self.reload();
|
||||
});
|
||||
},
|
||||
_onUserChanged: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.searchModel.dispatch('updateUserId', ev.data.userId);
|
||||
},
|
||||
_onUnlinkOrder: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
|
||||
this._rpc({
|
||||
route: '/lunch/trash',
|
||||
params: {
|
||||
user_id: this.searchModel.get('userId'),
|
||||
context: this.context,
|
||||
},
|
||||
}).then(function () {
|
||||
self.reload();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return LunchControllerCommon;
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
odoo.define('lunch.LunchKanbanController', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the Controller for the Lunch Kanban view, which is an
|
||||
* override of the KanbanController.
|
||||
*/
|
||||
|
||||
var KanbanController = require('web.KanbanController');
|
||||
var LunchControllerCommon = require('lunch.LunchControllerCommon');
|
||||
|
||||
var LunchKanbanController = KanbanController.extend(LunchControllerCommon , {
|
||||
custom_events: _.extend({}, KanbanController.prototype.custom_events, LunchControllerCommon.custom_events),
|
||||
});
|
||||
|
||||
return LunchKanbanController;
|
||||
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
odoo.define('lunch.LunchKanbanRecord', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the KanbanRecord for the Lunch Kanban view.
|
||||
*/
|
||||
|
||||
var KanbanRecord = require('web.KanbanRecord');
|
||||
|
||||
var LunchKanbanRecord = KanbanRecord.extend({
|
||||
events: _.extend({}, KanbanRecord.prototype.events, {
|
||||
'click': '_onSelectRecord',
|
||||
}),
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open the add product wizard
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev Click event
|
||||
*/
|
||||
_onSelectRecord: function (ev) {
|
||||
ev.preventDefault();
|
||||
// ignore clicks on oe_kanban_action elements
|
||||
if (!$(ev.target).hasClass('oe_kanban_action')) {
|
||||
this.trigger_up('open_wizard', {productId: this.recordData.product_id ? this.recordData.product_id.res_id: this.recordData.id});
|
||||
}
|
||||
},
|
||||
|
||||
_openRecord() {},
|
||||
});
|
||||
|
||||
return LunchKanbanRecord;
|
||||
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
odoo.define('lunch.LunchKanbanRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the Renderer for the Lunch Kanban view, which is an
|
||||
* override of the KanbanRenderer.
|
||||
*/
|
||||
|
||||
var LunchKanbanRecord = require('lunch.LunchKanbanRecord');
|
||||
|
||||
var KanbanRenderer = require('web.KanbanRenderer');
|
||||
|
||||
var LunchKanbanRenderer = KanbanRenderer.extend({
|
||||
config: _.extend({}, KanbanRenderer.prototype.config, {
|
||||
KanbanRecord: LunchKanbanRecord,
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.$el.addClass('o_lunch_view o_lunch_kanban_view position-relative align-content-start flex-grow-1 flex-shrink-1');
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
return LunchKanbanRenderer;
|
||||
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
odoo.define('lunch.LunchKanbanView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var LunchKanbanController = require('lunch.LunchKanbanController');
|
||||
var LunchKanbanRenderer = require('lunch.LunchKanbanRenderer');
|
||||
|
||||
var core = require('web.core');
|
||||
var KanbanView = require('web.KanbanView');
|
||||
var view_registry = require('web.view_registry');
|
||||
|
||||
var _lt = core._lt;
|
||||
|
||||
var LunchKanbanView = KanbanView.extend({
|
||||
config: _.extend({}, KanbanView.prototype.config, {
|
||||
Controller: LunchKanbanController,
|
||||
Renderer: LunchKanbanRenderer,
|
||||
}),
|
||||
display_name: _lt('Lunch Kanban'),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_createSearchModel(params, extraExtensions = {}) {
|
||||
Object.assign(extraExtensions, { Lunch: {} });
|
||||
return this._super(params, extraExtensions);
|
||||
},
|
||||
});
|
||||
|
||||
view_registry.add('lunch_kanban', LunchKanbanView);
|
||||
|
||||
return LunchKanbanView;
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
odoo.define('lunch.LunchListController', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the Controller for the Lunch List view, which is an
|
||||
* override of the ListController.
|
||||
*/
|
||||
|
||||
var ListController = require('web.ListController');
|
||||
var LunchControllerCommon = require('lunch.LunchControllerCommon');
|
||||
|
||||
var LunchListController = ListController.extend(LunchControllerCommon, {
|
||||
custom_events: _.extend({}, ListController.prototype.custom_events, LunchControllerCommon.custom_events),
|
||||
});
|
||||
|
||||
return LunchListController;
|
||||
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
odoo.define('lunch.LunchListRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the Renderer for the Lunch List view, which is an
|
||||
* override of the ListRenderer.
|
||||
*/
|
||||
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
|
||||
var LunchListRenderer = ListRenderer.extend({
|
||||
events: _.extend({}, ListRenderer.prototype.events, {
|
||||
'click .o_data_row': '_onClickListRow',
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.$el.addClass('o_lunch_view o_lunch_list_view');
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Override to add id of product_id in dataset.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderRow: function (record) {
|
||||
var tr = this._super.apply(this, arguments);
|
||||
tr.attr('data-product-id', record.data.id);
|
||||
return tr;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open the add product wizard
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev Click event
|
||||
*/
|
||||
_onClickListRow: function (ev) {
|
||||
ev.preventDefault();
|
||||
var productId = ev.currentTarget.dataset && ev.currentTarget.dataset.productId ? parseInt(ev.currentTarget.dataset.productId) : null;
|
||||
|
||||
if (productId) {
|
||||
this.trigger_up('open_wizard', {productId: productId});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return LunchListRenderer;
|
||||
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
odoo.define('lunch.LunchListView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var LunchListController = require('lunch.LunchListController');
|
||||
var LunchListRenderer = require('lunch.LunchListRenderer');
|
||||
|
||||
var core = require('web.core');
|
||||
var ListView = require('web.ListView');
|
||||
var view_registry = require('web.view_registry');
|
||||
|
||||
var _lt = core._lt;
|
||||
|
||||
var LunchListView = ListView.extend({
|
||||
config: _.extend({}, ListView.prototype.config, {
|
||||
Controller: LunchListController,
|
||||
Renderer: LunchListRenderer,
|
||||
}),
|
||||
display_name: _lt('Lunch List'),
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_createSearchModel(params, extraExtensions = {}) {
|
||||
Object.assign(extraExtensions, { Lunch: {} });
|
||||
return this._super(params, extraExtensions);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
view_registry.add('lunch_list', LunchListView);
|
||||
|
||||
return LunchListView;
|
||||
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
odoo.define('lunch.LunchMobile', function (require) {
|
||||
"use strict";
|
||||
|
||||
var config = require('web.config');
|
||||
var LunchWidget = require('lunch.LunchWidget');
|
||||
var LunchKanbanController = require('lunch.LunchKanbanController');
|
||||
var LunchListController = require('lunch.LunchListController');
|
||||
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
LunchWidget.include({
|
||||
template: "LunchWidgetMobile",
|
||||
|
||||
/**
|
||||
* Override to set the toggle state allowing initially open it.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
init: function (parent, params) {
|
||||
this._super.apply(this, arguments);
|
||||
this.keepOpen = params.keepOpen || undefined;
|
||||
},
|
||||
});
|
||||
|
||||
var mobileFunctions = {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.openWidget = false;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Override to add the widget's toggle state to its data.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderLunchWidget: function () {
|
||||
this.widgetData.keepOpen = this.openWidget;
|
||||
this.openWidget = false;
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onAddProduct: function () {
|
||||
this.openWidget = true;
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onRemoveProduct: function () {
|
||||
this.openWidget = true;
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
};
|
||||
|
||||
LunchKanbanController.include(mobileFunctions);
|
||||
LunchListController.include(mobileFunctions);
|
||||
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
odoo.define('lunch.LunchModel', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file defines the Model for the Lunch Kanban view, which is an
|
||||
* override of the KanbanModel.
|
||||
*/
|
||||
|
||||
var session = require('web.session');
|
||||
var BasicModel = require('web.BasicModel');
|
||||
|
||||
var LunchModel = BasicModel.extend({
|
||||
init: function () {
|
||||
this.locationId = false;
|
||||
this.userId = false;
|
||||
this._promInitLocation = null;
|
||||
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @return {Promise} resolved with the location domain
|
||||
*/
|
||||
getLocationDomain: function () {
|
||||
var self = this;
|
||||
return this._initUserLocation().then(function () {
|
||||
return self._buildLocationDomainLeaf() ? [self._buildLocationDomainLeaf()]: [];
|
||||
});
|
||||
},
|
||||
__load: function () {
|
||||
var self = this;
|
||||
var args = arguments;
|
||||
var _super = this._super;
|
||||
|
||||
return this._initUserLocation().then(function () {
|
||||
var params = args[0];
|
||||
self._addOrUpdate(params.domain, self._buildLocationDomainLeaf());
|
||||
|
||||
return _super.apply(self, args);
|
||||
});
|
||||
},
|
||||
__reload: function (id, options) {
|
||||
var domain = options && options.domain || this.localData[id].domain;
|
||||
|
||||
this._addOrUpdate(domain, this._buildLocationDomainLeaf());
|
||||
options = _.extend(options, {domain: domain});
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_addOrUpdate: function (domain, subDomain) {
|
||||
if (subDomain && subDomain.length) {
|
||||
var key = subDomain[0];
|
||||
var index = _.findIndex(domain, function (val) {
|
||||
return val[0] === key;
|
||||
});
|
||||
|
||||
if (index < 0) {
|
||||
domain.push(subDomain);
|
||||
} else {
|
||||
domain[index] = subDomain;
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
return domain;
|
||||
},
|
||||
/**
|
||||
* Builds the domain leaf corresponding to the current user's location
|
||||
*
|
||||
* @private
|
||||
* @return {(Array[])|undefined}
|
||||
*/
|
||||
_buildLocationDomainLeaf: function () {
|
||||
if (this.locationId) {
|
||||
return ['is_available_at', 'in', [this.locationId]];
|
||||
}
|
||||
},
|
||||
_getUserLocation: function () {
|
||||
return this._rpc({
|
||||
route: '/lunch/user_location_get',
|
||||
params: {
|
||||
context: session.user_context,
|
||||
user_id: this.userId,
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Gets the user location once.
|
||||
* Can be triggered from anywhere
|
||||
* Useful to inject the location domain in the search panel
|
||||
*
|
||||
* @private
|
||||
* @return {Promise}
|
||||
*/
|
||||
_initUserLocation: function () {
|
||||
var self = this;
|
||||
if (!this._promInitLocation) {
|
||||
this._promInitLocation = new Promise(function (resolve) {
|
||||
self._getUserLocation().then(function (locationId) {
|
||||
self.locationId = locationId;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this._promInitLocation;
|
||||
},
|
||||
_updateLocation: function (locationId) {
|
||||
this.locationId = locationId;
|
||||
return Promise.resolve();
|
||||
},
|
||||
_updateUser: function (userId) {
|
||||
this.userId = userId;
|
||||
this._promInitLocation = null;
|
||||
return this._initUserLocation();
|
||||
}
|
||||
});
|
||||
|
||||
return LunchModel;
|
||||
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
odoo.define("lunch/static/src/js/lunch_model_extension.js", function (require) {
|
||||
"use strict";
|
||||
|
||||
const ActionModel = require("web.ActionModel");
|
||||
|
||||
class LunchModelExtension extends ActionModel.Extension {
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Public
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @returns {any}
|
||||
*/
|
||||
get(property) {
|
||||
switch (property) {
|
||||
case "domain": return this.getDomain();
|
||||
case "userId": return this.state.userId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async load() {
|
||||
await this._updateLocationId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
prepareState() {
|
||||
Object.assign(this.state, {
|
||||
locationId: null,
|
||||
userId: null,
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Actions / Getters
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Array[] | null}
|
||||
*/
|
||||
getDomain() {
|
||||
if (this.state.locationId) {
|
||||
return [["is_available_at", "in", [this.state.locationId]]];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} locationId
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setLocationId(locationId) {
|
||||
this.state.locationId = locationId;
|
||||
this.env.services.rpc({
|
||||
route: "/lunch/user_location_set",
|
||||
params: {
|
||||
context: this.env.session.user_context,
|
||||
location_id: this.state.locationId,
|
||||
user_id: this.state.userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} userId
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateUserId(userId) {
|
||||
this.state.userId = userId;
|
||||
this.shouldLoad = true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Private
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async _updateLocationId() {
|
||||
this.state.locationId = await this.env.services.rpc({
|
||||
route: "/lunch/user_location_get",
|
||||
params: {
|
||||
context: this.env.session.user_context,
|
||||
user_id: this.state.userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ActionModel.registry.add("Lunch", LunchModelExtension, 20);
|
||||
|
||||
return LunchModelExtension;
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
odoo.define('lunch.LunchPaymentDialog', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Dialog = require('web.Dialog');
|
||||
|
||||
var LunchPaymentDialog = Dialog.extend({
|
||||
template: 'lunch.LunchPaymentDialog',
|
||||
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.message = options.message || '';
|
||||
},
|
||||
});
|
||||
|
||||
return LunchPaymentDialog;
|
||||
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
odoo.define('lunch.LunchWidget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var relationalFields = require('web.relational_fields');
|
||||
var session = require('web.session');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var _t = core._t;
|
||||
var FieldMany2One = relationalFields.FieldMany2One;
|
||||
|
||||
|
||||
var LunchMany2One = FieldMany2One.extend({
|
||||
start: function () {
|
||||
this.$el.addClass('w-100');
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var LunchWidget = Widget.extend({
|
||||
template: 'LunchWidget',
|
||||
custom_events: {
|
||||
field_changed: '_onFieldChanged',
|
||||
},
|
||||
events: {
|
||||
'click .o_add_product': '_onAddProduct',
|
||||
'click .o_lunch_widget_order_button': '_onOrderNow',
|
||||
'click .o_remove_product': '_onRemoveProduct',
|
||||
'click .o_lunch_widget_unlink': '_onUnlinkOrder',
|
||||
'click .o_lunch_open_wizard': '_onLunchOpenWizard',
|
||||
},
|
||||
|
||||
init: function (parent, params) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.is_manager = params.is_manager || false;
|
||||
this.group_portal_id = params.group_portal_id || false;
|
||||
this.userimage = params.userimage || '';
|
||||
this.username = params.username || '';
|
||||
|
||||
this.lunchUserField = null;
|
||||
|
||||
this.locations = params.locations || [];
|
||||
this.userLocation = params.user_location[1] || '';
|
||||
|
||||
const company_ids = [false].concat(session.user_context.allowed_company_ids || []);
|
||||
this.lunchLocationField = this._createMany2One('locations', 'lunch.location', this.userLocation, () => [
|
||||
['company_id', 'in', company_ids]
|
||||
]);
|
||||
|
||||
this.wallet = params.wallet || 0;
|
||||
this.raw_state = params.raw_state || 'new';
|
||||
this.state = params.state || _t('To Order');
|
||||
this.lines = params.lines || [];
|
||||
this.total = params.total || 0;
|
||||
|
||||
this.alerts = params.alerts || [];
|
||||
|
||||
this.currency = params.currency || session.get_currency(session.company_currency_id);
|
||||
},
|
||||
willStart: function () {
|
||||
var superDef = this._super.apply(this, arguments);
|
||||
|
||||
if (this.is_manager) {
|
||||
this.lunchUserField = this._createMany2One('users', 'res.users', this.username,
|
||||
() => [['groups_id', 'not in', [this.group_portal_id]]]
|
||||
);
|
||||
}
|
||||
return superDef;
|
||||
},
|
||||
renderElement: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.lunchUserField) {
|
||||
this.lunchUserField.appendTo(this.$('.o_lunch_user_field'));
|
||||
} else {
|
||||
this.$('.o_lunch_user_field').text(this.username);
|
||||
}
|
||||
|
||||
if (this.userLocation) {
|
||||
this.lunchLocationField.appendTo(this.$('.o_lunch_location_field'));
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_createMany2One: function (name, model, value, domain, context) {
|
||||
var fields = {};
|
||||
fields[name] = {type: 'many2one', relation: model, string: name};
|
||||
var data = {};
|
||||
data[name] = {data: {display_name: value}};
|
||||
|
||||
var record = {
|
||||
id: name,
|
||||
res_id: 1,
|
||||
model: 'dummy',
|
||||
fields: fields,
|
||||
fieldsInfo: {
|
||||
default: fields,
|
||||
},
|
||||
data: data,
|
||||
getDomain: domain || function () { return []; },
|
||||
getContext: context || function () { return {}; },
|
||||
};
|
||||
var options = {
|
||||
mode: 'edit',
|
||||
noOpen: true,
|
||||
attrs: {
|
||||
can_create: false,
|
||||
can_write: false,
|
||||
}
|
||||
};
|
||||
return new LunchMany2One(this, name, record, options);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_onAddProduct: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.trigger_up('add_product', {lineId: $(ev.currentTarget).data('id')});
|
||||
},
|
||||
_onOrderNow: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.trigger_up('order_now', {});
|
||||
},
|
||||
_onLunchOpenWizard: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
var target = $(ev.currentTarget);
|
||||
this.trigger_up('open_wizard', {productId: target.data('product-id'), lineId: target.data('id')});
|
||||
},
|
||||
_onFieldChanged: function (ev) {
|
||||
ev.stopPropagation();
|
||||
|
||||
if (ev.data.dataPointID === 'users') {
|
||||
this.trigger_up('change_user', {userId: ev.data.changes.users.id});
|
||||
} else if (ev.data.dataPointID === 'locations') {
|
||||
this.trigger_up('change_location', {locationId: ev.data.changes.locations.id});
|
||||
}
|
||||
},
|
||||
_onRemoveProduct: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.trigger_up('remove_product', {lineId: $(ev.currentTarget).data('id')});
|
||||
},
|
||||
_onUnlinkOrder: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.trigger_up('unlink_order', {});
|
||||
},
|
||||
});
|
||||
|
||||
return LunchWidget;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
|
||||
export const LunchRendererMixin = {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.action = useService("action");
|
||||
useBus(this.env.bus, 'lunch_open_order', (ev) => this.openOrderLine(ev.detail.productId));
|
||||
},
|
||||
|
||||
openOrderLine(productId, orderId) {
|
||||
let context = {};
|
||||
|
||||
if (this.env.searchModel.lunchState.userId) {
|
||||
context['default_user_id'] = this.env.searchModel.lunchState.userId;
|
||||
}
|
||||
|
||||
let action = {
|
||||
res_model: 'lunch.order',
|
||||
name: this.env._t('Configure Your Order'),
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
...context,
|
||||
default_product_id: productId,
|
||||
},
|
||||
};
|
||||
|
||||
if (orderId) {
|
||||
action['res_id'] = orderId;
|
||||
}
|
||||
|
||||
this.action.doAction(action, {
|
||||
onClose: () => this.env.bus.trigger('lunch_update_dashboard')
|
||||
});
|
||||
},
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
.o_lunch_content {
|
||||
.o_legacy_kanban_view {
|
||||
.o_kanban_renderer {
|
||||
flex: 1 1 100%;
|
||||
&.o_kanban_grouped {
|
||||
min-height: auto; // override min-height: 100%
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { patch } from '@web/core/utils/patch';
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
import { kanbanView } from '@web/views/kanban/kanban_view';
|
||||
import { KanbanRecord } from '@web/views/kanban/kanban_record';
|
||||
import { KanbanRenderer } from '@web/views/kanban/kanban_renderer';
|
||||
|
||||
import { LunchDashboard } from '../components/lunch_dashboard';
|
||||
import { LunchRendererMixin } from '../mixins/lunch_renderer_mixin';
|
||||
|
||||
import { LunchSearchModel } from './search_model';
|
||||
|
||||
|
||||
export class LunchKanbanRecord extends KanbanRecord {
|
||||
onGlobalClick(ev) {
|
||||
this.env.bus.trigger('lunch_open_order', {productId: this.props.record.resId});
|
||||
}
|
||||
}
|
||||
|
||||
export class LunchKanbanRenderer extends KanbanRenderer {}
|
||||
patch(LunchKanbanRenderer.prototype, 'lunch_kanban_renderer_mixin', LunchRendererMixin);
|
||||
|
||||
LunchKanbanRenderer.template = 'lunch.KanbanRenderer';
|
||||
LunchKanbanRenderer.components = {
|
||||
...LunchKanbanRenderer.components,
|
||||
LunchDashboard,
|
||||
KanbanRecord: LunchKanbanRecord,
|
||||
}
|
||||
|
||||
registry.category('views').add('lunch_kanban', {
|
||||
...kanbanView,
|
||||
Renderer: LunchKanbanRenderer,
|
||||
SearchModel: LunchSearchModel,
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="lunch.KanbanRenderer" owl="1">
|
||||
<div class="o_lunch_content">
|
||||
<LunchDashboard openOrderLine.bind="openOrderLine"/>
|
||||
|
||||
<div class="overflow-auto">
|
||||
<t t-call="web.KanbanRenderer"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,32 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { patch } from '@web/core/utils/patch';
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
import { listView } from '@web/views/list/list_view';
|
||||
import { ListRenderer } from '@web/views/list/list_renderer';
|
||||
|
||||
import { LunchDashboard } from '../components/lunch_dashboard';
|
||||
import { LunchRendererMixin } from '../mixins/lunch_renderer_mixin';
|
||||
|
||||
import { LunchSearchModel } from './search_model';
|
||||
|
||||
|
||||
export class LunchListRenderer extends ListRenderer {
|
||||
onCellClicked(record, column) {
|
||||
this.openOrderLine(record.resId);
|
||||
}
|
||||
}
|
||||
patch(LunchListRenderer.prototype, 'lunch_list_renderer_mixin', LunchRendererMixin);
|
||||
|
||||
LunchListRenderer.template = 'lunch.ListRenderer';
|
||||
LunchListRenderer.components = {
|
||||
...LunchListRenderer.components,
|
||||
LunchDashboard,
|
||||
}
|
||||
|
||||
registry.category('views').add('lunch_list', {
|
||||
...listView,
|
||||
Renderer: LunchListRenderer,
|
||||
SearchModel: LunchSearchModel,
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="lunch.ListRenderer" owl="1">
|
||||
<div class="o_lunch_content">
|
||||
<LunchDashboard openOrderLine.bind="openOrderLine"/>
|
||||
|
||||
<div class="overflow-auto">
|
||||
<t t-call="web.ListRenderer"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,67 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { Domain } from '@web/core/domain';
|
||||
import { SearchModel } from '@web/search/search_model';
|
||||
|
||||
const { useState, onWillStart } = owl;
|
||||
|
||||
|
||||
export class LunchSearchModel extends SearchModel {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
|
||||
this.rpc = useService('rpc');
|
||||
this.lunchState = useState({
|
||||
locationId: false,
|
||||
userId: false,
|
||||
});
|
||||
|
||||
onWillStart(async () => {
|
||||
const locationId = await this.rpc('/lunch/user_location_get', {});
|
||||
this.updateLocationId(locationId);
|
||||
});
|
||||
}
|
||||
|
||||
exportState() {
|
||||
const state = super.exportState();
|
||||
state.locationId = this.lunchState.locationId;
|
||||
state.userId = this.lunchState.userId;
|
||||
return state;
|
||||
}
|
||||
|
||||
_importState(state) {
|
||||
super._importState(...arguments);
|
||||
|
||||
if (state.locationId) {
|
||||
this.lunchState.locationId = state.locationId;
|
||||
}
|
||||
if (state.userId) {
|
||||
this.lunchState.userId = state.userId;
|
||||
}
|
||||
}
|
||||
|
||||
updateUserId(userId) {
|
||||
this.lunchState.userId = userId;
|
||||
this._notify();
|
||||
}
|
||||
|
||||
updateLocationId(locationId) {
|
||||
this.lunchState.locationId = locationId;
|
||||
this._notify();
|
||||
}
|
||||
|
||||
_getDomain(params = {}) {
|
||||
const domain = super._getDomain(params);
|
||||
|
||||
if (!this.lunchState.locationId) {
|
||||
return domain;
|
||||
}
|
||||
|
||||
return Domain.and([
|
||||
domain,
|
||||
[['is_available_at', '=', this.lunchState.locationId]]
|
||||
]).toList();
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<span t-name="LunchWidget">
|
||||
<t t-foreach="widget.alerts" t-as="alert" t-key="alert_index">
|
||||
<div class="alert alert-warning mb-0" role="alert">
|
||||
<t t-out="alert.message"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o_lunch_banner container-fluid">
|
||||
<div class="o_lunch_widget row py-3 py-md-0">
|
||||
<div class="o_lunch_widget_info col-12 col-md-4 card border-0">
|
||||
<div class="card-body row g-0 align-items-center">
|
||||
<div class="col-3 col-md-6 col-lg-3">
|
||||
<img class="o_image_64_cover rounded-circle" t-attf-src="{{ widget.userimage }}"/>
|
||||
</div>
|
||||
<div class="col-9 col-md-6 col-lg-9">
|
||||
<div class="ps-3">
|
||||
<div class="o_lunch_user_field py-1"/>
|
||||
<div class="o_lunch_location_field py-1"/>
|
||||
<div class="d-flex flex-row py-1">
|
||||
<span class="flex-grow-1">Your Account</span>
|
||||
<t t-call="currency_field">
|
||||
<t t-set="value" t-value="widget.wallet"/>
|
||||
<t t-set="currency" t-value="widget.currency"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_lunch_widget_info col-12 col-md-4 card border-0">
|
||||
<t t-if="!_.isEmpty(widget.lines)">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">
|
||||
Your order
|
||||
<button t-if="widget.raw_state != 'confirmed'" class="btn btn-sm btn-icon btn-link fa fa-trash o_lunch_widget_unlink"/>
|
||||
</h4>
|
||||
<ul class="list-unstyled o_lunch_widget_lines">
|
||||
<t t-set="states_mapping" t-value="{'new': 'warning', 'confirmed': 'success', 'sent': 'info', 'ordered': 'danger'}" />
|
||||
<li t-foreach="widget.lines" t-as="line" t-key="line_index">
|
||||
<t t-set="can_modify" t-value="!['sent', 'confirmed'].includes(line.raw_state)" />
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-0 flex-shrink-0 o_lunch_product_quantity">
|
||||
<button class="btn btn-sm btn-icon btn-link fa fa-minus-circle o_remove_product" t-if="can_modify" t-attf-data-id="{{ line.id }}"/>
|
||||
<span t-esc="line.quantity"/>
|
||||
<button class="btn btn-sm btn-icon btn-link fa fa-plus-circle o_add_product" t-if="can_modify" t-attf-data-id="{{ line.id }}"/>
|
||||
</div>
|
||||
<div class="flex-grow-1 ps-2">
|
||||
<button t-if="can_modify" t-esc="line.product[1]" t-attf-class="btn btn-link o_lunch_open_wizard" t-attf-data-product-id="{{ line.product[0] }}" t-attf-data-id="{{ line.id }}"/>
|
||||
<t t-else="" t-esc="line.product[1]" />
|
||||
<t t-set="state_pill" t-value="states_mapping[line.raw_state]" />
|
||||
<span t-esc="line.state" t-attf-class="badge rounded-pill bg-{{ state_pill }}" />
|
||||
</div>
|
||||
<div class="flex-grow-0">
|
||||
<t t-call="currency_field">
|
||||
<t t-set="value" t-value="line.product[2]"/>
|
||||
<t t-set="currency" t-value="widget.currency"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-foreach="line.toppings" t-as="topping" t-key="topping_index" class="d-flex flex-row">
|
||||
<div class="flex-grow-1 ps-5">
|
||||
<span>+ <t t-esc="topping[0]"/></span>
|
||||
</div>
|
||||
<div class="flex-grow-0">
|
||||
<t t-call="currency_field">
|
||||
<t t-set="value" t-value="topping[1]"/>
|
||||
<t t-set="currency" t-value="widget.currency"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<span t-if="line.note" t-esc="line.note" class="text-muted ps-5"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_lunch_widget_info col-12 col-md-4 card border-0">
|
||||
<t t-if="!_.isEmpty(widget.lines) && widget.raw_state == 'new'">
|
||||
<div class="card-body d-flex flex-column justify-content-between">
|
||||
<h4 class="card-title d-flex py-1">
|
||||
<span class="flex-grow-1">Total</span>
|
||||
<t t-call="currency_field">
|
||||
<t t-set="value" t-value="widget.total"/>
|
||||
<t t-set="currency" t-value="widget.currency"/>
|
||||
</t>
|
||||
</h4>
|
||||
<button t-if="widget.raw_state == 'new'" class="btn btn-primary w-100 o_lunch_widget_order_button">Order now</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span t-name="currency_field" class="o_field_monetary o_field_number o_field_widget">
|
||||
<t t-js="ctx">
|
||||
ctx.value = _.str.sprintf('%.2f', parseFloat(ctx.value));
|
||||
</t>
|
||||
<t t-if="currency">
|
||||
<t t-if="currency.position == 'after'">
|
||||
<t t-esc="value"/><t t-esc="currency.symbol"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="currency.symbol"/><t t-esc="value"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="value"/>
|
||||
</t>
|
||||
</span>
|
||||
|
||||
<div t-name="lunch.LunchPaymentDialog">
|
||||
<span t-esc="widget.message"/>
|
||||
</div>
|
||||
|
||||
<t t-name="LunchWidgetMobile">
|
||||
<details class="fixed-bottom" t-attf-open="#{widget.keepOpen}">
|
||||
<summary class="o_lunch_toggle_cart btn btn-primary w-100">
|
||||
<i class="fa fa-fw fa-shopping-cart"/>
|
||||
Your cart
|
||||
(<t t-call="currency_field">
|
||||
<t t-set="value" t-value="widget.total"/>
|
||||
<t t-set="currency" t-value="widget.currency"/>
|
||||
</t>)
|
||||
</summary>
|
||||
<t t-call="LunchWidget"/>
|
||||
</details>
|
||||
</t>
|
||||
</templates>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,59 +0,0 @@
|
||||
odoo.define('lunch.test_utils', function (require) {
|
||||
"use strict";
|
||||
|
||||
const AbstractStorageService = require('web.AbstractStorageService');
|
||||
const RamStorage = require('web.RamStorage');
|
||||
const {createView} = require('web.test_utils');
|
||||
|
||||
/**
|
||||
* Helper to create a lunch view with searchpanel
|
||||
*
|
||||
* @param {object} params
|
||||
*/
|
||||
async function createLunchView(params) {
|
||||
params.archs = params.archs || {};
|
||||
var searchArch = params.archs[`${params.model},false,search`] || '<search></search>';
|
||||
var searchPanelArch = `
|
||||
<searchpanel>
|
||||
<field name="category_id" select="multi" string="Categories" enable_counters="1"/>
|
||||
<field name="supplier_id" select="multi" string="Vendors" enable_counters="1"/>
|
||||
</searchpanel>
|
||||
`;
|
||||
searchArch = searchArch.split('</search>')[0] + searchPanelArch + '</search>';
|
||||
params.archs[`${params.model},false,search`] = searchArch;
|
||||
if (!params.services || !params.services.local_storage) {
|
||||
// the searchPanel uses the localStorage to store/retrieve default
|
||||
// active category value
|
||||
params.services = params.services || {};
|
||||
const RamStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
params.services.local_storage = RamStorageService;
|
||||
}
|
||||
return createView(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate a mockRPC function for the mandatory lunch routes (prefixed by '/lunch')
|
||||
*
|
||||
* @param {object} infos
|
||||
* @param {integer} userLocation
|
||||
*/
|
||||
function mockLunchRPC({infos, userLocation}) {
|
||||
return async function (route) {
|
||||
if (route === '/lunch/infos') {
|
||||
return Promise.resolve(infos);
|
||||
}
|
||||
if (route === '/lunch/user_location_get') {
|
||||
return Promise.resolve(userLocation);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
createLunchView,
|
||||
mockLunchRPC,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -200,7 +200,7 @@
|
||||
<field name="name">lunch.order.view.form</field>
|
||||
<field name="model">lunch.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<form class="flex-column">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="date" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
|
||||
@@ -113,8 +113,8 @@
|
||||
<field name="is_new"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_kanban_record">
|
||||
<field name="image_128" class="o_lunch_image o_kanban_image_fill_left d-none d-md-block" options="{'placeholder': '/lunch/static/img/lunch.png', 'size': [94, 94]}" widget="image"/>
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill">
|
||||
<field name="image_128" class="o_lunch_image o_kanban_image_fill_left" options="{'placeholder': '/lunch/static/img/lunch.png', 'size': [94, 94]}" widget="image"/>
|
||||
<div class="oe_kanban_details ml8">
|
||||
<div class="o_kanban_record_top">
|
||||
<div class="o_kanban_record_headings">
|
||||
@@ -160,7 +160,7 @@
|
||||
<field name="currency_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_kanban_record">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill">
|
||||
<div class="o_kanban_image_fill_left d-none d-md-block"
|
||||
t-attf-style="background-image:url('#{kanban_image('lunch.product', 'image_128', record.id.raw_value)}')"/>
|
||||
<div class="oe_kanban_details">
|
||||
|
||||
Reference in New Issue
Block a user