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: a new module to manage estate properties #163

Open
wants to merge 27 commits into
base: 18.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cd26d0b
[ADD] chap1: Manifest
malm-odoo Oct 21, 2024
98dc91c
[ADD] chap2: __init__.py
malm-odoo Oct 21, 2024
2430f51
[ADD] chap2: Content to manifest
malm-odoo Oct 21, 2024
8478e5f
[ADD] chap2: Manifest depends
malm-odoo Oct 21, 2024
be2d317
[FIX] chap2: Manifest syntax
malm-odoo Oct 21, 2024
760525b
[FIX] chap5: Changed dirs
malm-odoo Oct 21, 2024
640bb1d
[ADD] chap5: Added xml files
malm-odoo Oct 21, 2024
9324b1e
[ADD] chap5: License in manifest
malm-odoo Oct 22, 2024
9ff6ca6
[FIX] chap5: style
malm-odoo Oct 22, 2024
45fd826
[IMP] estate: Add a server action for better UX
aboo-odoo Oct 22, 2024
dddd880
[ADD] chap6: view form, filters, search, and group by
malm-odoo Oct 22, 2024
5539fb8
[ADD] estate: Add property types, a salesman, and a buyer
malm-odoo Oct 23, 2024
940160e
[ADD] estate: Added newline to fix style warnings
malm-odoo Oct 23, 2024
c49def8
[ADD] estate: Added property tags and offers page
malm-odoo Oct 23, 2024
3c8de6e
[ADD] estate: Added computed fiels and onchanges
malm-odoo Oct 23, 2024
a896aae
[ADD] estate: Added newline to fix style
malm-odoo Oct 23, 2024
1a7e21f
[ADD] estate: Added sold, cancel, accept, and refuse buttons. Alongsi…
malm-odoo Oct 23, 2024
2f82a7a
[ADD] estate: Added SQL and Python constraints
malm-odoo Oct 23, 2024
63198aa
[ADD] estate: Newline to fix style
malm-odoo Oct 23, 2024
b0c767f
[ADD] estate: Added the sprinkles
malm-odoo Oct 24, 2024
ae52a8a
[ADD] estate: Added oncreate method in offers module and users proper…
malm-odoo Oct 24, 2024
7d94274
[ADD] estate_account: Added a method inheriting the action_sold to li…
malm-odoo Oct 25, 2024
7de8a5f
[ADD] estate: Added kanban view
malm-odoo Oct 25, 2024
a23543e
[FIX] estate: Fixed views order in manifest
malm-odoo Oct 25, 2024
9fc9a00
[ADD] estate: Added .idea to gitignore
malm-odoo Oct 25, 2024
69f7e7f
[FIX] estate & estate_account: Fixed all the comments on PR
malm-odoo Oct 25, 2024
fb39311
[ADD] awesome_owl: Added counter, card, and todo list.
malm-odoo Oct 29, 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
7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/tutorials.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 106 additions & 0 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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",
'description': "Create real estate properties and keep track of status",
'depends': [
'base',
],
'data': [
'security/ir.model.access.csv',
'views/estate_menus.xml',
'views/estate_property_views.xml',
'views/estate_property_types_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/users.xml',
],
'license': "LGPL-3",
# data files containing optionally loaded demonstration data
'demo': [
"demo/demo.xml",
],
}
13 changes: 13 additions & 0 deletions estate/demo/demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="model_estate_property_action_cancel" model="ir.actions.server">
<field name="name">Mass cancel</field>
<field name="model_id" ref="estate.model_estate_property"/>
<field name="binding_model_id" ref="estate.model_estate_property"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">action = records.action_cancel()</field>
</record>
</data>
</odoo>
1 change: 1 addition & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, users

Choose a reason for hiding this comment

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

Better indent

Suggested change
from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, users
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import users

108 changes: 108 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare


class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'Create real estate properties and keep track of status'
_order = 'id desc'

name = fields.Char(string="Name", required=True)
description = fields.Text(string="Description")
postcode = fields.Char(string="Postcode")
date_availability = fields.Date(string="Date Availability", default=lambda self: fields.Datetime.today() + relativedelta(months=3), copy=False)
expected_price = fields.Float(string="Expected Price", required=True)
selling_price = fields.Float(string="Selling Price", readonly=True, copy=False)
bedrooms = fields.Integer(string="Bedrooms", default=2)
living_area = fields.Integer(string="Living Area")
facades = fields.Integer(string="Facades")
garage = fields.Boolean(string="Garage")
garden = fields.Boolean(string="Garden")
garden_area = fields.Integer(string="Garden Area")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[('north', "North"), ('south', "South"), ('east', "East"), ('west', "West")]
)
active = fields.Boolean(string="Active", default=True)
state = fields.Selection(
string="State",
default='new',
selection=[
('new', "New"),
('offer_received', "Offer Received"),
('offer_accepted', "Offer Accepted"),
('sold', "Sold"),
('cancelled', "Cancelled")
]
)
property_type_id = fields.Many2one('estate.property.types', 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")
tag_ids = fields.Many2many('estate.property.tag', string="Property Tags")
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers")
total_area = fields.Float(compute="_compute_total_area", string="Total Area")
best_price = fields.Float(compute="_compute_best_price", string="Best Price")
Comment on lines +40 to +46

Choose a reason for hiding this comment

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

Those are missing the string option, useful for translation 👍

Copy link
Author

Choose a reason for hiding this comment

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

Isn't it the second parameter in all of these? The string=""?


_sql_constraints = [
('check_expected_price', 'CHECK(expected_price > 0)',
"The expected price should be greater than 0."),
('check_selling_price', 'CHECK(buyer_id IS NULL OR selling_price > 0)',
"The selling price should be greater than 0."),
]

@api.constrains('selling_price', 'expected_price')
def _check_prices_difference(self):
for record in self:
if record.buyer_id and float_compare(record.selling_price, 0.9 * record.expected_price, 5) == -1:

Choose a reason for hiding this comment

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

Use of a meaningful variable name to ease the understanding of the method

Suggested change
for record in self:
if record.buyer_id and float_compare(record.selling_price, 0.9 * record.expected_price, 5) == -1:
for estate in self:
if estate.buyer_id and float_compare(record.selling_price, 0.9 * record.expected_price, 5) == -1:

raise UserError(_("Selling price cannot be lower than 90% of the expected price!"))

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

@api.depends('offer_ids.price')
def _compute_best_price(self):
for record in self:
prices = record.offer_ids.mapped('price')

Choose a reason for hiding this comment

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

No need of empty line here

record.best_price = max(prices) if len(prices) > 0 else 0

Choose a reason for hiding this comment

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

Lil python trick

Suggested change
record.best_price = max(prices) if len(prices) > 0 else 0
record.best_price = max(prices, 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 = ''

def action_cancel(self):
for record in self:
if record.state == 'cancelled':
raise UserError(_("Property already cancelled!"))
elif record.state == 'sold':
raise UserError(_("A sold property cannot be cancelled!"))
else:
self.state = 'cancelled'

return True

def action_sold(self):
for record in self:
if record.state == 'sold':
raise UserError(_("Property already sold!"))
elif record.state == 'cancelled':
raise UserError(_("A cancelled property cannot be sold!"))
else:
self.state = 'sold'

return True

@api.ondelete(at_uninstall=False)
def prevent_deletion(self):
for record in self:
if record.state != 'new' and record.state != 'cancelled':

Choose a reason for hiding this comment

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

Easier to add and remove values in the future

Suggested change
if record.state != 'new' and record.state != 'cancelled':
if record.state not in ['new', 'cancelled']:

raise UserError(_("You can only delete new or cancelled properties!"))
85 changes: 85 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from datetime import timedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Model representing the offers from partners to a specific property'
_order = 'price desc'

price = fields.Float(string="Price")
status = fields.Selection(
string="Status",
copy=False,
selection=[
('accepted', "Accepted"),
('refused', "Refused")
]
)
partner_id = fields.Many2one('res.partner', string="Partner", required=True)
property_id = fields.Many2one('estate.property', string="Property", required=True)
Comment on lines +21 to +22

Choose a reason for hiding this comment

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

String parameter

validity = fields.Integer(string="Validity", default=7)
date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline', string="Date Deadline")
create_date = fields.Date(default=lambda self: fields.Datetime.now())
property_type_id = fields.Many2one(related="property_id.property_type_id")

_sql_constraints = [
('check_offer_price', 'CHECK(price > 0)',
"The offer price should be greater than 0."),
]

@api.depends('validity', 'create_date')
def _compute_date_deadline(self):
for record in self:
record.date_deadline = record.create_date + timedelta(days=record.validity)

def _inverse_date_deadline(self):
for record in self:

Choose a reason for hiding this comment

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

No placing it everywhere but all you looped method need this change hehe

Suggested change
for record in self:
for offer in self:

delta = record.date_deadline - record.create_date

record.validity = delta.days

def action_confirm(self):
self.ensure_one()

if self.status == 'accepted':
raise UserError(_("Offer already accepted!"))
elif self.status == 'refused':
raise UserError(_("Can't accept a refused offer!"))
elif self.property_id.buyer_id:
raise UserError(_("Can't accept more than 1 offer!"))
else:
if float_compare(self.price, 0.9 * self.property_id.expected_price, 5) == -1:
raise UserError(_("Selling price cannot be lower than 90% of the expected price!"))

self.status = 'accepted'
self.property_id.state = 'offer_accepted'
self.property_id.selling_price = self.price
self.property_id.buyer_id = self.partner_id

return True

def action_refuse(self):
for record in self:
if record.status == 'refused':
raise UserError(_("Offer already refused!"))
elif record.status == 'accepted':
raise UserError(_("Can't refuse an accepted offer!"))
else:
record.status = 'refused'

return True

@api.model_create_multi
def create(self, vals):
for val in vals:
current_property_id = self.env['estate.property'].browse(val['property_id'])

if current_property_id.best_price > val['price']:
raise UserError(_("Can't create an offer with a price lower than the best offer!"))

current_property_id.state = 'offer_received'

return super().create(vals)
Loading