Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] pos_stock_available_online: Add new module #922

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pos_stock_available_online/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
wait a bot :)
1 change: 1 addition & 0 deletions pos_stock_available_online/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
20 changes: 20 additions & 0 deletions pos_stock_available_online/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Point of Sale Stock Available Online",
"version": "16.0.1.0.0",
"category": "Sales/Point of Sale",
"summary": "Show the available quantity of products in the Point of Sale ",
"depends": ["point_of_sale", "stock_available", "base_automation"],
"website": "https://github.com/OCA/pos",
"author": "Cetmix, Odoo Community Association (OCA)",
"maintainers": ["GabbasovDinar", "CetmixGitDrone"],
"installable": True,
"data": ["views/res_config_settings_view.xml"],
"assets": {
"point_of_sale.assets": [
"pos_stock_available_online/static/src/css/**/*.css",
"pos_stock_available_online/static/src/js/**/*.js",
"pos_stock_available_online/static/src/xml/**/*.xml",
],
},
"license": "AGPL-3",
}
5 changes: 5 additions & 0 deletions pos_stock_available_online/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import pos_config
from . import pos_session
from . import res_config_settings
from . import stock_quant
from . import stock_warehouse
53 changes: 53 additions & 0 deletions pos_stock_available_online/models/pos_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import logging

from odoo import fields, models

_logger = logging.getLogger(__name__)


class PosConfig(models.Model):
_inherit = "pos.config"

display_product_quantity = fields.Boolean(
default=True,
)
main_warehouse_id = fields.Many2one(
"stock.warehouse",
related="picking_type_id.warehouse_id",
store=True,
)
additional_warehouse_ids = fields.Many2many(
"stock.warehouse",
"pos_config_stock_warehouse_rel",
"pos_config_id",
"warehouse_id",
string="Additional Warehouses",
domain="[('company_id', '=', company_id)]",
help="For the selected warehouses will be displayed "
"quantity of available products in the POS",
)
minimum_product_quantity_alert = fields.Float(
default=0.0,
)

def _get_channel_name(self):
"""
Return full channel name as combination, POS Config ID and const CHANNEL
"""
self.ensure_one()
return '["{}","{}"]'.format("pos_stock_available_online", self.id)

def _notify_available_quantity(self, message):
"""
Notify POSes about product updates
"""
if not isinstance(message, list):
message = [message]
notifications = []
for config in self:
notifications.append(
[config._get_channel_name(), "pos.config/product_update", message]
)
if notifications:
self.env["bus.bus"]._sendmany(notifications)
_logger.debug("POS notifications for %s: %s", self.ids, notifications)
22 changes: 22 additions & 0 deletions pos_stock_available_online/models/pos_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from odoo import models


class PosSession(models.Model):
_inherit = "pos.session"

def _process_pos_ui_product_product(self, products):
config = self.config_id
if config.display_product_quantity:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return early and save a identation level

Suggested change
if config.display_product_quantity:
if not config.display_product_quantity:
return super()._process_pos_ui_product_product(products)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, @hugho-ad the negative condition is more difficult to read code I see no reason to change in this way

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not it's not, is called early return pattern
https://medium.com/swlh/return-early-pattern-3d18a41bba8

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hugho-ad we have to return "super" in any case. In your case, you offer to return only if this setting is not specified, which is not true.

product_obj = self.env["product.product"]
for product_info in products:
product = product_obj.browse(product_info["id"])
# prepared first main warehouse info
warehouse_info = [
config.main_warehouse_id._prepare_vals_for_pos(product)
]
# prepared additional warehouses info
for warehouse in config.additional_warehouse_ids:
warehouse_info.append(warehouse._prepare_vals_for_pos(product))
product_info["warehouse_info"] = warehouse_info

return super()._process_pos_ui_product_product(products)
23 changes: 23 additions & 0 deletions pos_stock_available_online/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

pos_display_product_quantity = fields.Boolean(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res.configs.settings are per company

this fields should be on pos.config form view instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starting with Odoo 16 all settings specific to POS are defined in this model, you can check the original Odoo code. https://github.com/odoo/odoo/blob/b0df10c1e497f65b891f7141055e7f339d11b4b1/addons/point_of_sale/models/res_config_settings.py#L48-L105

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome, I didn't know that

thanks!

related="pos_config_id.display_product_quantity",
readonly=False,
)
pos_main_warehouse_id = fields.Many2one(
"stock.warehouse",
related="pos_config_id.main_warehouse_id",
)
pos_additional_warehouse_ids = fields.Many2many(
"stock.warehouse",
related="pos_config_id.additional_warehouse_ids",
readonly=False,
)
pos_minimum_product_quantity_alert = fields.Float(
related="pos_config_id.minimum_product_quantity_alert",
readonly=False,
)
47 changes: 47 additions & 0 deletions pos_stock_available_online/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import logging

from odoo import models

_logger = logging.getLogger(__name__)


class StockQuant(models.Model):
_inherit = "stock.quant"

def _prepare_pos_message(self):
"""
Return prepared message to send to POS
"""
self.ensure_one()
return self.warehouse_id._prepare_vals_for_pos(self.product_id)

def _notify_pos(self):
"""
Send notification to POS
"""
pos_session_obj = self.env["pos.session"]
for quant in self:
warehouse_id = quant.warehouse_id.id
configs = pos_session_obj.search(
[
("state", "=", "opened"),
("config_id.display_product_quantity", "=", True),
"|",
("config_id.additional_warehouse_ids", "in", [warehouse_id]),
("config_id.main_warehouse_id", "=", warehouse_id),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also quant.product in producs inside categories setup on the pos.config?

"|",
("config_id.iface_available_categ_ids", "=", False),
(
"config_id.iface_available_categ_ids",
"in",
[quant.product_id.pos_categ_id.id],
),
],
).mapped("config_id")
if configs:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you got a for loop inside the _notify_available_quantity, if not record on the set, then will not perform any

Suggested change
if configs:
configs._notify_available_quantity(quant._prepare_pos_message())

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check is added to prevent the _prepare_pos_message method for the current quant from triggering unnecessarily if no config is found.

configs._notify_available_quantity(quant._prepare_pos_message())

def write(self, vals):
res = super().write(vals)
self._notify_pos()
return res
18 changes: 18 additions & 0 deletions pos_stock_available_online/models/stock_warehouse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from odoo import models


class StockWarehouse(models.Model):
_inherit = "stock.warehouse"

def _prepare_vals_for_pos(self, product):
"""
Prepare warehouse info data to send a POS
"""
self.ensure_one()
return {
"id": self.id,
"name": self.name,
"code": self.code,
"quantity": product.with_context(warehouse=self.id).immediately_usable_qty,
"product_id": product.id,
}
14 changes: 14 additions & 0 deletions pos_stock_available_online/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
In "Point of Sale" configuration "Product Quantity" section activate "Display Product Quantity" feature:
.. image:: ../static/img/pos_config.png

By default quantity is displayed for the warehouse that is used in the POS stock operation type.

You can add additional warehouses to show quantity in by adding them into "Additional Warehouses" field.

In this case the following information will be displayed on product tiles:

- Total quantity = quantity in the default warehouse + quantity in the additional warehouses

- Quantity in the default warehouse

- Quantity in the additional warehouses.
2 changes: 2 additions & 0 deletions pos_stock_available_online/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Cetmix <https://cetmix.com/>
* Dinar Gabbasov
6 changes: 6 additions & 0 deletions pos_stock_available_online/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
This module allows to display product quantities in selected locations in real time. Quantities are displayed directly on product tiles:
.. image:: ../static/img/pos_quantity.png

Once a product quantity is changed it will be simultaneously updated in all active POS.

This module depends on stock_available module which is available in https://github.com/OCA/stock-logistics-availability repo.
3 changes: 3 additions & 0 deletions pos_stock_available_online/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This module requires connection to update quantities and doesn't support offline mode.
Warehouses must belong to the same company as POS.
Offline mode support (probably additional module).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions pos_stock_available_online/static/src/css/pos.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.pos .product-list .warehouse-info {
padding: 0.5rem;
font-weight: bold;
display: flex;
border-top: 1px solid #efefef;
justify-content: space-between;
}
.pos .product-list .warehouse-info .warehouse {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.pos .product-list .warehouse-info .warehouse .quantity {
color: black;
padding: 1px 2px;
font-size: 11px;
}
.pos .product-list .warehouse-info .warehouse .warehouse-name {
display: block;
color: #696969;
font-size: 10px;
}
.pos .product-list .warehouse-info .warehouse.total .warehouse-name {
font-weight: bold;
}
.pos .product-list .warehouse-info .warehouse .quantity.available {
color: #32a868;
}
.pos .product-list .warehouse-info .warehouse .quantity.not-available {
color: #ef5350;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** @odoo-module **/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use
odoo.define'module.class', function (require) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also rename the fiele
Productitem.esm.js > Productitem_esm.js

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I see no reason to use the old approach of defining modules when there is OWL and the new approach, which also will not create problems in porting
  2. Why? the file with this extension will be picked up by the OCA .eslint rule for ESM modules.:
    see https://github.com/OCA/pos/blob/16.0/.eslintrc.yml#L11
    and [16.0] [MIG] pos_order_remove_line: migration to 16.0 #925 (comment)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GabbasovDinar thanks man, I need and update with v16.0


import ProductItem from "point_of_sale.ProductItem";
import Registries from "point_of_sale.Registries";
import {format} from "web.field_utils";
import utils from "web.utils";

const StockProductItem = (ProductItem) =>
class StockProductItem extends ProductItem {
format_quantity(quantity) {
const unit = this.env.pos.units_by_id[this.props.product.uom_id[0]];
var formattedQuantity = `${quantity}`;
if (unit) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (unit) {
if (!unit) return `${formattedQuantity}`;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use no-negative condition: https://eslint.org/docs/latest/rules/no-negated-condition

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! there is a eslint about that!

if (unit.rounding) {
var decimals = this.env.pos.dp["Product Unit of Measure"];
formattedQuantity = format.float(quantity, {
digits: [69, decimals],
});
} else {
formattedQuantity = utils.round_precision(quantity, 1).toFixed(0);
}
}
return `${formattedQuantity}`;
}
get display_total_quantity() {
return this.format_quantity(this.total_quantity);
}
get total_quantity() {
return this.warehouses.reduce(
(partialSum, warehouse) => partialSum + warehouse.quantity,
0
);
}
get warehouses() {
return this.props.product.warehouse_info;
}
};

Registries.Component.extend(ProductItem, StockProductItem);
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/** @odoo-module **/

import ProductsWidget from "point_of_sale.ProductsWidget";
import Registries from "point_of_sale.Registries";

const StockProductsWidget = (ProductsWidget) =>
class StockProductsWidget extends ProductsWidget {
setup() {
super.setup();
this.env.services.bus_service.addChannel(this._getChannelName());
this.env.services.bus_service.addEventListener(
"notification",
this._onNotification.bind(this)
);
}
_getChannelName() {
return JSON.stringify([
"pos_stock_available_online",
String(this.env.pos.config.id),
]);
}
_onNotification({detail: notifications}) {
var payloads = [];
for (const {payload, type} of notifications) {
if (type === "pos.config/product_update") {
payloads.push(payload);
}
}
this._handleNotification(payloads);
}
async _handleNotification(payloads) {
if (this.env.isDebug()) {
console.log("Payloads:", payloads);
}
const db = this.env.pos.db;
const ProductIds = [];
for (const payload of payloads) {
for (const message of payload) {
var product = db.get_product_by_id(message.product_id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could avoid this if you sent from backend only notifications used on the pos.config

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a new product is added to the POS, this product will not be available in the POS until the POS product is read again (for example, until the page is updated), this check is necessary so that the POS does not process information about the product that is available in the POS but not actually displayed (because the pos db is not yet updated)

if (product) {
// Update warehouse info of the product
var warehouse = product.warehouse_info.find(
(wh) => wh.id === message.id
);
if (warehouse) {
warehouse.quantity = message.quantity;
} else {
product.warehouse_info.push(message);
}
} else {
ProductIds.push(message.id);
}
}
}
if (ProductIds.length) {
await this.env.pos._addProducts([...new Set(ProductIds)], false);
}
// Re-render product list without category switching
this.render(true);
}
};

Registries.Component.extend(ProductsWidget, StockProductsWidget);
Loading