Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] estate: added new real estate module #108

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4e7a1d6
[ADD] estate: added new real estate module
krku-odoo Aug 1, 2024
bf62bdf
[ADD] estate: added new property in estate module
krku-odoo Aug 2, 2024
f855aea
[ADD] estate: Created new models for property type, tags and offers
krku-odoo Aug 5, 2024
f0ae4ec
[ADD] estate: Created Action and Constraints
krku-odoo Aug 7, 2024
947622a
[ADD] estate: Add The Sprinkles
krku-odoo Aug 12, 2024
5e7c24a
[ADD] estate: Added inheritance to extend the functionality of curre…
krku-odoo Aug 13, 2024
250a74b
[ADD] estate: Added Interact With Other Modules
krku-odoo Aug 13, 2024
0e9c0c0
[ADD] estate: Added Kanban View
krku-odoo Aug 14, 2024
8e13d18
[IMP] estate: Add standard property types and demo data
krku-odoo Aug 20, 2024
d811ef0
[ADD] estate: Added PDF Reports to the estate module
krku-odoo Aug 21, 2024
3c09613
[IMP] estate: wizard,controllers,security
krku-odoo Aug 28, 2024
453d964
[ADD] dental: Created a new module
krku-odoo Sep 4, 2024
0937960
[IMP] dental: Add dental module with invoicing functionality
krku-odoo Sep 5, 2024
44a3499
[IMP] dental: Add controller
krku-odoo Sep 6, 2024
550b9d0
[IMP] dental: Add breadcrumb and create form view template
krku-odoo Sep 10, 2024
92478f7
[ADD] installment: Add Installment for sale order.
krku-odoo Sep 20, 2024
456c8ed
[ADD] warranty: Add waranty feature for sale order
krku-odoo Sep 24, 2024
335e94c
[IMP] warranty: Add warranty fetures.
krku-odoo Sep 25, 2024
6057707
[ADD] warranty: Add warranty end_date in invoice report create
krku-odoo Sep 27, 2024
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
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
21 changes: 21 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Real Estate",
"version": "1.0",
"license": "LGPL-3",
"category": "Real Estate",
"summary": "Manage properties",
"depends": ["base_setup", "mail"],
"data": [
"security/ir.model.access.csv",
"views/real_estate_property.xml",
"views/estate_property_tree_views.xml",
"views/estate_property_offer_view.xml",
"views/property_type_views.xml",
"views/property_tag.xml",
"views/res_users_view.xml",
"views/real_estate_menu.xml",
],
"installable": True,
"application": True,
"auto_install": False,
}
6 changes: 6 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import estate_property
from . import test_model
from . import property_type
from . import property_tag
from . import property_offer
from . import inherited_model
135 changes: 135 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError


class MyModel(models.Model):
_name = "estate_property"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_name = "estate_property"
_name = "estate.property"

Modify the model name as per the convention.

_description = "Real Estate"
_inherit = ["mail.thread", "mail.activity.mixin"]
_order = "id desc"

name = fields.Char(string="Title", required=True)
description = fields.Text(string="Description")
postcode = fields.Char(string="Postcode")
date_availability = fields.Date(string="Available From")
expected_price = fields.Float(string="Expected Price")
selling_price = fields.Float(string="Selling Price")
bedrooms = fields.Integer(string="Bedrooms")
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(
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("offer receivedwest", "West"),
],
)
active = fields.Boolean(default=True)
state = fields.Selection(
string="Status",
selection=[
("new", "New"),
("offer received", "Offer Received"),
("offer accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
tracking=True,
default="new",
)

salesman_id = fields.Many2one(
"res.users", # The model this field relates to
string="Salesman", # Label for the field
default=lambda self: self.env.user.partner_id, # Set default to the current user
)
buyer_id = fields.Many2one(
"res.partner", # The model this field relates to
string="Buyer", # Label for the field
copy=False, # Prevent the field value from being copied when duplicating the record
)
property_type_id = fields.Many2one(
"real.estate.property.type", string="Property Type"
)
tag_ids = fields.Many2many("real.estate.property.tag", string="tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
total_area = fields.Float(compute="_compute_total_area")
best_offer = fields.Float(compute="_compute_best_offer")

@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", "best_offer")
def _compute_best_offer(self):
for record in self:
if record.offer_ids:
record.best_offer = max(record.offer_ids.mapped("price"))
else:
record.best_offer = 0.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

def action_cancel(self):
for record in self:
if record.state == "sold":
raise UserError("Sold property cannot be cancelled.")
record.state = "cancelled"

def action_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError("Cancelled property cannot be sold.")
record.state = "sold"

@api.depends("offer_ids.price", "selling_price")
def _compute_best_offer(self):
for record in self:
if record.offer_ids:
record.best_offer = max(record.offer_ids.mapped("price"))
else:
record.best_offer = 0.0

_sql_constraints = [

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, we define SQL constraints just after the field declarations.

("property_name", "unique(name)", "The property name must be unique!"),
(
"selling_price",
"check(selling_price >= 0)",
"A property selling price must be strictly positive.",
),
(
"expected_price",
"CHECK(expected_price >= 0)",
"A property expected price must be strictly positive.",
),
]

@api.constrains("selling_price", "expected_price")
def _check_selling(self):
for record in self:
if record.selling_price > 0:
price_per = (record.expected_price * 90) / 100
if record.selling_price < price_per:
raise ValidationError(
"selling price cannot be lower than 90% of the expected price."
)

@api.ondelete(at_uninstall=False)
def _check_state_before_delete(self):
for record in self:
if record.state not in ["new", "cancelled"]:
raise UserError(
"You cannot delete a property that is not in 'New' or 'Canceled' state."
)
12 changes: 12 additions & 0 deletions estate/models/inherited_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models, fields


class InheritedModel(models.Model):
_inherit = "res.users"

property_ids = fields.One2many(
"estate_property",
"salesman_id",
string="Offers",
domain="['|',('state', '=', 'offer received'),('state', '=', 'offer accepted')]",
)
71 changes: 71 additions & 0 deletions estate/models/property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from odoo import api, models, fields
from dateutil.relativedelta import relativedelta

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from odoo import api, models, fields
from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import relativedelta
from odoo import api, fields, model

We follows a convention in which we first import the external libraries and then imports from odoo in alphabetically order.

from odoo.exceptions import UserError


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

price = fields.Float(string="Price", required=True)
status = fields.Selection(
[("accepted", "Accepted"), ("refused", "Refused")],
string="Status",
copy=False,
)

partner_id = fields.Many2one("res.partner", string="Partner", required=True)
property_id = fields.Many2one(
"estate_property", string="Property", ondelete="cascade"
)
validity = fields.Integer(string="Validity (days)", default=7)
deadline_date = fields.Date(
string="Deadline", compute="_compute_deadline", inverse="_inverse_deadline"
)
property_type_id = fields.Many2one(
related="property_id.property_type_id", store=True
)

@api.depends("property_id.create_date", "validity")
def _compute_deadline(self):
for record in self:
if record.property_id.create_date:
record.deadline_date = record.property_id.create_date + relativedelta(
days=record.validity
)
else:
record.deadline_date = False
Comment on lines +32 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if record.property_id.create_date:
record.deadline_date = record.property_id.create_date + relativedelta(
days=record.validity
)
else:
record.deadline_date = False
record.deadline_date = record.property_id.create_date + relativedelta(
days=record.validity
)


def _inverse_deadline(self):
for record in self:
if record.property_id.create_date and record.deadline_date:
flag = fields.Date.from_string(record.deadline_date)
flag1 = fields.Date.from_string(record.property_id.create_date)
if flag and flag1:
record.validity = (flag - flag1).days
else:
record.validity = 7
else:
record.validity = 7

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if record.property_id.create_date and record.deadline_date:
flag = fields.Date.from_string(record.deadline_date)
flag1 = fields.Date.from_string(record.property_id.create_date)
if flag and flag1:
record.validity = (flag - flag1).days
else:
record.validity = 7
else:
record.validity = 7
record.validity = (record.deadline_date - record.property_id.create_date.date()).days

I think we can simplify it But make sure to check all the cases working fine.


def action_refused(self):
for record in self:
record.status = "refused"

def action_accepted(self):
for record in self:
record.status = "accepted"
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id

@api.model
def create(self, vals):
property_id = vals.get("property_id")
property_users = self.env["estate_property"].browse(property_id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
property_id = vals.get("property_id")
property_users = self.env["estate_property"].browse(property_id)
property = self.env["estate_property"].browse(vals.get("property_id"))

property_users.state = "offer received"
if property_users.offer_ids.filtered(lambda o: o.price >= vals.get("price")):
raise UserError(
"You cannot create an offer with a lower amount than an existing offer."
)
return super().create(vals)
12 changes: 12 additions & 0 deletions estate/models/property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models, fields


class PropertyTag(models.Model):
_name = "real.estate.property.tag"
_description = "Property Type"
_order = "name"

name = fields.Char(required=True)
color = fields.Integer()

_sql_constraints = [("name", "UNIQUE(name)", "A property tag name must be unique")]
24 changes: 24 additions & 0 deletions estate/models/property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from odoo import api, models, fields


class PropertyType(models.Model):
_name = "real.estate.property.type"
_description = "Property Type"
_order = "name"

name = fields.Char(string="Property Type", required=True)
description = fields.Text(string="Description")

property_ids = fields.One2many(
"estate_property", "property_type_id", string="Offers"
)
# nickname = fields.Char(related='property_ids.partner_id.name', store=True)
offer_ids = fields.One2many("estate.property.offer", "property_type_id", store=True)
sequence = fields.Integer("Sequence")
offer_count = fields.Integer(compute="_compute_offer_count")
_sql_constraints = [("name", "UNIQUE(name)", "A property type name must be unique")]

@api.depends("offer_ids")
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids)
10 changes: 10 additions & 0 deletions estate/models/test_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import models, fields


class MyModel(models.Model):
_name = "test_model"
_description = "My New Model"

name = fields.Char(string="Name", required=True)
description = fields.Text()
address = fields.Text()
5 changes: 5 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_estate_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1
estate.access_real_estate_property_type,access_real_estate_property_type,estate.model_real_estate_property_type,base.group_user,1,1,1,1
estate.access_real_estate_property_tag,access_real_estate_property_tag,estate.model_real_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
50 changes: 50 additions & 0 deletions estate/views/estate_property_offer_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<odoo>
<record id="estate_property_offer_action" model="ir.actions.act_window">
<field name="name">
Offer
</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">
tree,form
</field>
<field name="domain">
[('property_type_id', '=', active_id)]
</field>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<field name="view_mode">
tree,form
</field>
<field name="domain">
[('property_type_id', '=', active_id)]
</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('property_type_id', '=', active_id)]</field>

Check in other files also

</record>
<record id="view_estate_property_offer_tree" model="ir.ui.view">
<field name="name">
estate.property.offer.tree
</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<tree editable="bottom" decoration-bf="status=='accepted'" decoration-success="status=='accepted'" decoration-danger="status=='refused'">
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="deadline_date" />
<button name="action_accepted" type="object" icon="fa-check" title="done" invisible="status in ('accepted', 'refused')" />
<button name="action_refused" type="object" icon="fa-times" title="cancel" invisible="status in ('accepted', 'refused')" />
<field name="status" column_invisible="1" />
</tree>
</field>
</record>
<record id="view_estate_property_offer_form" model="ir.ui.view">
<field name="name">
estate.property.offer.form
</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form string="Properties Form">
<sheet>
<group>
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="deadline_date" />
<field name="status" />
</group>
</sheet>
</form>
</field>
</record>
</odoo>
Loading