Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a321dfa
[ADD] estate: implement estate advertisement module.
pupat-odoo Feb 5, 2026
9e1664e
[ADD] estate: estate_property table created.
pupat-odoo Feb 6, 2026
0568700
[IMP] estate: define the csv file used in security directory.
pupat-odoo Feb 8, 2026
9358125
[ADD] estate: secuity policy is added this covers access right proper…
pupat-odoo Feb 8, 2026
2d38553
[IMP] estate: estate_property table's attributes are added with some…
pupat-odoo Feb 9, 2026
30e0df5
[ADD] estate: add custom list, form and search views
pupat-odoo Feb 13, 2026
f2d7658
[ADD] estate: added relationship between models, covers(ch-7)
pupat-odoo Feb 16, 2026
615ccd3
[FIX] estate: fixed the warning related to naming convention, covers…
pupat-odoo Feb 16, 2026
ce34b60
[ADD] estate: added computed fields, and onchange method decorator,co…
pupat-odoo Feb 17, 2026
96ac9f4
[IMP] estate: implement action button and usererror function, covers(…
pupat-odoo Feb 19, 2026
ae13a47
[IMP] estate: implement data constraints and custom validations, cove…
pupat-odoo Feb 20, 2026
d822ba1
[IMP] estate: implemented advance UI features with improved search,co…
pupat-odoo Feb 24, 2026
37f35b0
[IMP] estate: implement python , model and view inheritance (ch-12)
pupat-odoo Feb 27, 2026
8933d70
[IMP] estate: implement invoice creation with link module estate_acco…
pupat-odoo Mar 5, 2026
a30afed
[IMP] estate: implemented kanban view using Qweb (ch-14)
pupat-odoo Mar 6, 2026
25dfacb
[ADD] purchase_global_discount: added global discount on purchase ord…
pupat-odoo Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# import the model directory
from . import models # noqa: F401
22 changes: 22 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "Real Estate Advertisement ",
"version": "1.0",
"depends": ["base"],
"website": "https://www.odoo.com/app/estate",
"summary": "This module is for Real estate advertisement.",
"category": "estate",
"data": [
"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_offers_views.xml",
"views/estate_menus.xml",
"views/res_users_view.xml",
"data/estate_demo_data.xml",
],
"installable": True,
"application": True,
"author": "odoo-pupat",
"license": "LGPL-3",
}
70 changes: 70 additions & 0 deletions estate/data/estate_demo_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="demo_type" model="estate.property.type">
<field name="name">Home</field>
</record>

<record id="demo_tag" model="estate.property.tag">
<field name="name">Luxurious</field>
</record>

<record id="demo_data_estate" model="estate.property">
<field name="name">Chitrakut Residency</field>
<field name="expected_price">900000</field>
<field name="postcode">191980</field>
<field name="description">Best residency in this city</field>
<field name="property_type_id" ref="demo_type"></field>
<field name="property_tag_ids" eval="[(4,ref('demo_tag'))]"></field>
</record>

<record id="demo_type2" model="estate.property.type">
<field name="name">Villa</field>
</record>

<record id="demo_tag2" model="estate.property.tag">
<field name="name">Cozy</field>
</record>

<record id="demo_data_estate2" model="estate.property">
<field name="name">Rooftop House</field>
<field name="expected_price">950000</field>
<field name="postcode">191989</field>
<field name="description">It feels like heaven</field>
<field name="property_type_id" ref="demo_type2"></field>
<field name="property_tag_ids" eval="[(4,ref('demo_tag2'))]"></field>
</record>

<record id="demo_type3" model="estate.property.type">
<field name="name">Penthouse</field>
</record>

<record id="demo_tag3" model="estate.property.tag">
<field name="name">Furnished</field>
</record>

<record id="demo_data_estate3" model="estate.property">
<field name="name">Swadesh PG</field>
<field name="expected_price">70000</field>
<field name="postcode">191980</field>
<field name="description">Best PG in this city</field>
<field name="property_type_id" ref="demo_type3"></field>
<field name="property_tag_ids" eval="[(4,ref('demo_tag3'))]"></field>
</record>

<record id="demo_type4" model="estate.property.type">
<field name="name">Palace</field>
</record>

<record id="demo_tag4" model="estate.property.tag">
<field name="name">Precious</field>
</record>

<record id="demo_data_estate4" model="estate.property">
<field name="name">De glance Palace</field>
<field name="expected_price">1000000</field>
<field name="postcode">191970</field>
<field name="description">Great Indian Palace</field>
<field name="property_type_id" ref="demo_type4"></field>
<field name="property_tag_ids" eval="[(4,ref('demo_tag4'))]"></field>
</record>
</odoo>
7 changes: 7 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import (
estate_property, # noqa: F401
estate_property_offer, # noqa: F401
estate_property_tag, # noqa: F401
estate_property_type, # noqa: F401
res_users, # noqa: F401
)
137 changes: 137 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = "estate.property"
_description = "estate property definition"
_order = "id desc"

name = fields.Char(string="Property Name", required=True)
description = fields.Text(string="Description", required=True)
postcode = fields.Char(string="Postcode")
validity = fields.Integer(default=7)
date_deadline = fields.Date()
date_availability = fields.Date(
string="Available From",
default=lambda self: fields.Date.add(fields.Date.today(), months=3),
copy=False,
)
expected_price = fields.Float(string="Expected Price", required=True)
selling_price = fields.Float(string="Selling Price", readonly=True, copy=False)
available = fields.Char()
bedrooms = fields.Integer(string="Bedrooms", default=2)
living_area = fields.Integer(string="Living Area (sqm)")
facades = fields.Integer(string="Facades")
garage = fields.Boolean(string="Garage")
garden = fields.Boolean(string="Garden")
garden_area = fields.Integer(string="Garden Area (sqm)")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
)

active = fields.Boolean(default=True)
state = fields.Selection(
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
required=True,
default="new",
copy=False,
)

property_type_id = fields.Many2one("estate.property.type", string="Property Type")

salesman_id = fields.Many2one(
"res.users",
string="Salesman",
default=lambda self: self.env.user,
)
buyer_id = fields.Many2one("res.partner", string=" Buyer")

property_offer_ids = fields.One2many("estate.property.offer", "property_id")
property_tag_ids = fields.Many2many("estate.property.tag", string="tag")
total_area = fields.Float(compute="_compute_total_area", string="Total Area")
best_price = fields.Float(
string="Best Offer",
compute="_compute_best_price",
store=True,
)

_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
"Expected price must be positive",
)

@api.depends("garden_area", "living_area")
def _compute_total_area(self):
for estate in self:
estate.total_area = estate.garden_area + estate.living_area

@api.depends("property_offer_ids.price")
def _compute_best_price(self):
for record in self:
if record.property_offer_ids:
record.best_price = max(record.property_offer_ids.mapped("price"))
else:
record.best_price = 0.0

@api.constrains("selling_price", "expected_price")
def _check_selling_price_validation(self):
for record in self:
if float_is_zero(record.selling_price, precision_digits=2):
continue

if float_is_zero(record.expected_price, precision_digits=2):
continue

price_limit = record.expected_price * 0.9
if (
float_compare(record.selling_price, price_limit, precision_digits=2)
== -1
):
raise ValidationError(
_(
"Selling price must not be less than 90%% of the expected price.",
),
)

@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 = ""

@api.ondelete(at_uninstall=False)
def _unlink_if_not_allowed(self):
for record in self:
if record.state not in ("new", "cancelled"):
raise UserError(_("User can delete only new or cancelled property"))

def action_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError(_("property can't be cancelled"))
record.state = "sold"
return True

def action_cancel(self):
for record in self:
if record.state == "sold":
raise UserError(_("Cancelled property can't be sold"))
record.state = "cancelled"
return True
103 changes: 103 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from datetime import timedelta

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


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer description"
_order = "price desc"

price = fields.Float(string="Price")
property_offer_ids = fields.Integer(string="Offer")
status = fields.Selection(
string="Status",
copy=False,
selection=[("accepted", "Accepted"), ("refused", "Refused")],
)
validity = fields.Integer(string="Validity(days)", default=7)
date_deadline = fields.Date(
compute="_compute_sum_date",
inverse="_compute_validity",
string="Deadline",
)

partner_id = fields.Many2one("res.partner", required=True, string="Partner")
property_id = fields.Many2one("estate.property", required=True)
property_type_id = fields.Many2one(
"estate.property.type",
related="property_id.property_type_id",
store=True,
readonly=True,
)

@api.depends("validity")
def _compute_sum_date(self):
for record in self:
record.date_deadline = fields.Date.today() + timedelta(days=record.validity)

_check_price = models.Constraint(
"CHECK(price > 0)",
"Offer Price field should always be positive",
)

def _compute_validity(self):
for record in self:
fields.Date.today() == record.date_deadline - timedelta(
days=record.validity,
)

@api.onchange("date_deadline")
def _onchange_validity(self):
if self.date_deadline:
create_date = fields.Date.to_date(self.create_date) or fields.Date.today()
self.validity = (self.date_deadline - create_date).days

@api.model
def create(self, vals_list):

for vals in vals_list:
property_id = vals.get("property_id")
price = vals.get("price")

if property_id and price:
property_rec = self.env["estate.property"].browse(property_id)

if property_rec.best_price and price <= property_rec.best_price:
raise UserError(
_("Offer price must be higher than the current best price."),
)
if property_rec.state == "new":
property_rec.state = "offer_received"

return super().create(vals_list)

def action_accepted(self):
accepted_records = self.search_count(
[
("property_id", "=", self.property_id),
("status", "=", "accepted"),
],
limit=1,
)
if accepted_records:
raise UserError(_(" multiple offer can't be accepted"))

self.status = "accepted"
self.property_id.selling_price = self.price
self.property_id.buyer_id = self.partner_id
self.property_id.state = "offer_accepted"
other_offers = self.search(
[
("property_id", "=", self.property_id),
("status", "!=", "accepted"),
],
)
other_offers.write({"status": "refused"})
return True

def action_refused(self):
for record in self:
record.status = "refused"
return True
15 changes: 15 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "estate property tag"
_order = "name"

name = fields.Char(string="property tag", required=True)
color = fields.Integer()

_check_name_unique = models.Constraint(
"unique(name)",
"The Property Tag must be unique.",
)
Loading