From 3f0c4abb6b0412e7a21214829f239a12342001f6 Mon Sep 17 00:00:00 2001 From: rvar-odoo Date: Fri, 1 Aug 2025 18:49:07 +0530 Subject: [PATCH 1/2] [ADD] pos_receipt: configure receipt layout -Now users can configure their receipt format for each POS configuration. -Users can choose receipt type - Receipt Logo: Logo is also customizable in receipts now - Header and Footer: receipt with personalized header & footer content --- pos_reciept_customize/__init__.py | 4 ++ pos_reciept_customize/__manifest__.py | 14 +++++ pos_reciept_customize/models/__init__.py | 4 ++ pos_reciept_customize/models/pos_config.py | 18 ++++++ .../models/res_config_settings.py | 19 +++++++ .../security/ir.model.access.csv | 2 + .../views/res_config_settings_views.xml | 22 ++++++++ pos_reciept_customize/wizard/__init__.py | 3 + .../wizard/reciepts_layout.py | 55 +++++++++++++++++++ .../wizard/reciepts_layout_view.xml | 29 ++++++++++ 10 files changed, 170 insertions(+) create mode 100644 pos_reciept_customize/__init__.py create mode 100644 pos_reciept_customize/__manifest__.py create mode 100644 pos_reciept_customize/models/__init__.py create mode 100644 pos_reciept_customize/models/pos_config.py create mode 100644 pos_reciept_customize/models/res_config_settings.py create mode 100644 pos_reciept_customize/security/ir.model.access.csv create mode 100644 pos_reciept_customize/views/res_config_settings_views.xml create mode 100644 pos_reciept_customize/wizard/__init__.py create mode 100644 pos_reciept_customize/wizard/reciepts_layout.py create mode 100644 pos_reciept_customize/wizard/reciepts_layout_view.xml diff --git a/pos_reciept_customize/__init__.py b/pos_reciept_customize/__init__.py new file mode 100644 index 0000000000..33bbab569d --- /dev/null +++ b/pos_reciept_customize/__init__.py @@ -0,0 +1,4 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import wizard diff --git a/pos_reciept_customize/__manifest__.py b/pos_reciept_customize/__manifest__.py new file mode 100644 index 0000000000..9b505de5a7 --- /dev/null +++ b/pos_reciept_customize/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "POS Receipt Customization", + "summary": "Customizable POS receipt with layouts and HTML header/footer", + "depends": ["point_of_sale"], + "category": "Point of Sale", + "author": "Rohit", + "installable": True, + "license": "LGPL-3", + "data": [ + "security/ir.model.access.csv", + "views/res_config_settings_views.xml", + "wizard/reciepts_layout_view.xml", + ], +} diff --git a/pos_reciept_customize/models/__init__.py b/pos_reciept_customize/models/__init__.py new file mode 100644 index 0000000000..14259d781e --- /dev/null +++ b/pos_reciept_customize/models/__init__.py @@ -0,0 +1,4 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import pos_config +from . import res_config_settings diff --git a/pos_reciept_customize/models/pos_config.py b/pos_reciept_customize/models/pos_config.py new file mode 100644 index 0000000000..0ba938f4e7 --- /dev/null +++ b/pos_reciept_customize/models/pos_config.py @@ -0,0 +1,18 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +class PosConfig(models.Model): + _inherit = "pos.config" + + receipt_layout = fields.Selection([ + ("light", "Light"), + ("boxes", "Boxes"), + ("lined", "Lined")], + string="Receipt Layout", + default="light", + ) + receipt_logo = fields.Binary(string="Receipt Logo", default=lambda self: self.env.company.logo) + receipt_header_html = fields.Html(string="Header", default="", help="Custom HTML header for receipts") + receipt_footer_html = fields.Html(string="Footer", default="", help="Custom HTML footer for receipts") diff --git a/pos_reciept_customize/models/res_config_settings.py b/pos_reciept_customize/models/res_config_settings.py new file mode 100644 index 0000000000..dde1946868 --- /dev/null +++ b/pos_reciept_customize/models/res_config_settings.py @@ -0,0 +1,19 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + pos_receipt_layout = fields.Selection(related="pos_config_id.receipt_layout", readonly=False) + + def action_view_receipt_layout(self): + return { + "type": "ir.actions.act_window", + "name": ("Configure your pos receipt"), + "res_model": "receipts.layout", + "view_mode": "form", + "target": "main", + "context": {"active_pos_config_id": self.pos_config_id.id, "dialog_size": "extra-large"}, + } diff --git a/pos_reciept_customize/security/ir.model.access.csv b/pos_reciept_customize/security/ir.model.access.csv new file mode 100644 index 0000000000..10f6514bf5 --- /dev/null +++ b/pos_reciept_customize/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +pos_reciept_customize.access_receipts_layout,access_receipts_layout,pos_reciept_customize.model_receipts_layout,base.group_user,1,1,1,0 diff --git a/pos_reciept_customize/views/res_config_settings_views.xml b/pos_reciept_customize/views/res_config_settings_views.xml new file mode 100644 index 0000000000..ff83bac532 --- /dev/null +++ b/pos_reciept_customize/views/res_config_settings_views.xml @@ -0,0 +1,22 @@ + + + + pos.config.form.inherit.receipt.layout + res.config.settings + + + + + +
+
+
+
+
+
+
diff --git a/pos_reciept_customize/wizard/__init__.py b/pos_reciept_customize/wizard/__init__.py new file mode 100644 index 0000000000..643b31b56e --- /dev/null +++ b/pos_reciept_customize/wizard/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import reciepts_layout diff --git a/pos_reciept_customize/wizard/reciepts_layout.py b/pos_reciept_customize/wizard/reciepts_layout.py new file mode 100644 index 0000000000..d2e44e4e8b --- /dev/null +++ b/pos_reciept_customize/wizard/reciepts_layout.py @@ -0,0 +1,55 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ReceiptsLayout(models.TransientModel): + _name = "receipts.layout" + _description = "Custom Reciept Template" + + pos_config_id = fields.Many2one( + "pos.config", + string="Point of Sale", + default=lambda self: self.env["pos.config"].browse( + self.env.context.get("active_pos_config_id") + ), + required=True, + ) + receipt_layout = fields.Selection(related="pos_config_id.receipt_layout", required=True) + receipt_logo = fields.Binary(related="pos_config_id.receipt_logo") + receipt_header = fields.Html(related="pos_config_id.receipt_header_html") + receipt_footer = fields.Html(related="pos_config_id.receipt_footer_html") + preview = fields.Html("_compute_receipt_preview") + + def receipt_layout_save(self): + return {"type": "ir.actions.act_window_close"} + + @api.depends("receipt_layout", "receipt_header", "receipt_footer", "receipt_logo") + def _compute_receipt_preview(self): + for wizard in self: + if wizard.pos_config_id: + wizard.preview = wizard.env["ir.ui.view"]._render_template( + wizard._get_template(), + { + "receipt_header": wizard.receipt_header, + "receipt_footer": wizard.receipt_footer, + "receipt_logo": wizard.receipt_logo, + }, + ) + else: + wizard.preview = False + + def _get_template(self): + is_restaurant = self.pos_config_id.module_pos_restaurant + if is_restaurant: + base_module = "pos_receipt.report_restaurant_preview" + else: + base_module = "pos_receipt.report_receipts_wizard_preview" + + layout_templates = { + "light": f"{base_module}_light", + "boxes": f"{base_module}_boxes", + "lined": f"{base_module}_lined", + } + + return layout_templates.get(self.receipt_layout, layout_templates["light"]) diff --git a/pos_reciept_customize/wizard/reciepts_layout_view.xml b/pos_reciept_customize/wizard/reciepts_layout_view.xml new file mode 100644 index 0000000000..5173462129 --- /dev/null +++ b/pos_reciept_customize/wizard/reciepts_layout_view.xml @@ -0,0 +1,29 @@ + + + + + Receipt Layout + receipts.layout + +
+ + + + + + + + +
+ +
+
+
+
+
+
+
+
+
From e31565a724277e7050a5cdb735d4108be25c0a81 Mon Sep 17 00:00:00 2001 From: rvar-odoo Date: Mon, 4 Aug 2025 12:07:34 +0530 Subject: [PATCH 2/2] [IMP] pos_receipt: configure receipt layout - Now users can configure their receipt format for each POS configuration. - Users can choose receipt type - Receipt Logo: Logo is also customizable in receipts now - Header and Footer: receipt with personalized header & footer content - Added support for selecting POS receipt layout (light, boxes, lined) dynamically based on backend configuration - Patched OrderReceipt component to apply the correct template at runtime --- pos_reciept_customize/__manifest__.py | 10 +- pos_reciept_customize/models/pos_config.py | 2 +- .../security/ir.model.access.csv | 2 +- .../static/src/receipt/order_receipt.js | 23 ++ .../static/src/receipt/order_receipt.xml | 321 ++++++++++++++++++ .../views/pos_restaurant_receipt_template.xml | 33 ++ .../views/pos_retail_receipt_template.xml | 283 +++++++++++++++ pos_reciept_customize/wizard/__init__.py | 2 +- ...{reciepts_layout.py => receipts_layout.py} | 14 +- ...yout_view.xml => receipts_layout_view.xml} | 0 10 files changed, 679 insertions(+), 11 deletions(-) create mode 100644 pos_reciept_customize/static/src/receipt/order_receipt.js create mode 100644 pos_reciept_customize/static/src/receipt/order_receipt.xml create mode 100644 pos_reciept_customize/views/pos_restaurant_receipt_template.xml create mode 100644 pos_reciept_customize/views/pos_retail_receipt_template.xml rename pos_reciept_customize/wizard/{reciepts_layout.py => receipts_layout.py} (83%) rename pos_reciept_customize/wizard/{reciepts_layout_view.xml => receipts_layout_view.xml} (100%) diff --git a/pos_reciept_customize/__manifest__.py b/pos_reciept_customize/__manifest__.py index 9b505de5a7..85f3f37a32 100644 --- a/pos_reciept_customize/__manifest__.py +++ b/pos_reciept_customize/__manifest__.py @@ -9,6 +9,14 @@ "data": [ "security/ir.model.access.csv", "views/res_config_settings_views.xml", - "wizard/reciepts_layout_view.xml", + "wizard/receipts_layout_view.xml", + "views/pos_retail_receipt_template.xml", + "views/pos_restaurant_receipt_template.xml", ], + "assets": { + "point_of_sale._assets_pos": [ + "pos_reciept_customize/static/src/**/*", + ] + }, + "auto_install": True, } diff --git a/pos_reciept_customize/models/pos_config.py b/pos_reciept_customize/models/pos_config.py index 0ba938f4e7..ba115d7399 100644 --- a/pos_reciept_customize/models/pos_config.py +++ b/pos_reciept_customize/models/pos_config.py @@ -13,6 +13,6 @@ class PosConfig(models.Model): string="Receipt Layout", default="light", ) - receipt_logo = fields.Binary(string="Receipt Logo", default=lambda self: self.env.company.logo) + receipt_logo = fields.Binary(related="company_id.logo", string="Receipt Logo") receipt_header_html = fields.Html(string="Header", default="", help="Custom HTML header for receipts") receipt_footer_html = fields.Html(string="Footer", default="", help="Custom HTML footer for receipts") diff --git a/pos_reciept_customize/security/ir.model.access.csv b/pos_reciept_customize/security/ir.model.access.csv index 10f6514bf5..8ac3deb7ca 100644 --- a/pos_reciept_customize/security/ir.model.access.csv +++ b/pos_reciept_customize/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -pos_reciept_customize.access_receipts_layout,access_receipts_layout,pos_reciept_customize.model_receipts_layout,base.group_user,1,1,1,0 +pos_reciept_customize.access_receipts_layout,access_receipts_layout,model_receipts_layout,base.group_user,1,1,1,1 diff --git a/pos_reciept_customize/static/src/receipt/order_receipt.js b/pos_reciept_customize/static/src/receipt/order_receipt.js new file mode 100644 index 0000000000..60d66ab6a8 --- /dev/null +++ b/pos_reciept_customize/static/src/receipt/order_receipt.js @@ -0,0 +1,23 @@ +import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt"; +import { patch } from "@web/core/utils/patch"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; +import { markup } from "@odoo/owl"; + +patch(OrderReceipt, { + template: "pos_receipt_customize.order_view_inherited" +}); + +patch(OrderReceipt.prototype, { + setup() { + this.pos = usePos(); + this.footer = markup(this.props.data.receipt_footer_html || ""); + }, + + get orderQuantity() { + return this.props.data.orderlines.reduce((accumulator, line) => accumulator + parseFloat(line.qty), 0); + }, + + get order() { + return this.pos.get_order() + } +}); diff --git a/pos_reciept_customize/static/src/receipt/order_receipt.xml b/pos_reciept_customize/static/src/receipt/order_receipt.xml new file mode 100644 index 0000000000..1ded0f8e9b --- /dev/null +++ b/pos_reciept_customize/static/src/receipt/order_receipt.xml @@ -0,0 +1,321 @@ + + + +
+ + +
+ + + + + + + + + + + + + + + + + +
NoItemAmount
+ + + + + +
+ + + X + + +
+
+ +
+
+
+ Total Qty + + + Sub Total $ + + +
+
+
+ + +
+ + + + + + + + + + + + + + +
TaxAmountBaseTotal
+ + + +
+
+ +
+ +
+ Expected delivery: +
+ +
+
+
+
+
+

Odoo Point of Sale

+
+
+
+
+ +
+
+ + + + + + + +
  • + + +
  • +
    +
    + +
    +
    --------------------------------
    +
    + + Untaxed Amount + +
    +
    + + + + + + + + + +
    +
    +
    --------------------------------
    +
    + TOTAL + +
    + +
    + Rounding + +
    +
    + To Pay + +
    +
    +
    + + +
    +
    + CHANGE + +
    + +
    + Discounts + +
    +
    +