-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
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', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import estate_property |
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") | ||
|
||
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() |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
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', | ||
} |
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 |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
@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") |
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'])]) | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if existing_offers: | ||||||||||||||||||||||||||||||||||||||||
raise ValidationError("The offer value must be higher than existing offers.") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
return super().create(vals) | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+43
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def action_accept_offer(self): | ||||||||||||||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||||||||||||||
record.status = 'accepted' | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def action_refuse_offer(self): | ||||||||||||||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||||||||||||||
record.status = 'refused' |
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')] |
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) |
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") |
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 |
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> |
There was a problem hiding this comment.
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 soThis applies to all your error messages