-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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 the sprinkles. #214
base: 18.0
Are you sure you want to change the base?
Changes from 10 commits
3e5bf34
5b71686
8178794
4167f29
a3687eb
56dbb8b
ba090b2
7fafa5a
17a1bb4
a4b2e36
8d01b45
63f21b7
4d02f7e
7454f12
7bf5968
7866130
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,4 @@ | ||
from . import models | ||
|
||
|
||
|
||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
'name': 'estate', | ||
|
||
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. Remove extra lines |
||
'category': 'real estate', | ||
|
||
'summary': 'create a estate property', | ||
|
||
'website': 'https://www.odoo.com', | ||
|
||
'depends': ['base'], | ||
'installable': True, | ||
'application': True, | ||
'license': 'LGPL-3', | ||
|
||
'data': [ | ||
'security/ir.model.access.csv', | ||
'views/estate_property_views.xml', | ||
'views/estate_property_type_views.xml', | ||
'views/estate_property_tags_views.xml', | ||
'views/estate_property_offer_views.xml', | ||
'views/estate_menus.xml' | ||
], | ||
|
||
} | ||
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. Use proper format |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from . import estate_property | ||
from . import estate_property_type | ||
from . import estate_property_tags | ||
from . import estate_property_offer | ||
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. arrange in alphabetical order |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
from odoo import models, fields | ||
from datetime import timedelta, date | ||
from odoo import models, fields, api | ||
from odoo.exceptions import UserError | ||
from odoo.exceptions import ValidationError | ||
from odoo.tools.float_utils import float_compare, float_is_zero | ||
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. Remove duplicated code and use alphabetical order |
||
|
||
|
||
|
||
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. Only 2 lines after importing files |
||
class EstateProperty(models.Model): | ||
_name = "estate.property" | ||
_description = "Real Estate Property" | ||
_order = 'id desc' | ||
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. add a new line after defining the class's private variables |
||
name = fields.Char(required=True) | ||
description = fields.Text(string="Description") | ||
postcode = fields.Char(string="Postcode") | ||
date_availability = fields.Date(copy=False, default=lambda self: date.today() + timedelta(days=90)) | ||
expected_price = fields.Float(string="Expected Price", required=True) | ||
selling_price = fields.Float(string="Selling Price") | ||
bedrooms = fields.Integer(default=2) | ||
active = fields.Boolean(default=True) | ||
living_area = fields.Integer(string="Living Area (sqm)") | ||
facades = fields.Integer(string="Facades") | ||
garage = fields.Boolean(string="Garage Available") | ||
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") | ||
] | ||
) | ||
state = fields.Selection(selection=[ | ||
('new', 'New'), | ||
('offer_received', 'Offer Received'), | ||
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. remove extra space |
||
('offer_accepted', 'Offer Accepted'), | ||
('sold', 'Sold'), | ||
('cancelled', 'Cancelled'), | ||
], default='new', string="Status", copy=False) | ||
property_type_id = fields.Many2one('estate.property.type', string="Property Type",required=True, | ||
options={'no_create': True, 'no_edit': True}) | ||
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. use proper formatting. Refer to code base. |
||
buyer_id = fields.Many2one('res.partner', string="Buyer") | ||
seller_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user,required=False) | ||
status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], string="Status") | ||
partner_id = fields.Many2one('res.partner', string="Partner",required=True, ondelete='restrict') | ||
salesperson = fields.Char(string = "Salesperson", required=True) | ||
buyer = fields.Char(string = "Buyers", required=True) | ||
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. Why buyer? |
||
price = fields.Float(string="Price") | ||
partners = fields.Char(string = "Partner", required=True) | ||
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. Why partners? |
||
tag_ids = fields.Many2one('estate.property.tag' , string="Tags") | ||
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. Why |
||
total_area = fields.Float(string="Total Area (sqm)", compute="_compute_total_area", store=True,copy=False) | ||
best_offers = fields.Float(string="Best Offers") | ||
offer_ids = fields.One2many( | ||
'estate.property.offer', | ||
'property_id', | ||
string="Offers" | ||
) | ||
validity = fields.Integer(string="Validity (days)") | ||
date_deadline = fields.Date( | ||
string="Deadline", | ||
compute="_compute_date_deadline", | ||
inverse="_inverse_date_deadline", | ||
store=True) | ||
tag_ids = fields.Many2many( | ||
'estate.property.tag', | ||
string='Tags' | ||
) | ||
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") | ||
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. defined twice |
||
best_price = fields.Float(string="Best Price", compute="_compute_best_price", store=True) | ||
offer_received = fields.Boolean(string="Offer Received", compute="_compute_offer_received", store=True) | ||
offer_accepted = fields.Boolean(string="Offer Accepted", compute="_compute_offer_accepted", store=True) | ||
|
||
|
||
|
||
@api.onchange('garden') | ||
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. Arrange all functions properly |
||
def _onchange_garden(self): | ||
if self.garden: | ||
self.garden_area = 10 | ||
self.garden_orientation = 'north' | ||
else: | ||
self.garden_area = 0 | ||
self.garden_orientation = False | ||
|
||
def action_cancel(self): | ||
for record in self: | ||
if record.state == 'sold': | ||
raise UserError("A sold property cannot be cancelled.") | ||
record.state = 'cancelled' | ||
|
||
def action_sold(self): | ||
for record in self: | ||
if record.state == 'cancelled': | ||
raise UserError("A cancelled property cannot be sold.") | ||
record.state = 'sold' | ||
|
||
_sql_constraints = [ | ||
('expected_price_positive', 'CHECK(expected_price > 0)', | ||
'The expected price must be strictly positive.'), | ||
('selling_price_positive', 'CHECK(selling_price >= 0)', | ||
'The selling price must be positive.') | ||
] | ||
|
||
@api.constrains('expected_price') | ||
def _check_expected_price(self): | ||
for record in self: | ||
if record.expected_price <= 0: | ||
raise ValidationError("The expected price must be strictly positive.") | ||
|
||
|
||
@api.depends('offer_ids.price') | ||
def _compute_best_price(self): | ||
for record in self: | ||
record.best_price = max(record.offer_ids.mapped('price'), default=0) | ||
|
||
@api.depends('living_area', 'garden_area') | ||
def _compute_total_area(self): | ||
for record in self: | ||
record.total_area = (record.living_area or 0) + (record.garden_area or 0) | ||
|
||
@api.constrains('expected_price', 'selling_price') | ||
def _check_selling_price(self): | ||
for record in self: | ||
if float_is_zero(record.selling_price, precision_rounding=0.01): | ||
continue | ||
min_price = record.expected_price * 0.9 | ||
if float_compare(record.selling_price, min_price, precision_rounding=0.01) < 0: | ||
raise ValidationError( | ||
"The selling price cannot be lower than 90% of the expected price." | ||
) | ||
def action_sold(self): | ||
self.state = 'sold' | ||
|
||
def action_cancel(self): | ||
self.state = 'canceled' | ||
|
||
|
||
@api.depends('state') | ||
def _compute_offer_received(self): | ||
for record in self: | ||
record.offer_received = record.state == 'offer_received' | ||
|
||
@api.depends('state') | ||
def _compute_offer_accepted(self): | ||
for record in self: | ||
record.offer_accepted = record.state == 'offer_accepted' | ||
|
||
@api.ondelete(at_uninstall=False) | ||
def _check_state_on_delete(self): | ||
for record in self: | ||
if record.state not in ('new', 'cancelled'): | ||
raise UserError( | ||
"You cannot delete a property unless it is in 'New' or 'Cancelled' state." | ||
) | ||
|
||
|
||
|
||
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. wrong indentation and extra spaces |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
from odoo import models, fields | ||
from datetime import timedelta | ||
from odoo.exceptions import UserError | ||
from odoo import models, fields, api | ||
|
||
|
||
class EstatePropertyOffer(models.Model): | ||
_name = 'estate.property.offer' | ||
_description = 'Real Estate Property Offer' | ||
_order = 'price desc' | ||
price = fields.Float() | ||
state= fields.Selection([('new', 'New'),('accepted', 'Accepted'), ('refused', 'Refused')], string= "Status" ,default='new') | ||
partner_id = fields.Many2one('res.partner', string="Partner", required=True) | ||
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. Why |
||
property_id = fields.Many2one('estate.property', string="Property", required=True) | ||
status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], string="Status") | ||
property_type_id = fields.Many2one( | ||
related='property_id.property_type_id', | ||
string="Property Type", | ||
readonly=True, | ||
store=True | ||
) | ||
buyer_id = fields.Many2one( | ||
'res.partner', | ||
string="Buyer", | ||
required=True | ||
) | ||
seller_id = fields.Many2one( | ||
'res.partner', | ||
string="Seller" | ||
) | ||
validity = fields.Integer(string="Validity (days)") | ||
date_deadline = fields.Date( | ||
string="Deadline", | ||
compute="_compute_date_deadline", | ||
inverse="_inverse_date_deadline", | ||
store=True | ||
) | ||
def action_offer_accepted(self): | ||
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.
|
||
pass | ||
|
||
@api.depends('create_date', 'validity') | ||
def _compute_date_deadline(self): | ||
for record in self: | ||
if record.create_date: | ||
record.date_deadline = record.create_date + timedelta(days=record.validity) | ||
else: | ||
record.date_deadline = fields.Date.today() + timedelta(days=record.validity) | ||
|
||
def _inverse_date_deadline(self): | ||
for record in self: | ||
if record.create_date and record.date_deadline: | ||
delta = (record.date_deadline - record.create_date.date()).days | ||
record.validity = max(delta, 0) | ||
else: | ||
record.validity = 7 | ||
|
||
def action_offer_accepted(self): | ||
for record in self: | ||
record.status = 'accepted' | ||
record.property_id.selling_price = record.price | ||
record.property_id.buyer_id = record.partner_id | ||
|
||
# set status 'refused' in the other offers of that particular property | ||
for offer in record.property_id.offer_ids: | ||
if offer.id != record.id: | ||
offer.status = 'refused' | ||
return True | ||
|
||
def action_offer_refused(self): | ||
for record in self: | ||
record.status = 'refused' | ||
return True | ||
|
||
_sql_constraints = [ | ||
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. Refer to code guidelines for proper file formatting |
||
('offer_price_positive', 'CHECK(price > 0)', | ||
'The offer price must be strictly positive.') | ||
] | ||
|
||
|
||
@api.model | ||
def create(self, vals): | ||
# Ensure property_id exists in vals | ||
property_id = self.env['estate.property'].browse(vals.get('property_id')) | ||
if not property_id: | ||
raise UserError("The property associated with this offer does not exist.") | ||
|
||
# Check if the new offer amount is lower than any existing offers | ||
existing_offers = self.search([('property_id', '=', property_id.id)]) | ||
for offer in existing_offers: | ||
if vals.get('price', 0.0) <= offer.price: | ||
raise UserError( | ||
"You cannot create an offer with a price lower than an existing offer." | ||
) | ||
|
||
# Update the property's state to 'Offer Received' | ||
property_id.state = 'offer_received' | ||
|
||
# Create the offer as usual | ||
return super(EstatePropertyOffer, self).create(vals) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from odoo import models, fields | ||
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. File not included in |
||
class ResUsers(models.Model): | ||
_inherit = 'res.users' | ||
|
||
property_ids = fields.One2many( | ||
'estate.property', | ||
'seller_id', | ||
string="Properties", | ||
domain=[('state', 'in', ['new', 'offer_received'])] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from odoo import models, fields | ||
class EstatePropertyTag(models.Model): | ||
_name = 'estate.property.tag' | ||
_description = 'Real Estate Property Tag' | ||
_order = "name" | ||
|
||
name = fields.Char(string="Tag Name", required=True) | ||
color = fields.Integer(string='Color') | ||
tag_ids = fields.Many2many( | ||
'estate.property.tag', | ||
Comment on lines
+9
to
+10
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. Relation with the same model? |
||
'estate_property_tag_rel', | ||
'tag_id1', | ||
'tag_id2', | ||
string="Related Tags" | ||
) | ||
|
||
_sql_constraints = [ | ||
('unique_tag_name', 'UNIQUE(name)', | ||
'The tag name must be unique.') | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from odoo import models, fields, api | ||
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. Check odoo coding guidelines and do proper formatting for all files |
||
class EstatePropertyType(models.Model): | ||
_name = 'estate.property.type' | ||
_description = 'Real Estate Property Type' | ||
_order = "sequence,name" | ||
|
||
sequence = fields.Integer(default=10) | ||
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. Why |
||
name = fields.Char(string="Type Name", required=True) | ||
property_ids = fields.One2many('estate.property', 'property_type_id', string="Properties") | ||
offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string="Offers") | ||
offer_count = fields.Integer(string="Offer Count", compute="_compute_offer_count") | ||
|
||
_sql_constraints = [ | ||
('unique_type_name', 'UNIQUE(name)', | ||
'The 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) | ||
|
||
def action_view_offers(self): | ||
self.ensure_one() | ||
return { | ||
'type': 'ir.actions.act_window', | ||
'name': 'Offers', | ||
'view_mode': 'tree,form', | ||
'res_model': 'estate.property.offer', | ||
'domain': [('property_type_id', '=', self.id)], | ||
'context': {'default_property_type_id': self.id}, | ||
} | ||
|
||
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. Remove new line |
||
|
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_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1 | ||
estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,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 | ||
estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 | ||
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. No need for |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
|
||
<menuitem id="estate_menu_root" name="Estate"/> | ||
<menuitem id="estate_properties_menu" name="Advertisements" parent="estate_menu_root" action="estate_property_action"/> | ||
<menuitem id="estate_properties_menu_two" name="Settings" parent="estate_menu_root"/> | ||
<menuitem id="estate_property_type_menu" name="Property Types" parent="estate_properties_menu_two" action="estate_property_type_action"/> | ||
<menuitem id="estate_property_tag_menu" name="Property Tags" parent="estate_properties_menu_two" action="estate_property_tag_action"/> | ||
</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.
Remove