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 @@
-
+
+
+
@@ -24,7 +26,7 @@
-
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
index 054e965..370c32c 100644
--- a/estate/views/estate_property_views.xml
+++ b/estate/views/estate_property_views.xml
@@ -28,6 +28,10 @@
estate.property