diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 637fa4bb972..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index 1a2ac9a2fed..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/components/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/components/number_card/number_card.js new file mode 100644 index 00000000000..9dc38a5c94e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/components/number_card/number_card.js @@ -0,0 +1,11 @@ +/** @odoo-module **/ + +import { Component, useState} from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.number_card"; + static props = { + title: String, + value: Number, + }; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/components/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/components/number_card/number_card.xml new file mode 100644 index 00000000000..46138efc70e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/components/number_card/number_card.xml @@ -0,0 +1,11 @@ + + + +
+ +
+

+ +

+
+
diff --git a/awesome_dashboard/static/src/dashboard/components/piechart/piechart.js b/awesome_dashboard/static/src/dashboard/components/piechart/piechart.js new file mode 100644 index 00000000000..116ce4c2c26 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/components/piechart/piechart.js @@ -0,0 +1,46 @@ +/** @odoo-module **/ + +import { Component, useRef, useEffect, onWillStart} from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + + +export class Piechart extends Component { + static template = "awesome_dashboard.piechart"; + static props = { + data: Array, + labels: Array, + slots: {type: Object, optional: true}, + size: Number, + }; + + /** + * Instantiates a Chart (Chart.js lib) to render the graph according to + * the current config. + */ + renderChart() { + if (this.chart) { + this.chart.destroy(); + } + this.chart = new Chart(this.canvasRef.el, { + type: 'pie', + data: { + labels: this.props.labels, + datasets: [{ + data: this.props.data, + borderWidth: 1 + }] + }, + options: { + responsive: true, + } + }); + } + + setup() { + this.canvasRef = useRef("canvas"); + + onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"])); + + useEffect(() => this.renderChart()); + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/components/piechart/piechart.xml b/awesome_dashboard/static/src/dashboard/components/piechart/piechart.xml new file mode 100644 index 00000000000..f8ec2bc8769 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/components/piechart/piechart.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..5ecc8a510b8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,98 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart, onWillRender} from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { _t } from "@web/core/l10n/translation"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { Piechart } from "./components/piechart/piechart"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, Piechart} + layout_display = { controlPanel: {} } + + setup() { + this.action = useService("action"); + this.dialog = useService("dialog"); + this.statsService = useService("awesome_dashboard.dashboard_stats"); + this.data_proxy = useState(this.statsService.getDataProxy()); + + onWillStart(async () => { + this.data_proxy.data = await this.statsService.fetchNow(); + }) + + this.items = registry.category("awesome_dashboard").get("dashboard_items"); + this.config = useState({ + disabledItems: browser.localStorage.getItem("AwesomeDashboard.config.disabledItems")?.split(",") || [] + }); + } + + openConfiguration() { + this.dialog.add(ConfigurationDialog, { + items: this.items, + disabledItems: this.config.disabledItems, + onUpdateConfiguration: this.updateConfiguration.bind(this), + }) + } + + updateConfiguration(newDisabledItems) { + this.config.disabledItems = newDisabledItems; + } + + isEnabled(itemId) { + return !this.config.disabledItems.includes(itemId); + } + + goToCustomers() { + this.action.doAction("base.action_partner_form"); + } + + goToLeads() { + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t(''), + target: 'current', + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']], + }); + } +} + +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); + +class ConfigurationDialog extends Component { + static template = "awesome_dashboard.ConfigurationDialog"; + static components = { Dialog, CheckBox }; + static props = ["close", "items", "disabledItems", "onUpdateConfiguration"]; + + setup() { + // A map where the key is the id of the dashboard item, and the value is whether the item is enabled or not. + this.item_enabled_map = useState(Object.fromEntries( + this.props.items.map(item => [item.id, !this.props.disabledItems.includes(item.id)]) + )); + } + + done() { + this.props.close(); + } + + onChange(checked, changedItem) { + this.item_enabled_map[changedItem.id] = checked; + + const newDisabledItems = Object.values(this.props.items).filter( + (item) => !this.item_enabled_map[item.id] + ).map((item) => item.id); + + browser.localStorage.setItem( + "AwesomeDashboard.config.disabledItems", + newDisabledItems, + ); + + this.props.onUpdateConfiguration(newDisabledItems); + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..6c7f8cb63b9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard{ + background-color: rgb(162, 162, 199); +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..cc83c02b2c5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,35 @@ + + + + + + + +
+ + + + + + +
+
+
+ + + 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); 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 + + + + +
+
+
+
+
+ +
+
+
+ +