+

-
+
Service Management
- Keep track of
- services and invoice
+ Keep track of services and invoice
+
-
-

-
+
+

+
Restaurant
- Run your bar or
- restaurant methodically
+ Run your bar or restaurant methodically
+
-
-
+

-
+
Hotel Management
- An
- all-inclusive
- hotel management application
+ An all-inclusive hotel management application
+
-
-
-
+
@@ -523,32 +460,25 @@
-
-
-
+
@@ -557,16 +487,14 @@
-

+
-
+
-
\ No newline at end of file
diff --git a/pos_traceability_validation/static/src/js/PoSEditListPopup.js b/pos_traceability_validation/static/src/js/PoSEditListPopup.js
new file mode 100644
index 000000000..99b09baab
--- /dev/null
+++ b/pos_traceability_validation/static/src/js/PoSEditListPopup.js
@@ -0,0 +1,74 @@
+odoo.define('pos_traceability_validation.PoSEditListPopup', function (require) {
+ 'use strict';
+ const EditListPopup = require('point_of_sale.EditListPopup');
+ const Registries = require('point_of_sale.Registries');
+ const { _lt } = require('@web/core/l10n/translation');
+ var rpc = require('web.rpc');
+ /**
+ * Extends EditListPopup for adding functionality for lot/ serial number
+ * validation
+ */
+ const PoSEditListPopup = (EditListPopup) =>
+ class extends EditListPopup {
+ constructor() {
+ super(...arguments);
+ this.product = this.props.product;
+ }
+ /**
+ * On confirming from the popup after adding lots/ serial numbers,
+ * the values are passed to the function validate_lots() for the
+ * validation. The corresponding error messages will be displayed
+ * on the popup if the lot is invalid or duplicated, or there is
+ * no insufficient stock.
+ */
+ async confirm() {
+ if (this.props.title == 'Lot/Serial Number(s) Required') {
+ var lot_string = this.state.array;
+ var lot_names = [];
+ for (var i = 0; i < lot_string.length; i++) {
+ if (lot_string[i].text != '') {
+ lot_names.push(lot_string[i].text);
+ }
+ }
+ const picking_type_id = this.env.pos.config && this.env.pos.config.picking_type_id && this.env.pos.config.picking_type_id[0]
+ const result = await rpc.query({
+ model: 'stock.production.lot',
+ method: 'validate_lots',
+ args: [lot_names, this.props.product, picking_type_id]
+ })
+ if (result != true) {
+ if(result[0] == 'no_stock') {
+ this.showPopup('ErrorPopup', {
+ 'title': _lt('Out of stock'),
+ 'body': _lt('The product is out of stock for ' + result[1] + '.')
+ });
+ } else if(result[0] == 'duplicate') {
+ this.showPopup('ErrorPopup', {
+ 'title': _lt('Duplicate Serial Number'),
+ 'body': _lt('Duplicate entry for ' + result[1] + '.')
+ });
+ } else if(result[0] == 'invalid') {
+ this.showPopup('ErrorPopup', {
+ 'title': _lt('Invalid Lot/ Serial Number'),
+ 'body': _lt('The Lot/ Serial Number ' + result[1] + ' is not available for this product.')
+ });
+ }
+ } else {
+ this.props.resolve({
+ confirmed: true,
+ payload: await this.getPayload()
+ });
+ this.trigger('close-popup');
+ }
+ } else {
+ this.props.resolve({
+ confirmed: true,
+ payload: await this.getPayload()
+ });
+ this.trigger('close-popup');
+ }
+ }
+ };
+ Registries.Component.extend(EditListPopup, PoSEditListPopup);
+ return EditListPopup;
+});
diff --git a/pos_traceability_validation/static/src/js/PoSOrderWidget.js b/pos_traceability_validation/static/src/js/PoSOrderWidget.js
new file mode 100644
index 000000000..d9282d26e
--- /dev/null
+++ b/pos_traceability_validation/static/src/js/PoSOrderWidget.js
@@ -0,0 +1,37 @@
+odoo.define('pos_traceability_validation.PoSOrderWidget', function (require) {
+ 'use strict';
+ const OrderWidget = require('point_of_sale.OrderWidget');
+ const Registries = require('point_of_sale.Registries');
+ /**
+ * Extends OrderWidget for passing the product IDs to the EditListPopup
+ * validation
+ */
+ const PoSOrderWidget = (OrderWidget) =>
+ class extends OrderWidget {
+ async _editPackLotLines(event) {
+ const orderline = event.detail.orderline;
+ const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot();
+ const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot);
+ const { confirmed, payload } = await this.showPopup('EditListPopup', {
+ title: this.env._t('Lot/Serial Number(s) Required'),
+ isSingleItem: isAllowOnlyOneLot,
+ array: packLotLinesToEdit,
+ product: orderline.product.id
+ });
+ if (confirmed) {
+ // Segregate the old and new packlot lines
+ const modifiedPackLotLines = Object.fromEntries(
+ payload.newArray.filter(item => item.id).map(item => [item.id, item.text])
+ );
+ const newPackLotLines = payload.newArray
+ .filter(item => !item.id)
+ .map(item => ({ lot_name: item.text }));
+
+ orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines });
+ }
+ this.order.select_orderline(event.detail.orderline);
+ }
+ }
+ Registries.Component.extend(OrderWidget, PoSOrderWidget);
+ return OrderWidget;
+});
diff --git a/pos_traceability_validation/static/src/js/PoSProductScreen.js b/pos_traceability_validation/static/src/js/PoSProductScreen.js
new file mode 100644
index 000000000..3faa72156
--- /dev/null
+++ b/pos_traceability_validation/static/src/js/PoSProductScreen.js
@@ -0,0 +1,90 @@
+odoo.define('pos_traceability_validation.PoSProductScreen', function (require) {
+ 'use strict';
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const Registries = require('point_of_sale.Registries');
+ /**
+ * Extends ProductScreen for passing the product ID to the EditListPopup
+ * validation
+ */
+ const PoSProductScreen = (ProductScreen) =>
+ class extends ProductScreen {
+ async _getAddProductOptions(product, base_code) {
+ let price_extra = 0.0;
+ let draftPackLotLines, weight, description, packLotLinesToEdit;
+ if (this.env.pos.config.product_configurator && _.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) {
+ let attributes = _.map(product.attribute_line_ids, (id) => this.env.pos.attributes_by_ptal_id[id])
+ .filter((attr) => attr !== undefined);
+ let { confirmed, payload } = await this.showPopup('ProductConfiguratorPopup', {
+ product: product,
+ attributes: attributes,
+ });
+ if (confirmed) {
+ description = payload.selected_attributes.join(', ');
+ price_extra += payload.price_extra;
+ } else {
+ return;
+ }
+ }
+ // Gather lot information if required.
+ if (['serial', 'lot'].includes(product.tracking) && (this.env.pos.picking_type.use_create_lots || this.env.pos.picking_type.use_existing_lots)) {
+ const isAllowOnlyOneLot = product.isAllowOnlyOneLot();
+ if (isAllowOnlyOneLot) {
+ packLotLinesToEdit = [];
+ } else {
+ const orderline = this.currentOrder
+ .get_orderlines()
+ .filter(line => !line.get_discount())
+ .find(line => line.product.id === product.id);
+ if (orderline) {
+ packLotLinesToEdit = orderline.getPackLotLinesToEdit();
+ } else {
+ packLotLinesToEdit = [];
+ }
+ }
+ const { confirmed, payload } = await this.showPopup('EditListPopup', {
+ title: this.env._t('Lot/Serial Number(s) Required'),
+ isSingleItem: isAllowOnlyOneLot,
+ array: packLotLinesToEdit,
+ product: product.id
+ });
+ if (confirmed) {
+ // Segregate the old and new packlot lines
+ const modifiedPackLotLines = Object.fromEntries(
+ payload.newArray.filter(item => item.id).map(item => [item.id, item.text])
+ );
+ const newPackLotLines = payload.newArray
+ .filter(item => !item.id)
+ .map(item => ({ lot_name: item.text }));
+
+ draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
+ } else {
+ // We don't proceed on adding product.
+ return;
+ }
+ }
+ // Take the weight if necessary.
+ if (product.to_weight && this.env.pos.config.iface_electronic_scale) {
+ // Show the ScaleScreen to weigh the product.
+ if (this.isScaleAvailable) {
+ const { confirmed, payload } = await this.showTempScreen('ScaleScreen', {
+ product,
+ });
+ if (confirmed) {
+ weight = payload.weight;
+ } else {
+ // do not add the product;
+ return;
+ }
+ } else {
+ await this._onScaleNotAvailable();
+ }
+ }
+ if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) {
+ weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty;
+ }
+ return { draftPackLotLines, quantity: weight, description, price_extra };
+ }
+ }
+ Registries.Component.extend(ProductScreen, PoSProductScreen);
+ return ProductScreen;
+});
diff --git a/pos_traceability_validation/static/src/js/pos_models.js b/pos_traceability_validation/static/src/js/pos_models.js
deleted file mode 100644
index 041caa851..000000000
--- a/pos_traceability_validation/static/src/js/pos_models.js
+++ /dev/null
@@ -1,74 +0,0 @@
-odoo.define('pos_traceability_validation.pos_models', function (require) {
-"use strict";
- const EditListPopup = require('point_of_sale.EditListPopup');
- const Registries = require('point_of_sale.Registries');
- var rpc = require('web.rpc');
-
- const PosEditlistpopup = (EditListPopup) =>
- class extends EditListPopup {
- async confirm() {
-
- if (this.props.title == 'Lot/Serial Number(s) Required'){
-
- var lot_string = this.state.array
- var lot_names = [];
- for (var i = 0; i < lot_string.length; i++) {
-
- if (lot_string[i].text != ""){
- lot_names.push(lot_string[i].text);
- }
-
- }
-
- const result = await rpc.query({
- model: 'serial_no.validation',
- method: 'validate_lots',
- args: [lot_names]
- })
-
- if(result != true){
- if(result[0] == 'no_stock'){
- this.showPopup('ErrorPopup', {
- 'title': this.env._t('Insufficient stock'),
- 'body': this.env._t("Insufficient stock for " + result[1]),
- });
-
- }
- else if(result[0] == 'duplicate'){
- this.showPopup('ErrorPopup', {
- 'title': this.env._t('Duplicate entry'),
- 'body': this.env._t("Duplicate entry for " + result[1]),
- });
- }
- else if(result[0] == 'except'){
- alert("Exception occured with " + result[1])
- this.showPopup('ErrorPopup', {
- 'title': this.env._t('Exception'),
- 'body': this.env._t("Exception occured with" + result[1]),
- });
- }
- }
- else{
- this.props.resolve({ confirmed: true, payload: await this.getPayload() });
- this.trigger('close-popup');
-
- }
- }
- else{
- this.props.resolve({ confirmed: true, payload: await this.getPayload() });
- this.trigger('close-popup');
- }
-
- }
-
- };
-
- Registries.Component.extend(EditListPopup, PosEditlistpopup);
-
- return EditListPopup;
-
-
-});
-
-
-