Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2aae33e
adding real estate module
amahm-odoo Jan 19, 2026
abd9e6b
Add estate property model
amahm-odoo Jan 19, 2026
9737ef2
adding security access rights
amahm-odoo Jan 20, 2026
cd2c469
Chapter 5 - UI -
amahm-odoo Jan 20, 2026
1069baa
Chapter 6 Basic Views
amahm-odoo Jan 20, 2026
1c82057
Chapter 7 commit
amahm-odoo Jan 20, 2026
cf4f2e6
Chapter 8 commit
amahm-odoo Jan 21, 2026
96ee9b6
Chapter 9
amahm-odoo Jan 21, 2026
6cb8167
fixing issues
amahm-odoo Jan 21, 2026
67a18b9
[IMP] estate: add constraints for data consistency
amahm-odoo Jan 21, 2026
292a400
[IMP] estate: add UI enhancements and stat buttons to property views
amahm-odoo Jan 21, 2026
2a17db6
[IMP] estate : add different view for state button
amahm-odoo Jan 22, 2026
06405ef
[IMP] estate : add inheirted view
amahm-odoo Jan 22, 2026
2c6b078
[ADD] estate_account: auto-generate invoices when properties are sold
amahm-odoo Jan 22, 2026
76b5284
[IMP] estate: add kanban view
amahm-odoo Jan 22, 2026
de85059
[ADD] awesome_owl: implement Todo List and core Owl components
amahm-odoo Jan 26, 2026
80d8322
[FIX] estate, estate_account
amahm-odoo Jan 26, 2026
d7046bf
[ADD] awesome dashboard
amahm-odoo Jan 26, 2026
7b631fd
[FIX] awesome_owl, awesome_dashboard: add new line
amahm-odoo Jan 26, 2026
0d96c0d
[FIX] awesome_owl, awesome_dashboard: add new line
amahm-odoo Jan 27, 2026
e462c50
[IMP] estate, estate_account: add secuirty roles
amahm-odoo Jan 28, 2026
66cbb82
[IMP] estate: add test cases for estate module
amahm-odoo Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,4 @@ dmypy.json

# Pyre type checker
.pyre/
ruff.toml
5 changes: 4 additions & 1 deletion awesome_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
],
'assets': {
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
'awesome_dashboard/static/src/dashboard_loader.js',
],
'awesome_dashboard.dashboard': [
'awesome_dashboard/static/src/dashboard/**/*',
]
},
'license': 'AGPL-3'
}
8 changes: 0 additions & 8 deletions awesome_dashboard/static/src/dashboard.js

This file was deleted.

8 changes: 0 additions & 8 deletions awesome_dashboard/static/src/dashboard.xml

This file was deleted.

30 changes: 30 additions & 0 deletions awesome_dashboard/static/src/dashboard/configuration_dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Component, useState } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";
import { registry } from "@web/core/registry";
import { browser } from "@web/core/browser/browser";

export class ConfigurationDialog extends Component {
static components = { Dialog };
static template = "awesome_dashboard.ConfigurationDialog";

setup() {
const allItems = registry.category("awesome_dashboard").getAll();
this.state = useState({
items: allItems.map((item) => ({
...item,
isEnabled: !this.props.initialRemovedItems.includes(item.id),
})),
});
}

apply() {
const removedIds = this.state.items
.filter((i) => !i.isEnabled)
.map((i) => i.id);

browser.localStorage.setItem("dashboard_removed_items", JSON.stringify(removedIds));

this.props.onApply(removedIds);
this.props.close();
}
}
23 changes: 23 additions & 0 deletions awesome_dashboard/static/src/dashboard/configuration_dialog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.ConfigurationDialog">
<Dialog title="'Dashboard Configuration'">
<div class="p-3">
<t t-foreach="state.items" t-as="item" t-key="item.id">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox"
t-att-id="item.id"
t-model="item.isEnabled"/>
<label class="form-check-label" t-att-for="item.id">
<t t-out="item.description"/>
</label>
</div>
</t>
</div>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="apply">Apply</button>
<button class="btn btn-secondary" t-on-click="props.close">Cancel</button>
</t>
</Dialog>
</t>
</templates>
3 changes: 3 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.o_dashboard {
background-color: gray;
}
67 changes: 67 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Component } 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/dashboard_item";
import { useState } from "@odoo/owl";
import { browser } from "@web/core/browser/browser";
import { ConfigurationDialog } from "./configuration_dialog";

class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
static components = { Layout, DashboardItem };

setup() {
this.action = useService("action");
this.statistics = useState(useService("statistics_service"));
this.dialogService = useService("dialog");
const savedConfig = browser.localStorage.getItem("dashboard_removed_items");
this.state = useState({
removedItems: savedConfig ? JSON.parse(savedConfig) : [],
});
}

get items() {
return registry
.category("awesome_dashboard")
.getAll()
.filter((item) => !this.state.removedItems.includes(item.id));
}

openConfiguration() {
this.dialogService.add(ConfigurationDialog, {
initialRemovedItems: this.state.removedItems,
onApply: (newRemovedIds) => {
this.state.removedItems = newRemovedIds;
},
});
}

openCustomers() {
this.action.doAction(
{
type: "ir.actions.act_window",
res_model: "res.partner",
name: "Partner Form",
view_mode: "kanban",
views: [[false, "kanban"]],
}
);
}

openLeads() {
this.action.doAction(
{
type: "ir.actions.act_window",
res_model: "crm.lead",
name: "Lead Form",
view_mode: "list,form",
views: [[false, "list"], [false, "form"]],
}
);
}


}

registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
32 changes: 32 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">
<div class="o_action">
<Layout className="'o_dashboard h-100'" display="{ controlPanel: {} }">
<t t-set-slot="control-panel-create-button">
<button class="btn btn-primary" t-on-click="openCustomers">
Customer
</button>
<button class="btn btn-primary" t-on-click="openLeads">
Leads
</button>
</t>
<t t-set-slot="control-panel-additional-actions">
<button t-on-click="openConfiguration" class="btn p-0 ms-1 border-0">
<i class="fa fa-cog"></i>
</button>
</t>
<t t-slot="content">
<t t-foreach="items" t-as="item" t-key="item.id">
<DashboardItem size="item.size || 1">
<t t-set="itemProp" t-value="item.props ? item.props(statistics) : {'data': statistics}"/>
<t t-component="item.Component" t-props="itemProp" />
</DashboardItem>
</t>
</t>
</Layout>
</div>
</t>

</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component } from "@odoo/owl";


export class DashboardItem extends Component {
static template = "awesome_dashboard.DashboardItem";
static props = {
size: { type: Number, optional: true },
slot: { type: Object, optional: true },
};
static defaultProps = {
size: 1,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.DashboardItem">
<div class="card shadow-sm m-2 p-3 border-0"
t-attf-style="width: {{ props.size * 18 }}rem;">
<t t-slot="default"/>
</div>
</t>
</templates>
70 changes: 70 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { registry } from "@web/core/registry";
import { NumberCard } from "./number_card/number_card";
import { PieChart } from "./pie_chart/pie_chart";

export const items = [
{
id: "new_orders",
description: "Number of new orders",
Component: NumberCard,
size: 1,
props: (data) => ({
title: "New Orders",
value: data.nb_new_orders,
}),
},
{
id: "total_amount",
description: "Total amount of orders",
Component: NumberCard,
size: 1,
props: (data) => ({
title: "Total Amount",
value: `${data.total_amount} €`,
}),
},
{
id: "avg_tshirt",
description: "Average number of t-shirts per order",
Component: NumberCard,
size: 1,
props: (data) => ({
title: "Avg T-Shirts/Order",
value: data.average_quantity,
}),
},
{
id: "cancelled",
description: "Number of cancelled orders",
Component: NumberCard,
size: 1,
props: (data) => ({
title: "Cancelled Orders",
value: data.nb_cancelled_orders,
}),
},
{
id: "avg_time",
description: "Average time from new to sent",
Component: NumberCard,
size: 2,
props: (data) => ({
title: "Avg Time (New to Sent)",
value: `${data.average_time} Days`,
}),
},
{
id: "pie_chart",
description: "Pie chart of shirts orders by size",
Component: PieChart,
size: 2,
props: (data) => ({
title: "Pie Chart of shirts orders by size",
data: data.orders_by_size,
}),
},
];

items.forEach(item => {
registry.category("awesome_dashboard").add(item.id, item);
});
Original file line number Diff line number Diff line change
@@ -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,
};
}
11 changes: 11 additions & 0 deletions awesome_dashboard/static/src/dashboard/number_card/number_card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.NumberCard">
<div class="card">
<div class="card-body">
<h5 class="card-title" t-esc="props.title"></h5>
<p class="card-text" t-esc="props.value"></p>
</div>
</div>
</t>
</templates>
45 changes: 45 additions & 0 deletions awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { loadJS } from "@web/core/assets";
import { Component, onWillStart, useRef, onMounted, onWillUnmount, useEffect } from "@odoo/owl";

export class PieChart extends Component {
static template = "awesome_dashboard.PieChart";
static props = {
title: String,
data: Object,
};

setup() {
this.canvasRef = useRef("canvas");
onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
onMounted(() => this.renderChart());
onWillUnmount(() => this.chart.destroy());
}

renderChart() {

if (this.chart) {
this.chart.destroy();
}

if (!this.props.data) {
return;
}

const config = {
type: "pie",
data: {
labels: Object.keys(this.props.data),
datasets: [
{
label: this.props.title,
data: Object.values(this.props.data),
},
],
},
};

this.chart = new Chart(this.canvasRef.el, config);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<t t-name="awesome_dashboard.PieChart">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<div class="fw-bold text-uppercase text-muted small mb-2">
<t t-out="props.title" />
</div>
<canvas t-ref="canvas"></canvas>
</div>
</t>
23 changes: 23 additions & 0 deletions awesome_dashboard/static/src/dashboard/statistics_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";
import { reactive } from "@odoo/owl";

const statisticsService = {
start(env) {
const statistics = reactive({});

async function loadStatistics() {
console.log("Loading statistics...");
const data = await rpc("/awesome_dashboard/statistics");
Object.assign(statistics, data);
console.log("Statistics loaded:", statistics);
}

const interval = setInterval(loadStatistics, 10 * 60 * 1000);
loadStatistics();

return statistics;
},
};

registry.category("services").add("statistics_service", statisticsService);
Loading