Skip to content

Commit

Permalink
[IMP] pos_product_expiry: Make it work in Offline mode
Browse files Browse the repository at this point in the history
  • Loading branch information
etobella committed Jan 17, 2024
1 parent 05ee3c6 commit 9d2a853
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 61 deletions.
5 changes: 4 additions & 1 deletion pos_product_expiry/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
"license": "AGPL-3",
"author": "Dixmit,INVITU,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/pos",
"depends": ["point_of_sale", "product_expiry"],
"depends": ["point_of_sale", "product_expiry", "pos_lot_selection"],
"assets": {
"point_of_sale._assets_pos": [
"pos_product_expiry/static/src/js/**/*.js",
],
"web.assets_tests": [
"pos_product_expiry/static/tests/tours/**/*",
],
},
"data": [
"views/res_config_settings_views.xml",
Expand Down
2 changes: 1 addition & 1 deletion pos_product_expiry/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from . import pos_session
from . import product_product
from . import pos_config
from . import res_config_settings
from . import stock_lot
32 changes: 0 additions & 32 deletions pos_product_expiry/models/product_product.py

This file was deleted.

21 changes: 21 additions & 0 deletions pos_product_expiry/models/stock_lot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import pytz

from odoo import models


class StockLot(models.Model):
_inherit = "stock.lot"

def _get_pos_info(self):
result = super()._get_pos_info()
if self.expiration_date:
timezone = pytz.timezone(
self._context.get("tz") or self.env.user.tz or "UTC"
)
result["expiration_date"] = self.expiration_date.astimezone(
timezone
).isoformat()
return result
57 changes: 30 additions & 27 deletions pos_product_expiry/static/src/js/app/models.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,52 @@
Copyright 2024 Dixmit
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
*/

import {ConnectionLostError} from "@web/core/network/rpc_service";
import {Orderline, Product} from "@point_of_sale/app/store/models";
import {ErrorPopup} from "@point_of_sale/app/errors/popups/error_popup";
import {OfflineErrorPopup} from "@point_of_sale/app/errors/popups/offline_error_popup";
import {Orderline} from "@point_of_sale/app/store/models";
import {_t} from "@web/core/l10n/translation";

import {patch} from "@web/core/utils/patch";

patch(Product.prototype, {
async checkProductLotExpiration(lot) {
const lotData = this.available_lot_for_pos_ids.filter((availableLot) => {
return lot === availableLot.name;
});
if (lotData.length === 0) {
await this.env.services.popup.add(ErrorPopup, {
title: _t("Problem with lots"),
body: _t("A lot was not found. No changes were applied."),
});
return true;
}
if (new Date(lotData[0].expiration_date) < new Date()) {
await this.env.services.popup.add(ErrorPopup, {
title: _t("Problem with lots"),
body: _t(
"A lot is expired and you are not enabled to sell expired lots. No changes were applied."
),
});
return true;
}
return false;
},
});

patch(Orderline.prototype, {
async setPackLotLines({modifiedPackLotLines, newPackLotLines}) {
if (
this.product.use_expiration_date &&
this.env.services.pos.config.check_lot_expiry
) {
var lotsToCheck = [];
for (const newLotLine of newPackLotLines) {
lotsToCheck.push(newLotLine.lot_name);
if (await this.product.checkProductLotExpiration(newLotLine.lot_name)) {
return;
}
}
for (const modifiedLotline of Object.values(modifiedPackLotLines)) {
lotsToCheck.push(modifiedLotline);
}
try {
const checked_lots_problem = await this.env.services.orm.call(
"product.product",
"check_pos_lots",
[[this.product.id], lotsToCheck, this.env.services.pos.company.id]
);
if (checked_lots_problem) {
await this.env.services.popup.add(ErrorPopup, {
title: _t("Problem with lots"),
body:
checked_lots_problem + " " + _t("No changes were applied."),
});
// We don't want to apply the changes in this case
if (await this.product.checkProductLotExpiration(modifiedLotline)) {
return;
}
} catch (error) {
if (error instanceof ConnectionLostError) {
this.env.services.popup.add(OfflineErrorPopup);
} else {
throw error;
}
}
}
return await super.setPackLotLines(...arguments);
Expand Down
51 changes: 51 additions & 0 deletions pos_product_expiry/static/tests/tours/ProductExpiry.tour.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** @odoo-module */
/*
Copyright 2023 Trobz Consulting
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
*/

import * as Chrome from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods";
import * as ErrorPopup from "@point_of_sale/../tests/tours/helpers/ErrorPopupTourMethods";
import * as PaymentScreen from "@point_of_sale/../tests/tours/helpers/PaymentScreenTourMethods";
import * as ProductScreen from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
import * as ReceiptScreen from "@point_of_sale/../tests/tours/helpers/ReceiptScreenTourMethods";
import {registry} from "@web/core/registry";
import {selectLotNumber} from "@pos_lot_selection/../tests/tours/LotSelection.tour.esm";

registry.category("web_tour.tours").add("ProductExpiryNotExpired", {
test: true,
url: "/pos/ui",
steps: () =>
[
ProductScreen.confirmOpeningPopup(),
ProductScreen.clickHomeCategory(),
ProductScreen.clickDisplayedProduct("Lot Product 1"),
selectLotNumber("10120000515"),
ProductScreen.selectedOrderlineHas("Lot Product 1"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.trackingMethodIsLot(),
Chrome.endTour(),
].flat(),
});

registry.category("web_tour.tours").add("ProductExpiryExpired", {
test: true,
url: "/pos/ui",
steps: () =>
[
ProductScreen.confirmOpeningPopup(),
ProductScreen.clickHomeCategory(),
ProductScreen.clickDisplayedProduct("Lot Product 1"),
selectLotNumber("10120000516"),
ProductScreen.selectedOrderlineHas("Lot Product 1"),
ErrorPopup.isShown(),
ErrorPopup.clickConfirm(),
ProductScreen.pressNumpad("⌫"),
ProductScreen.pressNumpad("⌫"),
// We need to clean the screen
ProductScreen.orderIsEmpty(),
Chrome.endTour(),
].flat(),
});
1 change: 1 addition & 0 deletions pos_product_expiry/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_frontend
75 changes: 75 additions & 0 deletions pos_product_expiry/tests/test_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright 2023 Trobz Consulting
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from datetime import datetime, timedelta

import odoo.tests

from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon


@odoo.tests.tagged("post_install", "-at_install")
class TestLotScanning(TestPointOfSaleHttpCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
now = datetime.now()
cls.lot_product_1 = cls.env["product.product"].create(
{
"name": "Lot Product 1",
"type": "product",
"tracking": "lot",
"categ_id": cls.env.ref("product.product_category_all").id,
"available_in_pos": True,
"use_expiration_date": True,
}
)
lots = cls.env["stock.lot"].create(
[
{
"name": "10120000515",
"product_id": cls.lot_product_1.id,
"company_id": cls.env.company.id,
"expiration_date": now + timedelta(days=1),
},
{
"name": "10120000516",
"product_id": cls.lot_product_1.id,
"company_id": cls.env.company.id,
"expiration_date": now + timedelta(days=-1),
},
]
)
location_id = cls.main_pos_config.picking_type_id.default_location_src_id.id
cls.env["stock.quant"].with_context(inventory_mode=True).create(
{
"product_id": cls.lot_product_1.id,
"inventory_quantity": 100,
"location_id": location_id,
"lot_id": lots[0].id,
}
).action_apply_inventory()
cls.env["stock.quant"].with_context(inventory_mode=True).create(
{
"product_id": cls.lot_product_1.id,
"inventory_quantity": 100,
"location_id": location_id,
"lot_id": lots[1].id,
}
).action_apply_inventory()

def test_lot_not_expired(self):
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour(
"/pos/ui?config_id=%d" % self.main_pos_config.id,
"ProductExpiryNotExpired",
login="pos_user",
)

def test_lot_expired(self):
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour(
"/pos/ui?config_id=%d" % self.main_pos_config.id,
"ProductExpiryExpired",
login="pos_user",
)

0 comments on commit 9d2a853

Please sign in to comment.