Skip to content

Commit

Permalink
[IMP] estate: added computed fields, onchanges, error handling, const…
Browse files Browse the repository at this point in the history
…raints, and inverse methods

Day 4:
- Implemented computed field total_area as the sum of
  living_area and garden_area in estate.property.
- Added total_area to estate_property_views.xml.
- Used computed field with mapped() to define best_price as
  the maximum offer price in estate.property.
- Added inverse method for date_deadline in estate.property.offer
  - date_deadline is computed as create_date + validity days.
  - Implemented inverse method to update validity based on date_deadline.
- Added onchange method to auto-set garden_area (10) and
  orientation (North) when garden is True, and reset values
  when unset.
- Implemented property state transitions: added functionality to
  cancel and mark a property as sold.
- Used UserError for validation and error handling in business logic.
- Applied SQL constraints:
  - estate.property: applied expected_price > 0, selling_price ≥ 0.
  - estate.property.offer: applied offer_price > 0.
  - Implemented unique constraints on estate.property.tag and
    estate.property.type names.
- Implemented Python constraint in estate.property to ensure
  selling_price is at least 90% of expected_price.
  • Loading branch information
nmak-odoo committed Feb 7, 2025
1 parent ea0ff74 commit 0610372
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 18 deletions.
2 changes: 1 addition & 1 deletion estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import estate_property
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
74 changes: 69 additions & 5 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import fields, models
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Properties involved in estate"
_description = "Properties/Advertisements created and managed in estate"
_sql_constraints = [
(
"check_expected_price",
"CHECK(expected_price > 0)",
"The expected price must be strictly positive.",
),
(
"check_selling_price",
"CHECK(selling_price >= 0)",
"The selling price must be positive.",
),
]

name = fields.Char(string="Name of the property", required=True)
description = fields.Text(string="Property Description")
Expand All @@ -31,6 +44,7 @@ class EstateProperty(models.Model):
("west", "West"),
]
)
total_area = fields.Float(compute="_compute_total_area")
active = fields.Boolean(default=True)
state = fields.Selection(
selection=[
Expand All @@ -49,8 +63,58 @@ class EstateProperty(models.Model):
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
salesperson_id = fields.Many2one(
"res.users", string="Salesperson", default= lambda self: self.env.user, required=True
"res.users",
string="Salesperson",
default=lambda self: self.env.user,
required=True,
)
buyer_id = fields.Many2one(
"res.partner", string="Buyer", copy = False
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
best_price = fields.Float(
compute="_compute_best_price", string="Best Offer", store=True
)

@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")
def _compute_best_price(self):
for record in self:
record.best_price = max(record.offer_ids.mapped("price"), default=0)

@api.onchange("garden")
def _onChange_garden(self):
if self.garden:
self.garden_area = 10
self.garden_orientation = "north"
else:
self.garden_area = 0
self.garden_orientation = False

def action_sold(self):
for record in self:
if record.state == "canceled":
raise UserError("Canceled property cannot be sold")
if not record.buyer_id or not record.selling_price:
raise UserError(
"Property must have an accepted offer before being sold"
)
record.state = "sold"

def action_cancel(self):
for record in self:
if record.state == "sold":
raise UserError("Sold Property can't be canceled")
record.state = "canceled"

@api.constrains("selling_price", "expected_price")
def _check_selling_price(self):
for record in self:
if (
record.selling_price > 0
and record.selling_price < record.expected_price * 0.9
):
raise ValidationError(
"The selling price cannot be less than 90% of the expected price"
)
43 changes: 42 additions & 1 deletion estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import fields, models
from odoo import api, fields, models
from odoo.exceptions import UserError


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Offer on property"
_sql_constraints = [
("check_price", "CHECK(price > 0)", "Offer price must be strictly positive."),
]

price = fields.Float(string="Price", required=True)
status = fields.Selection(
Expand All @@ -15,3 +19,40 @@ class EstatePropertyOffer(models.Model):
)
partner_id = fields.Many2one("res.partner", string="Partner", required=True)
property_id = fields.Many2one("estate.property", string="Property", required=True)
validity = fields.Integer(default=7, string="Validity (days)")
deadline = fields.Date(
string="Deadline", compute="_compute_deadline", inverse="_inverse_deadline", store="True"
)

@api.depends("validity")
def _compute_deadline(self):
for offer in self:
if offer.create_date:
offer.deadline = fields.Date.add(offer.create_date, days=offer.validity)
else:
offer.deadline = fields.Date.today()

def _inverse_deadline(self):
for offer in self:
if offer.create_date:
offer.validity = (offer.deadline - offer.create_date.date()).days
else:
offer.validity = 7

def action_accept(self):
for offer in self:
if offer.property_id.state == "sold":
raise UserError("Offer cannot be accepted on sold property")
for existing_offer in offer.property_id.offer_ids:
if existing_offer.status == "accepted":
raise UserError("Only one offer can be accepted for a property")
offer.status = "accepted"
offer.property_id.selling_price = offer.price
offer.property_id.buyer_id = offer.partner_id
offer.property_id.state = "offer_accepted"

def action_refuse(self):
for offer in self:
if offer.status == "accepted":
raise UserError("Accepted offer cannot be refused")
offer.status = "refused"
9 changes: 6 additions & 3 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import models,fields
from odoo import models, fields


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "tags attached to property"

name = fields.Char(string="Property Tag" , required=True)
_sql_constraints = [
("unique_tag_name", "UNIQUE(name)", "Property tag name must be unique."),
]

name = fields.Char(string="Property Tag", required=True)
3 changes: 3 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Real Estate Property Type"
_sql_constraints = [
("unique_type_name", "UNIQUE(name)", "Property type name must be unique."),
]

name = fields.Char(string="Property Type", required=True)
4 changes: 2 additions & 2 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<list string="Property Types">
<field name="name"/>
<field name="name" />
</list>
</field>
</record>
Expand All @@ -17,7 +17,7 @@
<form string="Property Type">
<sheet>
<group>
<field name="name"/>
<field name="name" />
</group>
</sheet>
</form>
Expand Down
19 changes: 13 additions & 6 deletions estate/views/estate_property_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="Property">
<header>
<button name="action_sold" type="object" string="Sold" class="oe_highlight" />
<button name="action_cancel" type="object" string="Cancel" class="oe_highlight" />
</header>
<sheet>
<h1>
<field name="name" />
Expand All @@ -33,16 +37,14 @@
</div>
<group>
<group>
<field name="property_type_id" />
<field name="postcode" />
<field name="date_availability" />
</group>
<group>
<field name="expected_price" />
<field name="selling_price" />
</group>
<group>
<field name="state" />
<field name="property_type_id" />
<field name="best_price" />
</group>
</group>
<notebook>
Expand All @@ -56,21 +58,26 @@
<field name="garden" />
<field name="garden_area" />
<field name="garden_orientation" />
<field name="total_area" />
</group>
</page>
<page string="Offers">
<field name="offer_ids">
<list>
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="deadline" />
<field name="status" />
<button name="action_accept" type="object" icon="fa-check" />
<button name="action_refuse" type="object" icon="fa-times"/>
</list>
</field>
</page>
<page string="Other information">
<group>
<field name="salesperson_id"/>
<field name="buyer_id"/>
<field name="salesperson_id" />
<field name="buyer_id" />
</group>
</page>
</notebook>
Expand Down

0 comments on commit 0610372

Please sign in to comment.