From 6d3767d98ae02b473aaf8a81d8c642fcba9d4d73 Mon Sep 17 00:00:00 2001 From: Celina Devigili Date: Mon, 29 Jan 2024 17:38:36 -0300 Subject: [PATCH 1/2] [ADD] estate module --- estate/__init__.py | 1 + estate/__manifest__.py | 12 +++ estate/models/__init__.py | 4 + estate/models/estate_property.py | 54 ++++++++++ estate/models/estate_property_offer.py | 26 +++++ estate/models/estate_property_tag.py | 10 ++ estate/models/estate_property_type.py | 9 ++ estate/security/ir.model.access.csv | 5 + estate/views/estate_menus.xml | 8 ++ estate/views/estate_property_offer_views.xml | 34 +++++++ estate/views/estate_property_type_views.xml | 43 ++++++++ estate/views/estate_property_views.xml | 101 +++++++++++++++++++ 12 files changed, 307 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_type_views.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 000000000..28c545daf --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': 'Real Estate Management', + 'depends': ['base'], + 'application': True, + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_menus.xml', + ] +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 000000000..09b2099fe --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 000000000..7b150469f --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,54 @@ +from odoo import fields, models, api + +class EstateProperty(models.Model): + + _name = 'estate.property' + _description = 'Real Estate Property Model' + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date( + copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) + expected_price = fields.Float(required=True,) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + [('north','North'),('south', 'South'), ('east', 'East'), ('west','West')]) + active = fields.Boolean(default=True) + state = fields.Selection([('new', 'New'), ('offer_received', 'Offer Received'),('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'),('canceled', 'Canceled')],required=True, copy=False, default='new') + property_type_id = fields.Many2one('estate.property.type') + tag_ids = fields.Many2many('estate.property.tag') + user_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user) + partner_id = fields.Many2one('res.partner', string='Buyer', copy=False) + offer_ids = fields.One2many('estate.property.offer','property_id') + total_area = fields.Float(compute='_compute_total_area') + best_price = fields.Float(compute='_compute_best_price') + + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for record in self: + record.total_area= record.living_area + record.garden_area + + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price') or [0]) + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = False + self.garden_orientation = False + \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 000000000..380f5b9f8 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,26 @@ +from odoo import fields, models, api + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Real Estate Property Offers' + + + price = fields.Float() + status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], copy=False) + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate.property', required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute='_compute_date_deadline', inverse= '_inverse_date_deadline') + + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for record in self: + record.date_deadline = fields.Date.add(record.create_date, days=record.validity) if record.create_date else False + + + def _inverse_date_deadline(self): + for record in self: + record.validity = (record.date_deadline - record.create_date.date()).days + \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 000000000..871b59ecd --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Real Estate Property Tags' + + + name = fields.Char(required=True) + diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 000000000..360795cf5 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Real Estate Property Types' + + + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 000000000..0db13e578 --- /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.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 +estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 +estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 000000000..f0f214614 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 000000000..3dc873291 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,34 @@ + + + + estate.property.offer.tree + estate.property.offer + + + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + + + +
+
+
+
\ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 000000000..013eb39d1 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,43 @@ + + + + estate.property.type.tree + estate.property.type + + + + + + + + + estate.property.type.form + estate.property.type + +
+ + +
+ +
+
+
+
+
+
+ + + estate.property.type.search + estate.property.type + + + + + + + + + Property Types + estate.property.type + +
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 000000000..2460f27a4 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,101 @@ + + + + estate.property.tree + estate.property + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + + +
+

+ +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + Properties + estate.property + tree,form + +
From 5d76aebacdbe5e04f95cf4da6cd9612ddc3ea654 Mon Sep 17 00:00:00 2001 From: Celina Devigili Date: Fri, 9 Feb 2024 17:11:20 -0300 Subject: [PATCH 2/2] [ADD] estate module --- estate/__manifest__.py | 4 +- estate/models/__init__.py | 3 +- estate/models/estate_property.py | 73 +++++++++++++++++++- estate/models/estate_property_offer.py | 57 +++++++++++++-- estate/models/estate_property_tag.py | 16 ++++- estate/models/estate_property_type.py | 29 +++++++- estate/models/res_users.py | 10 +++ estate/security/ir.model.access.csv | 3 +- estate/views/estate_menus.xml | 3 +- estate/views/estate_property_offer_views.xml | 15 +++- estate/views/estate_property_tag_views.xml | 19 +++++ estate/views/estate_property_type_views.xml | 17 ++++- estate/views/estate_property_views.xml | 25 ++++--- estate/views/res_users_views.xml | 17 +++++ estate_account/__init__.py | 1 + estate_account/__manifest__.py | 5 ++ estate_account/models/__init__.py | 1 + estate_account/models/__manifest__.py | 0 estate_account/models/estate_property.py | 9 +++ 19 files changed, 279 insertions(+), 28 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/res_users_views.xml create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/__manifest__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 28c545daf..60b41e593 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,7 +6,9 @@ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', + 'views/res_users_views.xml', 'views/estate_menus.xml', ] -} \ No newline at end of file +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe..9a2189b63 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,5 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7b150469f..cac5d6025 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,12 @@ from odoo import fields, models, api +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_utils class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate Property Model' + _order = "id desc" name = fields.Char(required=True) description = fields.Text() @@ -21,8 +24,13 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( [('north','North'),('south', 'South'), ('east', 'East'), ('west','West')]) active = fields.Boolean(default=True) - state = fields.Selection([('new', 'New'), ('offer_received', 'Offer Received'),('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'),('canceled', 'Canceled')],required=True, copy=False, default='new') + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Canceled') + ],required=True, copy=False, default='new') property_type_id = fields.Many2one('estate.property.type') tag_ids = fields.Many2many('estate.property.tag') user_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user) @@ -51,4 +59,63 @@ def _onchange_garden(self): else: self.garden_area = False self.garden_orientation = False - \ No newline at end of file + + + def action_button_cancel(self): + for record in self: + if self.state == 'sold': + raise UserError ('A sold property cannot be canceled') + else: + record.state = 'canceled' + return True + + + def action_button_sold(self): + for record in self: + if self.state == 'canceled': + raise UserError ('A canceled property cannot be sold') + else: + record.state = 'sold' + return True + + @api.constrains('expected_price') + def _check_expected_price(self): + for record in self: + if record.expected_price < 0: + raise ValidationError('The Expected Price should be positive.') + + + @api.constrains('selling_price') + def _check_selling_price(self): + for record in self: + if record.selling_price < 0: + raise ValidationError('The Selling Price should be positive.') + + + @api.constrains('selling_price', 'expected_price', 'offer_ids.price', 'offer_ids.status') + def _check_offer_selling_price(self): + for record in self: + if 'accepted' in record.offer_ids.mapped('status') and record.selling_price >= 0 and record.expected_price > 0: + min_selling_price = 0.9*record.expected_price + if float_utils.float_compare(record.selling_price, min_selling_price, precision_digits=2) == -1: + raise ValidationError( + 'Selling Price should be higher than 90 percent of the Expected Price. Reduce the expected price if you want accept this offer') + + + @api.ondelete(at_uninstall=False) + def _unlink_if_property_new_canceled(self): + for record in self: + if record.state in ('offer_received','offer_accepted','sold'): + raise UserError("Can't delete an active property!") + + +#2024-02-02 20:53:56,630 442 WARNING real_estate odoo.models: method estate.property._check_offer_selling_price: @constrains parameter 'offer_ids.price' is not a field name +#2024-02-02 20:53:56,630 442 WARNING real_estate odoo.models: method estate.property._check_offer_selling_price: @constrains parameter 'offer_ids.status' is not a field name +# to_do: Make the estate.property.tag list views editable. +# state in property list view make it invisible? +# status in property offer list view make it invisible? +# chapter 12: stat button on estate.property.type pointing to the estate.property.offer action?? +# use the type="action" attribute +# At this point, clicking on the stat button should display all offers. +# We still need to filter out the offers. On the estate.property.offer action, add a domain that defines property_type_id as equal to the active_id (= the current record +# chapter 13: Add the property_ids field to the base.view_users_form in a new notebook page. diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 380f5b9f8..74000bc48 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,15 +1,17 @@ from odoo import fields, models, api - +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' _description = 'Real Estate Property Offers' + _order = "price desc" price = fields.Float() status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], copy=False) partner_id = fields.Many2one('res.partner', required=True) property_id = fields.Many2one('estate.property', required=True) + property_type_id = fields.Many2one(related="property_id.property_type_id", store=True) validity = fields.Integer(default=7) date_deadline = fields.Date(compute='_compute_date_deadline', inverse= '_inverse_date_deadline') @@ -17,10 +19,57 @@ class EstatePropertyOffer(models.Model): @api.depends('create_date', 'validity') def _compute_date_deadline(self): for record in self: - record.date_deadline = fields.Date.add(record.create_date, days=record.validity) if record.create_date else False + record.date_deadline = fields.Date.add(record.create_date, days=record.validity) if record.create_date else False def _inverse_date_deadline(self): + for record in self.filtered('date_deadline'): + create_date = (record.create_date or fields.Datetime.now()).date() + record.validity = (record.date_deadline - create_date).days + + + @api.model + def create(self, vals): + + res = super(EstatePropertyOffer, self).create(vals) + + existing_offer = self.search([ + ('property_id', '=', vals.get('property_id')), + ('price', '>', vals.get('price')), + ], limit=1) + + if existing_offer: + raise UserError("Cannot create an offer with a lower price than an existing one.") + + self.env['estate.property'].browse(vals.get('property_id')).write({'state':'offer_received'}) + + return res + + + def action_button_refuse(self): + for record in self: + if self.status == 'accepted': + raise UserError ('Cannot refuse an accepted offer') + else: + record.status = 'refused' + return True + + + def action_button_accept(self): for record in self: - record.validity = (record.date_deadline - record.create_date.date()).days - \ No newline at end of file + if 'accepted' in record.property_id.offer_ids.mapped('status'): + raise UserError ('Cannot accept more than one offer') + else: + record.status = 'accepted' + record.property_id.state = 'offer_accepted' + record.property_id.selling_price = record.price + record.property_id.partner_id = record.partner_id + return True + + + @api.constrains('price') + def _check_offer_price(self): + for record in self: + if record.price < 0: + raise UserError('The Offer Price should be positive.') + diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 871b59ecd..0ebaa534c 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,10 +1,22 @@ -from odoo import fields, models +from odoo import fields, models, api +from odoo.exceptions import ValidationError class EstatePropertyTag(models.Model): _name = 'estate.property.tag' _description = 'Real Estate Property Tags' - + _order = "name desc" + _sql_constraints = [('tag_unique', 'unique (name)','Property tag name should be unique!')] name = fields.Char(required=True) + color = fields.Integer() + + # @api.constrains('name') + # def _check_name(self): + # for record in self: + # if record.name in record.name.mapped(): + # raise ValidationError('Property tag name should be unique!') + + + diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 360795cf5..3a225f817 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,9 +1,34 @@ -from odoo import fields, models +from odoo import fields, models, api class EstatePropertyType(models.Model): _name = 'estate.property.type' _description = 'Real Estate Property Types' + _order = "sequence, name desc" + _sql_constraints = [ + ('type_unique', 'unique (name)','Property type name should be unique!')] + name = fields.Char(required=True) + sequence = fields.Integer('Sequence', default=1, help="Used to order types") + property_ids = fields.One2many('estate.property', 'property_type_id') + offer_ids = fields.One2many('estate.property.offer', 'property_type_id') + offer_count = fields.Integer(compute='_compute_offer_count') - name = fields.Char(required=True) \ No newline at end of file + @api.depends('offer_ids') + def _compute_offer_count(self): + for record in self: + record.offer_count =len(record.property_ids.mapped('offer_ids')) + + +class EstatePropertyTypeLine(models.Model): + _name = 'estate.property.type.line' + _description = 'Real Estate Property Types Line' + + + property_ids = fields.Many2one('estate.property.type') + name = fields.Char() + expected_price = fields.Float() + state = fields.Selection([('new', 'New'), ('offer_received', 'Offer Received'),('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'),('canceled', 'Canceled')],required=True, copy=False, default='new') + + \ No newline at end of file diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 000000000..20eaa01d3 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = 'res.users' + + + property_ids = fields.One2many('estate.property','user_id',domain="[('user_id', '=', id)]") + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0db13e578..f1807d55e 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 -estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file +estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 +estate.access_estate_property_type_line,access_estate_property_type_line,estate.model_estate_property_type_line,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index f0f214614..e9d68f4b3 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -5,4 +5,5 @@ - \ No newline at end of file + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 3dc873291..ef1264834 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -4,12 +4,14 @@ estate.property.offer.tree estate.property.offer - + - +