Skip to content

Commit

Permalink
[ADD] pos_stock_available_online: Add new module to sync product avai…
Browse files Browse the repository at this point in the history
…lability with POS
  • Loading branch information
GabbasovDinar committed Jan 25, 2023
1 parent dbf3aa3 commit 942fcd6
Show file tree
Hide file tree
Showing 22 changed files with 420 additions and 0 deletions.
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 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": "LGPL-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
47 changes: 47 additions & 0 deletions pos_stock_available_online/models/pos_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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,
)
additional_warehouse_ids = fields.Many2many(
"stock.warehouse",
"pos_config_stock_warehouse_rel",
"pos_config_id",
"warehouse_id",
string="Additional Warehouses",
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)
20 changes: 20 additions & 0 deletions pos_stock_available_online/models/pos_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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:
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.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)
19 changes: 19 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,19 @@
from odoo import fields, models


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

pos_display_product_quantity = fields.Boolean(
related="pos_config_id.display_product_quantity",
readonly=False,
)
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,
)
40 changes: 40 additions & 0 deletions pos_stock_available_online/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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"),
("display_product_quantity", "=", True),
"|",
("config_id.additional_warehouse_ids", "in", [warehouse_id]),
("config_id.warehouse_id", "=", warehouse_id),
],
).mapped("config_id")
if configs:
configs._notify_available_quantity(quant._prepare_pos_message())

def write(self, vals):
res = super().write(vals)
self._notify_pos()
return res
17 changes: 17 additions & 0 deletions pos_stock_available_online/models/stock_warehouse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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,
"quantity": product.with_context(warehouse=self.id).immediately_usable_qty,
"product_id": product.id,
}
1 change: 1 addition & 0 deletions pos_stock_available_online/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Select PoS > Configuration > Settings > select 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
1 change: 1 addition & 0 deletions pos_stock_available_online/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO
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 {
position: absolute;
right: 0;
top: 0;
padding: 0 5px;
font-weight: bold;
}
.pos .product-list .warehouse-info .warehouse {
padding: 5px 0;
margin-left: auto;
width: fit-content;
}
.pos .product-list .warehouse-info .warehouse .quantity {
color: white;
padding: 4px;
float: right;
}
.pos .product-list .warehouse-info .warehouse .warehouse-name {
display: block;
color: #696969;
font-size: 10px;
text-align: right;
}
.pos .product-list .warehouse-info .warehouse.total .warehouse-name {
font-weight: bold;
}
.pos .product-list .warehouse-info .warehouse .quantity.available {
background: #32a868;
}
.pos .product-list .warehouse-info .warehouse .quantity.not-available {
background: #ef5350;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @odoo-module **/

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) {
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.props.product.warehouse_info.reduce(
(partialSum, warehouse) => partialSum + warehouse.quantity,
0
);
}
};

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);
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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">

<t
t-name="ProductItem"
t-inherit="point_of_sale.ProductItem"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//div[hasclass('product-img')]" position="before">
<div t-if="env.pos.config.display_product_quantity" class="warehouse-info">
<div class="warehouse total">
<span
t-attf-class="quantity {{total_quantity > env.pos.config.minimum_product_quantity_alert ? 'available' : 'not-available'}}"
>
<t t-esc="display_total_quantity" />
</span>
<span class="warehouse-name">
Total
</span>
</div>
<t
t-foreach="props.product.warehouse_info"
t-as="warehouse"
t-key="warehouse.id"
>
<div class="warehouse">
<span
t-attf-class="quantity {{warehouse.quantity > env.pos.config.minimum_product_quantity_alert ? 'available' : 'not-available'}}"
>
<t t-esc="format_quantity(warehouse.quantity)" />
</span>
<span class="warehouse-name">
<t t-esc="warehouse.name" />
</span>
</div>
</t>
</div>
</xpath>
</t>

</templates>
Loading

0 comments on commit 942fcd6

Please sign in to comment.