-
Notifications
You must be signed in to change notification settings - Fork 2.6k
JEDEL Onboarding #998
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: 19.0
Are you sure you want to change the base?
JEDEL Onboarding #998
Changes from 16 commits
f8e1bc0
c558c6b
cd52496
8cb8dc5
a0182ea
a449045
ded2b66
286c828
fa3f1ea
f9ed8a6
2ff906f
67147af
a0a6f39
abc4cdf
1c19652
c5e045e
3ef6943
6a76b10
dd2110b
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,15 @@ | ||
{ | ||
'name': "Real Estate", | ||
'depends': ['base'], | ||
'application': True, | ||
'author': "Jeanne Delneste", | ||
'license': "LGPL-3", | ||
'data': [ | ||
'views/estate_property_views.xml', | ||
'views/estate_property_type_views.xml', | ||
'views/estate_property_tag_views.xml', | ||
'views/estate_property_offer_views.xml', | ||
'views/estate_menus.xml', | ||
'security/ir.model.access.csv' | ||
], | ||
} |
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_tag | ||
from . import estate_property_offer |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,87 @@ | ||||||||||||||||||
from odoo import api, models, fields | ||||||||||||||||||
from odoo.exceptions import UserError | ||||||||||||||||||
import datetime | ||||||||||||||||||
from dateutil.relativedelta import relativedelta | ||||||||||||||||||
from odoo.tools.float_utils import float_compare, float_is_zero | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
class EstateProperty(models.Model): | ||||||||||||||||||
_name = "estate.property" | ||||||||||||||||||
_description = "Property for the Real Estate app" | ||||||||||||||||||
|
||||||||||||||||||
name = fields.Char(required=True) | ||||||||||||||||||
description = fields.Text() | ||||||||||||||||||
postcode = fields.Char() | ||||||||||||||||||
notes = fields.Html() | ||||||||||||||||||
date_availability = fields.Date(copy=False, default=datetime.date.today() + relativedelta(months=+3)) | ||||||||||||||||||
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")], copy=False, required=True, default="new") | ||||||||||||||||||
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
Line is way too long |
||||||||||||||||||
property_type_id = fields.Many2one("estate.property.type", string="Type") | ||||||||||||||||||
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) | ||||||||||||||||||
salesperson_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) | ||||||||||||||||||
tag_ids = fields.Many2many("estate.property.tag", string="Tags") | ||||||||||||||||||
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") | ||||||||||||||||||
total_area = fields.Integer(compute="_compute_total_area", string="Total Area (sqm)") | ||||||||||||||||||
best_price = fields.Float(compute="_compute_best_offer", string="Best Offer") | ||||||||||||||||||
|
||||||||||||||||||
_positive_expected_price = models.Constraint( | ||||||||||||||||||
'CHECK(expected_price > 0)', | ||||||||||||||||||
'The expected price of a property must be strictly positive' | ||||||||||||||||||
) | ||||||||||||||||||
_positive_selling_price = models.Constraint( | ||||||||||||||||||
'CHECK(selling_price >= 0)', | ||||||||||||||||||
'The selling price of a property must be positive' | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
@api.depends("living_area", "garden_area") | ||||||||||||||||||
def _compute_total_area(self): | ||||||||||||||||||
for record in self: | ||||||||||||||||||
record.total_area = record.living_area + record.garden_area | ||||||||||||||||||
|
||||||||||||||||||
@api.depends("offer_ids.price") | ||||||||||||||||||
def _compute_best_offer(self): | ||||||||||||||||||
for record in self: | ||||||||||||||||||
prices = record.offer_ids.mapped("price") | ||||||||||||||||||
if len(prices) > 0: | ||||||||||||||||||
record.best_price = max(prices) | ||||||||||||||||||
else: | ||||||||||||||||||
record.best_price = 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 = None | ||||||||||||||||||
|
||||||||||||||||||
def sell_property(self): | ||||||||||||||||||
for record in self: | ||||||||||||||||||
if record.state == "cancelled": | ||||||||||||||||||
raise UserError("Error - You cannot sell a cancelled property !") | ||||||||||||||||||
record.state = "sold" | ||||||||||||||||||
return True | ||||||||||||||||||
|
||||||||||||||||||
def cancel_property(self): | ||||||||||||||||||
for record in self: | ||||||||||||||||||
if record.state == "sold": | ||||||||||||||||||
raise UserError("Error - You cannot cancel a sold property !") | ||||||||||||||||||
record.state = "cancelled" | ||||||||||||||||||
return True | ||||||||||||||||||
|
||||||||||||||||||
@api.constrains('expected_price', 'selling_price') | ||||||||||||||||||
def _check_selling_price(self): | ||||||||||||||||||
for record in self: | ||||||||||||||||||
if not float_is_zero(record.selling_price, precision_digits=3): | ||||||||||||||||||
if float_compare(record.selling_price, record.expected_price*0.9, precision_digits=3) < 0: | ||||||||||||||||||
raise UserError(r"The selling price must be at least 90% of the expected price !") | ||||||||||||||||||
Comment on lines
83
to
88
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. Be careful about model attribute order: https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html#symbols-and-conventions |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from odoo import api, models, fields | ||
from odoo.exceptions import UserError | ||
import datetime | ||
|
||
|
||
class EstatePropertyOffer(models.Model): | ||
_name = "estate.property.offer" | ||
_description = "Offers for properties" | ||
|
||
price = fields.Float() | ||
status = fields.Selection(copy=False, selection=[("accepted", "Accepted"), ("refused", "Refused")]) | ||
partner_id = fields.Many2one("res.partner", required=True, string="Partner") | ||
property_id = fields.Many2one("estate.property", required=True) | ||
validity = fields.Integer(default=7, string="Validity (days)") | ||
create_date = fields.Date(copy=False, default=lambda self: datetime.date.today(), readonly=True) | ||
date_deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline", string="Deadline") | ||
|
||
_positive_price = models.Constraint( | ||
'CHECK(price > 0)', | ||
'The price of an offer must be strictly positive' | ||
) | ||
|
||
@api.depends("create_date", "validity") | ||
def _compute_deadline(self): | ||
for offer in self: | ||
offer.date_deadline = fields.Date.add(offer.create_date, days=offer.validity) | ||
|
||
def _inverse_deadline(self): | ||
for offer in self: | ||
delta = offer.date_deadline - offer.create_date | ||
offer.validity = delta.days | ||
Comment on lines
24
to
32
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. create_date is not required here, what happens if you remove it ? |
||
|
||
def accept_offer(self): | ||
for offer in self: | ||
for other_offer in offer.property_id.offer_ids: | ||
if other_offer.status == "accepted" and offer.id != other_offer.id: | ||
raise UserError("An offer is already accepted...") | ||
offer.status = "accepted" | ||
offer.property_id.buyer_id = offer.partner_id | ||
offer.property_id.selling_price = offer.price | ||
|
||
def refuse_offer(self): | ||
for offer in self: | ||
offer.status = "refused" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class EstatePropertyTag(models.Model): | ||
_name = "estate.property.tag" | ||
_description = "Tag of properties" | ||
|
||
name = fields.Char(required=True) | ||
|
||
_unique_tag = models.Constraint( | ||
'unique(name)', | ||
'The tag name must be unique', | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class EstatePropertyType(models.Model): | ||
_name = "estate.property.type" | ||
_description = "Type of properties" | ||
|
||
name = fields.Char(required=True) | ||
|
||
_unique_type = models.Constraint( | ||
'unique(name)', | ||
'The type name must be unique', | ||
) |
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 | ||
estate.access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 | ||
estate.access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
estate.access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
estate.access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<odoo> | ||
<menuitem id="estate_menu_root" name="Real Estate"> | ||
<menuitem id="first_level_advertissements_menu" name="Advertissements"> | ||
<menuitem id="property_action_menu" action="estate_property_first_action"/> | ||
</menuitem> | ||
<menuitem id="first_level_settings_menu" name="Settings"> | ||
<menuitem id="property_types_action_menu" action="estate_property_types"/> | ||
<menuitem id="property_tags_action_menu" action="estate_property_tag"/> | ||
</menuitem> | ||
</menuitem> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_offer_list_view" model="ir.ui.view"> | ||
<field name="name">estate.property.offer.list</field> | ||
<field name="model">estate.property.offer</field> | ||
<field name="arch" type="xml"> | ||
<list string="List View"> | ||
<field name="price" width="200px"/> | ||
<field name="partner_id" width="300px"/> | ||
<field name="validity" width="100px"/> | ||
<field name="date_deadline" width="100px"/> | ||
<button name="accept_offer" type="object" string="Accept" icon="fa-check" width="50px"/> | ||
<button name="refuse_offer" type="object" string="Refuse" icon="fa-times" width="50px"/> | ||
<field name="status" width="100px"/> | ||
</list> | ||
</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_tag" model="ir.actions.act_window"> | ||
<field name="name">Property Tags</field> | ||
<field name="res_model">estate.property.tag</field> | ||
<field name="view_mode">list</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_type_list_view" model="ir.ui.view"> | ||
<field name="name">estate.property.type.list</field> | ||
<field name="model">estate.property.type</field> | ||
<field name="arch" type="xml"> | ||
<list string="List View"> | ||
<field name="name"/> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<record id="estate_property_types" model="ir.actions.act_window"> | ||
<field name="name">Property Types</field> | ||
<field name="res_model">estate.property.type</field> | ||
<field name="view_mode">list</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,106 @@ | ||||||
<?xml version="1.0"?> | ||||||
<odoo> | ||||||
<record id="estate_property_form_view" model="ir.ui.view"> | ||||||
<field name="name">estate.property.form</field> | ||||||
<field name="model">estate.property</field> | ||||||
<field name="arch" type="xml"> | ||||||
<form string="Estate Property"> | ||||||
<header> | ||||||
<button name="sell_property" type="object" string="Sold"/> | ||||||
<button name="cancel_property" type="object" string="Cancel"/> | ||||||
</header> | ||||||
<sheet> | ||||||
<div class="oe_title"> | ||||||
<h1><field name="name"/></h1> | ||||||
</div> | ||||||
<field name="tag_ids" widget="many2many_tags"/> | ||||||
<separator/> | ||||||
<group> | ||||||
<group> | ||||||
<field name="property_type_id"/> | ||||||
<field name="postcode"/> | ||||||
<field name="date_availability" string="Available From"/> | ||||||
</group> | ||||||
<group> | ||||||
<field name="expected_price"/> | ||||||
<field name="best_price"/> | ||||||
<field name="selling_price"/> | ||||||
</group> | ||||||
</group> | ||||||
<separator/> | ||||||
<notebook> | ||||||
<page string="Description"> | ||||||
<group> | ||||||
<field name="description"/> | ||||||
<field name="bedrooms"/> | ||||||
<field name="living_area" string="Living Area (sqm)"/> | ||||||
<field name="facades"/> | ||||||
<field name="garage"/> | ||||||
<field name="garden"/> | ||||||
<field name="garden_area" string="Garden Area (sqm)"/> | ||||||
<field name="garden_orientation"/> | ||||||
<field name="total_area"/> | ||||||
<field name="state"/> | ||||||
</group> | ||||||
</page> | ||||||
<page name="notes" string="Notes"> | ||||||
<field name="notes"/> | ||||||
</page> | ||||||
<page string="Offers"> | ||||||
<field name="offer_ids"/> | ||||||
</page> | ||||||
<page string="Other Info"> | ||||||
<group> | ||||||
<field name="buyer_id"/> | ||||||
<field name="salesperson_id"/> | ||||||
</group> | ||||||
</page> | ||||||
</notebook> | ||||||
</sheet> | ||||||
</form> | ||||||
</field> | ||||||
</record> | ||||||
|
||||||
<record id="estate_property_list_view" model="ir.ui.view"> | ||||||
<field name="name">estate.property.list</field> | ||||||
<field name="model">estate.property</field> | ||||||
<field name="arch" type="xml"> | ||||||
<list string="Channel"> | ||||||
<field name="name" width="300px"/> | ||||||
<field name="property_type_id" width="80px"/> | ||||||
<field name="tag_ids" width="80px" widget="many2many_tags"/> | ||||||
<field name="postcode" width="60px"/> | ||||||
<field name="bedrooms"/> | ||||||
<field name="living_area" string="Living Area (sqm)"/> | ||||||
<field name="expected_price"/> | ||||||
<field name="selling_price"/> | ||||||
<field name="date_availability" string="Available From"/> | ||||||
</list> | ||||||
</field> | ||||||
</record> | ||||||
|
||||||
<record id="estate_property_search" model="ir.ui.view"> | ||||||
<field name="name">estate.property.search</field> | ||||||
<field name="model">estate.property</field> | ||||||
<field name="arch" type="xml"> | ||||||
<search string="Estate Properties"> | ||||||
<field name="name" string="Title"/> | ||||||
<field name="property_type_id"/> | ||||||
<field name="postcode"/> | ||||||
<field name="expected_price"/> | ||||||
<field name="bedrooms"/> | ||||||
<field name="living_area" string="Living Area (sqm)"/> | ||||||
<field name="facades"/> | ||||||
<filter string="Available" name="available" domain="['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]"/> | ||||||
|
<filter string="Available" name="available" domain="['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]"/> | |
<filter string="Available" name="available" domain="[('state', 'in', ('new', 'offer_received'))]"/> |
Simpler way when comparing the same variable :)
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.
Use a lambda here, or the version that has been evaluated the first time will always be used.
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.
And why
months=+3
?