From 426b023d3ffe74e3a33ca9a52482777feef88a00 Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Mon, 24 Mar 2025 10:12:08 +0100 Subject: [PATCH 01/12] [ADD] realestatinator: Helps you manage real estate properties. --- realestatinator/__init__.py | 1 + realestatinator/__manifest__.py | 22 ++ realestatinator/models/__init__.py | 6 + realestatinator/models/estate_property.py | 109 ++++++ .../models/estate_property_offer.py | 56 +++ .../models/estate_property_tags.py | 11 + .../models/estate_property_type.py | 21 + realestatinator/models/res_partner.py | 8 + realestatinator/models/res_users.py | 9 + realestatinator/security/ir.model.access.csv | 5 + realestatinator/views/estate_menus.xml | 20 + .../views/estate_property_offer_views.xml | 54 +++ .../views/estate_property_tags_views.xml | 16 + .../views/estate_property_type_views.xml | 86 ++++ .../views/estate_property_views.xml | 366 ++++++++++++++++++ 15 files changed, 790 insertions(+) create mode 100644 realestatinator/__init__.py create mode 100644 realestatinator/__manifest__.py create mode 100644 realestatinator/models/__init__.py create mode 100644 realestatinator/models/estate_property.py create mode 100644 realestatinator/models/estate_property_offer.py create mode 100644 realestatinator/models/estate_property_tags.py create mode 100644 realestatinator/models/estate_property_type.py create mode 100644 realestatinator/models/res_partner.py create mode 100644 realestatinator/models/res_users.py create mode 100644 realestatinator/security/ir.model.access.csv create mode 100644 realestatinator/views/estate_menus.xml create mode 100644 realestatinator/views/estate_property_offer_views.xml create mode 100644 realestatinator/views/estate_property_tags_views.xml create mode 100644 realestatinator/views/estate_property_type_views.xml create mode 100644 realestatinator/views/estate_property_views.xml diff --git a/realestatinator/__init__.py b/realestatinator/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/realestatinator/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/realestatinator/__manifest__.py b/realestatinator/__manifest__.py new file mode 100644 index 00000000000..4beb62d3f2a --- /dev/null +++ b/realestatinator/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': 'real-estate-inator', + 'description': 'Inator that helps you find real estate.', + 'category': 'Tutorials/RealEstateInator', + 'author': 'gato', + 'depends': [ + 'base', + 'web', + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tags_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_menus.xml', + ], + 'installable': True, + 'application': True, + 'auto_install': False, + 'version': '0.1', +} diff --git a/realestatinator/models/__init__.py b/realestatinator/models/__init__.py new file mode 100644 index 00000000000..006822a2a9c --- /dev/null +++ b/realestatinator/models/__init__.py @@ -0,0 +1,6 @@ +from . import res_users +from . import res_partner +from . import estate_property +from . import estate_property_type +from . import estate_property_tags +from . import estate_property_offer diff --git a/realestatinator/models/estate_property.py b/realestatinator/models/estate_property.py new file mode 100644 index 00000000000..5b99a636862 --- /dev/null +++ b/realestatinator/models/estate_property.py @@ -0,0 +1,109 @@ +from odoo import api, exceptions, fields, models + +class EstatePropery(models.Model): + _name = 'estate_property' + _description = 'real estate property' + _order = 'id desc' + _sql_constraints = [ + ('check_expected_price_positive', 'CHECK (0 < expected_price)', 'Check that the expected price is strictly positive'), + + ] + + + sequence = fields.Integer('Sequence', default=0) + name = fields.Char('Title', required=True) + description = fields.Text('Description') + postcode = fields.Char('Postcode') + date_availability = fields.Date('Available Date', copy=False, default=fields.Date.add(fields.Date.today(), months=+3)) + expected_price = fields.Float('Expected Price') + selling_price = fields.Float('Selling Price', readonly=True, copy=False) + bedrooms = fields.Integer('Bedrooms', default=2) + living_area = fields.Integer('Living Area') + facades = fields.Integer('Facades') + garage = fields.Boolean('Garage') + garden = fields.Boolean('Garden') + garden_area = fields.Integer('Garden Area') + garden_orientation = fields.Selection(string='Garden Orientation', selection=[ + ('N', 'North'), + ('S', 'South'), + ('E', 'East'), + ('W', 'West') + ]) + active = fields.Boolean('Active', default=False) + state = fields.Selection(string='State', selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], default='new') + property_type_id = fields.Many2one('estate.property.type', string='Property Type') + buyer = fields.Many2one('res.partner', string='Buyer', copy=False) + sales_person = fields.Many2one('res.users', string='Sales Person', default=lambda self: self.env.user) + tag_ids = fields.Many2many('estate.property.tags', string='Tags') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') + total_area = fields.Integer('Total Area', readonly=True, compute='_compute_total_area') + best_price = fields.Float('Best Offer', compute='_compute_best_price') + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for line in self: + line.total_area = line.living_area + line.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + prices = [0] + record.offer_ids.mapped('price') + record.best_price = max(prices) + + @api.onchange('garden') + def _set_garden_properties(self): + for record in self: + # record.garden_orientation = 'N' if record.garden else '' + # record.garden_area = 10 if record.garden else 0 + + if record.garden: + if record.garden_orientation not in ['N', 'E', 'W', 'S']: + record.garden_orientation = 'N' + if record.garden_area == 0: + record.garden_area = 10 + else: + record.garden_orientation = '' + record.garden_area = 0 + + def mark_cancelled(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError('This property is already cancelled.') + + if record.state == 'sold': + raise exceptions.UserError('This property cannot be cancelled because it has already been sold.') + + record.state = 'cancelled' + record.active = False + + def mark_sold(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError('This property is already sold.') + + + if record.state == 'cancelled': + raise exceptions.UserError('This property cannot be sold because it has already been cancelled.') + + record.state = 'sold' + record.active = False + + @api.constrains('selling_price') + def _check_selling_price(self): + for record in self: + if record.state not in ['offer_accepted', 'sold']: + return + if record.selling_price < 0.9 * record.expected_price: + raise exceptions.ValidationError('Selling price must be at least 90% of expected price.') + + @api.ondelete(at_uninstall=False) + def _unlink(self): + for record in self: + if record.state not in ['new', 'cancelled']: + raise exceptions.UserError('Property must be either new or cancelled to be deleted.') diff --git a/realestatinator/models/estate_property_offer.py b/realestatinator/models/estate_property_offer.py new file mode 100644 index 00000000000..6cef0a6597f --- /dev/null +++ b/realestatinator/models/estate_property_offer.py @@ -0,0 +1,56 @@ +from odoo import api, exceptions, fields, models + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'estate property offer' + _order = 'price desc' + _sql_constraints = [ + ('check_offer_price_positive', 'CHECK (0 < price)', 'Check that the offer price is strictly positive.'), + ] + creation_date = fields.Date('Creation Date', default=fields.Date.today()) + price = fields.Float('Price') + status = fields.Selection(string='Status', selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], copy=False) + partner_id = fields.Many2one('res.partner', string='Partner') + property_id = fields.Many2one('estate_property', string='Property') + validity = fields.Integer('Validity', default=7) + date_deadline = fields.Date('Deadline', compute='_compute_deadline', inverse='_inverse_deadline') + property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) + + + @api.depends('validity') + def _compute_deadline(self): + for record in self: + record.date_deadline = fields.Date.add(record.creation_date, days=record.validity) + + def _inverse_deadline(self): + for record in self: + delta = record.date_deadline - record.creation_date + record.validity = delta.days + + def refuse_offer(self): + for record in self: + if record.status == 'accepted': + record.property_id.state = 'offer_received' + record.property_id.selling_price = 0 + record.property_id.buyer = None + record.status = 'refused' + + def accept_offer(self): + for record in self: + if record.property_id.selling_price != 0: + raise exceptions.UserError('An offer as already been accepted for this property.') + record.property_id.state = 'offer_accepted' + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer = record.partner_id + @api.model + def create(self, vals): + estate_property = self.env['estate_property'].browse(vals["property_id"]) + if vals["price"] < estate_property.best_price: + raise exceptions.UserError(f'Offer must be higher than the current best offer({estate_property.best_price})') + if estate_property.state == 'new': + estate_property.state = 'offer_received' + return super().create(vals) diff --git a/realestatinator/models/estate_property_tags.py b/realestatinator/models/estate_property_tags.py new file mode 100644 index 00000000000..b822ea41fbe --- /dev/null +++ b/realestatinator/models/estate_property_tags.py @@ -0,0 +1,11 @@ +from odoo import fields, models + +class EstatePropertyTags(models.Model): + _name = 'estate.property.tags' + _description = 'estate property tag' + _order = 'name' + _sql_constraints = [ + ('name_unique', 'UNIQUE (name)', 'make sure tag name is unique.') + ] + name = fields.Char('Name', required=True) + color = fields.Integer('Colour') diff --git a/realestatinator/models/estate_property_type.py b/realestatinator/models/estate_property_type.py new file mode 100644 index 00000000000..909360efc7c --- /dev/null +++ b/realestatinator/models/estate_property_type.py @@ -0,0 +1,21 @@ +from odoo import api, fields, models + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'real estate property type' + _order = 'name, sequence' + _sql_constraints = [ + ('name_unique', 'UNIQUE (name)', 'make sure type name is unique.') + ] + + name = fields.Char('Name', required=True) + property_ids = fields.One2many('estate_property', 'property_type_id', string='Property Type') + sequence = fields.Integer('sequence', default=1) + offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers') + offer_count = fields.Integer(string='Offer Count', compute='_count_offers') + + + @api.depends('offer_ids') + def _count_offers(self): + for record in self: + record.offer_count = len(record.offer_ids) diff --git a/realestatinator/models/res_partner.py b/realestatinator/models/res_partner.py new file mode 100644 index 00000000000..8319abec5f6 --- /dev/null +++ b/realestatinator/models/res_partner.py @@ -0,0 +1,8 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models +from odoo.osv import expression + + +class Partner(models.Model): + _inherit = 'res.partner' diff --git a/realestatinator/models/res_users.py b/realestatinator/models/res_users.py new file mode 100644 index 00000000000..dcd0d6d5f59 --- /dev/null +++ b/realestatinator/models/res_users.py @@ -0,0 +1,9 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class Users(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many('estate_property', 'sales_person', string='Properties') diff --git a/realestatinator/security/ir.model.access.csv b/realestatinator/security/ir.model.access.csv new file mode 100644 index 00000000000..417deb122c9 --- /dev/null +++ b/realestatinator/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 +realestatinator.access_estate_property,access_estate_property,realestatinator.model_estate_property,base.group_user,1,1,1,1 +realestatinator.access_estate_property_type,access_estate_property_type,realestatinator.model_estate_property_type,base.group_user,1,1,1,1 +realestatinator.access_estate_property_tags,access_estate_property_tags,realestatinator.model_estate_property_tags,base.group_user,1,1,1,1 +realestatinator.access_estate_property_offer,access_estate_property_offer,realestatinator.model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/realestatinator/views/estate_menus.xml b/realestatinator/views/estate_menus.xml new file mode 100644 index 00000000000..556d28bda51 --- /dev/null +++ b/realestatinator/views/estate_menus.xml @@ -0,0 +1,20 @@ + + +<<<<<<< HEAD + + + + + + + +======= + + + + + + + +>>>>>>> 114af13f0 ([ADD] real-estate-inator: Inator that helps you manage and sell real estate properties) + diff --git a/realestatinator/views/estate_property_offer_views.xml b/realestatinator/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..463aed2c9db --- /dev/null +++ b/realestatinator/views/estate_property_offer_views.xml @@ -0,0 +1,54 @@ + + +<<<<<<< HEAD + + estate.property.offer.action + estate.property.offer + list,form + [('property_type_id', '=', active_id)] + + + estate.property.offer.view.list + estate.property.offer + + + + + + + + + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..31f8045231a 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,9 @@ /** @odoo-module **/ import { Component } from "@odoo/owl"; +import { Counter } from './counter/counter'; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter }; } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..f040e7079d0 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -3,7 +3,8 @@
- hello world + +
From 1b00261ff503d935a80452299dc51dc6c142cd5e Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Fri, 21 Mar 2025 10:02:43 +0100 Subject: [PATCH 04/12] [ADD] Card: a basic card component A basic card component with a title and a description task-xxxxx --- awesome_owl/static/src/card/card.js | 15 ++++++++++++++ awesome_owl/static/src/card/card.xml | 29 +++++++++++++++++++++++++++ awesome_owl/static/src/playground.js | 7 ++++--- awesome_owl/static/src/playground.xml | 16 +++++++++------ 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..74c8d76115b --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,15 @@ +import { Component, markup } from '@odoo/owl'; + +export class Card extends Component { + static template = 'card.card'; + + setup() { + this.test_title = "

Why do I need to put quotes INSIDE quotes???

"; + this.test_content = markup('

This limitation of passing the contents of quotation marks as raw javascript is really annoying.
I mean, who would want to put quotation marks INSIDE ANOTHER PAIR OF QUOTATION MARKS EVERY SINGLE TIME THEY WANT TO PASS TEXT TO A COMPONENT!
Though, I guess it could be useful to pass a javascript function to the component.
If for whatever reason you wanted to do that.

'); + this.title = this.props.title ? this.props.title : this.test_title; + this.content = this.props.content ? this.props.content : this.test_content; + if (this.props.ace) { + this.props.ace(this); + } + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..ccaf9be5fd0 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,29 @@ + + + + +
+
+

+
+
+ +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 31f8045231a..6f61f3f6940 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,9 +1,10 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, markup } from "@odoo/owl"; import { Counter } from './counter/counter'; +import { Card } from './card/card'; export class Playground extends Component { - static template = "awesome_owl.playground"; - static components = { Counter }; + static template = "awesome_owl.playground"; + static components = { Counter, Card }; } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index f040e7079d0..7856c658698 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,11 +1,15 @@ - -
- - -
-
+ +
+ + + + + + +
+
From df9915587ed55f377874e45eb89e1cda1268193f Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Fri, 21 Mar 2025 15:12:12 +0100 Subject: [PATCH 05/12] [ADD] TodoList and TodoItem: Components to manage a todo list and its items task-xxxxx --- awesome_owl/static/src/card/card.js | 16 ++++- awesome_owl/static/src/card/card.xml | 21 +------ awesome_owl/static/src/counter/counter.js | 18 +++--- awesome_owl/static/src/playground.js | 15 ++++- awesome_owl/static/src/playground.xml | 40 ++++++++++++- awesome_owl/static/src/todo_item/todo_item.js | 31 ++++++++++ .../static/src/todo_item/todo_item.xml | 12 ++++ awesome_owl/static/src/todo_list/todo_list.js | 59 +++++++++++++++++++ .../static/src/todo_list/todo_list.xml | 17 ++++++ 9 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 awesome_owl/static/src/todo_item/todo_item.js create mode 100644 awesome_owl/static/src/todo_item/todo_item.xml create mode 100644 awesome_owl/static/src/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/todo_list/todo_list.xml diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 74c8d76115b..8d6753e80b4 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -2,7 +2,21 @@ import { Component, markup } from '@odoo/owl'; export class Card extends Component { static template = 'card.card'; - + static props = { + title: { + type: 'String', + optional: 'true', + }, + content: { + type: 'String', + optional: 'true', + }, + ace: { + type: 'Function', + optional: 'true', + }, + }; + setup() { this.test_title = "

Why do I need to put quotes INSIDE quotes???

"; this.test_content = markup('

This limitation of passing the contents of quotation marks as raw javascript is really annoying.
I mean, who would want to put quotation marks INSIDE ANOTHER PAIR OF QUOTATION MARKS EVERY SINGLE TIME THEY WANT TO PASS TEXT TO A COMPONENT!
Though, I guess it could be useful to pass a javascript function to the component.
If for whatever reason you wanted to do that.

'); diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index ccaf9be5fd0..b456711bccc 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -1,26 +1,7 @@ - -
+

diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js index f92478278f1..bb422e38fb7 100644 --- a/awesome_owl/static/src/counter/counter.js +++ b/awesome_owl/static/src/counter/counter.js @@ -1,12 +1,16 @@ import { Component, useState } from "@odoo/owl"; export class Counter extends Component { - static template = 'counter.counter'; - setup() { - this.counter = useState({value: 0}); - } + static template = 'counter.counter'; + static props = { + onChange: {type: 'Function', optional: 'true',}, + } + setup() { + this.counter = useState({value: 0}); + } - increment() { - this.counter.value++; - } + increment() { + this.counter.value++; + this.props.onChange('Prout!'); + } } diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 6f61f3f6940..e287f7cc380 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,10 +1,21 @@ /** @odoo-module **/ -import { Component, markup } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; import { Counter } from './counter/counter'; import { Card } from './card/card'; +import { TodoList } from './todo_list/todo_list'; export class Playground extends Component { static template = "awesome_owl.playground"; - static components = { Counter, Card }; + static components = { Counter, Card, TodoList }; + static props = {}; + + setup() { + this.counter = useState({value: 0}); + } + + + updateTotal(str) { + this.counter.value++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 7856c658698..9a3feeb9960 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,13 +2,47 @@ +
- - + + +

Total :

- + +
diff --git a/awesome_owl/static/src/todo_item/todo_item.js b/awesome_owl/static/src/todo_item/todo_item.js new file mode 100644 index 00000000000..76f3335b3e9 --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.js @@ -0,0 +1,31 @@ +import { Component, useState } from '@odoo/owl'; + +export class TodoItem extends Component { + static template = 'todo_item.todo_item'; + static props = { + id: {type: 'Number',}, + title: {type: 'String', optional: 'true',}, + description: {type: 'String', optional: 'true',}, + isCompleted: {type: 'Boolean', optional: 'true',}, + update: {type: 'Function',}, + remove: {type: 'Function',}, + } + setup() { + this.contents = useState({ + id: this.props.id, + title: this.props.title ? this.props.title : 'No Title.', + description: this.props.description ? this.props.description : 'No Description.', + isCompleted: this.props.isCompleted, + }); + + } + + click() { + this.contents.isCompleted = !this.contents.isCompleted; + this.props.update(this.contents); + } + + rubbish_bin() { + this.props.remove(this.contents); + } +} diff --git a/awesome_owl/static/src/todo_item/todo_item.xml b/awesome_owl/static/src/todo_item/todo_item.xml new file mode 100644 index 00000000000..c761c60d087 --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.xml @@ -0,0 +1,12 @@ + + + + +
+
+

+ +
+
+ +
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..e7f31340816 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,59 @@ +import { Component, useState, markup } from '@odoo/owl'; +import { TodoItem } from '../todo_item/todo_item' + +export class TodoList extends Component { + static template = 'todo_list.todo_list'; + static props = {}; + static components = {TodoItem}; + + setup() { + this.list = useState({items: []}); + + this.list.items = JSON.parse(window.localStorage.getItem('todo')); + } + + update(item) { + for (let i = 0;i < this.list.items.length;i++) { + if (this.list.items[i].id == item.id) { + this.list.items[i].title = item.title; + this.list.items[i].description = item.description; + this.list.items[i].isCompleted = item.isCompleted; + } + } + + window.localStorage.setItem('todo', JSON.stringify(this.list.items)); + } + + create_task(e) { + if (e.keyCode != 13) return; + let newTask = { + id: 0, + title: document.getElementById('title').value, + description: document.getElementById('description').value, + isCompleted: false, + } + + if (newTask.title == '' || newTask.description == '') return; + + for (let i = 0;i < this.list.items.length;i++) { + if (this.list.items[i].id >= newTask.id) { + newTask.id = this.list.items[i].id; + } + } + newTask.id++; + this.list.items = [...this.list.items, newTask]; + window.localStorage.setItem('todo', JSON.stringify(this.list.items)); + document.getElementById('title').value = ''; + document.getElementById('description').value = ''; + } + + remove(item) { + const index = this.list.items.findIndex((elt) => elt.id == item.id); + if (index >= 0) { + this.list.items.splice(index, 1); + } + + + window.localStorage.setItem('todo', JSON.stringify(this.list.items)); + } +} 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..5cc812aa94e --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,17 @@ + + + + +
+
+ + +
+ + + + +
+
+ +
From a10a151bbe3620053a30be201b3b00dc7f3fe050 Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Mon, 24 Mar 2025 09:40:50 +0100 Subject: [PATCH 06/12] [IMP] Card: modified Card component to use default slot. --- awesome_owl/static/src/card/card.js | 13 +- awesome_owl/static/src/card/card.xml | 2 +- awesome_owl/static/src/playground.xml | 20 +- realestatinator/models/estate_property.py | 2 +- .../models/estate_property_offer.py | 4 +- .../models/estate_property_type.py | 2 +- realestatinator/models/res_users.py | 2 +- realestatinator/views/estate_menus.xml | 10 - .../views/estate_property_offer_views.xml | 31 +- .../views/estate_property_tags_views.xml | 10 +- .../views/estate_property_type_views.xml | 45 +-- .../views/estate_property_views.xml | 268 ++++-------------- 12 files changed, 79 insertions(+), 330 deletions(-) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 8d6753e80b4..5ab3330b027 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -7,21 +7,18 @@ export class Card extends Component { type: 'String', optional: 'true', }, - content: { - type: 'String', - optional: 'true', - }, ace: { type: 'Function', optional: 'true', }, + slots: { + type: 'Object', + optional: 'true', + }, }; setup() { - this.test_title = "

Why do I need to put quotes INSIDE quotes???

"; - this.test_content = markup('

This limitation of passing the contents of quotation marks as raw javascript is really annoying.
I mean, who would want to put quotation marks INSIDE ANOTHER PAIR OF QUOTATION MARKS EVERY SINGLE TIME THEY WANT TO PASS TEXT TO A COMPONENT!
Though, I guess it could be useful to pass a javascript function to the component.
If for whatever reason you wanted to do that.

'); - this.title = this.props.title ? this.props.title : this.test_title; - this.content = this.props.content ? this.props.content : this.test_content; + this.title = this.props.title ? this.props.title : 'No Title'; if (this.props.ace) { this.props.ace(this); } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index b456711bccc..b794691059d 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -3,7 +3,7 @@
-

+
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 9a3feeb9960..b86252a4d20 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -35,13 +35,21 @@ }
- - -

Total :

- + +

I told you it was really cool!

+
- - + + + + +

Total :

+
+ + + + +
diff --git a/realestatinator/models/estate_property.py b/realestatinator/models/estate_property.py index 5b99a636862..948ec1a726c 100644 --- a/realestatinator/models/estate_property.py +++ b/realestatinator/models/estate_property.py @@ -1,7 +1,7 @@ from odoo import api, exceptions, fields, models class EstatePropery(models.Model): - _name = 'estate_property' + _name = 'estate.property' _description = 'real estate property' _order = 'id desc' _sql_constraints = [ diff --git a/realestatinator/models/estate_property_offer.py b/realestatinator/models/estate_property_offer.py index 6cef0a6597f..9e3293737cb 100644 --- a/realestatinator/models/estate_property_offer.py +++ b/realestatinator/models/estate_property_offer.py @@ -14,7 +14,7 @@ class EstatePropertyOffer(models.Model): ('refused', 'Refused') ], copy=False) partner_id = fields.Many2one('res.partner', string='Partner') - property_id = fields.Many2one('estate_property', string='Property') + property_id = fields.Many2one('estate.property', string='Property') validity = fields.Integer('Validity', default=7) date_deadline = fields.Date('Deadline', compute='_compute_deadline', inverse='_inverse_deadline') property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) @@ -48,7 +48,7 @@ def accept_offer(self): record.property_id.buyer = record.partner_id @api.model def create(self, vals): - estate_property = self.env['estate_property'].browse(vals["property_id"]) + estate_property = self.env['estate.property'].browse(vals["property_id"]) if vals["price"] < estate_property.best_price: raise exceptions.UserError(f'Offer must be higher than the current best offer({estate_property.best_price})') if estate_property.state == 'new': diff --git a/realestatinator/models/estate_property_type.py b/realestatinator/models/estate_property_type.py index 909360efc7c..aee463bd757 100644 --- a/realestatinator/models/estate_property_type.py +++ b/realestatinator/models/estate_property_type.py @@ -9,7 +9,7 @@ class EstatePropertyType(models.Model): ] name = fields.Char('Name', required=True) - property_ids = fields.One2many('estate_property', 'property_type_id', string='Property Type') + property_ids = fields.One2many('estate.property', 'property_type_id', string='Property Type') sequence = fields.Integer('sequence', default=1) offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers') offer_count = fields.Integer(string='Offer Count', compute='_count_offers') diff --git a/realestatinator/models/res_users.py b/realestatinator/models/res_users.py index dcd0d6d5f59..ebd3eb31d5f 100644 --- a/realestatinator/models/res_users.py +++ b/realestatinator/models/res_users.py @@ -6,4 +6,4 @@ class Users(models.Model): _inherit = 'res.users' - property_ids = fields.One2many('estate_property', 'sales_person', string='Properties') + property_ids = fields.One2many('estate.property', 'sales_person', string='Properties') diff --git a/realestatinator/views/estate_menus.xml b/realestatinator/views/estate_menus.xml index 556d28bda51..9733dff5122 100644 --- a/realestatinator/views/estate_menus.xml +++ b/realestatinator/views/estate_menus.xml @@ -1,14 +1,5 @@ -<<<<<<< HEAD - - - - - - - -======= @@ -16,5 +7,4 @@ ->>>>>>> 114af13f0 ([ADD] real-estate-inator: Inator that helps you manage and sell real estate properties) diff --git a/realestatinator/views/estate_property_offer_views.xml b/realestatinator/views/estate_property_offer_views.xml index 463aed2c9db..5818faedc0d 100644 --- a/realestatinator/views/estate_property_offer_views.xml +++ b/realestatinator/views/estate_property_offer_views.xml @@ -1,39 +1,13 @@ -<<<<<<< HEAD - - estate.property.offer.action - estate.property.offer - list,form - [('property_type_id', '=', active_id)] - - - estate.property.offer.view.list - estate.property.offer - - - - - + + +
diff --git a/awesome_owl/static/src/todo_item/todo_item.xml b/awesome_owl/static/src/todo_item/todo_item.xml index c761c60d087..150d09e4ffc 100644 --- a/awesome_owl/static/src/todo_item/todo_item.xml +++ b/awesome_owl/static/src/todo_item/todo_item.xml @@ -5,7 +5,7 @@

- +
From c03fee461d30382bdde7093fac89da0a4ddd6cab Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Mon, 24 Mar 2025 13:19:13 +0100 Subject: [PATCH 08/12] [IMP] dashboard: Added Layout and two action buttons --- .../inspectionProfiles/profiles_settings.xml | 6 +++++ .idea/modules.xml | 8 +++++++ .idea/tutorials.iml | 8 +++++++ awesome_dashboard/static/src/dashboard.js | 22 +++++++++++++++++- awesome_dashboard/static/src/dashboard.scss | 3 +++ awesome_dashboard/static/src/dashboard.xml | 7 ++++-- awesome_owl/static/src/.playground.xml.swp | Bin 12288 -> 0 bytes awesome_owl/static/src/card/.card.js.swp | Bin 12288 -> 0 bytes awesome_owl/static/src/card/.card.xml.swp | Bin 12288 -> 0 bytes 9 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/tutorials.iml create mode 100644 awesome_dashboard/static/src/dashboard.scss delete mode 100644 awesome_owl/static/src/.playground.xml.swp delete mode 100644 awesome_owl/static/src/card/.card.js.swp delete mode 100644 awesome_owl/static/src/card/.card.xml.swp diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000000..105ce2da2d6 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000000..5c1c8885418 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tutorials.iml b/.idea/tutorials.iml new file mode 100644 index 00000000000..d0876a78d06 --- /dev/null +++ b/.idea/tutorials.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 637fa4bb972..d8b34c328b6 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -2,9 +2,29 @@ import { Component } from "@odoo/owl"; import { registry } from "@web/core/registry"; - +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout }; + + setup() { + this.action = useService('action'); + } + + customersButton() { + this.action.doAction('base.action_partner_form'); + } + + leadsButton() { + this.action.doAction({ + type: 'ir.actions.act_window', + name: 'Leads', + target: 'current', + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']], + }); + } } registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard.scss new file mode 100644 index 00000000000..573b767c3d0 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: lightgreen; +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 1a2ac9a2fed..4f779eb7336 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -1,8 +1,11 @@ - - hello dashboard + + hello dashboard + + + diff --git a/awesome_owl/static/src/.playground.xml.swp b/awesome_owl/static/src/.playground.xml.swp deleted file mode 100644 index 3d23209411ac16d94c50933c29fddcc6f43af9ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2y>A>v7>6f8NNhp~3QZ8r+?vP@dwV`Bl5^R+6qb-8ND<^vA)&e5@!ce|JFA^p zpHJbVL_##7ra?lYp^FqGiu?hHl7fbUE);Z7(DB=yb9^?jok$db-m@O>$Gq>(JI~Bo zwmjqByVq~l~krt&h<*}h3H4lv2Z?1Nx zQbOBU%%nBBeBaD;Y@Ki_jct|2r55u^W$Gg9?&kW~$5JC1AOj~EsNHt7agMF`*LqFp zO4#AAT)1^o7PXTBGC&5%02v?yWPl8i0W$DEGT_Qn>|>1Q^kPg`7ROVEj*Ck=kO4A4 z2FL&zAOmE843GgbKnBPF86X4yK?5RV?2iDPLGk$i|K8vKKb>dnTkrtf1D}8ofC4u_ z4{-1T`0XXeegxlvufXR(fcL;#;0>?}E`sO5ujd&10o(^)f-k^l;5LvT12@4s=zhbPd6u#7!9#ElNN@#2AOsgc0Gglm?KCGrvV~oyZ>y!SrPC2RaB+0H zbv*Wui#J7;wWsK0|2#-Gu|w6#iko6{DvF8hj#ZHj!@A5++m>-chyD?$>FHyWrVH+r z({dQDwih$yraU*5Ph0#r#?$f&k}o7 z{fA0FG7O`)ToqPjveW4-2I`}qFd^mWI(J5Ad~Ry49G?lxD=D;|!T=%Wv{Gc5DkcNomw6i&2;>!!!dqYmJeAEF^bNOb z>(urfRpDOY4mNIUxf^AwlBrXsK%3EZ?mG@LWt%}dqM9g_ye4xluzB3ajZKxw zDyl@LYKZ7xE_bhTKS91_F*#$6`8tr|rYgNtNZVu~X*s8z>j5uJA^rWC%G`9oFZV9_ z>wnU5Bdk@G>@BhOy)8tJSAS`5e0P6CJeDL&9{>E~*d9NyrAQH9R?^C9M@WKG*;oJo diff --git a/awesome_owl/static/src/card/.card.js.swp b/awesome_owl/static/src/card/.card.js.swp deleted file mode 100644 index a63dcd9a9ad01fcdddd008dc281ff3ff3b05cc72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2O=}ZD7{{lAFQL|o9($QaWK&2twIYO6+d@I`pdMPqgHX1+le*3B&ag9UDj^D9 zd;>p+pTfH$_24HEFMbC-_@7DA1SyG^9xBh0-)!dDnb~=MGnbIrS$cTqF0cE`4A(`* zPG9}L`*^O-<}$`)B&7{cx^@F27n494sm3;aZU?Nn!W2g4aggX*&=y*zVq3P8nl^!n z!+S{US!EBrk@c2Igq2FSo<1BGcW)@RuDfDDj<|Hgn(Q|tw@In~do+`mu#b?;Z{Mh3_L86X2>fDDiUGC&5%02v?yWPl8u zKm$R<*pKs!y_&(~@&Ete_y5;f#ty&-@ESY?E$|3zfK_kH?oGEZXdt6VDW2cf9g>cR>h#!c=j!)FV;gScyNVv`7di;r|Zjxl?$SsQ&#FVyIT zjL>G=MOOIX)NCx2nGu=64AUTQ6{_OhRI6J^jVEy$+hfi$@-FN+9WEhz z4qEs|?ArlGBB4xh%vy_R%@g}7$Re-d4Pu^kc2rvoF@IDX?ZYGvTNSJI7}jHqJMW(4 zTXqG#p*EK@=2~d6Y16NC2AM^Wofo3Yt&j#U_fju+?Jm*6KyE(+YoJ?2QIg6E4w44< zY*)_(%`+tsxWBU}LgPX@kw!P%uA1EMc9Dg4RM!(b^N|sVYGT^ Tly`YEmnrwwd*|@m3C8^cdpKBc diff --git a/awesome_owl/static/src/card/.card.xml.swp b/awesome_owl/static/src/card/.card.xml.swp deleted file mode 100644 index cd617e89930e29ace75fa6d331805a1d39b360e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2zmC)}5XOCUC-G0vAYJ1#qD-=P2x*0qy$(S^$I*h<@h%Zg9L1Bpy9Rn*;vVB9 zUH~du=n#)V$2i%gpt!9UeUUzoJ)W72e_a&MC$C?;l+*NB@N-v)=Z}6Z-yTfGK_i4O zy^ro~&r*|5oNmdt*M@$G1+!tGo_tqn7czZjgRkuyf9A44nktip&SXY+jsqv0)u?JhKar@nq z*^ujdPNj;R<-~m66Zu9Or$Z>zoEphW+ODc~ccHkVZxZ7w^S)4nQDYGsOrgg2kUftr z+Q#^@YL Date: Mon, 24 Mar 2025 13:29:32 +0100 Subject: [PATCH 09/12] [IMP] awesome_dashboard: added pie chart. Please kill me so I never have to do this again. --- awesome_dashboard/static/src/dashboard.js | 17 +- awesome_dashboard/static/src/dashboard.xml | 17 +- .../src/dashboard_item/dashboard_item.js | 13 + .../src/dashboard_item/dashboard_item.xml | 10 + .../static/src/piechart/piechart.js | 35 ++ .../static/src/piechart/piechart.xml | 8 + awesome_dashboard/static/src/stats_service.js | 18 + realestatinator/models/__init__.py | 8 +- realestatinator/models/estate_property.py | 192 ++++----- .../models/estate_property_offer.py | 111 ++--- .../models/estate_property_tags.py | 17 +- .../models/estate_property_type.py | 33 +- realestatinator/models/res_partner.py | 2 +- realestatinator/models/res_users.py | 6 +- .../views/estate_property_offer_views.xml | 48 +-- .../views/estate_property_views.xml | 390 +++++++++--------- 16 files changed, 519 insertions(+), 406 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/piechart/piechart.js create mode 100644 awesome_dashboard/static/src/piechart/piechart.xml create mode 100644 awesome_dashboard/static/src/stats_service.js diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index d8b34c328b6..1781e1e34f4 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,15 +1,28 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, onWillStart } 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 { PieChart } from "./piechart/piechart"; + class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout }; + static components = { Layout, DashboardItem, PieChart }; setup() { this.action = useService('action'); + + this.statsService = useService('awesome_dashboard.stats'); + this.stats = this.statsService.test(); + + + onWillStart(async () => { + this.stats = Object.entries(await this.statsService.getValuesMem()).filter((elt) => {return typeof(elt[1]) == typeof(1);}); + this.salesData = (await this.statsService.getValuesMem()).orders_by_size + }); + } customersButton() { diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 4f779eb7336..e7b5ecc3491 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -1,10 +1,19 @@ - - hello dashboard - - + + + + + +

+

+
+
+ + + +
diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..afbe6b57e65 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js @@ -0,0 +1,13 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = 'dashboard_item.dashboard_item'; + static props = { + size: {type: 'Number', optional: 'true',}, + slots: {type: 'Object', optional: 'true',}, + } + + setup() { + this.size = this.props.size ? this.props.size : 1; + } +} diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..138e870e914 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -0,0 +1,10 @@ + + + +
+ +
+ +
+ +
diff --git a/awesome_dashboard/static/src/piechart/piechart.js b/awesome_dashboard/static/src/piechart/piechart.js new file mode 100644 index 00000000000..d05151efacc --- /dev/null +++ b/awesome_dashboard/static/src/piechart/piechart.js @@ -0,0 +1,35 @@ +import { Component, onWillStart, useRef, onMounted } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = 'piechart.piechart'; + static props = { + data: {type: 'Object',}, + }; + + setup() { + onMounted(async () => { + const chartJS = await loadJS("/web/static/lib/Chart/Chart.js"); + + const config = { + type: 'pie', + data: { + labels: Object.keys(this.props.data), + datasets: [ + { + label: 'ds1', + data: Object.values(this.props.data), + } + ], + }, + options: { + responsive: true, + }, + }; + + + new Chart(document.getElementById('piechart'), config); + }); + + } +} diff --git a/awesome_dashboard/static/src/piechart/piechart.xml b/awesome_dashboard/static/src/piechart/piechart.xml new file mode 100644 index 00000000000..e7a85e48ad7 --- /dev/null +++ b/awesome_dashboard/static/src/piechart/piechart.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/awesome_dashboard/static/src/stats_service.js b/awesome_dashboard/static/src/stats_service.js new file mode 100644 index 00000000000..a37c9e82344 --- /dev/null +++ b/awesome_dashboard/static/src/stats_service.js @@ -0,0 +1,18 @@ + +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { memoize } from "@web/core/utils/functions"; + + +const test = () => {return [['test', 2], ['test2', 3]];} + +const getValues = async () => {return await rpc('/awesome_dashboard/statistics')}; + +export const StatsService = { + start() { + const getValuesMem = memoize(getValues); + return { test, getValues, getValuesMem }; + } +} + +registry.category("services").add("awesome_dashboard.stats", StatsService); diff --git a/realestatinator/models/__init__.py b/realestatinator/models/__init__.py index 006822a2a9c..a9bb2a2206a 100644 --- a/realestatinator/models/__init__.py +++ b/realestatinator/models/__init__.py @@ -1,6 +1,6 @@ -from . import res_users -from . import res_partner from . import estate_property -from . import estate_property_type -from . import estate_property_tags from . import estate_property_offer +from . import estate_property_tags +from . import estate_property_type +from . import res_partner +from . import res_users diff --git a/realestatinator/models/estate_property.py b/realestatinator/models/estate_property.py index 948ec1a726c..586776a9980 100644 --- a/realestatinator/models/estate_property.py +++ b/realestatinator/models/estate_property.py @@ -1,109 +1,109 @@ from odoo import api, exceptions, fields, models + class EstatePropery(models.Model): - _name = 'estate.property' - _description = 'real estate property' - _order = 'id desc' - _sql_constraints = [ - ('check_expected_price_positive', 'CHECK (0 < expected_price)', 'Check that the expected price is strictly positive'), - - ] + _name = 'estate.property' + _description = 'real estate property' + _order = 'id desc' + _sql_constraints = [ + ('check_expected_price_positive', 'CHECK (0 < expected_price)', 'Check that the expected price is strictly positive'), + + ] - sequence = fields.Integer('Sequence', default=0) - name = fields.Char('Title', required=True) - description = fields.Text('Description') - postcode = fields.Char('Postcode') - date_availability = fields.Date('Available Date', copy=False, default=fields.Date.add(fields.Date.today(), months=+3)) - expected_price = fields.Float('Expected Price') - selling_price = fields.Float('Selling Price', readonly=True, copy=False) - bedrooms = fields.Integer('Bedrooms', default=2) - living_area = fields.Integer('Living Area') - facades = fields.Integer('Facades') - garage = fields.Boolean('Garage') - garden = fields.Boolean('Garden') - garden_area = fields.Integer('Garden Area') - garden_orientation = fields.Selection(string='Garden Orientation', selection=[ - ('N', 'North'), - ('S', 'South'), - ('E', 'East'), - ('W', 'West') - ]) - active = fields.Boolean('Active', default=False) - state = fields.Selection(string='State', selection=[ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled') - ], default='new') - property_type_id = fields.Many2one('estate.property.type', string='Property Type') - buyer = fields.Many2one('res.partner', string='Buyer', copy=False) - sales_person = fields.Many2one('res.users', string='Sales Person', default=lambda self: self.env.user) - tag_ids = fields.Many2many('estate.property.tags', string='Tags') - offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') - total_area = fields.Integer('Total Area', readonly=True, compute='_compute_total_area') - best_price = fields.Float('Best Offer', compute='_compute_best_price') + sequence = fields.Integer('Sequence', default=0) + name = fields.Char('Title', required=True) + description = fields.Text('Description') + postcode = fields.Char('Postcode') + date_availability = fields.Date('Available Date', copy=False, default=fields.Date.add(fields.Date.today(), months=+3)) + expected_price = fields.Float('Expected Price') + selling_price = fields.Float('Selling Price', readonly=True, copy=False) + bedrooms = fields.Integer('Bedrooms', default=2) + living_area = fields.Integer('Living Area') + facades = fields.Integer('Facades') + garage = fields.Boolean('Garage') + garden = fields.Boolean('Garden') + garden_area = fields.Integer('Garden Area') + garden_orientation = fields.Selection(string='Garden Orientation', selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ]) + active = fields.Boolean('Active', default=False) + state = fields.Selection(string='State', selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], default='new') + property_type_id = fields.Many2one('estate.property.type', string='Property Type') + buyer = fields.Many2one('res.partner', string='Buyer', copy=False) + sales_person = fields.Many2one('res.users', string='Sales Person', default=lambda self: self.env.user) + tag_ids = fields.Many2many('estate.property.tags', string='Tags') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') + total_area = fields.Integer('Total Area', readonly=True, compute='_compute_total_area') + best_price = fields.Float('Best Offer', compute='_compute_best_price') - @api.depends('living_area', 'garden_area') - def _compute_total_area(self): - for line in self: - line.total_area = line.living_area + line.garden_area + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for line in self: + line.total_area = line.living_area + line.garden_area - @api.depends('offer_ids.price') - def _compute_best_price(self): - for record in self: - prices = [0] + record.offer_ids.mapped('price') - record.best_price = max(prices) + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + prices = [0] + record.offer_ids.mapped('price') + record.best_price = max(prices) - @api.onchange('garden') - def _set_garden_properties(self): - for record in self: - # record.garden_orientation = 'N' if record.garden else '' - # record.garden_area = 10 if record.garden else 0 + @api.onchange('garden') + def _set_garden_properties(self): + self.garden_orientation = 'north' if self.garden else '' + self.garden_area = 10 if self.garden else 0 - if record.garden: - if record.garden_orientation not in ['N', 'E', 'W', 'S']: - record.garden_orientation = 'N' - if record.garden_area == 0: - record.garden_area = 10 - else: - record.garden_orientation = '' - record.garden_area = 0 + # if record.garden: + # if record.garden_orientation not in ['north', 'east', 'west', 'south']: + # record.garden_orientation = 'north' + # if record.garden_area == 0: + # record.garden_area = 10 + # else: + # record.garden_orientation = '' + # record.garden_area = 0 - def mark_cancelled(self): - for record in self: - if record.state == 'cancelled': - raise exceptions.UserError('This property is already cancelled.') - - if record.state == 'sold': - raise exceptions.UserError('This property cannot be cancelled because it has already been sold.') - - record.state = 'cancelled' - record.active = False - - def mark_sold(self): - for record in self: - if record.state == 'sold': - raise exceptions.UserError('This property is already sold.') + def mark_cancelled(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError('This property is already cancelled.') + + if record.state == 'sold': + raise exceptions.UserError('This property cannot be cancelled because it has already been sold.') + + record.state = 'cancelled' + record.active = False + + def mark_sold(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError('This property is already sold.') - - if record.state == 'cancelled': - raise exceptions.UserError('This property cannot be sold because it has already been cancelled.') - - record.state = 'sold' - record.active = False + + if record.state == 'cancelled': + raise exceptions.UserError('This property cannot be sold because it has already been cancelled.') + + record.state = 'sold' + record.active = False - @api.constrains('selling_price') - def _check_selling_price(self): - for record in self: - if record.state not in ['offer_accepted', 'sold']: - return - if record.selling_price < 0.9 * record.expected_price: - raise exceptions.ValidationError('Selling price must be at least 90% of expected price.') + @api.constrains('selling_price') + def _check_selling_price(self): + for record in self: + if record.state not in ['offer_accepted', 'sold']: + return + if record.selling_price < 0.9 * record.expected_price: + raise exceptions.ValidationError('Selling price must be at least 90% of expected price.') - @api.ondelete(at_uninstall=False) - def _unlink(self): - for record in self: - if record.state not in ['new', 'cancelled']: - raise exceptions.UserError('Property must be either new or cancelled to be deleted.') + @api.ondelete(at_uninstall=False) + def _unlink(self): + for record in self: + if record.state not in ['new', 'cancelled']: + raise exceptions.UserError('Property must be either new or cancelled to be deleted.') diff --git a/realestatinator/models/estate_property_offer.py b/realestatinator/models/estate_property_offer.py index 9e3293737cb..de27042de20 100644 --- a/realestatinator/models/estate_property_offer.py +++ b/realestatinator/models/estate_property_offer.py @@ -1,56 +1,61 @@ from odoo import api, exceptions, fields, models + class EstatePropertyOffer(models.Model): - _name = 'estate.property.offer' - _description = 'estate property offer' - _order = 'price desc' - _sql_constraints = [ - ('check_offer_price_positive', 'CHECK (0 < price)', 'Check that the offer price is strictly positive.'), - ] - creation_date = fields.Date('Creation Date', default=fields.Date.today()) - price = fields.Float('Price') - status = fields.Selection(string='Status', selection=[ - ('accepted', 'Accepted'), - ('refused', 'Refused') - ], copy=False) - partner_id = fields.Many2one('res.partner', string='Partner') - property_id = fields.Many2one('estate.property', string='Property') - validity = fields.Integer('Validity', default=7) - date_deadline = fields.Date('Deadline', compute='_compute_deadline', inverse='_inverse_deadline') - property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) - - - @api.depends('validity') - def _compute_deadline(self): - for record in self: - record.date_deadline = fields.Date.add(record.creation_date, days=record.validity) - - def _inverse_deadline(self): - for record in self: - delta = record.date_deadline - record.creation_date - record.validity = delta.days - - def refuse_offer(self): - for record in self: - if record.status == 'accepted': - record.property_id.state = 'offer_received' - record.property_id.selling_price = 0 - record.property_id.buyer = None - record.status = 'refused' - - def accept_offer(self): - for record in self: - if record.property_id.selling_price != 0: - raise exceptions.UserError('An offer as already been accepted for this property.') - record.property_id.state = 'offer_accepted' - record.status = 'accepted' - record.property_id.selling_price = record.price - record.property_id.buyer = record.partner_id - @api.model - def create(self, vals): - estate_property = self.env['estate.property'].browse(vals["property_id"]) - if vals["price"] < estate_property.best_price: - raise exceptions.UserError(f'Offer must be higher than the current best offer({estate_property.best_price})') - if estate_property.state == 'new': - estate_property.state = 'offer_received' - return super().create(vals) + _name = 'estate.property.offer' + _description = 'estate property offer' + _order = 'price desc' + + + creation_date = fields.Date('Creation Date', default=fields.Date.today()) + price = fields.Float('Price') + status = fields.Selection(string='Status', selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], copy=False) + partner_id = fields.Many2one('res.partner', string='Partner') + property_id = fields.Many2one('estate.property', string='Property') + validity = fields.Integer('Validity', default=7, help='Number of days the offer is valid.') + date_deadline = fields.Date('Deadline', compute='_compute_deadline', inverse='_inverse_deadline') + property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) + + + _sql_constraints = [ + ('check_offer_price_positive', 'CHECK (0 < price)', 'Check that the offer price is strictly positive.'), + ] + + + @api.depends('validity') + def _compute_deadline(self): + for record in self: + record.date_deadline = fields.Date.add(record.creation_date, days=record.validity) + + def _inverse_deadline(self): + for record in self: + delta = record.date_deadline - record.creation_date + record.validity = delta.days + + def refuse_offer(self): + for record in self: + if record.status == 'accepted': + record.property_id.state = 'offer_received' + record.property_id.selling_price = 0 + record.property_id.buyer = None + record.status = 'refused' + + def accept_offer(self): + for record in self: + if record.property_id.selling_price != 0: + raise exceptions.UserError('An offer as already been accepted for this property.') + record.property_id.state = 'offer_accepted' + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer = record.partner_id + @api.model + def create(self, vals): + estate_property = self.env['estate.property'].browse(vals["property_id"]) + if vals["price"] < estate_property.best_price: + raise exceptions.UserError(f'Offer must be higher than the current best offer({estate_property.best_price})') + if estate_property.state == 'new': + estate_property.state = 'offer_received' + return super().create(vals) diff --git a/realestatinator/models/estate_property_tags.py b/realestatinator/models/estate_property_tags.py index b822ea41fbe..559fb1e0c84 100644 --- a/realestatinator/models/estate_property_tags.py +++ b/realestatinator/models/estate_property_tags.py @@ -1,11 +1,12 @@ from odoo import fields, models + class EstatePropertyTags(models.Model): - _name = 'estate.property.tags' - _description = 'estate property tag' - _order = 'name' - _sql_constraints = [ - ('name_unique', 'UNIQUE (name)', 'make sure tag name is unique.') - ] - name = fields.Char('Name', required=True) - color = fields.Integer('Colour') + _name = 'estate.property.tags' + _description = 'estate property tag' + _order = 'name' + _sql_constraints = [ + ('name_unique', 'UNIQUE (name)', 'make sure tag name is unique.') + ] + name = fields.Char('Name', required=True) + color = fields.Integer('Colour') diff --git a/realestatinator/models/estate_property_type.py b/realestatinator/models/estate_property_type.py index aee463bd757..1327e76c8c2 100644 --- a/realestatinator/models/estate_property_type.py +++ b/realestatinator/models/estate_property_type.py @@ -1,21 +1,22 @@ from odoo import api, fields, models + class EstatePropertyType(models.Model): - _name = 'estate.property.type' - _description = 'real estate property type' - _order = 'name, sequence' - _sql_constraints = [ - ('name_unique', 'UNIQUE (name)', 'make sure type name is unique.') - ] - - name = fields.Char('Name', required=True) - property_ids = fields.One2many('estate.property', 'property_type_id', string='Property Type') - sequence = fields.Integer('sequence', default=1) - offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers') - offer_count = fields.Integer(string='Offer Count', compute='_count_offers') + _name = 'estate.property.type' + _description = 'real estate property type' + _order = 'name, sequence' + _sql_constraints = [ + ('name_unique', 'UNIQUE (name)', 'make sure type name is unique.') + ] + + name = fields.Char('Name', required=True) + property_ids = fields.One2many('estate.property', 'property_type_id', string='Property Type') + sequence = fields.Integer('sequence', default=1) + offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers') + offer_count = fields.Integer(string='Offer Count', compute='_count_offers') - @api.depends('offer_ids') - def _count_offers(self): - for record in self: - record.offer_count = len(record.offer_ids) + @api.depends('offer_ids') + def _count_offers(self): + for record in self: + record.offer_count = len(record.offer_ids) diff --git a/realestatinator/models/res_partner.py b/realestatinator/models/res_partner.py index 8319abec5f6..ba42cb0213e 100644 --- a/realestatinator/models/res_partner.py +++ b/realestatinator/models/res_partner.py @@ -5,4 +5,4 @@ class Partner(models.Model): - _inherit = 'res.partner' + _inherit = 'res.partner' diff --git a/realestatinator/models/res_users.py b/realestatinator/models/res_users.py index ebd3eb31d5f..ec4b54f380d 100644 --- a/realestatinator/models/res_users.py +++ b/realestatinator/models/res_users.py @@ -4,6 +4,6 @@ class Users(models.Model): - _inherit = 'res.users' - - property_ids = fields.One2many('estate.property', 'sales_person', string='Properties') + _inherit = 'res.users' + + property_ids = fields.One2many('estate.property', 'sales_person', string='Properties') diff --git a/realestatinator/views/estate_property_offer_views.xml b/realestatinator/views/estate_property_offer_views.xml index 5818faedc0d..4e5716552ad 100644 --- a/realestatinator/views/estate_property_offer_views.xml +++ b/realestatinator/views/estate_property_offer_views.xml @@ -1,27 +1,27 @@ - - estate.property.offer.action - estate.property.offer - list,form - [('property_type_id', '=', active_id)] - - - estate.property.offer.view.list - estate.property.offer - - - - - - - -

-

-
+

+ + + + + - - - -
diff --git a/awesome_dashboard/static/src/dashboard/liste.js b/awesome_dashboard/static/src/dashboard/liste.js new file mode 100644 index 00000000000..9d25459ed1a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/liste.js @@ -0,0 +1,63 @@ +import { registry } from "@web/core/registry"; + + +const numbers = [ + { + title: 'Some title.', + value: 103, + }, + { + title: 'Some other title.', + value: 418, + }, + { + title: 'Average Quantity', + source: 'average_quantity', + }, + { + title: 'Average Time', + source: 'average_time', + }, + { + title: 'Number of Cancelled Orders', + source: 'nb_cancelled_orders', + }, + { + title: 'Number of New Orders', + source: 'nb_new_orders', + }, + { + title: 'Total Amount', + source: 'total_amount', + }, +]; + +registry.category('awesome_dashboard.data').add('numbers', numbers); + +const graphs = [ + { + id: 'graph1', + title: 'An graph.', + data: { + value1: 9, + value2: 3, + value3: 9, + }, + }, + { + id: 'graph2', + title: 'Another graph.', + data: { + 'Jean-Eud le Tacos Vegan': 1536, + 'Bérénice la Saucisse Créatrice': 32, + 'Monsieur Puel Monsieur': 3, + }, + }, + { + id: 'shirt_size_pie', + title: 'T-Shirt Sales by Size', + source: 'orders_by_size', + }, +]; + +registry.category('awesome_dashboard.data').add('graphs', graphs); diff --git a/awesome_dashboard/static/src/dashboard/number_card/.number_card.js.swp b/awesome_dashboard/static/src/dashboard/number_card/.number_card.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..54c30b08d54593927eed7ac641dff53508ae1c25 GIT binary patch literal 12288 zcmeI2!D%r{Xv8-<|#D|IK{!%U+gDul3^LW4i9I5oi|( zxqA8c=)>q1S=b^(41_S@)7pq>u^KU{g`8`BYbFfrMQ2*1A&XSN_P7!;-ySfv(-SNm z1WGd<_5+pngDRM0agV3lePeL?{;rz4Bn~10Bwz?+dbqm2NN%p(Xqm_BjcfGM#pkAr z4GACtB!C2v01`j~NB{{Sf&YYnmNoJY_PS8+_ey!Mow%2ectZk600|%gB!C2v01`j~ zNB{{S0VIF~&L9CcAY|zrAs<2J|Nrs#|F0!NzJtDj4nglhTcFz@4|H^%ke{G0pj}W1 z8iHPeI-nZpaFLK0^Z;}RbOdYs0DT3027LnAbzz>fDtyD~U26L>6|#tQz*CIBVo`R&P=xNeIYp>VTUDYCb#x-J2u&mw zp*Hopf<7}DzGtS$DUTGV2ghZ&Wh$M9$>`~6#k#hMlBWk|7w&o|%AX#X+C&Mssunmu z+I-UWB})}wc9B<}6|?@juP7qJuxPqZ>&x&a5& zX%2A8Va;P1F>T&3&po&9^x;k7`%14TE+i@?J0tqm)Jd%PGO erXr>d-w*8RcynoV9Mf@bUOMj==kWI;u>ApX5Nj6z literal 0 HcmV?d00001 diff --git a/awesome_dashboard/static/src/dashboard/number_card/.number_card.xml.swp b/awesome_dashboard/static/src/dashboard/number_card/.number_card.xml.swp new file mode 100644 index 0000000000000000000000000000000000000000..70c961054c83af55ff82dab8358c6e1d1d06955c GIT binary patch literal 12288 zcmeI&&u-H&90zc3;!-3A8$#mfdFR$$E1_zn*#T)12W|{;L6e)9G?EiX_OE3i1kb?J z?7GL`1$Y2>0)DOvR3MG(rhP5_V#j}ePCmCNo(*3;e<3G>k>K(`i1SC^svkQ~#P(|; zeCd7a{$HsK`Mxt9`R>Z8pHf1-y3m4rsEv!-ytC0a_N+8^00Izz00bZa0SG_<0uX?}4HPh?5SKeb+_}#W z&;S2_egA*u`wNaQ93MH}a2#+v=Gf!dX&n=0fx`O}XfC^^6aj=RI!rviV3xdhhI172f%9Bwbz2sS4J%PgS0gq`r;C zR4t8LSf%q!&qpZR3t!f4HC3lCPx=!jk8|-8#660! literal 0 HcmV?d00001 diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..6833f774c5b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,27 @@ +import { Component } from "@odoo/owl"; +import { Card } from "../card/card"; + + +export class NumberCard extends Component { + static template = 'number_card.number_card'; + static components = { Card }; + static props = { + title: {type: 'String', optional: 'true',}, + value: {type: 'Number', optional: 'true',}, + size: {type: 'Number', optional: 'true',}, + stringProps: {type: 'String', optional: 'true',}, + + } + setup() { + if (this.props.stringProps) { + this.stringProps = JSON.parse(this.props.stringProps); + this.title = this.stringProps.title ? this.stringProps.title : 'No Title.'; + this.value = this.stringProps.value ? this.stringProps.value : 0; + this.size = this.stringProps.size; + } else { + this.title = this.props.title ? this.props.title : 'No Title.'; + this.value = this.props.value ? this.props.value : 0; + this.size = this.props.size; + } + } +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..c91a38c7d28 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,10 @@ + + + + +

+

+
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.js.swp b/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..3c565d7ca8e62bd481c645014e8202dc2a5db456 GIT binary patch literal 12288 zcmeI2O^6&t6vr!pU?wp}A}T6EISI^cnDp#sO|tH0qqqjlLO#gi2PA8@XS!y#vprpH zSIzEDHe*093SRs;co7645(%Dj@*;RK9yABRtK=pM#*3oh|5f$O^dx4Jqmdf;y{WEx z^;*K$wfCt4=!y1qdH ztH$WImTBlms_L%@C0pW5;H$-&^wXfKw6DW@Ri*XnpfU-?nL4%h3f@_@ZZg;C6mSX* z6lm+lo{4R2eDByORqiZ4#UI-7(xBayoB~b(r+`zyDc}@v3OEIv0#1Sdy#hMfz%F2x z8?%{;?0dsa-`S)4atb&FoB~b(r+`zyDc}@v3OEIv0!{&^fK%WeRKO1yTiAp@{ojYd zRH;{R(~oKY_2nJK$~b0+<0e z?q}>ba20$5J_R3v1#kvD0Uif`Ze{Eb@B_F8z6ald&%q_|A@~5i4?18ToB*?+0?Od` zEsR|Om%(S?B6tt1fE2tAJg^5m1~!Ag3djk*248|p;7xE2q+ki028Y2RFaZvLaj+3= z0Do^r9B>_61K)zH;0m}1J^}B72G|5XhW7KI40eJi!K2_2@G#f`%=+Ae`^yd4Dc}@v z3jFsK=(0kWF>6_&&>fkAE$pt6MJ+yhF;re%##*duPw7r1yqaHMYNoOs2QxB~DWB#| z5u0M!_AkI+>3%I7$|HRG5MS82YYnb1iq-N+_uq>PCTYJvTnD{(tu2bxQ%8Nh=rtlK z)3Pb1ejLaa671$<`$y&x_inuMdEk#Er%I!x3NMY3*-K`e%+qA{k=ak?0GSCg&yexS z)X3Dy1Y`u6MrocVG9;diTe7W0OD>E4oYu&zTIC`MBax^MZ^=NIy50T$sfYuST4`l` z6uN5>l&zv`3~t$~nPkJxuY^%_(ss}LQ>Scfi+-vxnpmvx87i0aL#}WvL?dk4RW-f3 zW`h*E6$()(q(UWF>u_02a-Ft?t(J)nQ9H@a^!m>8S*L7gp?lzKf07@sofUQME&EYh zC_4aZ80oneBEKf0N$9JvIyP~^hBgtD(8zVlHW*(#WW&XtSR6;bQGz!`dNj2BaiTz8 zAe2sJCp$A?gJ;@P<4Bg+c1neAr|}d;8MxUa$)~Y+?I_~s&-47!Q9TZ*XoRr{C>r_> zxAQ>O+bt1muPOB5NKo z^Dvk$?piZx!=PKFHaiC6ieN22U1}%g1zeQ9CYn83Xii2q(}CWOr9*?`Ge^bpnq5Nj zqTNs3ja0^4hdLr}^N^%6QJ$%?>tF+h=Wg;)sL44{{NWI$=GlBuQ-e)jiY2GASDNHw zI3Of}$r1*`A@DpD0MB;onVnUlNpKMi+ zyA5V~Q|-`v`$FZMpxEr(@nc@%r%KqpXqPbOW42uXB;49e`r%T*&a&soTw1yQm``@> z=ZvDbvF(-R(=9{ySgB%exG=bDSJHXle5~E7iS)K{N$r8Ny{c~OY_`C_d;KBX_sF@? z1X7X^=FM{4>;DO(7bl;^$MjaCRP)PgISM!rTB?Z znEv=D0e)N*pXtR$KE^^Mf@?1aZ=|xti=KxY0?O*(ncH)8m)2z}sz`); hJ@492gXQY|4p#bi0#y!v?<(2dG6-zn!(WLI@gJi%7TN#+ literal 0 HcmV?d00001 diff --git a/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.xml.swp b/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.xml.swp new file mode 100644 index 0000000000000000000000000000000000000000..4b8abe91fa31a5125d2f920faea15cc8ab45cf36 GIT binary patch literal 12288 zcmeI&!Aiq07zgldw+cGZ7pU>-*13U<*>;E^2zqlOUPji2t0559}exR5_!6ayq{#A$X;Zt^Fi%? zl&<_W6d?eCWr0oBY#i2YZlCYDyF1vR_NjB|Nr^-{|C+AC|)UE zC?bjriW7=siZ+G4=YRqY0uX=z1Rwwb2tWV=5P$##Ah1>fzGvhpPlb^>WXz#y$Q0o;3(9rRXYpQB&0v?HMA~g5x3dbKd!)ZzCDBvbBsicbUQQB@`?{{_bAOmi%oRZVk znHW)OapHYGpt`?!5SPxuw{ + + `); + this.render = async () => { + + this.context = document.getElementById(this.id); + if (this.context == null || this.context == undefined) { + return; + } + + if (this.chart) { + this.chart.destroy(); + } + + const chartJS = await loadJS("/web/static/lib/Chart/Chart.js"); + + const config = { + type: 'pie', + data: { + labels: Object.keys(this.data), + datasets: [ + { + label: 'ds1', + data: Object.values(this.data), + } + ], + }, + options: { + responsive: true, + }, + }; + + + this.chart = new Chart(this.context, config); + + } + + onWillRender(() => { + this.render(); + }); + + onMounted(() => { + this.render(); + /* + // epilepsy mode + this.context.onmousemove = () => { + const hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] + const genHex = () => `${hex[Math.floor(Math.random() * 16)]}${hex[Math.floor(Math.random() * 16)]}` + const genColor = () => `#${genHex()}${genHex()}${genHex()}` + this.context.style.backgroundColor = genColor(); + } + */ + }); + + + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_card/pie_card.xml b/awesome_dashboard/static/src/dashboard/pie_card/pie_card.xml new file mode 100644 index 00000000000..45654bd3ada --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_card/pie_card.xml @@ -0,0 +1,10 @@ + + + + +

+ +
+
+ +
diff --git a/awesome_dashboard/static/src/piechart/piechart.js b/awesome_dashboard/static/src/dashboard/piechart/piechart.js similarity index 55% rename from awesome_dashboard/static/src/piechart/piechart.js rename to awesome_dashboard/static/src/dashboard/piechart/piechart.js index d05151efacc..0c1128f0211 100644 --- a/awesome_dashboard/static/src/piechart/piechart.js +++ b/awesome_dashboard/static/src/dashboard/piechart/piechart.js @@ -1,4 +1,4 @@ -import { Component, onWillStart, useRef, onMounted } from "@odoo/owl"; +import { Component, onMounted, useState, onWillRender } from "@odoo/owl"; import { loadJS } from "@web/core/assets"; export class PieChart extends Component { @@ -8,7 +8,21 @@ export class PieChart extends Component { }; setup() { - onMounted(async () => { + this.state = useState({data: null}); + + this.chart = null; + + this.render = async () => { + + this.context = document.getElementById('piechart'); + if (this.context == null || this.context == undefined) { + return; + } + + if (this.chart) { + this.chart.destroy(); + } + const chartJS = await loadJS("/web/static/lib/Chart/Chart.js"); const config = { @@ -28,8 +42,18 @@ export class PieChart extends Component { }; - new Chart(document.getElementById('piechart'), config); + this.chart = new Chart(this.context, config); + + } + + onWillRender(() => { + this.render(); }); + onMounted(() => { + this.render(); + }); + + } } diff --git a/awesome_dashboard/static/src/piechart/piechart.xml b/awesome_dashboard/static/src/dashboard/piechart/piechart.xml similarity index 100% rename from awesome_dashboard/static/src/piechart/piechart.xml rename to awesome_dashboard/static/src/dashboard/piechart/piechart.xml diff --git a/awesome_dashboard/static/src/dashboard/stats_service.js b/awesome_dashboard/static/src/dashboard/stats_service.js new file mode 100644 index 00000000000..6500e91dcf9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/stats_service.js @@ -0,0 +1,45 @@ +import { reactive } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { memoize } from "@web/core/utils/functions"; + +const cache = reactive({values: null}, () => { + if (callback) { + callback(cache.values); + } + +}); +let isActive = true; +let callback = null; + +const nextReload = () => { + setTimeout(async () => { + + if (isActive) { + nextReload(); + cache.values = await rpc('/awesome_dashboard/statistics'); + } + + }, 600000); +} + +const setActive = (cb) => {isActive = true;nextReload();callback = cb;}; +const clearActive = () => {isActive = false;callback = null}; + +const test = () => {return [['test', 2], ['test2', 3]];} + +const getValues = async () => { + if (cache.values == null) { + cache.values = await rpc('/awesome_dashboard/statistics'); + } + + return cache.values; +}; + +export const StatsService = { + start() { + return { test, getValues, setActive, clearActive }; + } +} + +registry.category("services").add("awesome_dashboard.stats", StatsService); diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml deleted file mode 100644 index 138e870e914..00000000000 --- a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - -
- -
- -
- -
diff --git a/awesome_dashboard/static/src/dashboard_loader.js b/awesome_dashboard/static/src/dashboard_loader.js new file mode 100644 index 00000000000..0ed60d7cb1c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,16 @@ +import { Component } from "@odoo/owl"; +import { LazyComponent } from "@web/core/assets"; +import { registry } from "@web/core/registry"; +import { Dashboard } from "./dashboard/dashboard"; +import { PieChart } from "./dashboard/piechart/piechart"; + +export class DashboardLoader extends Component { + static template = 'dashboard_loader.dashboard_loader'; + static components = { Dashboard, LazyComponent } + setup() { + this.thing = 'awesome_dashboard.dashboard' + } +} + + +registry.category('actions').add('awesome_dashboard.dashboard', DashboardLoader) diff --git a/awesome_dashboard/static/src/dashboard_loader.xml b/awesome_dashboard/static/src/dashboard_loader.xml new file mode 100644 index 00000000000..1cc8a2ad101 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/awesome_dashboard/static/src/stats_service.js b/awesome_dashboard/static/src/stats_service.js deleted file mode 100644 index a37c9e82344..00000000000 --- a/awesome_dashboard/static/src/stats_service.js +++ /dev/null @@ -1,18 +0,0 @@ - -import { registry } from "@web/core/registry"; -import { rpc } from "@web/core/network/rpc"; -import { memoize } from "@web/core/utils/functions"; - - -const test = () => {return [['test', 2], ['test2', 3]];} - -const getValues = async () => {return await rpc('/awesome_dashboard/statistics')}; - -export const StatsService = { - start() { - const getValuesMem = memoize(getValues); - return { test, getValues, getValuesMem }; - } -} - -registry.category("services").add("awesome_dashboard.stats", StatsService); diff --git a/realestatinator/models/estate_property.py b/realestatinator/models/estate_property.py index 586776a9980..8a5f656ce6a 100644 --- a/realestatinator/models/estate_property.py +++ b/realestatinator/models/estate_property.py @@ -1,14 +1,10 @@ -from odoo import api, exceptions, fields, models +from odoo import _, api, exceptions, fields, models class EstatePropery(models.Model): _name = 'estate.property' _description = 'real estate property' _order = 'id desc' - _sql_constraints = [ - ('check_expected_price_positive', 'CHECK (0 < expected_price)', 'Check that the expected price is strictly positive'), - - ] sequence = fields.Integer('Sequence', default=0) @@ -45,6 +41,12 @@ class EstatePropery(models.Model): offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') total_area = fields.Integer('Total Area', readonly=True, compute='_compute_total_area') best_price = fields.Float('Best Offer', compute='_compute_best_price') + + + _sql_constraints = [ + ('check_expected_price_positive', 'CHECK (0 < expected_price)', 'Check that the expected price is strictly positive'), + + ] @api.depends('living_area', 'garden_area') def _compute_total_area(self): @@ -74,10 +76,10 @@ def _set_garden_properties(self): def mark_cancelled(self): for record in self: if record.state == 'cancelled': - raise exceptions.UserError('This property is already cancelled.') + raise exceptions.UserError(_('This property is already cancelled.')) if record.state == 'sold': - raise exceptions.UserError('This property cannot be cancelled because it has already been sold.') + raise exceptions.UserError(_('This property cannot be cancelled because it has already been sold.')) record.state = 'cancelled' record.active = False @@ -85,11 +87,11 @@ def mark_cancelled(self): def mark_sold(self): for record in self: if record.state == 'sold': - raise exceptions.UserError('This property is already sold.') + raise exceptions.UserError(_('This property is already sold.')) if record.state == 'cancelled': - raise exceptions.UserError('This property cannot be sold because it has already been cancelled.') + raise exceptions.UserError(_('This property cannot be sold because it has already been cancelled.')) record.state = 'sold' record.active = False @@ -100,10 +102,10 @@ def _check_selling_price(self): if record.state not in ['offer_accepted', 'sold']: return if record.selling_price < 0.9 * record.expected_price: - raise exceptions.ValidationError('Selling price must be at least 90% of expected price.') + raise exceptions.ValidationError(_('Selling price must be at least 90% of expected price.')) @api.ondelete(at_uninstall=False) def _unlink(self): for record in self: if record.state not in ['new', 'cancelled']: - raise exceptions.UserError('Property must be either new or cancelled to be deleted.') + raise exceptions.UserError(_('Property must be either new or cancelled to be deleted.')) diff --git a/realestatinator/models/estate_property_offer.py b/realestatinator/models/estate_property_offer.py index de27042de20..9a5bb8688e4 100644 --- a/realestatinator/models/estate_property_offer.py +++ b/realestatinator/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import api, exceptions, fields, models +from odoo import _, api, exceptions, fields, models class EstatePropertyOffer(models.Model): @@ -46,13 +46,19 @@ def refuse_offer(self): def accept_offer(self): for record in self: if record.property_id.selling_price != 0: - raise exceptions.UserError('An offer as already been accepted for this property.') + raise exceptions.UserError(_('An offer as already been accepted for this property.')) record.property_id.state = 'offer_accepted' record.status = 'accepted' record.property_id.selling_price = record.price record.property_id.buyer = record.partner_id - @api.model + + + @api.model_create_multi def create(self, vals): + if "property_id" not in vals.keys(): + raise exceptions.UserError(f'A property must be provided for an offer.') + if "price" not in vals.keys(): + raise exceptions.UserError(f'A price must be provided for an offer.') estate_property = self.env['estate.property'].browse(vals["property_id"]) if vals["price"] < estate_property.best_price: raise exceptions.UserError(f'Offer must be higher than the current best offer({estate_property.best_price})') From b43ff98206879fe4f2a766ef3438f82d2bc7ec8d Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Thu, 27 Mar 2025 13:19:24 +0100 Subject: [PATCH 11/12] [FIX] awesome_dashboard: Fixed error on updating pie chart --- .../static/src/dashboard/.dashboard.js.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/.dashboard.xml.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/.liste.js.swp | Bin 12288 -> 12288 bytes .../src/dashboard/.stats_service.js.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/dashboard.js | 7 ++++++ .../static/src/dashboard/dashboard.xml | 2 +- .../src/dashboard/pie_card/.pie_card.js.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/pie_card/pie_card.js | 20 ++++++++++++++---- 8 files changed, 24 insertions(+), 5 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard/.dashboard.js.swp b/awesome_dashboard/static/src/dashboard/.dashboard.js.swp index b829a9effbbe165d3c9c97b36379ad5427717549..9cde4dffa79c00f98925726a6a4d6572725bc3a5 100644 GIT binary patch delta 572 zcmY+=JxE(o6u|Lg)R@;aM8)DDg*zmYfW|7KDQZ-RXofln34$m|OopqO(o?K6=kM9z(4=Nn$5~5xt zG5jYR^Y(~iQp89b#@CW>FM95+nkhEam%W~!)gt4y&!aPTgBo4Sdqm>sM=yTA7WsuN zqEKi?BktTHW!&HrCSn*t6y4~A8!nu^5;?^Q3iyOUyhj)hnyksaCh`{>m_Qw_TwKH< z4zLLut4JUY7mn&gK4Tma^r8oE;X>KTCvc1-l(3I&y% zu!C)E;c7r(UU9sdAs)O9le delta 464 zcmaLTJ4iwS6vpwRnPnyFDm^Zh&``dTYH3O=dq6s5OQoeqlw8mtYAA;sOX^P18Z)Lb%= z%l=!M$JM4~T*2zJJF}_uVs53{*W3$&UEyx7#Q+X^7AJiRN3gVM;`zTAq@@y delta 44 ycmZojXh={ { + this.toUpdate.push(f) + }; this.testData = registry.category('awesome_dashboard.data').get('graphs').map((elt) => JSON.stringify(elt)); this.state = useState({numbers: null, graphs: null, data: null, happygary: 'ᕦ( ᐛ )ᕡ'}); this.onStatsUpdate = (vals) => { + for (const f of this.toUpdate) { + f(); + } this.state.data = vals; } diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index cab57ef6de3..c621ef36374 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -9,7 +9,7 @@ - + diff --git a/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.js.swp b/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.js.swp index 3c565d7ca8e62bd481c645014e8202dc2a5db456..5702e54b1c0ff198696765cebb5adc060b371441 100644 GIT binary patch delta 981 zcmZ|OPe>F|9KiA4YN=D^u2_&n@XBy^tyU5L;pRVz=s!VN5kzk5=x%H?>+Ec1vaY8N zQ9&*J(Ta>t5rMW4(IL|6l44MF>sp8GloZ|SJ1&VHddz3$&CKup-u&KUhP#Hl)aja| zdk?CnK&{X`BF{R$^zLytNZxS~v)eSUO>UXkHRE>mWlKHt?(}rN$m5l{&^~7f?W$WO zj8?Q@2g)#4A~J&)xQtV%MIk<}5gElz+`uKAK@F-gS1j@kqZmN~R$HNArJF~BA@XI?=g#exSQs58@DhF0}=G% z2s#ji7cPtyh&16hTlN|+@f=SvfpI*6v!zp*#y#A{9gJXU%eq$#icd!XY~xQ9k{ASdZIL)9|Z7;xx^UL=%BER2s^W;_wF^xkN~)~(KXcgWU# zKEG;d&CL?+Q9j?2$b@Z0jo!n~D7C3Y8Oc~oRaUD1MdaM?H5P6i6G8UAQg)9u$YY@5_N)+RQ69 zBc;2sQc^a?0!i7*PLz=%%3n_XPG@n>`ReP;>+`zSTh`vxqSd&|g>Z=U+n)w2)he-c zh#38bv38bs>3z|3tn`h0`YiG5_!NO0%$`4B8eie zc)=siv4UlI(11vS$Q#1A#7=?)WRpTiH?ok4uXvG9+~XEExW+#Auz?`fF%2(T(Trj^ z@opD6!69}L!WyRF$2i>ffJg~N2IBB-<567T6nj|24Ez{DCmK Ap8x;= diff --git a/awesome_dashboard/static/src/dashboard/pie_card/pie_card.js b/awesome_dashboard/static/src/dashboard/pie_card/pie_card.js index ee6f88d733c..5821aca426f 100644 --- a/awesome_dashboard/static/src/dashboard/pie_card/pie_card.js +++ b/awesome_dashboard/static/src/dashboard/pie_card/pie_card.js @@ -1,4 +1,4 @@ -import { Component, onMounted, useState, onWillRender, markup } from "@odoo/owl"; +import { Component, onMounted, useState, onWillRender, markup, onWillPatch, onWillUpdateProps } from "@odoo/owl"; import { loadJS } from "@web/core/assets"; import { Card } from "../card/card"; @@ -12,6 +12,7 @@ export class PieCard extends Component { size: {type: 'Number', optional: 'true',}, id: {type: 'String', optional: 'true',}, stringProps: {type: 'String', optional: 'true',}, + registerUpdate: {type: 'Function', optional: 'true',}, }; setup() { @@ -34,6 +35,17 @@ export class PieCard extends Component { this.title = this.props.title; } + + + if (this.props.registerUpdate) { + if ((this.props.stringProps != null && this.props.stringProps != undefined) && this.stringProps.source) { + this.props.registerUpdate(() => { + if (this.chart) { + this.chart.destroy(); + } + }); + } + } this.state = useState({data: null}); @@ -52,8 +64,9 @@ export class PieCard extends Component { return; } - if (this.chart) { - this.chart.destroy(); + try { + this.chart.clear(); + } catch (exception) { } const chartJS = await loadJS("/web/static/lib/Chart/Chart.js"); @@ -74,7 +87,6 @@ export class PieCard extends Component { }, }; - this.chart = new Chart(this.context, config); } From 7f83284643be3cfdf9f898c06665f2d4e3c17189 Mon Sep 17 00:00:00 2001 From: "gabriel (gato)" Date: Thu, 27 Mar 2025 14:54:55 +0100 Subject: [PATCH 12/12] [FIX] awesome_dashboard: Fixed error on updating pie chart. Again. --- .../static/src/dashboard/.dashboard.js.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/.dashboard.xml.swp | Bin 12288 -> 12288 bytes .../src/dashboard/.stats_service.js.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/dashboard.js | 8 +------ .../static/src/dashboard/dashboard.xml | 2 +- .../src/dashboard/pie_card/.pie_card.js.swp | Bin 12288 -> 12288 bytes .../static/src/dashboard/pie_card/pie_card.js | 21 +++++------------- .../static/src/dashboard/stats_service.js | 2 +- 8 files changed, 8 insertions(+), 25 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard/.dashboard.js.swp b/awesome_dashboard/static/src/dashboard/.dashboard.js.swp index 9cde4dffa79c00f98925726a6a4d6572725bc3a5..939b964c0cc5cd1b8e9fe47f870c75a75b6b66cb 100644 GIT binary patch delta 511 zcmZ9|&nv@W9Ki9It>eci4@jUfBPb!cKm?mw*1B0d>t3wEb$kRwZAXg!Bj4do7ju^)9UM_Nn8(iZE zhge4jL-1h`c6{nYu5gAN(pbS9>hV}6vWoFCNs5oe12-(Fg&HrVBG1Uci?0%{j!T^4 z1iQ!}jv)-d3m5uf#fO#$;0<})VjtT`Yn>utl3sMd4jZiKK;f^>v4c%4V-zm*!H6$S zR;B4JQXX2{tD?cs>|!jMuue_Hf}uz>(blYVnwyk)vtIiON%mNxzDb3EULc&1tw>N delta 568 zcmY+=&nu*H9Ki99U*mcFXpEH8=G(?JBZigy2!k3i2M%f`wXF>184l~wJde??EN3@8 zi9Z3Y!jyx9%_2X`#buqB^VS@kR^G_Lr(X5x`}xtg&*I49h#IYrhWk}Zph1Wdk!bf_ zBJ6IG>>&|z+%%UDD|bRy?yTXorzGPp-p>&k$a`%ac08(gAyq6AK|7jpRwQzY1VT_K zM=2f(MKbt@Kd=x+4?<|fClo@%Wr4^A&as7W=tL`8@Tkjz{MSY9v5GUgUBs6b%XyXLCX=S3$BpTF%nG0dd$jhR!H ztqf%v%AT0C0=5~M9*@~^znXJaYP`-t&Gn!@|I+`i9+;l7CVYmUMKABkcw*ABGPNLQf(^x30voKA^-pY diff --git a/awesome_dashboard/static/src/dashboard/.dashboard.xml.swp b/awesome_dashboard/static/src/dashboard/.dashboard.xml.swp index 6e6327f812289a9c1d05a31eb698242610aacfdb..64c966ad3b9390b85756689976c7565ec91ab46c 100644 GIT binary patch delta 103 zcmZojXh={7#Q+X^7AJiRN3fqK%P~Wmw};n yGN*#I)KVa8E)Y)y;%*@B0^(XAp2E9XQQ$q#WM5r_$q~BlleP4uHs8}#VgdkO6dG{= delta 140 zcmZojXh={(^b diff --git a/awesome_dashboard/static/src/dashboard/.stats_service.js.swp b/awesome_dashboard/static/src/dashboard/.stats_service.js.swp index 637097aa3a0519a93e3b6e25d4bfe75d564ab9d3..502785453dfad6a2b7fb36d8ba983cfe675a429f 100644 GIT binary patch delta 163 zcmZojXh={n=vp&Ffin&`q+4#a*y>S1mXZ7X4!mF!HS!)Xfu7#Q+X^7AJiRN3fqK%Uizn}LC8 zGN(d%qyQfS10N9c0`U=E28K2uZUtf=Aod1gZ6M|Y;)^^C4CjFOED&!8;w?bD8HlF> maTgGW0C6A?`vb8b5HoE)sbIyunN>%SiP_A+V6(X1OGW^-{vP1~ diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index 8bd7a48d720..138a50ba13f 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -14,17 +14,11 @@ class AwesomeDashboard extends Component { static components = { Layout, Card, PieCard, NumberCard, PieChart }; setup() { - this.toUpdate = []; - this.registerUpdate = (f) => { - this.toUpdate.push(f) - }; this.testData = registry.category('awesome_dashboard.data').get('graphs').map((elt) => JSON.stringify(elt)); this.state = useState({numbers: null, graphs: null, data: null, happygary: 'ᕦ( ᐛ )ᕡ'}); + this.onStatsUpdate = (vals) => { - for (const f of this.toUpdate) { - f(); - } this.state.data = vals; } diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index c621ef36374..cab57ef6de3 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -9,7 +9,7 @@ - + diff --git a/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.js.swp b/awesome_dashboard/static/src/dashboard/pie_card/.pie_card.js.swp index 5702e54b1c0ff198696765cebb5adc060b371441..f99ad83afb7fab570af11c9d5fabf0059c2f47c0 100644 GIT binary patch delta 798 zcmYk)TWHf@6u|M5$#k9FIv=Ls1wu7jJ83K8idvk)CSG7qiVo4O)3s~C8fimyLT$kZ zQ7E$nl%WXP*`ZZYgRg?%i#TuKoT&Ju4!i6{JxyzeCG?vms~g(R`;*k zwXIWK>t8KIgUI;q5B=?~pj35<=zY3gxWD3d$EZt*S_>McUD?)Zk&km{r$+4-;-*XF z1iG;gYv9C0y~rEfKn^?L!AzaV6ejT=4{#c%uo)egaf(diF-9?rA*W=emn4j(Sb`t( zMZV(|Uf?;N;VGVA1SJ%327^dr4?3|0UTFAID{>cCF^nQEqX*sCUYiwJN8&>bzBzac zyoQA`inxG0($En{46Ud~9p2XPbuf;X7{fhWM+sL@Ko$dtV55EUZXOFG2n9dcj<1-) zBtGL4>}?uB5f^b5gGga7f_TVwmT(CtkwhNHa2`YWKkm4bbIjw~V)K^PSe+3^M$EFd z90@cntlSZCI5OT4qoXBYkLERP*`nX2KQk(425ReBg~M*M%G!ito@);6j7L?E5lI|w zkM$-EulhIj_eD}hQXlZlKHz|UC}sSssvFcwgTFswR1%L7i>Lg|G9pI}>rr!?Vr}*M k6f3>5smdDj#T2tGkZ)8oYBZ@QRQD$Pj}Pca4!T3~3&y0O7XSbN delta 1040 zcmZwGPe>F|90%~Cdc4nz;E7BD8-9Vnrai&5pYh)66$!85o4=b;YD;O%yz2^fQ0a2+l}Erem!OY{XMU>r;^yp*A1=(Iu^ zl)}O`qItLv(=ZHIVGu4uH+Kb2Vp1t;)%Y)S3X0u zjLsVvgL7~SI-vtvAqW+~Lpi)ICwc`la1U~zK`-<`8&rcIc<{hl8PNxL2P?1)Q!tsq zEs(qeMIqRBB~BW_cKXzD3LlHHm%>il0-f(QGsvM=ai#nm>kAuFAfn^^E@ zN|T%&&H2UZ{Eq_(F4r3#so&3Z?Tn(TUCzF*Ke!+2eud5Y4gC_UX> { - if (this.chart) { - this.chart.destroy(); - } - }); - } - } this.state = useState({data: null}); @@ -64,10 +53,6 @@ export class PieCard extends Component { return; } - try { - this.chart.clear(); - } catch (exception) { - } const chartJS = await loadJS("/web/static/lib/Chart/Chart.js"); @@ -87,10 +72,14 @@ export class PieCard extends Component { }, }; + const tempChart = Chart.getChart(this.context); + if (tempChart) tempChart.destroy(); + this.chart = new Chart(this.context, config); } + onWillRender(() => { this.render(); }); diff --git a/awesome_dashboard/static/src/dashboard/stats_service.js b/awesome_dashboard/static/src/dashboard/stats_service.js index 6500e91dcf9..65debdaf182 100644 --- a/awesome_dashboard/static/src/dashboard/stats_service.js +++ b/awesome_dashboard/static/src/dashboard/stats_service.js @@ -20,7 +20,7 @@ const nextReload = () => { cache.values = await rpc('/awesome_dashboard/statistics'); } - }, 600000); + }, 5000); } const setActive = (cb) => {isActive = true;nextReload();callback = cb;};