diff --git a/awesome_clicker/__init__.py b/awesome_clicker/__init__.py index 40a96afc6ff..e69de29bb2d 100644 --- a/awesome_clicker/__init__.py +++ b/awesome_clicker/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py index 56dc2f779b9..0ee7372dadf 100644 --- a/awesome_clicker/__manifest__.py +++ b/awesome_clicker/__manifest__.py @@ -1,29 +1,23 @@ -# -*- coding: utf-8 -*- { - 'name': "Awesome Clicker", - - 'summary': """ + "name": "Awesome Clicker", + "summary": """ Starting module for "Master the Odoo web framework, chapter 1: Build a Clicker game" """, - - 'description': """ + "description": """ Starting module for "Master the Odoo web framework, chapter 1: Build a Clicker game" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com/", - 'category': 'Tutorials', - 'version': '0.1', - 'application': True, - 'installable': True, - 'depends': ['base', 'web'], - - 'data': [], - 'assets': { - 'web.assets_backend': [ - 'awesome_clicker/static/src/**/*', + "author": "Odoo", + "website": "https://www.odoo.com/", + "category": "Tutorials", + "version": "0.1", + "application": True, + "installable": True, + "depends": ["base", "web"], + "data": [], + "assets": { + "web.assets_backend": [ + "awesome_clicker/static/src/**/*", ], - }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py index b0f26a9a602..f705942f8f1 100644 --- a/awesome_dashboard/__init__.py +++ b/awesome_dashboard/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- from . import controllers diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..0be8062fbbb 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,30 +1,25 @@ -# -*- 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', + "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/**/*', + "assets": { + "web.assets_backend": [ + "awesome_dashboard/static/src/**/*", ], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_dashboard/controllers/__init__.py b/awesome_dashboard/controllers/__init__.py index 457bae27e11..f705942f8f1 100644 --- a/awesome_dashboard/controllers/__init__.py +++ b/awesome_dashboard/controllers/__init__.py @@ -1,3 +1,2 @@ -# -*- 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..2d0d9f4cbff 100644 --- a/awesome_dashboard/controllers/controllers.py +++ b/awesome_dashboard/controllers/controllers.py @@ -1,15 +1,14 @@ -# -*- 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 +21,14 @@ 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": { + "m": random.randint(0, 150), + "s": random.randint(0, 150), + "xl": random.randint(0, 150), }, - 'total_amount': random.randint(100, 1000) + "total_amount": random.randint(100, 1000), } - diff --git a/awesome_dashboard/static/src/components/dashboard/chart/chart.js b/awesome_dashboard/static/src/components/dashboard/chart/chart.js new file mode 100644 index 00000000000..0e30001b345 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/chart/chart.js @@ -0,0 +1,49 @@ +import { Component, useRef, onWillStart, onMounted, onWillUnmount } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class DashboardChart extends Component { + static template = "awesome_dashboard.DashboardChart"; + static props = { + label: { type: String, optional: true, default: "Dataset" }, + data: { type: Object, required: true }, + }; + setup() { + this.chartRef = useRef("DashboardChart"); + onWillStart(async () => { + // Ensure Chart.js library is loaded before we try to use it + await loadJS("/web/static/lib/Chart/Chart.js"); + }); + + onMounted(() => { + // The canvas now exists in the DOM + this.renderChart(); + }); + } + + renderChart() { + if (this.chart) { + this.chart.destroy(); + } + const labels = Object.keys(this.props.data); + const data = Object.values(this.props.data); + this.chart = new Chart(this.chartRef.el, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + label: this.props.label, + data: data, + },], + }, + options: { + responsive: true, + maintainAspectRatio: false, // <--- Add this line + } + }); + console.log("Chart rendered with data:", this.chartRef.el); + } + + + +} diff --git a/awesome_dashboard/static/src/components/dashboard/chart/chart.xml b/awesome_dashboard/static/src/components/dashboard/chart/chart.xml new file mode 100644 index 00000000000..232b4f78c06 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/chart/chart.xml @@ -0,0 +1,8 @@ + + + +
+ +
+
+
diff --git a/awesome_dashboard/static/src/components/dashboard/dashboard.js b/awesome_dashboard/static/src/components/dashboard/dashboard.js new file mode 100644 index 00000000000..5612e3ec714 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboard.js @@ -0,0 +1,100 @@ +import { Component, 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 "./dashboardItem"; +import { DashboardChart } from "./chart/chart"; +import { LazyComponent } from "@web/core/assets"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; + +export class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, DashboardChart, LazyComponent }; + + setup() { + let savedDisabledItems = []; + try { + const rawValue = browser.localStorage.getItem("disabledDashboardItems"); + // Only parse if the value exists + savedDisabledItems = rawValue ? JSON.parse(rawValue) : []; + } catch (e) { + console.error("Failed to parse local storage, resetting to empty array"); + savedDisabledItems = []; + } + + this.state = useState({ + controlPanel: {}, + disabledItems: savedDisabledItems, + }); + + this.items = registry.category("awesome_dashboard").getAll(); + this.action = useService("action"); + this.statisticsService = useState(useService("awesome_dashboard.statistics")); + console.log("Statistics:", this.statisticsService); + this.state.result = this.statisticsService; + this.dialog = useService("dialog"); + + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "crm.lead", + views : [ + [false, "list"], + [false, "form"] + ],}); + } + openConfiguration() { + this.dialog.add(ConfigurationDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + onUpdateConfiguration: this.updateConfiguration.bind(this), + }); + } + updateConfiguration(newdisabledItems) { + this.state.disabledItems = newdisabledItems; + } +} + +class ConfigurationDialog extends Component { + static template = "awesome_dashboard.ConfigurationDialog"; + + static components = { Dialog, CheckBox }; + + static props = ["close", "items", "disabledItems", "onUpdateConfiguration"]; + + setup() { + this.items = useState(this.props.items.map((item) => { + return { + ...item, + enabled: !this.props.disabledItems.includes(item.id), + } + })); + } + + done() { + this.props.close(); + } + + onChange(checked, changedItem) { + changedItem.enabled = checked; + const newdisabledItems = Object.values(this.items) + .filter((item) => !item.enabled) + .map((item) => item.id); + + // FIX: Wrap the array in JSON.stringify + browser.localStorage.setItem( + "disabledDashboardItems", + JSON.stringify(newdisabledItems) + ); + + this.props.onUpdateConfiguration(newdisabledItems); + } +} +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/components/dashboard/dashboard.scss b/awesome_dashboard/static/src/components/dashboard/dashboard.scss new file mode 100644 index 00000000000..9b3ced3a2f3 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboard.scss @@ -0,0 +1,4 @@ +.o_dashboard { + background-color: grey; + display: flex; +} diff --git a/awesome_dashboard/static/src/components/dashboard/dashboard.xml b/awesome_dashboard/static/src/components/dashboard/dashboard.xml new file mode 100644 index 00000000000..b6920d85100 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboard.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + Which cards do you wish to see? + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/components/dashboard/dashboardItem.js b/awesome_dashboard/static/src/components/dashboard/dashboardItem.js new file mode 100644 index 00000000000..104c31e66ce --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboardItem.js @@ -0,0 +1,16 @@ +import { Component } from "@odoo/owl"; + + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { type: Number, default: 1, optional: true }, + content: { type: String, optional: true }, + slots: { type: Object, optional: true }, + }; + + setup() { + this.state = { size: this.props.size, width: 18*this.props.size , content: this.props.content }; + } + +} diff --git a/awesome_dashboard/static/src/components/dashboard/dashboardItem.scss b/awesome_dashboard/static/src/components/dashboard/dashboardItem.scss new file mode 100644 index 00000000000..623438ef15b --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboardItem.scss @@ -0,0 +1,49 @@ +.o_dashboard_item { + background: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + padding: 1rem; + box-shadow: 0 1px 3px rgba(27, 31, 35, 0.04); + transition: box-shadow 150ms ease, transform 150ms ease; + overflow: hidden; + color: inherit; + display: block; + text-align: center; + margin: 0.5rem; + + &:hover { + box-shadow: 0 8px 24px rgba(27, 31, 35, 0.08); + transform: translateY(-3px); + } + + /* optional structure helpers */ + &__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.5rem; + } + + &__title { + font-weight: 600; + font-size: 1rem; + color: #222; + } + + &__body { + font-size: 0.95rem; + color: #57606a; + } +} + +.o_dashboard_item_content { + font-size: 1rem; + font-weight: 700; + color: #111827; + margin-bottom: 0.5rem; +} +.o_dashboard_item_stat { + font-size: 0.875rem; + text-align: center; + color: #3cbf4e; +} diff --git a/awesome_dashboard/static/src/components/dashboard/dashboardItem.xml b/awesome_dashboard/static/src/components/dashboard/dashboardItem.xml new file mode 100644 index 00000000000..6db3f6b35ca --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboardItem.xml @@ -0,0 +1,14 @@ + + + + + +
+
+ +
+ + + + + diff --git a/awesome_dashboard/static/src/components/dashboard/dashboard_items.js b/awesome_dashboard/static/src/components/dashboard/dashboard_items.js new file mode 100644 index 00000000000..2f21cf1fdab --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/dashboard_items.js @@ -0,0 +1,62 @@ +import { NumberCard } from "./numberCard/numberCard"; +import { PieChartCard } from "./pieChartCard/pieChartCard"; +import { registry } from "@web/core/registry"; + +const items = [ + { + id: "number_of_new_orders", + component: NumberCard, + description: "Number of new orders", + props: (data) => ({ + title: "New Orders", + value: data.nb_new_orders }), + size: 10 + }, + { + id: "total_amount", + component: NumberCard, + description: "Total amount of sales", + props: (data) => ({ + title: "Total Sales", + value: data.total_amount }), + size: 10 + }, + { + id: "average_order_duration", + component: NumberCard, + description: "Average duration till confirmation or cancellation", + props: (data) => ({ + title: "Avg. Order Duration", + value: data.average_time }), + size: 10 + }, + { + id: "average_tshirt_amount", + component: NumberCard, + description: "Average amount of sold t-shirts", + props: (data) => ({ + title: "Avg. amount of T-shirts", + value: data.average_quantity }), + size: 10 + }, + { + id: "number_of_cancelled_orders", + component: NumberCard, + description: "Total number of cancelled orders", + props: (data) => ({ + title: "Number of Cancelled Orders", + value: data.nb_cancelled_orders }), + size: 10 + }, + { + id: "size_distribution", + component: PieChartCard, + description: "Distribution of t-shirt sizes sold", + props: (data) => ({ + title: "T-shirt Size Distribution", + values: data.orders_by_size }), + size: 20 + }, +] + +items.forEach(item => registry.category("awesome_dashboard").add(item.id, item)); diff --git a/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.js b/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.js new file mode 100644 index 00000000000..fa70106f4a9 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.js @@ -0,0 +1,15 @@ +import { Component } from "@odoo/owl"; + + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: { type: String, required: true }, + value: { type: Number, required: true }, + }; + + setup() { + this.state = { title: this.props.title, value: this.props.value }; + } + +} diff --git a/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.scss b/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.scss new file mode 100644 index 00000000000..e6f4dc17779 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.scss @@ -0,0 +1,49 @@ +.o_number_card { + background: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + padding: 1rem; + box-shadow: 0 1px 3px rgba(27, 31, 35, 0.04); + transition: box-shadow 150ms ease, transform 150ms ease; + overflow: hidden; + color: inherit; + display: block; + text-align: center; + margin: 0.5rem; + + &:hover { + box-shadow: 0 8px 24px rgba(27, 31, 35, 0.08); + transform: translateY(-3px); + } + + /* optional structure helpers */ + &__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.5rem; + } + + &__title { + font-weight: 600; + font-size: 1rem; + color: #222; + } + + &__body { + font-size: 0.50rem; + color: #57606a; + } +} + +// .o_dashboard_item_content { +// font-size: 1rem; +// font-weight: 700; +// color: #111827; +// margin-bottom: 0.5rem; +// } +// .o_dashboard_item_stat { +// font-size: 2.875rem; +// text-align: center; +// color: #3cbf4e; +// } diff --git a/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.xml b/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.xml new file mode 100644 index 00000000000..99bcaf76606 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/numberCard/numberCard.xml @@ -0,0 +1,11 @@ + + + + + +
+
+ + + + diff --git a/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.js b/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.js new file mode 100644 index 00000000000..ef4f8244cc8 --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.js @@ -0,0 +1,12 @@ +import { Component } from "@odoo/owl"; +import { DashboardChart } from "../chart/chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { DashboardChart }; + static props = { + title: { type: String, required: true }, + values: { type: Object, required: true }, + // label: { type: String, optional: true, default: "Dataset" }, + }; +} diff --git a/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.scss b/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.xml b/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.xml new file mode 100644 index 00000000000..cd7ca05098f --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/pieChartCard/pieChartCard.xml @@ -0,0 +1,11 @@ + + + + + +
+ + + + + diff --git a/awesome_dashboard/static/src/components/dashboard/statistics_service.js b/awesome_dashboard/static/src/components/dashboard/statistics_service.js new file mode 100644 index 00000000000..0db55c213cb --- /dev/null +++ b/awesome_dashboard/static/src/components/dashboard/statistics_service.js @@ -0,0 +1,23 @@ +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; + +export const StatisticsService = { + start() { + const statistics = reactive({ + isReady: false, + }); + + async function loadStatistics() { + const updates = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, updates, { isReady: true }); + + } + setInterval(loadStatistics, 60000); // Refresh every minute + loadStatistics(); + + return statistics; + } +}; + +registry.category("services").add("awesome_dashboard.statistics", StatisticsService); 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_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..b277aafcaca --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,14 @@ +import { LazyComponent } from "@web/core/assets"; +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { AwesomeDashboard } from "./components/dashboard/dashboard"; +import { xml } from "@odoo/owl"; + +export class ExampleComponentLoader extends Component { + static components = { LazyComponent, AwesomeDashboard }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", ExampleComponentLoader); diff --git a/awesome_gallery/__init__.py b/awesome_gallery/__init__.py index a0fdc10fe11..0650744f6bc 100644 --- a/awesome_gallery/__init__.py +++ b/awesome_gallery/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- from . import models diff --git a/awesome_gallery/__manifest__.py b/awesome_gallery/__manifest__.py index f0fe026a9c6..76200eff3a4 100644 --- a/awesome_gallery/__manifest__.py +++ b/awesome_gallery/__manifest__.py @@ -1,27 +1,24 @@ -# -*- coding: utf-8 -*- { - 'name': "Gallery View", - 'summary': """ + "name": "Gallery View", + "summary": """ Starting module for "Master the Odoo web framework, chapter 3: Create a Gallery View" """, - - 'description': """ + "description": """ Starting module for "Master the Odoo web framework, chapter 3: Create a Gallery View" """, - - 'version': '0.1', - 'application': True, - 'category': 'Tutorials', - 'installable': True, - 'depends': ['web', 'contacts'], - 'data': [ - 'views/views.xml', + "version": "0.1", + "application": True, + "category": "Tutorials", + "installable": True, + "depends": ["web", "contacts"], + "data": [ + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_gallery/static/src/**/*', + "assets": { + "web.assets_backend": [ + "awesome_gallery/static/src/**/*", ], }, - 'author': 'Odoo S.A.', - 'license': 'AGPL-3' + "author": "Odoo S.A.", + "license": "AGPL-3", } diff --git a/awesome_gallery/models/__init__.py b/awesome_gallery/models/__init__.py index 7f0930ee744..c1bbf7a7f7d 100644 --- a/awesome_gallery/models/__init__.py +++ b/awesome_gallery/models/__init__.py @@ -1,4 +1,2 @@ -# -*- coding: utf-8 -*- # import filename_python_file_within_folder_or_subfolder -from . import ir_action -from . import ir_ui_view +from . import ir_action, ir_ui_view diff --git a/awesome_gallery/models/ir_action.py b/awesome_gallery/models/ir_action.py index eae20acbf5c..00d0ff823a3 100644 --- a/awesome_gallery/models/ir_action.py +++ b/awesome_gallery/models/ir_action.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- from odoo import fields, models class ActWindowView(models.Model): - _inherit = 'ir.actions.act_window.view' + _inherit = "ir.actions.act_window.view" - view_mode = fields.Selection(selection_add=[ - ('gallery', "Awesome Gallery") - ], ondelete={'gallery': 'cascade'}) + view_mode = fields.Selection( + selection_add=[("gallery", "Awesome Gallery")], ondelete={"gallery": "cascade"}, + ) diff --git a/awesome_gallery/models/ir_ui_view.py b/awesome_gallery/models/ir_ui_view.py index 0c11b8298ac..72d3816cb90 100644 --- a/awesome_gallery/models/ir_ui_view.py +++ b/awesome_gallery/models/ir_ui_view.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- from odoo import fields, models class View(models.Model): - _inherit = 'ir.ui.view' + _inherit = "ir.ui.view" - type = fields.Selection(selection_add=[('gallery', "Awesome Gallery")]) + type = fields.Selection(selection_add=[("gallery", "Awesome Gallery")]) diff --git a/awesome_kanban/__init__.py b/awesome_kanban/__init__.py index 40a96afc6ff..e69de29bb2d 100644 --- a/awesome_kanban/__init__.py +++ b/awesome_kanban/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/awesome_kanban/__manifest__.py b/awesome_kanban/__manifest__.py index 6f31bc8de0d..c388dc533b2 100644 --- a/awesome_kanban/__manifest__.py +++ b/awesome_kanban/__manifest__.py @@ -1,27 +1,24 @@ -# -*- coding: utf-8 -*- { - 'name': "Awesome Kanban", - 'summary': """ + "name": "Awesome Kanban", + "summary": """ Starting module for "Master the Odoo web framework, chapter 4: Customize a kanban view" """, - - 'description': """ + "description": """ Starting module for "Master the Odoo web framework, chapter 4: Customize a kanban view. """, - - 'version': '0.1', - 'application': True, - 'category': 'Tutorials', - 'installable': True, - 'depends': ['web', 'crm'], - 'data': [ - 'views/views.xml', + "version": "0.1", + "application": True, + "category": "Tutorials", + "installable": True, + "depends": ["web", "crm"], + "data": [ + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_kanban/static/src/**/*', + "assets": { + "web.assets_backend": [ + "awesome_kanban/static/src/**/*", ], }, - 'author': 'Odoo S.A.', - 'license': 'AGPL-3' + "author": "Odoo S.A.", + "license": "AGPL-3", } diff --git a/awesome_kanban/static/src/awesome_kanban_view.js b/awesome_kanban/static/src/awesome_kanban_view.js index 0da52b22c9d..d4f0211ca90 100644 --- a/awesome_kanban/static/src/awesome_kanban_view.js +++ b/awesome_kanban/static/src/awesome_kanban_view.js @@ -1 +1 @@ -// TODO: Define here your AwesomeKanban view +// Todo: Define here your AwesomeKanban view diff --git a/awesome_owl/__init__.py b/awesome_owl/__init__.py index 457bae27e11..f705942f8f1 100644 --- a/awesome_owl/__init__.py +++ b/awesome_owl/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 55002ab81de..52b5287856c 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -1,43 +1,37 @@ -# -*- coding: utf-8 -*- { - 'name': "Awesome Owl", - - 'summary': """ + "name": "Awesome Owl", + "summary": """ Starting module for "Discover the JS framework, chapter 1: Owl components" """, - - 'description': """ + "description": """ Starting module for "Discover the JS framework, chapter 1: Owl components" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com", - + "author": "Odoo", + "website": "https://www.odoo.com", # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list - 'category': 'Tutorials', - 'version': '0.1', - + "category": "Tutorials", + "version": "0.1", # any module necessary for this one to work correctly - 'depends': ['base', 'web'], - 'application': True, - 'installable': True, - 'data': [ - 'views/templates.xml', + "depends": ["base", "web"], + "application": True, + "installable": True, + "data": [ + "views/templates.xml", ], - 'assets': { - 'awesome_owl.assets_playground': [ - ('include', 'web._assets_helpers'), - ('include', 'web._assets_backend_helpers'), - 'web/static/src/scss/pre_variables.scss', - 'web/static/lib/bootstrap/scss/_variables.scss', - 'web/static/lib/bootstrap/scss/_maps.scss', - ('include', 'web._assets_bootstrap'), - ('include', 'web._assets_core'), - 'web/static/src/libs/fontawesome/css/font-awesome.css', - 'awesome_owl/static/src/**/*', + "assets": { + "awesome_owl.assets_playground": [ + ("include", "web._assets_helpers"), + ("include", "web._assets_backend_helpers"), + "web/static/src/scss/pre_variables.scss", + "web/static/lib/bootstrap/scss/_variables.scss", + "web/static/lib/bootstrap/scss/_maps.scss", + ("include", "web._assets_bootstrap"), + ("include", "web._assets_core"), + "web/static/src/libs/fontawesome/css/font-awesome.css", + "awesome_owl/static/src/**/*", ], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_owl/controllers/__init__.py b/awesome_owl/controllers/__init__.py index 457bae27e11..f705942f8f1 100644 --- a/awesome_owl/controllers/__init__.py +++ b/awesome_owl/controllers/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_owl/controllers/controllers.py b/awesome_owl/controllers/controllers.py index bccfd6fe283..6e83f679212 100644 --- a/awesome_owl/controllers/controllers.py +++ b/awesome_owl/controllers/controllers.py @@ -1,10 +1,11 @@ from odoo import http -from odoo.http import request, route +from odoo.http import request + class OwlPlayground(http.Controller): - @http.route(['/awesome_owl'], type='http', auth='public') + @http.route(["/awesome_owl"], type="http", auth="public") def show_playground(self): """ Renders the owl playground page """ - return request.render('awesome_owl.playground') + return request.render("awesome_owl.playground") diff --git a/awesome_owl/static/src/components/card/card.js b/awesome_owl/static/src/components/card/card.js new file mode 100644 index 00000000000..39927688c0e --- /dev/null +++ b/awesome_owl/static/src/components/card/card.js @@ -0,0 +1,24 @@ +import { Component, useState } from "@odoo/owl"; +// import { props } from "@odoo/owl"; + +export class Card extends Component { + static template = "my_module.Card"; + static props = { + title: { + type: String, + required: true, + }, + slots: { + type: Object, + optional: true, + }, + }; + setup() { + // this.state = {title: this.props.title, content: this.props.content, html: this.props.html}; + this.state = useState({title: this.props.title, visibile: false}); + } + toggleVisibility() { + this.state.visibile = !this.state.visibile; + } + +} diff --git a/awesome_owl/static/src/components/card/card.xml b/awesome_owl/static/src/components/card/card.xml new file mode 100644 index 00000000000..b26b369d796 --- /dev/null +++ b/awesome_owl/static/src/components/card/card.xml @@ -0,0 +1,15 @@ + + + + +
+
+ +
+ + + +
+
+ +
diff --git a/awesome_owl/static/src/components/counter/counter.js b/awesome_owl/static/src/components/counter/counter.js new file mode 100644 index 00000000000..f6422e822f6 --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.js @@ -0,0 +1,17 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "my_module.Counter"; + static props = { + onChange: {type: Function, optional: true} + }; + + setup() { + this.state = useState({value: 0}); + } + increment() { + this.state.value += 1; + this.props.onChange?.(1); + } + +} diff --git a/awesome_owl/static/src/components/counter/counter.xml b/awesome_owl/static/src/components/counter/counter.xml new file mode 100644 index 00000000000..575313d5d1e --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.xml @@ -0,0 +1,9 @@ + + + + +

Counter:

+ +
+ +
diff --git a/awesome_owl/static/src/components/todolist/todoitem.js b/awesome_owl/static/src/components/todolist/todoitem.js new file mode 100644 index 00000000000..05ade71dde6 --- /dev/null +++ b/awesome_owl/static/src/components/todolist/todoitem.js @@ -0,0 +1,29 @@ +import { Component, useState } from "@odoo/owl"; + + +export class Todoitem extends Component { + static template = "my_module.Todoitem"; + static props = { + id: {type: Number, required: true}, + description: {type: String, required: true}, + isCompleted: {type: Boolean, optional: true, default: false}, + toggleState: {type: Function, optional: true}, + removeTodo: {type: Function, optional: true} + }; + + setup() { + this.todo = useState({ + id: this.props.id, + description: this.props.description, + isCompleted: this.props.isCompleted, + }); + } + toggleState(ev) { + this.todo.isCompleted = ev.target.checked; + this.props.toggleState?.(this.todo.id, this.todo.isCompleted); + } + removeTodo() { + console.log("Removing todo", this.todo.id); + this.props.removeTodo?.(this.todo.id); + } +} diff --git a/awesome_owl/static/src/components/todolist/todoitem.xml b/awesome_owl/static/src/components/todolist/todoitem.xml new file mode 100644 index 00000000000..42572bbc70e --- /dev/null +++ b/awesome_owl/static/src/components/todolist/todoitem.xml @@ -0,0 +1,10 @@ + + + + +
+ - +
+
+ +
diff --git a/awesome_owl/static/src/components/todolist/todolist.js b/awesome_owl/static/src/components/todolist/todolist.js new file mode 100644 index 00000000000..43908430aaf --- /dev/null +++ b/awesome_owl/static/src/components/todolist/todolist.js @@ -0,0 +1,38 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { Todoitem } from "./todoitem.js"; +// import { useAutofocus } from "@/hooks/useAutofocus.js"; + +export class Todolist extends Component { + static template = "my_module.Todolist"; + static components = { Todoitem }; + + setup() { + this.myRef = useRef("todoInput"); + onMounted(() => { + this.myRef.el.focus(); + }); + this.todos = useState({ + items: [], + }); + } + addTodo(ev) { + this.myRef.el.focus(); + if(ev.keyCode===13 && ev.target.value.trim()!==""){ + const newId = this.todos.items.length + ? this.todos.items[this.todos.items.length - 1].id + 1 + : 1; + this.todos.items.push({ id: newId, description: ev.target.value.trim(), isCompleted: false }); + ev.target.value = ""; + } + } + toggleState(id, isCompleted) { + const todo = this.todos.items.find((item) => item.id === id); + if (todo) { + todo.isCompleted = isCompleted; + } + } + onRemove(id) { + console.log("Todolist onRemove", id); + this.todos.items = this.todos.items.filter((item) => item.id !== id); + } +} diff --git a/awesome_owl/static/src/components/todolist/todolist.xml b/awesome_owl/static/src/components/todolist/todolist.xml new file mode 100644 index 00000000000..0cb41471470 --- /dev/null +++ b/awesome_owl/static/src/components/todolist/todolist.xml @@ -0,0 +1,19 @@ + + + + +
+ +
    + +
  • + + +
  • +
    +
+ +
+
+ +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..706dae0dfc5 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,15 @@ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +// import { Todolist } from "./components/todolist/todolist.js"; +import { Card } from "./components/card/card.js"; +import { Counter } from "./components/counter/counter.js"; + export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Card, Counter }; + + + setup() { + } + } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..492bf2c5529 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -3,7 +3,11 @@
- hello world +

Playground Component

+ + + +
diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..65c7c4ece00 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,20 @@ +{ + "name": "Estate Advirtisements", + "version": "1.0", + "summary": "Track advertisements added for real estate and allow selling them", + "website": "https://www.odoo.com/app/estate", + "depends": ["base"], + "application": True, + "installable": True, + "author": "moali", + "license": "LGPL-3", + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_property_tags_views.xml", + "views/estate_property_offers_views.xml", + "views/estate_property_types_views.xml", + "views/estate_res_users_views.xml", + "views/menus.xml", + ], +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..40d84580ce0 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property, property_offer, property_tag, property_type, res_user diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..21caa9af540 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,112 @@ +from odoo import api, exceptions, fields, models, tools + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + _order = "id desc" + + name = fields.Char(string="Property Name", required=True) + description = fields.Char(string="Property Description") + postcode = fields.Char(string="Postal Code") + date_availability = fields.Date( + string="Available From", + default=fields.Date.add(value=fields.Date.today(), months=3), + copy=False, + ) + expected_price = fields.Float(string="Expected Selling Price", required=True) + selling_price = fields.Float( + string="Actual Selling Price", readonly=True, copy=False, + ) + bedrooms = fields.Integer(string="Number of Bedrooms", default=2) + living_area = fields.Float(string="Living Area (sqm)") + facades = fields.Integer(string="Number of Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Float(string="Garden Area (sqm)", compute="_automate_garden") + garden_orientation = fields.Selection( + string="Garden Orientation", + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], + compute="_automate_garden", + ) + total_area = fields.Float(string="Total Area (sqm)", compute="_compute_area") + active = fields.Boolean(string="Active", default=True) + status = fields.Selection( + string="Status", + selection=[ + ("new", "New"), + ("offer received", "Offer Received"), + ("offer accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + default="new", + copy=False, + ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + user_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) + partner_id = fields.Many2one("res.partner", string="Buyer", copy=False) + property_tag_ids = fields.Many2many("estate.property.tag", string="Property Tag") + property_offers_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + best_offer = fields.Float(string="Best Offer", compute="_compute_best_offer", default=0) + + @api.depends("living_area", "garden_area") + def _compute_area(self): + for record in self: + record.total_area = record.garden_area + record.living_area + + @api.depends("property_offers_ids.price") + def _compute_best_offer(self): + for record in self: + record.best_offer = max(record.mapped("property_offers_ids.price"), default=0) + + @api.onchange("garden") + def _automate_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = None + + def sell_apartment(self): + for record in self: + if record.status == "cancelled": + err_msg = "Cancelled apartments can not be sold" + raise exceptions.UserError(err_msg) + record.status = "sold" + return True + + def cancel_apartment(self): + for record in self: + if record.status == "sold": + err_msg = "Sold apartments can not be cancelled" + raise exceptions.UserError(err_msg) + record.status = "cancelled" + return False + + _check_expected_price = models.Constraint( + 'CHECK(expected_price >= 0)', + 'The expected price of an should be greater than 0.', + ) + + @api.constrains("expected_price", "selling_price") + def _check_valid_transaction(self): + for record in self: + if record.property_offers_ids: + if tools.float_compare(record.selling_price, 0.9 * record.expected_price, 2) < 1: + err_msg = "Selling price can not be less than 90% of your expected price. Lower your expected price to accept this transaction." + raise exceptions.ValidationError(err_msg) + # TODO check this + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_cancelled(self): + for record in self: + if record.status not in ("new", "cancelled"): + err_msg = "Can not delete estate properties that have offers" + raise exceptions.UserError(err_msg) diff --git a/estate/models/property_offer.py b/estate/models/property_offer.py new file mode 100644 index 00000000000..fbaa0d77233 --- /dev/null +++ b/estate/models/property_offer.py @@ -0,0 +1,66 @@ +from odoo import _, api, exceptions, fields, models + + +class PropertyOffer (models.Model): + _name = "estate.property.offer" + _description = "Property Purchase Offers" + _order = "price desc" + + price = fields.Float(string="Price") + property_id = fields.Many2one("estate.property", string="Property Name", required=True, ondelete="cascade") + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_type_id = fields.Many2one("estate.property.type", related="property_id.property_type_id", string="Property Type", store=True) + status = fields.Selection(string="Status", selection=[("accepted", "Accepted"), ("refused", "Refused")], copy=False) + validity = fields.Integer(string="Validity", default="7") + date_deadline = fields.Date(string="Deadline", compute="_compute_validity", inverse="_inverse_date") + + @api.depends("validity") + def _compute_validity(self): + for record in self: + if record.create_date: + record.date_deadline = fields.Date.add(record.create_date, days=record.validity) + else: + record.date_deadline = fields.Date.add(fields.Date.today(), days=record.validity) + + def _inverse_date(self): + for record in self: + if record.create_date: + record.validity = (record.date_deadline - fields.Date.to_date(record.create_date)).days + else: + record.validity = (record.date_deadline - fields.Date.today()).days + + def action_confirm(self): + for record in self: + if not record.status: + if record.property_id.status in ("sold", "cancelled", "offer accepted"): + err_msg = "This offer can not be accepted" + raise exceptions.UserError(err_msg) + record.property_id.status = "offer accepted" + record.status = "accepted" + record.property_id.selling_price = record.price + record.property_id.active = False + record.property_id.partner_id = record.partner_id + + return True + + def action_cancel(self): + for record in self: + if not record.status: + record.status = "refused" + return True + + @api.constrains("price") + def _check_price(self): + for record in self: + if record.price < 0: + err_msg = "Enter a valid offer" + raise exceptions.ValidationError(err_msg) + if record.price < record.property_id.best_offer: + # err_msg = + raise exceptions.UserError(_("You can not enter an offer below %(amount)s"), amount=record.property_id.best_offer) + + @api.model + def create(self, vals_list): + offer = super().create(vals_list) + offer.property_id.status = 'offer received' + return offer diff --git a/estate/models/property_tag.py b/estate/models/property_tag.py new file mode 100644 index 00000000000..39e085aaa63 --- /dev/null +++ b/estate/models/property_tag.py @@ -0,0 +1,15 @@ +from odoo import fields, models + + +class PropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tag" + _order = "name" + + name = fields.Char(string="Property Tag", required=True) + color = fields.Integer('Color') + + _check_tag_uniqueness = models.Constraint( + 'unique(name)', + 'This tag is already used, please use it or add a new tag', + ) diff --git a/estate/models/property_type.py b/estate/models/property_type.py new file mode 100644 index 00000000000..b03d55e758d --- /dev/null +++ b/estate/models/property_type.py @@ -0,0 +1,23 @@ +from odoo import api, fields, models + + +class PropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + _order = "sequence" + + name = fields.Char(string="Property Type", required=True) + estate_property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") + sequence = fields.Integer("Sequence", default=1) + offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") + offer_count = fields.Integer("# Offers", compute="_compute_offer_count") + + _check_type_uniqueness = models.Constraint( + 'unique(name)', + 'This type is already used, please use it or add a new type', + ) + + @api.depends("offer_ids") + def _compute_offer_count(self): + for record in self: + record.offer_count = len(record.offer_ids) diff --git a/estate/models/res_user.py b/estate/models/res_user.py new file mode 100644 index 00000000000..ecc83e3f281 --- /dev/null +++ b/estate/models/res_user.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many("estate.property", "user_id", string="Estate Properties") diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0795184f311 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +estate_property_access,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +estate_property_type_access,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +estate_property_tag_access,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +estate_property_offer_access,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml new file mode 100644 index 00000000000..e6baddecee8 --- /dev/null +++ b/estate/views/estate_property_offers_views.xml @@ -0,0 +1,51 @@ + + + + + Property Offers + estate.property.offer + list,form + [('property_type_id', '=', active_id)] + {'default_property_id': active_id} + + + + Property Offer Form + estate.property.offer + +
+ +

+ Property Offer +

+ + + + + + + + + +
+
+
+
+ + + Property Offer List + estate.property.offer + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + Property Type List + estate.property.type + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..aab84325fe9 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,148 @@ + + + + Properties + estate.property + list,form,kanban + {'search_default_available':True} + + + + Property Form + estate.property + +
+
+
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + Property Kanban + estate.property + + + + + + + + + + +
+ +
+ Expected Price: + +
+
+ Best Offer: + +
+
+ Selling Price: + +
+ + +
+
+
+
+
+
+ + + Property List + estate.property + + + + + + + + + + + + + + + + + Property Search + estate.property + + + + + + + + + + + + + + + +
diff --git a/estate/views/estate_res_users_views.xml b/estate/views/estate_res_users_views.xml new file mode 100644 index 00000000000..3d8f9f5fb7d --- /dev/null +++ b/estate/views/estate_res_users_views.xml @@ -0,0 +1,16 @@ + + + + Inherited Property View + res.users + + + + + + + + + + + diff --git a/estate/views/menus.xml b/estate/views/menus.xml new file mode 100644 index 00000000000..4bff1d24f7a --- /dev/null +++ b/estate/views/menus.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..a28ca09e968 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,14 @@ +{ + 'name': 'Estate Account', + 'version': '1.0', + "summary": "Link between invoicing and estate sales", + "website": "https://www.odoo.com/app/estate_account", + "depends": ["estate", "account"], + "application": True, + "installable": True, + "author": "moali", + "license": "LGPL-3", + "data": [ + ], + +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..02b688798a3 --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_account diff --git a/estate_account/models/estate_account.py b/estate_account/models/estate_account.py new file mode 100644 index 00000000000..ad716e87d77 --- /dev/null +++ b/estate_account/models/estate_account.py @@ -0,0 +1,25 @@ +from odoo import Command, models + + +class EstateProperty(models.Model): + # _name = "estate.account" + _inherit = "estate.property" + + def sell_apartment(self): + self.env['account.move'].create({ + 'partner_id': self.partner_id.id, + 'move_type': 'out_invoice', + 'line_ids': [ + Command.create({ + 'name': f"Property Sale: {self.name}", + 'quantity': 1.0, + 'price_unit': 0.06 * self.selling_price, + }), + Command.create({ + 'name': "Administrative Fees", + 'quantity': 1.0, + 'price_unit': 100, + }), + ], + }) + return super().sell_apartment() diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv new file mode 100644 index 00000000000..301b7dab167 --- /dev/null +++ b/estate_account/security/ir.model.access.csv @@ -0,0 +1 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000000..6a7ce0e3a7e --- /dev/null +++ b/ruff.toml @@ -0,0 +1,85 @@ +# automatically generated file by the runbot nightly ruff checks, do not modify +# for ruff version 0.11.4 (or higher) +# note: 'E241', 'E272', 'E201', 'E221' are ignored on runbot in test files when formating a table like structure (more than two space) +# some rules present here are not enabled on runbot (yet) but are still advised to follow when possible : ["B904", "COM812", "E741", "EM101", "I001", "RET", "RUF021", "TRY002", "UP006", "UP007"] + + +target-version = "py310" + +[lint] +preview = true +select = [ + "BLE", # flake8-blind-except + "C", # flake8-comprehensions + "COM", # flake8-commas + "E", # pycodestyle Error + "EM", # flake8-errmsg + "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PLC", # Pylint Convention + "PLE", # Pylint Error + "PLW", # Pylint Warning + "PYI", # flake8-pyi + "RET", # flake8-return + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "T", # flake8-print + "TC", # flake8-type-checking + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "W", # pycodestyle Warning + "YTT", # flake8-2020 +] +ignore = [ + "C408", # unnecessary-collection-call + "C420", # unnecessary-dict-comprehension-for-iterable + "C901", # complex-structure + "E266", # multiple-leading-hashes-for-block-comment + "E501", # line-too-long + "E302", + "E301", + "E713", # not-in-test + "EM102", # f-string-in-exception + "FA100", # future-rewritable-type-annotation + "PGH003", # blanket-type-ignore + "PIE790", # unnecessary-placeholder + "PIE808", # unnecessary-range-start + "PLC2701", # import-private-name + "PLW2901", # redefined-loop-name + "RUF001", # ambiguous-unicode-character-string + "RUF005", # collection-literal-concatenation + "RUF012", # mutable-class-default + "RUF100", # unused-noqa + "SIM102", # collapsible-if + "SIM108", # if-else-block-instead-of-if-exp + "SIM117", # multiple-with-statements + "TID252", # relative-imports + "TRY003", # raise-vanilla-args + "TRY300", # try-consider-else + "TRY400", # error-instead-of-exception + "UP031", # printf-string-formatting + "UP038", # non-pep604-isinstance +] + +[lint.per-file-ignores] +"**/__init__.py" = [ + "F401", # unused-import +] + +[lint.isort] +# https://www.odoo.com/documentation/master/contributing/development/coding_guidelines.html#imports +section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] +known-first-party = ["odoo"] +known-local-folder = ["odoo.addons"] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000000..98ae51447b6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[pycodestyle] +ignore = ["E501","E302","E301"] diff --git a/website_airproof/__manifest__.py b/website_airproof/__manifest__.py index 2c2c62d6a18..48267ad98aa 100644 --- a/website_airproof/__manifest__.py +++ b/website_airproof/__manifest__.py @@ -1,59 +1,69 @@ { - 'name': 'Airproof Theme', - 'description': 'Airproof Theme - Drones, modelling, camera', - 'category': 'Website/Theme', + "name": "Airproof Theme", + "description": "Airproof Theme - Drones, modelling, camera", + "category": "Website/Theme", # 'version': '18.0.1.0', - 'author': 'PSBE Designers', - 'license': 'LGPL-3', - 'depends': ['website_sale', 'website_sale_wishlist', 'website_blog', 'website_mass_mailing'], - 'data': [ + "author": "PSBE Designers", + "license": "LGPL-3", + "depends": [ + "website_sale", + "website_sale_wishlist", + "website_blog", + "website_mass_mailing", + ], + "data": [ # Snippets - 'views/snippets/options.xml', - 'views/snippets/s_airproof_carousel.xml', + "views/snippets/options.xml", + "views/snippets/s_airproof_carousel.xml", # Options - 'data/presets.xml', - 'data/website.xml', + "data/presets.xml", + "data/website.xml", # Menu - 'data/menu.xml', + "data/menu.xml", # Gradients - 'data/gradients.xml', + "data/gradients.xml", # Shapes - 'data/shapes.xml', + "data/shapes.xml", # Pages - 'data/pages/home.xml', - 'data/pages/contact.xml', + "data/pages/home.xml", + "data/pages/contact.xml", # Frontend - 'views/new_page_template_templates.xml', - 'views/website_templates.xml', - 'views/website_sale_templates.xml', - 'views/website_sale_wishlist_templates.xml', + "views/new_page_template_templates.xml", + "views/website_templates.xml", + "views/website_sale_templates.xml", + "views/website_sale_wishlist_templates.xml", # Images - 'data/images.xml', + "data/images.xml", ], - 'assets': { - 'web._assets_primary_variables': [ - 'website_airproof/static/src/scss/primary_variables.scss', + "assets": { + "web._assets_primary_variables": [ + "website_airproof/static/src/scss/primary_variables.scss", ], - 'web._assets_frontend_helpers': [ - ('prepend', 'website_airproof/static/src/scss/bootstrap_overridden.scss'), + "web._assets_frontend_helpers": [ + ("prepend", "website_airproof/static/src/scss/bootstrap_overridden.scss"), ], - 'web.assets_frontend': [ + "web.assets_frontend": [ # SCSS - 'website_airproof/static/src/scss/font.scss', - 'website_airproof/static/src/scss/components/mouse_follower.scss', - 'website_airproof/static/src/scss/layout/header.scss', - 'website_airproof/static/src/scss/pages/product_page.scss', - 'website_airproof/static/src/scss/pages/shop.scss', - 'website_airproof/static/src/scss/snippets/caroussel.scss', - 'website_airproof/static/src/scss/snippets/newsletter.scss', - 'website_airproof/static/src/snippets/s_airproof_carousel/000.scss', + "website_airproof/static/src/scss/font.scss", + "website_airproof/static/src/scss/components/mouse_follower.scss", + "website_airproof/static/src/scss/layout/header.scss", + "website_airproof/static/src/scss/pages/product_page.scss", + "website_airproof/static/src/scss/pages/shop.scss", + "website_airproof/static/src/scss/snippets/caroussel.scss", + "website_airproof/static/src/scss/snippets/newsletter.scss", + "website_airproof/static/src/snippets/s_airproof_carousel/000.scss", # JS - 'website_airproof/static/src/js/mouse_follower.js', + "website_airproof/static/src/js/mouse_follower.js", ], }, - 'new_page_templates': { - 'airproof': { - 'services': ['s_parallax', 's_airproof_key_benefits_h2', 's_call_to_action', 's_airproof_carousel'] - } + "new_page_templates": { + "airproof": { + "services": [ + "s_parallax", + "s_airproof_key_benefits_h2", + "s_call_to_action", + "s_airproof_carousel", + ], + }, }, }