Skip to content

mfre - Technical Training #819

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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_account/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
8 changes: 8 additions & 0 deletions estate_account/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
'name': 'Estate Account Module',
'depends': ['base', 'account'],
'data': [
'security/ir.model.access.csv',
],
'license': 'LGPL-3',
}
1 change: 1 addition & 0 deletions estate_account/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property
48 changes: 48 additions & 0 deletions estate_account/models/estate_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo import Command


class EstateAccount(models.Model):
_inherit = 'estate.property'

def action_sold(self):
for property_record in self:
if property_record.state == 'cancelled':
raise UserError("A cancelled property cannot be set as sold")

Choose a reason for hiding this comment

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

Most strings are automatically made translatable: field names, module and model names, etc. But this is not the case for error messages. To make them translatable, use the _() method like so

Suggested change
raise UserError("A cancelled property cannot be set as sold")
raise UserError(_"A cancelled property cannot be set as sold"))

This applies to all your error messages


partner_id = property_record.buyer_id
selling_price = property_record.selling_price

invoice_line_1 = Command.create(
{
'name': 'Property Sale',
'quantity': 1,
'price_unit': selling_price * 1.06,
},
)

invoice_line_2 = Command.create(
{
'name': 'Administrative Fees',
'quantity': 1,
'price_unit': 100.00,
},
)

journal_id = self.env['account.journal'].search([('type', '=', 'sale')], limit=1)

if not journal_id:
raise UserError("No sale journal found")

invoice_values = {
'partner_id': partner_id.id,
'move_type': 'out_invoice',
'journal_id': journal_id.id,
'invoice_date': fields.Date.today(),
'invoice_line_ids': [invoice_line_1, invoice_line_2],
}

self.env['account.move'].create(invoice_values)

return super().action_sold()
2 changes: 2 additions & 0 deletions estate_account/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
'id','name','model_id:id','group_id:id','perm_read','perm_write','perm_create','perm_unlink'
'model_estate_property_base_group_allow_all','model.estate.property.base.group.allow_all','model_estate_property','base.group_user',1,1,1,1
1 change: 1 addition & 0 deletions estate_property/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
14 changes: 14 additions & 0 deletions estate_property/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
'name': 'Estate Module',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/estate_res_users.xml',
'views/estate_property_offer_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_views.xml',
'views/estate_menus.xml',
],
'license': 'LGPL-3',
}
5 changes: 5 additions & 0 deletions estate_property/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import res_users
156 changes: 156 additions & 0 deletions estate_property/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from datetime import date
from dateutil.relativedelta import relativedelta

from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'Real Estate Property'
_order = 'id desc'
_sql_constraints = [
(
'expected_price_strictly_positive',
'CHECK(expected_price > 0)',
'Expected price must be strictly positive',
),
(
'selling_price_positive',
'CHECK(selling_price >= 0)',
'Selling price must be positive',
),
]

property_type_id = fields.Many2one(
'estate.property.type',
string='Property Type',
)
buyer_id = fields.Many2one(
'res.partner',
compute='_compute_infos_from_accepted_offer',
string='Buyer',
copy=False,
)
salesperson_id = fields.Many2one(
'res.users',
string='Salesperson',
default=lambda self: self.env.user,
copy=False,
)
tag_ids = fields.Many2many('estate.property.tag')
offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers')
total_area = fields.Float(compute='_compute_total_area', string='Total Area (sqm)')
best_offer = fields.Float(compute='_compute_best_offer', string='Best Offer')
active = fields.Boolean(default=True)
name = fields.Char(string='Title', required=True)
description = fields.Text(string='Description')
postcode = fields.Char(string='Postcode')
date_availability = fields.Date(
string='Available From',
copy=False,
default=date.today() + relativedelta(months=3), # noqa: DTZ011
)
expected_price = fields.Float(string='Expected Price', required=True)
selling_price = fields.Float(
compute='_compute_infos_from_accepted_offer',
string='Selling Price',
readonly=True,
copy=False,
)
bedrooms = fields.Integer(string='Bedrooms', default=2)
living_area = fields.Integer(string='Living Area (sqm)')
facades = fields.Integer(string='Number of Facades')
garage = fields.Boolean(string='Has Garage?')
garden = fields.Boolean(string='Has Garden?')
garden_area = fields.Integer(string='Garden Area (sqm)')
garden_orientation = fields.Selection(
selection=[
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West'),
],
string='Garden Orientation',
)
state = fields.Selection(
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
],
string='Status',
required=True,
copy=False,
default='new',
)
note = fields.Text('Special mentions about the house')

@api.depends('offer_ids.status')
def _compute_infos_from_accepted_offer(self):
for property in self:
for offer in property.offer_ids:
if offer.status == 'accepted':
property.buyer_id = offer.partner_id
property.selling_price = offer.price
return
property.buyer_id = None
property.selling_price = None

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

@api.depends('offer_ids.price')
def _compute_best_offer(self):
for property in self:
if property.offer_ids:
property.best_offer = max(map(lambda r: r.price, property.offer_ids))
else:
property.best_offer = None
Comment on lines +111 to +114

Choose a reason for hiding this comment

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

Suggested change
if property.offer_ids:
property.best_offer = max(map(lambda r: r.price, property.offer_ids))
else:
property.best_offer = None
property.best_offer = max(property.offer_ids.mapped('price'), default=0.0)


@api.constrains('expected_price', 'selling_price')
def _check_selling_price(self):
for property in self:
if not float_is_zero(property.selling_price, precision_digits=2):
if (
float_compare(
property.selling_price,
property.expected_price * 0.9,
precision_digits=2,
)
< 0
):
raise ValidationError("The selling price cannot be lower than 90% of the expected price!")

@api.onchange('garden')
def _onchange_partner_id(self):
if self.garden:
self.garden_area = 10
self.garden_orientation = 'north'
else:
self.garden_area = 0
self.garden_orientation = None

@api.ondelete(at_uninstall=False)
def _unlink_only_new_and_cancelled(self):
if any(property.state != 'new' and property.state != 'cancelled' for property in self):
raise UserError("Can't delete if state is not New or Cancelled!")

def action_sold(self):
for property in self:
if property.state != 'cancelled':
property.state = 'sold'
return True
raise UserError("A cancelled property cannot be set as sold")

def action_cancel(self):
for property in self:
if property.state != 'sold':
property.state = 'cancelled'
return True
raise UserError("A sold property cannot be set as cancelled")
64 changes: 64 additions & 0 deletions estate_property/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from datetime import timedelta

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


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Real Estate Property Offer'
_order = 'price desc'
_sql_constraints = [
(
'offer_price_strictly_positive',
'CHECK(price > 0)',
'Offer price must be strictly positive',
),
]

date_deadline = fields.Date(
compute='_compute_date_deadline',
inverse='_inverse_date_deadline',
string='Date Deadline',
)
price = fields.Float()
validity = fields.Integer(default=7, string='Validity (days)')
status = fields.Selection(
[('accepted', 'Accepted'), ('refused', 'Refused')],
copy=False,
)
partner_id = fields.Many2one('res.partner', required=True)
property_id = fields.Many2one('estate.property', required=True)
property_type_id = fields.Many2one(related='property_id.property_type_id', store=True)

@api.depends('validity')
def _compute_date_deadline(self):
for record in self:
record.date_deadline = (record.create_date.date() or fields.Date.now()) + timedelta(days=record.validity)

def _inverse_date_deadline(self):
for record in self:
record.validity = (record.date_deadline -(record.create_date.date() or fields.Date.now())).days

@api.model
def create(self, vals):
property_id = vals.get('property_id')

property = self.env['estate.property'].browse(property_id)

property.state = 'offer_received'

existing_offers = self.env['estate.property.offer'].search([('property_id', '=', property_id), ('price', '>=', vals['price'])])

Choose a reason for hiding this comment

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

A search is unnecessary since you have the property already

Suggested change
existing_offers = self.env['estate.property.offer'].search([('property_id', '=', property_id), ('price', '>=', vals['price'])])
existing_offers = property.offer_ids.filter(lambda offer: offer.price > vals['price']


if existing_offers:
raise ValidationError("The offer value must be higher than existing offers.")

return super().create(vals)
Comment on lines +43 to +56

Choose a reason for hiding this comment

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

We'd prefer for your create to be done in batch for better performances. Use

Suggested change
@api.model
def create(self, vals):
property_id = vals.get('property_id')
property = self.env['estate.property'].browse(property_id)
property.state = 'offer_received'
existing_offers = self.env['estate.property.offer'].search([('property_id', '=', property_id), ('price', '>=', vals['price'])])
if existing_offers:
raise ValidationError("The offer value must be higher than existing offers.")
return super().create(vals)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
...
return super().create(vals_list)


def action_accept_offer(self):
for record in self:
record.status = 'accepted'

def action_refuse_offer(self):
for record in self:
record.status = 'refused'
11 changes: 11 additions & 0 deletions estate_property/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'Real Estate Property Tag'

name = fields.Char(string='Name', required=True)
color = fields.Integer(string='Color')
_order = 'name'
_sql_constraints = [('name_unique', 'UNIQUE(name)', 'Property tag name must be unique')]
21 changes: 21 additions & 0 deletions estate_property/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from odoo import api, fields, models


class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = 'Real Estate Property Type'
_order = 'name'

sequence = fields.Integer('Sequence', default=1)

name = fields.Char(string='Title', required=True)
property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties')
_sql_constraints = [('name_unique', 'UNIQUE(name)', 'Property type name must be unique')]

offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers')
offer_count = fields.Integer(compute='_compute_offer_count', string='Offer Count')

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


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

property_ids = fields.One2many("estate.property", "salesperson_id", string="Properties")
5 changes: 5 additions & 0 deletions estate_property/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'
'model_estate_property_base_group_allow_all','model.estate.property.base.group.allow_all','model_estate_property','base.group_user',1,1,1,1
'model_estate_property_type_base_group_allow_all','model.estate.property.type.base.group.allow_all','model_estate_property_type','base.group_user',1,1,1,1
'model_estate_property_tag_base_group_allow_all','model.estate.property.tag.base.group.allow_all','model_estate_property_tag','base.group_user',1,1,1,1
'model_estate_property_offer_base_group_allow_all','model.estate.property.offer.base.group.allow_all','model_estate_property_offer','base.group_user',1,1,1,1
12 changes: 12 additions & 0 deletions estate_property/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<odoo>
<menuitem id="estate_property_menu" name="Real Estate">
<menuitem id="estate_property_menu_advertisement" name="Advertisements">
<menuitem id="estate_property_menu_advertisement_property" action="estate_property_advertisement_action" />
</menuitem>
<menuitem id="estate_property_menu_setting" name="Settings">
<menuitem id="estate_property_menu_setting_type" action="estate_property_type_action" />
<menuitem id="estate_property_menu_setting_tag" action="estate_property_tag_action" />
</menuitem>
</menuitem>

</odoo>
Loading