From ac9d991d26132bd8fbf0b85cc8788f0e0fc59d42 Mon Sep 17 00:00:00 2001 From: Matt Marcha Date: Tue, 23 Jul 2024 09:11:57 -1000 Subject: [PATCH] chapt 9-10 --- estate/models/estate_property.py | 57 +++++++++++++++++++- estate/models/estate_property_offer.py | 42 +++++++++++++-- estate/views/estate_property_offer_views.xml | 6 ++- estate/views/estate_property_views.xml | 5 ++ 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b7603bc..2b0365e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,22 @@ # -*- coding: utf-8 -*- -from odoo import api, fields, models +from odoo import api, fields, models, exceptions +from odoo.tools import float_utils as floatTool class EstateProperty(models.Model): + + # ------------- Private attributes ------------------------- # + _name = "estate.property" _description = "Properties for the Estate module" + _sql_constraints = [ + ('check_expected_price', 'CHECK (expected_price > 0)', 'Expected rice should be superior to 0'), + ('check_selling_price', 'CHECK (selling_price >= 0)', 'Selling price cannot be negative'), + ('unique_name', 'UNIQUE (name)', 'A property with this name aready exists. Name should be unique.'), + ] + + # ------------- Fields ------------------------- # + name = fields.Char(required=True) description = fields.Text() active = fields.Boolean(default=True) @@ -28,6 +40,8 @@ class EstateProperty(models.Model): total_area = fields.Integer(compute="_get_total_area", readonly=True) best_price = fields.Float(compute="_compute_best_price") + # ------------- Compute methods ------------------------- # + @api.depends('living_area', 'garden_area') def _get_total_area(self): for entry in self: @@ -38,6 +52,8 @@ class EstateProperty(models.Model): for record in self: record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else None + # ------------- OnChange ------------------------- # + @api.onchange('garden') def _onchange_garden(self): if self.garden: @@ -52,4 +68,41 @@ class EstateProperty(models.Model): if self.date_availability < fields.Date.today(): return {'warning': { 'title': ("Warning"), - 'message': ("The date is in the past.")}} \ No newline at end of file + 'message': ("The date is in the past.")}} + + # ------------- Constrains ------------------------- # + + @api.constrains('selling_price', 'expected_price') + def _check_selling_price(self): + for property in self: + if ( + not floatTool.float_is_zero(property.selling_price, precision_rounding=0.01) + and floatTool.float_compare((property.expected_price * 0.9), property.selling_price, precision_rounding=0.01) > 0 + ): + raise exceptions.ValidationError("Selling price can't be less than 90% of the expected price") + + # ------------- Actions ------------------------- # + + def action_sold(self): + if self.exists(): + if self.state == 'cancelled': + raise exceptions.UserError('A cancelled property cannot be sold') + return False + else: + self.state = 'sold' + return True + else: + raise exceptions.MissingError('Property not found') + return False + + def action_cancel(self): + if self.exists(): + if self.state == 'sold': + raise exceptions.UserError('A sold property cannot be cancelled') + return False + else: + self.state = 'cancelled' + return True + else: + raise exceptions.MissingError('Property not found') + return False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 0a28458..792461f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,17 +1,22 @@ # -*- coding: utf-8 -*- -from odoo import api, fields, models +from odoo import api, fields, models, exceptions +from odoo.tools import float_utils as floatTool class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Offers made on properties" price = fields.Float() - status = fields.Selection(copy=False, selection=[('accepted', 'Accepted'), ('refused', 'Refused')]) + state = fields.Selection(copy=False, selection=[('accepted', 'Accepted'), ('refused', 'Refused')]) partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True) validity = fields.Integer(default=7, string='validity (days)') date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + _sql_constraints = [ + ('check_price', 'CHECK (price > 0)', 'Expected price should be superior to 0'), + ] + @api.depends('validity', 'create_date') def _compute_date_deadline(self): for record in self: @@ -19,4 +24,35 @@ class EstatePropertyOffer(models.Model): def _inverse_date_deadline(self): for record in self: - record.validity = (record.date_deadline - (record.create_date.date() or fields.Date.today())).days \ No newline at end of file + record.validity = (record.date_deadline - (record.create_date.date() or fields.Date.today())).days + + def action_accept(self): + if self.exists(): + if "accepted" in self.mapped("property_id.offer_ids.state"): + raise exceptions.UserError("An offer as already been accepted.") + self.write( + { + "state": "accepted", + } + ) + return self.mapped("property_id").write( + { + "state": "offer_accepted", + "selling_price": self.price, + "buyer_id": self.partner_id.id, + } + ) + else: + raise exceptions.MissingError('Offer not found') + + def action_reject(self): + if self.exists(): + if self.state == 'accepted': + raise exceptions.UserError('Cannot accept an offer refused') + return False + else: + self.state = 'refused' + return True + else: + raise exceptions.MissingError('Offer not found') + return False \ 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 6ca370a..d43a079 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,7 +9,9 @@ - +