-
Notifications
You must be signed in to change notification settings - Fork 2k
[ADD] estate:Create first Estate module #731
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
base: 18.0
Are you sure you want to change the base?
Changes from all commits
2ec5183
ee36135
6fa1a8a
5bc7a96
8b38906
4489954
7140196
e09e56c
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,9 @@ | ||
{ | ||
"name": "estate.account", | ||
"depends": ["base", "account", "realestate"], | ||
"data": [ | ||
"security/ir.model.access.csv", | ||
], | ||
"application": True, | ||
"installable": True, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import estate_account | ||
from . import realestate |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class EstateAccount(models.Model): | ||
_name = "estate.account" | ||
name = fields.Char(required=True) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from odoo import models, fields, Command | ||
|
||
|
||
class Realestate(models.Model): | ||
_name = "realestate" | ||
_inherit = "realestate" | ||
|
||
def action_set_property_sold(self): | ||
self.env["account.move"].create( | ||
{ | ||
"partner_id": super().buyer_id.id, | ||
"move_type": "out_invoice", | ||
"line_ids": [ | ||
Command.create( | ||
{ | ||
"name": self.name, | ||
"quantity": 1, | ||
"price_unit": super().selling_price, | ||
} | ||
) | ||
], | ||
} | ||
) | ||
return super().action_set_property_sold() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||
access_estate_account,access.estate_acount,model_estate_account,,1,1,1,1 | ||
access_realestate,access.realestate,model_realestate,,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,16 @@ | ||
{ | ||
"name": "realestate", | ||
"depends": ["base"], | ||
"data": [ | ||
"security/ir.model.access.csv", | ||
"views/estate_view.xml", | ||
"views/owner_view.xml", | ||
"views/tag_view.xml", | ||
"views/seller_view.xml", | ||
"views/buyer_view.xml", | ||
"views/offer_view.xml", | ||
"views/type_view.xml", | ||
], | ||
"application": True, | ||
"installable": True, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from . import realestate | ||
from . import owner | ||
from . import types | ||
from . import tags | ||
from . import seller | ||
from . import buyer | ||
from . import offer | ||
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,8 @@ | ||||||
from odoo import fields, models | ||||||
|
||||||
|
||||||
class Buyer(models.Model): | ||||||
_name = "buyer" | ||||||
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
(etc...) |
||||||
|
||||||
name = fields.Char(required=True) | ||||||
properties_ids = fields.One2many("realestate", "buyer_id") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from odoo import api, fields, models | ||
from datetime import timedelta | ||
from odoo.exceptions import UserError | ||
|
||
|
||
class Offer(models.Model): | ||
_name = "offer" | ||
_order = "price desc" | ||
price = fields.Float() | ||
status = fields.Selection([("accepted", "Accepted"), ("refused", "Refused")]) | ||
buyer_id = fields.Many2one("res.partner") | ||
property_id = fields.Many2one("realestate") | ||
property_type_id = fields.Many2one(related="property_id.type_id") | ||
state = fields.Selection(related="property_id.state") | ||
validity = fields.Integer() | ||
deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline") | ||
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. It's a good habit to add the suffix |
||
highest_offer = fields.Float(compute="_calc_highest_price") | ||
|
||
@api.depends("validity") | ||
def _compute_deadline(self): | ||
for record in self: | ||
record.deadline = ( | ||
record.create_date or fields.Datetime.now() | ||
).date() + timedelta(days=record.validity) | ||
|
||
def _inverse_deadline(self): | ||
for record in self: | ||
record.validity = ( | ||
record.deadline - (record.create_date or fields.Datetime.now()).date() | ||
).days | ||
|
||
def set_offer_accepted(self): | ||
for record in self: | ||
if record.property_id.state == "sold": | ||
raise UserError("This property is already sold") | ||
record.status = "accepted" | ||
record.property_id.selling_price = record.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. You can use 'mapped |
||
record.property_id.buyer_id = record.buyer_id | ||
record.property_id.state = "offer_accepted" | ||
|
||
def set_offer_refused(self): | ||
for record in self: | ||
if record.property_id.state == "sold": | ||
raise UserError("This property is already sold") | ||
self.status = "refused" | ||
|
||
_sql_constraints = [ | ||
( | ||
"check_offer_price", | ||
"CHECK(price> 0)", | ||
"Offer price must be positive.", | ||
) | ||
] | ||
|
||
def create(self, vals): | ||
res = super(Offer, self).create(vals) | ||
|
||
if res.price < res.property_id.best_price: | ||
raise UserError("Please set a higher price!") | ||
return res |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class Owner(models.Model): | ||
_name = "owner" | ||
|
||
name = fields.Char(required=True) | ||
address = fields.Char() | ||
phone = fields.Char() | ||
properties_ids = fields.One2many("realestate", "owner_id") |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,124 @@ | ||||||
from odoo import api, fields, models | ||||||
from odoo.exceptions import UserError, ValidationError | ||||||
|
||||||
|
||||||
class Realestate(models.Model): | ||||||
_name = "realestate" | ||||||
_description = "Estate" | ||||||
_order = "type_id" | ||||||
name = fields.Char(required=True) | ||||||
description = fields.Text() | ||||||
postcode = fields.Char() | ||||||
date_availability = fields.Date() | ||||||
expected_price = fields.Float(required=True) | ||||||
selling_price = fields.Float(readonly=True, copy=False) | ||||||
bedrooms = fields.Integer(default=2) | ||||||
living_area = fields.Integer() | ||||||
facades = fields.Integer() | ||||||
garage = fields.Boolean() | ||||||
garden = fields.Boolean() | ||||||
garden_area = fields.Integer() | ||||||
|
||||||
garden_orientation = fields.Selection( | ||||||
selection=[ | ||||||
("north", "North"), | ||||||
("south", "South"), | ||||||
("east", "East"), | ||||||
("west", "West"), | ||||||
], | ||||||
) | ||||||
active = fields.Boolean(default=True) | ||||||
state = fields.Selection( | ||||||
selection=[ | ||||||
("new", "New"), | ||||||
("offer_received", "Offer Received"), | ||||||
("offer_accepted", "Offer Accepted"), | ||||||
("sold", "Sold"), | ||||||
("cancelled", "Cancelled"), | ||||||
], | ||||||
default="new", | ||||||
) | ||||||
owner_id = fields.Many2one("buyer") | ||||||
type_id = fields.Many2one("types", widget="handle") | ||||||
tag_ids = fields.Many2many("tags", string="Tags") | ||||||
seller_id = fields.Many2one("seller") | ||||||
buyer_id = fields.Many2one("res.partner") | ||||||
offer_ids = fields.One2many("offer", "property_id") | ||||||
status = fields.Selection(related="offer_ids.status") | ||||||
price = fields.Float(related="offer_ids.price") | ||||||
validity = fields.Integer(related="offer_ids.validity") | ||||||
deadline = fields.Date(related="offer_ids.deadline") | ||||||
Comment on lines
+47
to
+50
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. You cannot created related fields that pass through an x2many (you can look at the warning in the Doc) What are you trying to accomplish here? What do you think the value should be? |
||||||
total_area = fields.Integer(compute="_compute_total") | ||||||
best_price = fields.Float(compute="_max_offer_price") | ||||||
|
||||||
@api.depends("living_area", "garden_area") | ||||||
def _compute_total(self): | ||||||
for record in self: | ||||||
record.total_area = record.living_area + record.garden_area | ||||||
|
||||||
@api.depends("offer_ids") | ||||||
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. Depends only on offer_ids, not offer_ids.price, which means the method won’t be triggered when an offer’s price changes, unless a new offer is added/removed. |
||||||
def _max_offer_price(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. the method should start with |
||||||
for record in self: | ||||||
record.best_price = max(record.offer_ids.mapped("price"), 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 = "" | ||||||
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
|
||||||
|
||||||
def action_set_property_sold(self): | ||||||
for record in self: | ||||||
if record.state == "cancelled": | ||||||
raise UserError("Cancelled properties cannot be sold") | ||||||
record.state = "sold" | ||||||
|
||||||
def action_set_property_cancelled(self): | ||||||
for record in self: | ||||||
if record.state == "sold": | ||||||
raise UserError("Sold properties cannot be Cancelled") | ||||||
record.state = "cancelled" | ||||||
|
||||||
# def action_set_offer_accepted(self): | ||||||
# for record in self: | ||||||
# if record.status | ||||||
|
||||||
_sql_constraints = [ | ||||||
( | ||||||
"check_expected_price", | ||||||
"CHECK(expected_price> 0)", | ||||||
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. nitpick:
Suggested change
(more places) |
||||||
"Expected price must be positive.", | ||||||
), | ||||||
( | ||||||
"check_selling_price", | ||||||
"CHECK(selling_price> 0)", | ||||||
"Selling price must be positive.", | ||||||
), | ||||||
( | ||||||
"check_offer_price", | ||||||
"CHECK(price> 0)", | ||||||
"Offer price must be positive.", | ||||||
), | ||||||
] | ||||||
|
||||||
@api.constrains("selling_price") | ||||||
def _validate_selling_price(self): | ||||||
for record in self: | ||||||
if record.selling_price < 0.9 * record.expected_price: | ||||||
raise ValidationError( | ||||||
"Selling price must be at least 90'%' of the expected 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.
|
||||||
) | ||||||
|
||||||
@api.constrains("offer_ids") | ||||||
def _check_for_offers(self): | ||||||
if len(self.offer_ids) > 0: | ||||||
self.state = "offer_received" | ||||||
|
||||||
@api.ondelete(at_uninstall=False) | ||||||
def _unlink_new_properties(self): | ||||||
for record in self: | ||||||
if record.state != "new" or record.state != "cancelled": | ||||||
raise UserError("Property cannot be deleted!") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class Seller(models.Model): | ||
_name = "seller" | ||
|
||
name = fields.Char(required=True) | ||
properties_ids = fields.One2many("realestate", "seller_id") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class Tags(models.Model): | ||
_name = "tags" | ||
_order = "name" | ||
name = fields.Char(required=True) | ||
color = fields.Integer(string="Color") # <-- add this |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class Types(models.Model): | ||
_name = "types" | ||
_order = "sequence" | ||
name = fields.Char(required=True) | ||
properties_ids = fields.One2many("realestate", "type_id") | ||
sequence = fields.Integer("Sequence", default=1) | ||
offer_ids = fields.One2many("offer", "property_type_id") | ||
offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
||
@api.depends("offer_ids") | ||
def _compute_offer_count(self): | ||
for record in self: | ||
record.offer_count = len(record.offer_ids) | ||
|
||
_sql_constraints = [ | ||
( | ||
"unique_name", | ||
"unique(name)", | ||
"Name already exists", | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||
access_realestate,access.realestate,model_realestate,,1,1,1,1 | ||
access_owner,access.owner,model_owner,,1,1,1,1 | ||
access_types,access.types,model_types,,1,1,1,1 | ||
access_tags,access.tags,model_tags,,1,1,1,1 | ||
access_seller,access.seller,model_seller,,1,1,1,1 | ||
access_buyer,access.buyer,model_buyer,,1,1,1,1 | ||
access_offer,access.offer,model_offer,,1,1,1,1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<odoo> | ||
|
||
|
||
<record id="buyer_action" model="ir.actions.act_window"> | ||
<field name="name">Buyer</field> | ||
<field name="res_model">buyer</field> | ||
<field name="view_mode">list,form</field> | ||
</record> | ||
|
||
<menuitem id="real_estate_root_menu" name="Real Estate"> | ||
<menuitem id="Users_menu" name="Users"> | ||
<menuitem id="buyer_menu_item" name="Buyers" action="buyer_action" /> | ||
</menuitem> | ||
</menuitem> | ||
|
||
|
||
</odoo> | ||
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. Some of your files are missing the eol character at the end of the last line (hence the red signs in the github ui) -- could you configure your editor so it doesn't do that please? |
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.
nitpick: by convention, we prefix model names with the name of the module