diff --git a/pos_second_uom/__init__.py b/pos_second_uom/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/pos_second_uom/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_second_uom/__manifest__.py b/pos_second_uom/__manifest__.py new file mode 100644 index 00000000000..07b66ae0db7 --- /dev/null +++ b/pos_second_uom/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "POS Second UoM", + "description": """ + This module allows cashier to sell product in second unit of measure + """, + "depends": [ + "product", + "point_of_sale", + ], + "data": ["views/product_template.xml"], + "assets": { + "point_of_sale._assets_pos": [ + "pos_second_uom/static/src/**/*", + ] + }, + "license": "LGPL-3", + "application": True, + "installable": True, +} diff --git a/pos_second_uom/models/__init__.py b/pos_second_uom/models/__init__.py new file mode 100644 index 00000000000..e8fa8f6bf1e --- /dev/null +++ b/pos_second_uom/models/__init__.py @@ -0,0 +1 @@ +from . import product_template diff --git a/pos_second_uom/models/product_template.py b/pos_second_uom/models/product_template.py new file mode 100644 index 00000000000..199f1e54def --- /dev/null +++ b/pos_second_uom/models/product_template.py @@ -0,0 +1,31 @@ +from odoo import fields, models, api + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + uom_category_id = fields.Many2one( + comodel_name="uom.uom", + string="UoM Category", + related="uom_id.category_id", + store=True, + ) + + pos_secondary_uom_id = fields.Many2one( + comodel_name="uom.uom", + string="Secondary Unit of Measure", + domain="[('category_id', '=', uom_category_id), ('id', '!=', uom_id)]", + ) + + @api.onchange("uom_id") + def _onchange_uom_id(self): + self.pos_secondary_uom_id = False + + +class ProductProduct(models.Model): + _name = "product.product" + _inherit = ["product.product"] + + @api.model + def _load_pos_data_fields(self, config_id): + return super()._load_pos_data_fields(config_id) + ["pos_secondary_uom_id"] diff --git a/pos_second_uom/static/src/js/add_quantity.js b/pos_second_uom/static/src/js/add_quantity.js new file mode 100644 index 00000000000..40f0cde14c9 --- /dev/null +++ b/pos_second_uom/static/src/js/add_quantity.js @@ -0,0 +1,45 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; + + +export class SecondaryUomDialog extends Component { + static template = "pos_secondary_uom.SecondaryUomDialog"; + static components = { Dialog }; + + setup() { + this.pos = usePos(); + this.state = useState({ quantity: 1 }); + + this.orderline = this.props.orderline; + const uomIds = this.orderline.product_id.uom_id.category_id.uom_ids; + + this.baseUom = uomIds[0]; + this.secondaryUom = uomIds[1]; + this.conversionRatio = 1; + if (this.secondaryUom && this.baseUom) { + const secondaryFactor = this.secondaryUom.factor; + const baseFactor = this.baseUom.factor; + this.conversionRatio = baseFactor / secondaryFactor; + } + } + + confirm() { + const enteredQty = parseFloat(this.state.quantity); + if (isNaN(enteredQty) || enteredQty <= 0) { + this.pos.env.services.notification.add("Enter a valid quantity", { type: "warning" }); + return; + } + + const baseQty = enteredQty * this.conversionRatio; + this.orderline.set_quantity(baseQty); + this.orderline.uom_id = this.secondaryUom; + this.props.close(); + } + + cancel() { + this.props.close(); + } +} \ No newline at end of file diff --git a/pos_second_uom/static/src/js/add_quantity.xml b/pos_second_uom/static/src/js/add_quantity.xml new file mode 100644 index 00000000000..76b6d2d55be --- /dev/null +++ b/pos_second_uom/static/src/js/add_quantity.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pos_second_uom/static/src/js/control_button.js b/pos_second_uom/static/src/js/control_button.js new file mode 100644 index 00000000000..a28ec8edbd0 --- /dev/null +++ b/pos_second_uom/static/src/js/control_button.js @@ -0,0 +1,23 @@ + +import { patch } from "@web/core/utils/patch"; +import { ControlButtons } from "@point_of_sale/app/screens/product_screen/control_buttons/control_buttons"; +import { SecondaryUomDialog } from "./add_quantity"; + + +patch(ControlButtons.prototype, { + async openSecondaryUomDialog() { + const order = this.pos.get_order(); + const orderline = order.get_selected_orderline(); + + if (!orderline.product_id.uom_id.category_id.uom_ids[1]) { + this.env.services.notification.add("Select a product with a secondary UoM first.", { + type: "warning", + }); + return; + } + + this.dialog.add(SecondaryUomDialog, { + orderline, + }); + }, +}); \ No newline at end of file diff --git a/pos_second_uom/tests/__init__.py b/pos_second_uom/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pos_second_uom/views/product_template.xml b/pos_second_uom/views/product_template.xml new file mode 100644 index 00000000000..4c9a668e68e --- /dev/null +++ b/pos_second_uom/views/product_template.xml @@ -0,0 +1,12 @@ + + + product.template.form.secondary.uom + product.template + + + + + + + + \ No newline at end of file