-
-
Notifications
You must be signed in to change notification settings - Fork 602
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
wait a bot :) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
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", | ||
} |
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 |
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) |
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: | ||
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) |
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
) |
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), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this check is added to prevent the |
||||||
configs._notify_available_quantity(quant._prepare_pos_message()) | ||||||
|
||||||
def write(self, vals): | ||||||
res = super().write(vals) | ||||||
self._notify_pos() | ||||||
return res |
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, | ||
} |
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* Cetmix <https://cetmix.com/> | ||
* Dinar Gabbasov |
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. |
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). |
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 **/ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also rename the fiele There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.