- hello world
+
+
+
+
+
+
+
+
The sum is:
+
+
+
+ Card 1 content
+
+
+
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js
new file mode 100644
index 00000000000..d2010abbcc6
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_item.js
@@ -0,0 +1,25 @@
+import { Component } from "@odoo/owl";
+
+export class TodoItem extends Component {
+ static template = "awesome_owl.TodoItem";
+ static props = {
+ todo: {
+ type: Object,
+ shape: {
+ id: { type: Number },
+ description: { type: String },
+ isCompleted: { type: Boolean },
+ },
+ },
+ toggleState: { type: Function },
+ removeTodo: { type: Function },
+ };
+
+ onChange() {
+ this.props.toggleState(this.props.todo.id);
+ }
+
+ remove() {
+ this.props.removeTodo(this.props.todo.id);
+ }
+}
diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml
new file mode 100644
index 00000000000..d0a5e5728c0
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_item.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js
new file mode 100644
index 00000000000..0811a933bcb
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_list.js
@@ -0,0 +1,41 @@
+import { Component, useState } from "@odoo/owl";
+import { TodoItem } from "./todo_item";
+import { useAutoFocus } from "../utils";
+
+export class TodoList extends Component {
+ static template = "awesome_owl.TodoList";
+ static components = {
+ TodoItem,
+ };
+
+ setup() {
+ this.todos = useState([]);
+ this.nextId = 1;
+ this.todoInput = useAutoFocus("todoInput");
+ }
+
+ addTodo(ev) {
+ if (ev.keyCode === 13) {
+ this.todos.push({
+ id: this.nextId++,
+ description: ev.target.value,
+ isCompleted: false,
+ });
+ ev.target.value = "";
+ }
+ }
+
+ toggleTodo(todoId) {
+ const todo = this.todos.find((t) => t.id === todoId);
+ if (todo) {
+ todo.isCompleted = !todo.isCompleted;
+ }
+ }
+
+ removeTodo(todoId) {
+ const index = this.todos.findIndex((t) => t.id === todoId);
+ if (index >= 0) {
+ this.todos.splice(index, 1);
+ }
+ }
+}
diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml
new file mode 100644
index 00000000000..0c27106c82d
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_list.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js
new file mode 100644
index 00000000000..ba2a364f7d2
--- /dev/null
+++ b/awesome_owl/static/src/utils.js
@@ -0,0 +1,11 @@
+import { useRef, onMounted } from "@odoo/owl";
+
+export function useAutoFocus(name) {
+ const ref = useRef(name);
+ onMounted(() => {
+ if (ref.el) {
+ ref.el.focus();
+ }
+ });
+ return ref;
+}
From 78d39d13b96fc9c723ada46dab1d35616a1eb821 Mon Sep 17 00:00:00 2001
From: "Tudor-Calin Panzaru (tupan)"
Date: Wed, 28 Jan 2026 16:41:49 +0100
Subject: [PATCH 17/18] [IMP] awesome_dashboard: build a dashboard (CHAPTER 2)
---
.gitignore | 2 +
awesome_dashboard/__init__.py | 2 -
awesome_dashboard/__manifest__.py | 41 ++++++------
awesome_dashboard/controllers/__init__.py | 4 +-
awesome_dashboard/controllers/controllers.py | 28 ++++----
awesome_dashboard/static/src/dashboard.js | 8 ---
awesome_dashboard/static/src/dashboard.xml | 8 ---
.../src/dashboard/configuration_dashboard.js | 27 ++++++++
.../src/dashboard/configuration_dashboard.xml | 19 ++++++
.../static/src/dashboard/dahsboard.scss | 3 +
.../static/src/dashboard/dashboard.js | 57 ++++++++++++++++
.../static/src/dashboard/dashboard.xml | 20 ++++++
.../static/src/dashboard/dashboard_item.js | 12 ++++
.../static/src/dashboard/dashboard_item.xml | 8 +++
.../static/src/dashboard/dashboard_items.js | 66 +++++++++++++++++++
.../static/src/dashboard/number_card.js | 9 +++
.../static/src/dashboard/number_card.xml | 9 +++
.../static/src/dashboard/pie_chart.js | 34 ++++++++++
.../static/src/dashboard/pie_chart.xml | 8 +++
.../static/src/dashboard/pie_chart_card.js | 13 ++++
.../static/src/dashboard/pie_chart_card.xml | 9 +++
.../static/src/dashboard_action.js | 15 +++++
.../static/src/statistics_service.js | 23 +++++++
23 files changed, 368 insertions(+), 57 deletions(-)
delete mode 100644 awesome_dashboard/static/src/dashboard.js
delete mode 100644 awesome_dashboard/static/src/dashboard.xml
create mode 100644 awesome_dashboard/static/src/dashboard/configuration_dashboard.js
create mode 100644 awesome_dashboard/static/src/dashboard/configuration_dashboard.xml
create mode 100644 awesome_dashboard/static/src/dashboard/dahsboard.scss
create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js
create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml
create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item.js
create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item.xml
create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js
create mode 100644 awesome_dashboard/static/src/dashboard/number_card.js
create mode 100644 awesome_dashboard/static/src/dashboard/number_card.xml
create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart.js
create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart.xml
create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card.js
create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card.xml
create mode 100644 awesome_dashboard/static/src/dashboard_action.js
create mode 100644 awesome_dashboard/static/src/statistics_service.js
diff --git a/.gitignore b/.gitignore
index b6e47617de1..dc80942f943 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,5 @@ dmypy.json
# Pyre type checker
.pyre/
+
+ruff.toml
diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py
index b0f26a9a602..e046e49fbe2 100644
--- a/awesome_dashboard/__init__.py
+++ b/awesome_dashboard/__init__.py
@@ -1,3 +1 @@
-# -*- coding: utf-8 -*-
-
from . import controllers
diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py
index a1cd72893d7..ac09d85fb52 100644
--- a/awesome_dashboard/__manifest__.py
+++ b/awesome_dashboard/__manifest__.py
@@ -1,30 +1,27 @@
-# -*- coding: utf-8 -*-
{
- 'name': "Awesome Dashboard",
-
- 'summary': """
+ "name": "Awesome Dashboard",
+ "summary": """
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
""",
-
- 'description': """
+ "description": """
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
""",
-
- 'author': "Odoo",
- 'website': "https://www.odoo.com/",
- 'category': 'Tutorials',
- 'version': '0.1',
- 'application': True,
- 'installable': True,
- 'depends': ['base', 'web', 'mail', 'crm'],
-
- 'data': [
- 'views/views.xml',
- ],
- 'assets': {
- 'web.assets_backend': [
- 'awesome_dashboard/static/src/**/*',
+ "author": "Odoo",
+ "website": "https://www.odoo.com/",
+ "category": "Tutorials",
+ "version": "0.1",
+ "application": True,
+ "installable": True,
+ "depends": ["base", "web", "mail", "crm"],
+ "data": ["views/views.xml"],
+ "assets": {
+ "web.assets_backend": [
+ "awesome_dashboard/static/src/statistics_service.js",
+ "awesome_dashboard/static/src/dashboard_action.js",
+ "awesome_dashboard/static/src/dashboard/**/*.xml",
+ "awesome_dashboard/static/src/dashboard/**/*.scss",
],
+ "awesome_dashboard.dashboard": ["awesome_dashboard/static/src/dashboard/**/*.js"],
},
- 'license': 'AGPL-3'
+ "license": "AGPL-3",
}
diff --git a/awesome_dashboard/controllers/__init__.py b/awesome_dashboard/controllers/__init__.py
index 457bae27e11..e046e49fbe2 100644
--- a/awesome_dashboard/controllers/__init__.py
+++ b/awesome_dashboard/controllers/__init__.py
@@ -1,3 +1 @@
-# -*- coding: utf-8 -*-
-
-from . import controllers
\ No newline at end of file
+from . import controllers
diff --git a/awesome_dashboard/controllers/controllers.py b/awesome_dashboard/controllers/controllers.py
index 05977d3bd7f..48ae74809c4 100644
--- a/awesome_dashboard/controllers/controllers.py
+++ b/awesome_dashboard/controllers/controllers.py
@@ -1,15 +1,13 @@
-# -*- coding: utf-8 -*-
-
import logging
import random
from odoo import http
-from odoo.http import request
logger = logging.getLogger(__name__)
+
class AwesomeDashboard(http.Controller):
- @http.route('/awesome_dashboard/statistics', type='jsonrpc', auth='user')
+ @http.route("/awesome_dashboard/statistics", type="jsonrpc", auth="user")
def get_statistics(self):
"""
Returns a dict of statistics about the orders:
@@ -22,15 +20,17 @@ def get_statistics(self):
"""
return {
- 'average_quantity': random.randint(4, 12),
- 'average_time': random.randint(4, 123),
- 'nb_cancelled_orders': random.randint(0, 50),
- 'nb_new_orders': random.randint(10, 200),
- 'orders_by_size': {
- 'm': random.randint(0, 150),
- 's': random.randint(0, 150),
- 'xl': random.randint(0, 150),
+ "average_quantity": random.randint(4, 12),
+ "average_time": random.randint(4, 123),
+ "nb_cancelled_orders": random.randint(0, 50),
+ "nb_new_orders": random.randint(10, 200),
+ "orders_by_size": {
+ "xs": random.randint(0, 150),
+ "s": random.randint(0, 150),
+ "m": random.randint(0, 150),
+ "l": random.randint(0, 150),
+ "xl": random.randint(0, 150),
+ "xxl": random.randint(0, 150),
},
- 'total_amount': random.randint(100, 1000)
+ "total_amount": random.randint(100, 1000),
}
-
diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js
deleted file mode 100644
index c4fb245621b..00000000000
--- a/awesome_dashboard/static/src/dashboard.js
+++ /dev/null
@@ -1,8 +0,0 @@
-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/configuration_dashboard.js b/awesome_dashboard/static/src/dashboard/configuration_dashboard.js
new file mode 100644
index 00000000000..cc01e2a0752
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/configuration_dashboard.js
@@ -0,0 +1,27 @@
+import { Component, useState } from "@odoo/owl";
+import { Dialog } from "@web/core/dialog/dialog";
+import { CheckBox } from "@web/core/checkbox/checkbox";
+
+export class ConfigurationDashboard extends Component {
+ static template = "awesome_dashboard.ConfigurationDashboard";
+ static components = {
+ Dialog,
+ CheckBox,
+ };
+ static props = ["close", "items", "disabledItems", "onApply"];
+
+ setup() {
+ this.items = useState(
+ this.props.items.map((item) => ({
+ ...item,
+ enabled: !this.props.disabledItems.includes(item.id),
+ }))
+ );
+ }
+
+ onApply() {
+ const disabledItems = this.items.filter((item) => !item.enabled).map((item) => item.id);
+ this.props.onApply(disabledItems);
+ this.props.close();
+ }
+}
diff --git a/awesome_dashboard/static/src/dashboard/configuration_dashboard.xml b/awesome_dashboard/static/src/dashboard/configuration_dashboard.xml
new file mode 100644
index 00000000000..2bf860f331c
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/configuration_dashboard.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dahsboard.scss b/awesome_dashboard/static/src/dashboard/dahsboard.scss
new file mode 100644
index 00000000000..979249193f7
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dahsboard.scss
@@ -0,0 +1,3 @@
+.o_dashboard {
+ background-color: #bd917a;
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js
new file mode 100644
index 00000000000..bbabb4887be
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.js
@@ -0,0 +1,57 @@
+import { Component, onWillStart, useState } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+import { Layout } from "@web/search/layout";
+import { useService } from "@web/core/utils/hooks";
+import { DashboardItem } from "./dashboard_item";
+import { browser } from "@web/core/browser/browser";
+import { ConfigurationDashboard } from "./configuration_dashboard";
+
+class AwesomeDashboard extends Component {
+ static template = "awesome_dashboard.AwesomeDashboard";
+ static components = {
+ Layout,
+ DashboardItem,
+ };
+
+ setup() {
+ this.action = useService("action");
+ this.dialog = useService("dialog");
+ this.statisticsService = useService("awesome_dashboard.statistics");
+ this.statistics = useState(this.statisticsService.statistics);
+
+ const storedConfig = browser.localStorage.getItem("disabledDashboardItems");
+ this.state = useState({
+ disabledItems: storedConfig ? JSON.parse(storedConfig) : [],
+ });
+ }
+ openCustomers() {
+ this.action.doAction("base.action_partner_form");
+ }
+ openLeads() {
+ this.action.doAction({
+ type: "ir.actions.act_window",
+ name: "Leads",
+ res_model: "crm.lead",
+ views: [
+ [false, "list"],
+ [false, "form"],
+ ],
+ });
+ }
+ get filteredItems() {
+ const allItems = registry.category("dashboard_items").getAll();
+ return allItems.filter((item) => !this.state.disabledItems.includes(item.id));
+ }
+ openConfiguration() {
+ this.dialog.add(ConfigurationDashboard, {
+ items: registry.category("dashboard_items").getAll(),
+ disabledItems: this.state.disabledItems,
+ onApply: (disabledItems) => {
+ this.state.disabledItems = disabledItems;
+ browser.localStorage.setItem("disabledDashboardItems", JSON.stringify(disabledItems));
+ },
+ });
+ }
+}
+
+registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml
new file mode 100644
index 00000000000..858cda6207e
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js
new file mode 100644
index 00000000000..883d465e6a1
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js
@@ -0,0 +1,12 @@
+import { Component } from "@odoo/owl";
+
+export class DashboardItem extends Component {
+ static template = "awesome_dashboard.DashboardItem";
+ static props = {
+ slots: { type: Object },
+ size: { type: Number, optional: true },
+ };
+ static defaultProps = {
+ size: 1,
+ };
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item.xml
new file mode 100644
index 00000000000..dae593ce3be
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
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..a91fd41a972
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js
@@ -0,0 +1,66 @@
+import { NumberCard } from "./number_card";
+import { PieChartCard } from "./pie_chart_card";
+import { registry } from "@web/core/registry";
+
+const dashboardItemsRegistry = registry.category("dashboard_items");
+
+dashboardItemsRegistry.add("average_quantity", {
+ id: "average_quantity",
+ description: "Average amount of t-shirt",
+ Component: NumberCard,
+ props: (stats) => ({
+ title: "Average amount of t-shirts / order",
+ value: stats.average_quantity,
+ }),
+});
+
+dashboardItemsRegistry.add("average_time", {
+ id: "average_time",
+ description: "Average time for an order",
+ Component: NumberCard,
+ props: (stats) => ({
+ title: "Average time for an order (hours)",
+ value: stats.average_time,
+ }),
+});
+
+dashboardItemsRegistry.add("total_amount", {
+ id: "total_amount",
+ description: "Total amount",
+ Component: NumberCard,
+ props: (stats) => ({
+ title: "Total amount of new orders",
+ value: stats.total_amount,
+ }),
+});
+
+dashboardItemsRegistry.add("nb_cancelled_orders", {
+ id: "nb_cancelled_orders",
+ description: "Cancelled orders this month",
+ Component: NumberCard,
+ props: (stats) => ({
+ title: "Number of cancelled orders",
+ value: stats.nb_cancelled_orders,
+ }),
+});
+
+dashboardItemsRegistry.add("nb_new_orders", {
+ id: "nb_new_orders",
+ description: "New orders",
+ Component: NumberCard,
+ props: (stats) => ({
+ title: "Number of new orders",
+ value: stats.nb_new_orders,
+ }),
+});
+
+dashboardItemsRegistry.add("orders_by_size", {
+ id: "orders_by_size",
+ description: "Orders by size",
+ Component: PieChartCard,
+ size: 2,
+ props: (stats) => ({
+ title: "Shirt orders by size",
+ data: stats.orders_by_size,
+ }),
+});
diff --git a/awesome_dashboard/static/src/dashboard/number_card.js b/awesome_dashboard/static/src/dashboard/number_card.js
new file mode 100644
index 00000000000..0d1ae8deadf
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/number_card.js
@@ -0,0 +1,9 @@
+import { Component } from "@odoo/owl";
+
+export class NumberCard extends Component {
+ static template = "awesome_dashboard.NumberCard";
+ static props = {
+ title: String,
+ value: Number,
+ };
+}
diff --git a/awesome_dashboard/static/src/dashboard/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card.xml
new file mode 100644
index 00000000000..799154889b9
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/number_card.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart.js
new file mode 100644
index 00000000000..50b5b07b847
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart.js
@@ -0,0 +1,34 @@
+import { Component, onWillStart, onMounted, useRef, onPatched, onWillUnmount } from "@odoo/owl";
+import { loadJS } from "@web/core/assets";
+
+export class PieChart extends Component {
+ static template = "awesome_dashboard.PieChart";
+ static props = {
+ data: { type: Object },
+ };
+
+ setup() {
+ this.canvaRef = useRef("canvas");
+ onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
+ onMounted(() => this.renderChart());
+ onPatched(() => this.renderChart());
+ onWillUnmount(() => this.chart.destroy());
+ }
+
+ renderChart() {
+ if (this.chart) {
+ this.chart.destroy();
+ }
+ this.chart = new Chart(this.canvaRef.el, {
+ type: "pie",
+ data: {
+ labels: Object.keys(this.props.data),
+ datasets: [
+ {
+ data: Object.values(this.props.data),
+ },
+ ],
+ },
+ });
+ }
+}
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart.xml
new file mode 100644
index 00000000000..8b56b05acb1
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card.js
new file mode 100644
index 00000000000..ab83d398227
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart_card.js
@@ -0,0 +1,13 @@
+import { Component } from "@odoo/owl";
+import { PieChart } from "./pie_chart";
+
+export class PieChartCard extends Component {
+ static template = "awesome_dashboard.PieChartCard";
+ static components = {
+ PieChart,
+ };
+ static props = {
+ title: { type: String },
+ data: { type: Object },
+ };
+}
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card.xml
new file mode 100644
index 00000000000..fb45abd2c42
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart_card.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js
new file mode 100644
index 00000000000..a734f8c01b2
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard_action.js
@@ -0,0 +1,15 @@
+import { Component, xml } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+import { LazyComponent } from "@web/core/assets";
+
+class AwesomeDashboardLoader extends Component {
+ static components = { LazyComponent };
+ static template = xml`
+
+ `;
+}
+
+registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);
diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js
new file mode 100644
index 00000000000..abd53215be4
--- /dev/null
+++ b/awesome_dashboard/static/src/statistics_service.js
@@ -0,0 +1,23 @@
+import { registry } from "@web/core/registry";
+import { rpc } from "@web/core/network/rpc";
+import { reactive } from "@odoo/owl";
+
+export const statisticsService = {
+ dependencies: [],
+ start() {
+ const statistics = reactive({});
+ async function _fetchStats() {
+ const data = await rpc("/awesome_dashboard/statistics");
+ Object.assign(statistics, data);
+ }
+ _fetchStats();
+
+ setInterval(_fetchStats, 5000);
+
+ return {
+ statistics,
+ };
+ },
+};
+
+registry.category("services").add("awesome_dashboard.statistics", statisticsService);
From ec93f7a4c52959d725b836cb320178ac48bb07b3 Mon Sep 17 00:00:00 2001
From: "Tudor-Calin Panzaru (tupan)"
Date: Wed, 28 Jan 2026 16:55:10 +0100
Subject: [PATCH 18/18] [FIX] estate_account: fix styling on EstateProperty
class
---
estate_account/models/estate_property.py | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py
index 50b0277bee7..cb30fa70d2a 100644
--- a/estate_account/models/estate_property.py
+++ b/estate_account/models/estate_property.py
@@ -7,17 +7,13 @@ class EstateProperty(models.Model):
def action_sold(self):
for record in self:
self.env["account.move"].create(
- [
- {
- "partner_id": record.buyer_id.id,
- "move_type": "out_invoice",
- "invoice_line_ids": [
- Command.create(
- {"name": record.name, "quantity": 1, "price_unit": record.selling_price * 0.06}
- ),
- Command.create({"name": "Administrative fees", "quantity": 1, "price_unit": 100}),
- ],
- }
- ]
+ {
+ "partner_id": record.buyer_id.id,
+ "move_type": "out_invoice",
+ "invoice_line_ids": [
+ Command.create({"name": record.name, "quantity": 1, "price_unit": record.selling_price * 0.06}),
+ Command.create({"name": "Administrative fees", "quantity": 1, "price_unit": 100}),
+ ],
+ }
)
return super().action_sold()