diff --git a/pos_product_pack/README.rst b/pos_product_pack/README.rst new file mode 100644 index 000000000..307734168 --- /dev/null +++ b/pos_product_pack/README.rst @@ -0,0 +1,101 @@ +================ +Pos Product Pack +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:7b09d3ab71f2301a0893b7075c2e7aa43625340eed4617e8f09c8878dd2194ec + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--pack-lightgray.png?logo=github + :target: https://github.com/OCA/product-pack/tree/14.0/pos_product_pack + :alt: OCA/product-pack +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-pack-14-0/product-pack-14-0-pos_product_pack + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-pack&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to use product packs in POS. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +* Define some product packs with products inside. +* Allow them to be visible in POS. +* In POS, select the Pack product. The contained products will be pushed + in the order. + +* When you change the quantity for the pack product, the quantity change in + linked lines accordingly. +* When you remove the pack product, the linked lines will be removed. + +Known issues / Roadmap +====================== + +* Take into account multiple line quantities. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Denis Roussel + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-rousseldenis| image:: https://github.com/rousseldenis.png?size=40px + :target: https://github.com/rousseldenis + :alt: rousseldenis + +Current `maintainer `__: + +|maintainer-rousseldenis| + +This module is part of the `OCA/product-pack `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_product_pack/__init__.py b/pos_product_pack/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/pos_product_pack/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_product_pack/__manifest__.py b/pos_product_pack/__manifest__.py new file mode 100644 index 000000000..fbe69d9ed --- /dev/null +++ b/pos_product_pack/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Pos Product Pack", + "summary": """ + Allows to sell product packs on POS sessions""", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "maintainers": ["rousseldenis"], + "website": "https://github.com/OCA/product-pack", + "depends": [ + "product_pack", + "point_of_sale", + ], + "data": [ + "views/pos_product_pack.xml", + ], +} diff --git a/pos_product_pack/models/__init__.py b/pos_product_pack/models/__init__.py new file mode 100644 index 000000000..3aa06b88a --- /dev/null +++ b/pos_product_pack/models/__init__.py @@ -0,0 +1 @@ +from . import pos_order_line diff --git a/pos_product_pack/models/pos_order_line.py b/pos_product_pack/models/pos_order_line.py new file mode 100644 index 000000000..2b5d02acc --- /dev/null +++ b/pos_product_pack/models/pos_order_line.py @@ -0,0 +1,11 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PosOrderLine(models.Model): + _inherit = "pos.order.line" + + pack_line_id = fields.Many2one( + comodel_name="product.pack.line", + ) diff --git a/pos_product_pack/readme/CONTRIBUTORS.rst b/pos_product_pack/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..9179ee4b8 --- /dev/null +++ b/pos_product_pack/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Denis Roussel diff --git a/pos_product_pack/readme/DESCRIPTION.rst b/pos_product_pack/readme/DESCRIPTION.rst new file mode 100644 index 000000000..51fa209e2 --- /dev/null +++ b/pos_product_pack/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to use product packs in POS. diff --git a/pos_product_pack/readme/ROADMAP.rst b/pos_product_pack/readme/ROADMAP.rst new file mode 100644 index 000000000..e5f325775 --- /dev/null +++ b/pos_product_pack/readme/ROADMAP.rst @@ -0,0 +1 @@ +* Take into account multiple line quantities. diff --git a/pos_product_pack/readme/USAGE.rst b/pos_product_pack/readme/USAGE.rst new file mode 100644 index 000000000..19a90457f --- /dev/null +++ b/pos_product_pack/readme/USAGE.rst @@ -0,0 +1,8 @@ +* Define some product packs with products inside. +* Allow them to be visible in POS. +* In POS, select the Pack product. The contained products will be pushed + in the order. + +* When you change the quantity for the pack product, the quantity change in + linked lines accordingly. +* When you remove the pack product, the linked lines will be removed. diff --git a/pos_product_pack/static/description/icon.png b/pos_product_pack/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/pos_product_pack/static/description/icon.png differ diff --git a/pos_product_pack/static/description/index.html b/pos_product_pack/static/description/index.html new file mode 100644 index 000000000..cbf8f5071 --- /dev/null +++ b/pos_product_pack/static/description/index.html @@ -0,0 +1,443 @@ + + + + + + +Pos Product Pack + + + +
+

Pos Product Pack

+ + +

Beta License: AGPL-3 OCA/product-pack Translate me on Weblate Try me on Runboat

+

This module allows to use product packs in POS.

+

Table of contents

+ +
+

Usage

+
    +
  • Define some product packs with products inside.
  • +
  • Allow them to be visible in POS.
  • +
  • In POS, select the Pack product. The contained products will be pushed +in the order.
  • +
  • When you change the quantity for the pack product, the quantity change in +linked lines accordingly.
  • +
  • When you remove the pack product, the linked lines will be removed.
  • +
+
+
+

Known issues / Roadmap

+
    +
  • Take into account multiple line quantities.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

rousseldenis

+

This module is part of the OCA/product-pack project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_product_pack/static/src/js/models.js b/pos_product_pack/static/src/js/models.js new file mode 100644 index 000000000..a818d6ab8 --- /dev/null +++ b/pos_product_pack/static/src/js/models.js @@ -0,0 +1,237 @@ +odoo.define("pos_product_pack.models", function (require) { + "use strict"; + + var models = require("point_of_sale.models"); + + models.load_fields("product.template", ["pack_ok"]); + models.load_fields("product.product", [ + "pack_ok", + "pack_line_ids", + "pack_type", + "pack_component_price", + ]); + // Load product.pack.line that parent is avaiable in pos + models.load_models([ + { + model: "product.pack.line", + fields: ["parent_product_id", "quantity", "product_id"], + // Condition: function (self) { return self.product_by_id; }, + domain: function () { + return [["parent_product_id.available_in_pos", "=", true]]; + }, + loaded: function (self, product_pack_line_ids) { + self.product_pack_line_by_id = {}; + _.map(product_pack_line_ids, function (product_pack_line) { + self.product_pack_line_by_id[ + product_pack_line.id + ] = product_pack_line; + }); + }, + }, + ]); + + var _super_order = models.Order.prototype; + models.Order = models.Order.extend({ + add_product_pack: function (line) { + // Get parent children and for each of them, add the product + var self = this; + var pack_lines = line.get_pack_lines(); + _.forEach(pack_lines, function (pack_line) { + var product = self.pos.db.get_product_by_id(pack_line.product_id[0]); + if (product) { + var new_line = new models.Orderline( + {}, + {pos: line.order.pos, order: line.order, product: product} + ); + // Get an existing pack line with same id + var to_merge_line = line.get_pack_line_can_be_merged_with( + pack_line + ); + if (to_merge_line) { + to_merge_line.merge(new_line); + } else { + // We cannot merge with another existing line + self.add_product(product, { + pack_parent_line_id: line, + pack_line_id: pack_line, + merge: false, + }); + } + } + }); + }, + + // @Override + add_product(product, options) { + _super_order.add_product.apply(this, arguments); + var line = this.get_selected_orderline(); + // Added product is a pack - We add products contained in that + // pack. + if (product.pack_ok) { + this.add_product_pack(line); + } + // Added product is one contained in a parent pack. + // We add the line in children variable in parent pack. + if ("pack_parent_line_id" in options) { + if (line.pack_parent_line_id === null) { + line.pack_parent_line_id = options.pack_parent_line_id; + } + if ( + !options.pack_parent_line_id.pack_child_line_ids.some( + (child) => child === line.id + ) + ) { + var parent_line = this.get_orderline( + options.pack_parent_line_id.id + ); + parent_line.pack_child_line_ids.push(line.id); + // Force a save as pack child lines are just ids + parent_line.order.save_to_db(); + } + } + if ("pack_line_id" in options) { + line.pack_line_id = options.pack_line_id; + line.order.save_to_db(); + } + }, + // @Override + remove_orderline: function (line) { + line.remove_pack_line(); + _super_order.remove_orderline.apply(this, arguments); + }, + // @Override + add_orderline: function (line) { + _super_order.add_orderline.apply(this, arguments); + if (line.order == this) { + if (line.pack_parent_line_id) { + line.pack_parent_line_id.pack_child_line_ids.push(line.id); + } + } + }, + }); + + var _super_order_line = models.Orderline.prototype; + models.Orderline = models.Orderline.extend({ + initialize() { + // Define pack parent and children values + this.pack_parent_line_id = null; + this.pack_line_id = null; + this.pack_child_line_ids = []; + _super_order_line.initialize.apply(this, arguments); + }, + get_pack_lines: function () { + // Return pack lines from pack product + var pack_line_ids = this.get_product().pack_line_ids; + var lines = []; + for (var i = 0; i < pack_line_ids.length; i++) { + if (this.pos.product_pack_line_by_id[pack_line_ids[i]]) { + lines.push(this.pos.product_pack_line_by_id[pack_line_ids[i]]); + } + } + return lines; + }, + get_pack_line_can_be_merged_with: function (pack_line) { + // Use first the same method as in point_of_sale to get the product + // to merge with. + // If found, check if pack_line is the same or not. If not, + // return false. The add_product method would be called with + // options.merge === false. + var to_merge_orderline = null; + for (var i = 0; i < this.order.orderlines.length; i++) { + var current_line = this.order.orderlines.at(i); + if ( + current_line.pack_line_id && + current_line.pack_line_id.id === pack_line.id + ) { + to_merge_orderline = this.order.orderlines.at(i); + break; + } + } + return to_merge_orderline; + }, + remove_pack_line: function () { + var self = this; + // Check if line is a pack one and has children + if (this.product.pack_ok) { + // The line to remove is a product pack + // So, we remove the concerning children lines + if (this.pack_child_line_ids) { + this.pack_child_line_ids.forEach(function (child_line_id) { + var child_line = self.order.get_orderline(child_line_id); + if (child_line) { + self.order.remove_orderline(child_line); + } + }); + } + } + // Check if line is a pack child, then, remove itself from + // parent pack lines + if (this.pack_parent_line_id) { + if (this.pack_parent_line_id.pack_child_line_ids.includes(this.id)) { + const index = this.pack_parent_line_id.pack_child_line_ids.indexOf( + this.id + ); + this.pack_parent_line_id.pack_child_line_ids.splice(index, 1); + } + } + }, + set_pack_lines_quantity: function (quantity) { + var self = this; + if (this.product.pack_ok && this.pack_child_line_ids.length) { + this.pack_child_line_ids.forEach(function (child_line_id) { + var child_line = self.order.get_orderline(child_line_id); + if (child_line) { + child_line.set_quantity(quantity); + } + }); + } + }, + merge: function () { + if (this.pack_line_id) { + // This would avoid to call set_quantity twice + return; + } + return _super_order_line.merge.apply(this, arguments); + }, + // @Override + set_quantity: function (quantity) { + _super_order_line.set_quantity.apply(this, arguments); + this.set_pack_lines_quantity(quantity); + }, + get_full_product_name: function () { + var name = _super_order_line.get_full_product_name.apply(this, arguments); + if (this.pack_line_id) { + name = "> " + name; + } + return name; + }, + // @Override + init_from_JSON: function (json) { + _super_order_line.init_from_JSON.apply(this, arguments); + if (json.pack_parent_line_id) { + this.pack_parent_line_id = this.order.get_orderline( + json.pack_parent_line_id + ); + } + if (json.pack_line_id) { + this.pack_line_id = this.pos.product_pack_line_by_id[json.pack_line_id]; + } + if (json.pack_child_line_ids && json.pack_child_line_ids.length !== 0) { + this.pack_child_line_ids = json.pack_child_line_ids; + } + }, + // @Override + export_as_JSON: function () { + const json = _super_order_line.export_as_JSON.apply(this, arguments); + json.pack_child_line_ids = this.pack_child_line_ids; + // Export parent + if (this.pack_parent_line_id) { + json.pack_parent_line_id = this.pack_parent_line_id.id; + } + if (this.pack_line_id) { + json.pack_line_id = this.pack_line_id.id; + } + return json; + }, + }); +}); diff --git a/pos_product_pack/views/pos_product_pack.xml b/pos_product_pack/views/pos_product_pack.xml new file mode 100644 index 000000000..c78819404 --- /dev/null +++ b/pos_product_pack/views/pos_product_pack.xml @@ -0,0 +1,13 @@ + + + +