From d0e13d55444f6cbec7762f2c89fcffe5ce31d4cf Mon Sep 17 00:00:00 2001 From: dijo-odoo Date: Fri, 21 Mar 2025 18:34:32 +0530 Subject: [PATCH 1/2] [ADD] custom_pos_layout: add new billing layouts 1) Added new billing layouts in PoS 2) Allow user to see the preview of billing from PoS setting menu 3) User can see and set the header , footer and layout type from wizard preview 4) Modified configuration view for PoS settings --- custom_pos_layout/__init__.py | 3 + custom_pos_layout/__manifest__.py | 22 ++ custom_pos_layout/controllers/__init__.py | 1 + .../controllers/res_config_param.py | 17 ++ custom_pos_layout/models/__init__.py | 1 + .../models/res_config_settings.py | 47 ++++ .../security/ir.model.access.csv | 2 + custom_pos_layout/static/src/order_lines.scss | 91 +++++++ .../static/src/overrides/order_recipt.js | 34 +++ .../static/src/overrides/order_recipt.xml | 147 ++++++++++ .../static/src/overrides/orderline_patch.js | 46 ++++ custom_pos_layout/views/report_templates.xml | 257 ++++++++++++++++++ .../views/res_config_settings_views.xml | 27 ++ custom_pos_layout/wizard/__init__.py | 1 + .../wizard/pos_receipt_preview_wizard.py | 53 ++++ .../wizard/pos_receipt_preview_wizard.xml | 39 +++ custom_pos_layout/wizard/preview.scss | 9 + 17 files changed, 797 insertions(+) create mode 100644 custom_pos_layout/__init__.py create mode 100644 custom_pos_layout/__manifest__.py create mode 100644 custom_pos_layout/controllers/__init__.py create mode 100644 custom_pos_layout/controllers/res_config_param.py create mode 100644 custom_pos_layout/models/__init__.py create mode 100644 custom_pos_layout/models/res_config_settings.py create mode 100644 custom_pos_layout/security/ir.model.access.csv create mode 100644 custom_pos_layout/static/src/order_lines.scss create mode 100644 custom_pos_layout/static/src/overrides/order_recipt.js create mode 100644 custom_pos_layout/static/src/overrides/order_recipt.xml create mode 100644 custom_pos_layout/static/src/overrides/orderline_patch.js create mode 100644 custom_pos_layout/views/report_templates.xml create mode 100644 custom_pos_layout/views/res_config_settings_views.xml create mode 100644 custom_pos_layout/wizard/__init__.py create mode 100644 custom_pos_layout/wizard/pos_receipt_preview_wizard.py create mode 100644 custom_pos_layout/wizard/pos_receipt_preview_wizard.xml create mode 100644 custom_pos_layout/wizard/preview.scss diff --git a/custom_pos_layout/__init__.py b/custom_pos_layout/__init__.py new file mode 100644 index 00000000000..e4f4917aea4 --- /dev/null +++ b/custom_pos_layout/__init__.py @@ -0,0 +1,3 @@ +from . import controllers +from . import models +from . import wizard diff --git a/custom_pos_layout/__manifest__.py b/custom_pos_layout/__manifest__.py new file mode 100644 index 00000000000..a0052599321 --- /dev/null +++ b/custom_pos_layout/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': 'POS Custom Bill', + 'version': '1.0', + 'category': 'Point of Sale', + 'summary': 'Customizes the PoS order receipt format', + 'author': 'Your Name', + 'depends': ['point_of_sale'], + 'data' : [ + 'security/ir.model.access.csv', + 'views/res_config_settings_views.xml', + 'views/report_templates.xml', + 'wizard/pos_receipt_preview_wizard.xml', + ], + 'assets': { + 'point_of_sale._assets_pos': [ + 'custom_pos_layout/static/src/**/*', + ], + }, + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/custom_pos_layout/controllers/__init__.py b/custom_pos_layout/controllers/__init__.py new file mode 100644 index 00000000000..161c9930a29 --- /dev/null +++ b/custom_pos_layout/controllers/__init__.py @@ -0,0 +1 @@ +from . import res_config_param diff --git a/custom_pos_layout/controllers/res_config_param.py b/custom_pos_layout/controllers/res_config_param.py new file mode 100644 index 00000000000..b72737a8ffa --- /dev/null +++ b/custom_pos_layout/controllers/res_config_param.py @@ -0,0 +1,17 @@ +from odoo import models,fields +from odoo import http +from odoo.http import request + +class POSConfigInfo(http.Controller): + + @http.route("/pos/get_display_layout",type='json',auth='user') + def get_display_layout(self): + user_id = request.env.user.id + pos_session = request.env['pos.session'].sudo().search([ + ('user_id', '=' , user_id) + ],limit=1) + pos_config = request.env['pos.config'].search([('id','=',pos_session.config_id.id)]) + disp_layout = pos_config.pos_disp_type + print("IN CONTROLLER ===>>>===<<<===") + print(disp_layout) + return {'disp_layout': disp_layout} diff --git a/custom_pos_layout/models/__init__.py b/custom_pos_layout/models/__init__.py new file mode 100644 index 00000000000..0deb68c4680 --- /dev/null +++ b/custom_pos_layout/models/__init__.py @@ -0,0 +1 @@ +from . import res_config_settings diff --git a/custom_pos_layout/models/res_config_settings.py b/custom_pos_layout/models/res_config_settings.py new file mode 100644 index 00000000000..701e9ff7bb9 --- /dev/null +++ b/custom_pos_layout/models/res_config_settings.py @@ -0,0 +1,47 @@ +from odoo import api,fields, models + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + """pos_disp_type = fields.Selection([ + ('default', 'Default'), + ('lined', 'Lined'), + ('boxed', 'Boxed'), + ],string="POS Recipt Layout",config_parameter="pos.receipt_layout")""" + + pos_disp_type = fields.Selection([ + ('default', 'Default'), + ('lined', 'Lined'), + ('boxed', 'Boxed'), + ],string="POS Recipt Layout",compute='_compute_disp_type',inverse='_set_disp_type',store=True,readonly=False) + + @api.depends('pos_config_id') + def _compute_disp_type(self): + for res_config in self: + res_config.pos_disp_type = res_config.pos_config_id.pos_disp_type + print("COmpute is called") + + def _set_disp_type(self): + """Write the selected value back to pos.config""" + for res_config in self: + if res_config.pos_config_id: + res_config.pos_config_id.pos_disp_type = res_config.pos_disp_type + + def action_open_receipt_preview_wizard(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'POS Receipt Preview', + 'res_model': 'pos.receipt.preview.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': {'active_id': self.ids}, + } + +class PosConfig(models.Model): + _inherit='pos.config' + + pos_disp_type = fields.Selection([ + ('default', 'Default'), + ('lined', 'Lined'), + ('boxed', 'Boxed'), + ],string="POS Recipt Layout",store=True) diff --git a/custom_pos_layout/security/ir.model.access.csv b/custom_pos_layout/security/ir.model.access.csv new file mode 100644 index 00000000000..a98a03e1404 --- /dev/null +++ b/custom_pos_layout/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 +custom_pos_layout.access_pos_receipt_preview_wizard,access_pos_receipt_preview_wizard,custom_pos_layout.model_pos_receipt_preview_wizard,base.group_user,1,1,1,1 diff --git a/custom_pos_layout/static/src/order_lines.scss b/custom_pos_layout/static/src/order_lines.scss new file mode 100644 index 00000000000..ff97708c534 --- /dev/null +++ b/custom_pos_layout/static/src/order_lines.scss @@ -0,0 +1,91 @@ +.order-lines-table { + width: 100%; + border-collapse: collapse; +} + +.order-lines-table thead { + border-top: 2px solid black; + border-bottom: 2px solid black; +} + + +.col-no { + width: 10%; + text-align: center; +} + +.col-product { + width: 40%; + text-align: left; +} + +.col-qty { + width: 15%; + text-align: center; +} + +.col-price { + width: 15%; + text-align: right; +} + + +.order-lines-table tbody tr { + //border-bottom: 1px solid lightgray; +} + +.boxed-order-table { + width: 100%; + border-collapse: collapse; +} + + +.boxed-header { + background-color: #ddd; + font-weight: bold; + text-align: center; + border: 2px solid black; +} + +.boxed-order-table th, +.boxed-order-table td { + border: 2px solid black; + padding: 6px; + text-align: center; +} + +.boxed-total-section { + width: 100%; + border-left: 2px solid black; + border-right: 2px solid black; + border-bottom: 2px solid black; + display: flex; + justify-content: space-between; + padding: 6px; + font-weight: bold; +} +#boxed-summary-table { + width: 100%; + border-collapse: collapse; +} + +#boxed-summary-table td { + padding: 5px; + border: 1px solid black; +} + +.bordered-bottom { + border-bottom: 1px solid black; +} + +.payment-section { + text-align: right; +} + +.payment-name { + float: left; +} + +.payment-amount { + float: right; +} diff --git a/custom_pos_layout/static/src/overrides/order_recipt.js b/custom_pos_layout/static/src/overrides/order_recipt.js new file mode 100644 index 00000000000..defdab1dc83 --- /dev/null +++ b/custom_pos_layout/static/src/overrides/order_recipt.js @@ -0,0 +1,34 @@ +import { patch } from "@web/core/utils/patch"; +import { useState, onWillStart } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; +import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt"; + +patch(OrderReceipt.prototype, { + setup() { + super.setup(...arguments); + console.log("PROPSSS ") + console.log("from order_recipt"); + this.state = useState({ + disp_layout: "default", // Default layout + }); + + onWillStart(async ()=>{ + const result = await rpc("/pos/get_display_layout", {}); + console.log("THE OBJECT OF RPC IS ",result); + if (result && result.disp_layout) { + this.state.disp_layout = result.disp_layout; + } + }) + + this.receipt = this.props.data; // Access receipt data from props + let totalQty = 0; + if (this.receipt && this.receipt.orderlines) { + this.receipt.orderlines.forEach(line => { + totalQty += parseFloat(line.qty); + }); + this.receipt.totalQty = totalQty; // Add totalQty to receipt data + } + console.log("TAX TOTALS"); + console.log(this.props.data.taxTotals.subtotals); + }, +}); diff --git a/custom_pos_layout/static/src/overrides/order_recipt.xml b/custom_pos_layout/static/src/overrides/order_recipt.xml new file mode 100644 index 00000000000..dfb03c79316 --- /dev/null +++ b/custom_pos_layout/static/src/overrides/order_recipt.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
NoItemAmount
+
+ + + + + + + + + + + + + + + + + + +
NoProductQuantityPriceTotal
+
+
+ + + +

----------------------------------------------

+ + + + + + + + +
+ Total Quantity: + + + Sub-Total: + +
+
+ + +
+
+ +
+
+
+ + + + + + + + + + + + +
+ Total Qty + + Sub Total +
+ + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
TaxAmountBaseTotal
+ 2.5% + + + + + + +
+
+ + + + +
+
diff --git a/custom_pos_layout/static/src/overrides/orderline_patch.js b/custom_pos_layout/static/src/overrides/orderline_patch.js new file mode 100644 index 00000000000..3934af7f495 --- /dev/null +++ b/custom_pos_layout/static/src/overrides/orderline_patch.js @@ -0,0 +1,46 @@ +/** @odoo-module **/ +import { patch } from "@web/core/utils/patch"; +import { Orderline } from "@point_of_sale/app/generic_components/orderline/orderline"; +import { xml } from "@odoo/owl"; + +patch(Orderline.prototype, { + setup() { + super.setup(...arguments); + }, +}); + +Orderline.props = { + ...Orderline.props, // Keep existing props + isTableFormat: { type: Boolean, optional: true, default: false }, + isBoxedFormat: { type: Boolean, optional: true, default: false }, + lineIndex: { type: Number, optional: true }, // New prop for numbering +}; + +Orderline.template = xml` + + + + + + + + + + + + + + + +
+ x
+ HSN: + + + + + + + + +`; diff --git a/custom_pos_layout/views/report_templates.xml b/custom_pos_layout/views/report_templates.xml new file mode 100644 index 00000000000..285b9ecf494 --- /dev/null +++ b/custom_pos_layout/views/report_templates.xml @@ -0,0 +1,257 @@ + + + diff --git a/custom_pos_layout/views/res_config_settings_views.xml b/custom_pos_layout/views/res_config_settings_views.xml new file mode 100644 index 00000000000..fcb644031f0 --- /dev/null +++ b/custom_pos_layout/views/res_config_settings_views.xml @@ -0,0 +1,27 @@ + + + res.config.setting.view.form.layout + res.config.settings + + + + +
+
+
+
+
+ +
+
+
+ + + + +
+ + + + + + +
+ +
+ + + Which cards do you wish to see ? + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..6cc81a7544b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,11 @@ +/** @odoo-module **/ + +import { Component, useState} from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.dashboard_item"; + static props = { + slots: {type: Object, optional: true}, + size: Number, + }; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..7680601fbf6 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,14 @@ + + + +
+
+ +

+ Default Content +

+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..d28b5fedf48 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,73 @@ +import { Piechart } from "./components/piechart/piechart"; +import { NumberCard } from "./components/number_card/number_card"; +import { registry } from "@web/core/registry"; + +const item1 = { + id: "average_quantity", + description: "Average amount of t-shirt", + Component: NumberCard, + + size: 1, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity + }), +}; +const item2 = { + id: "average_time", + description: 'AVG time for an order to go from "new" to "sent" or "cancelled"', + Component: NumberCard, + + size: 1, + props: (data) => ({ + title: 'AVG time for an order to go from "new" to "sent" or "cancelled"', + value: data.average_time + }), +}; +const item3 = { + id: "nb_new_orders", + description: "New orders", + Component: NumberCard, + + size: 1, + props: (data) => ({ + title: "New orders this month", + value: data.nb_new_orders + }), +}; +const item4 = { + id: "nb_cancelled_orders", + description: "Cancelled orders", + Component: NumberCard, + + size: 1, + props: (data) => ({ + title: "Cancelled orders this month", + value: data.nb_cancelled_orders + }), +}; +const item5 = { + id: "total_amount", + description: "Total orders", + Component: NumberCard, + + size: 1, + props: (data) => ({ + title: "Total orders", + value: data.total_amount + }), +}; + +const item6 = { + id: "orders_per_size", + description: "Shirt Orders per size", + Component: Piechart, + + size: 1, + props: (data) => ({ + labels: Object.keys(data.orders_by_size), + data: Object.values(data.orders_by_size) + }), +} + +registry.category("awesome_dashboard").add("dashboard_items", [item1, item2, item3, item4, item5, item6]); diff --git a/awesome_dashboard/static/src/dashboard/services/dashboard_stats.js b/awesome_dashboard/static/src/dashboard/services/dashboard_stats.js new file mode 100644 index 00000000000..735bafbfe1a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/services/dashboard_stats.js @@ -0,0 +1,41 @@ +import { registry } from "@web/core/registry"; +import { memoize } from "@web/core/utils/functions"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function fetch() { + sleep(1000); + return await rpc("/awesome_dashboard/statistics"); +} + +const dashboardStatsService = { + async: ["fetchNow"], + + + start(env, {}) { + const data_proxy = reactive({data: {}}); + + // Better to not use memoize since the cache needs to be invalidated. + // memoized_fetch = memoize(fetch); + + setInterval(async ()=>{ + data_proxy.data = await fetch(); + }, 10000); + + const getDataProxy = ()=>{ + return data_proxy; + } + + const fetchNow = ()=>{ + return fetch(); + } + + return { getDataProxy, fetchNow }; + }, +}; + +registry.category("services").add("awesome_dashboard.dashboard_stats", dashboardStatsService); \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..f1187685f7f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,12 @@ +import { Component, xml} from "@odoo/owl"; +import { LazyComponent } from '@web/core/assets' +import { registry } from "@web/core/registry"; + +export class AwesomeDashboardLoader extends Component { + static components = { LazyComponent}; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);