-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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: a new module to manage estate properties #163
base: 18.0
Are you sure you want to change the base?
Conversation
Ping Lucky you I'm your reviewer :D |
c929665
to
d55d464
Compare
6d1449f
to
489b815
Compare
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.
Very good work, mainly styling comments 👍
Two last comments I can make so far are:
- Could you rename your PR to something like
[ADD] estate: a new module to manage estate properties
and embellish a bit your description - Mind that commit messages (and PR title) must be as followed
[TAG] module(s): short description
. The body of your commit message is as important as the title. I advice you to build that body use the following three sections:- Problem
- Objective
- Solution
and end your commit message with a reference to the task your assigned to (here there is none so you can put task-xxxxxx). Check the commit message I pushed on your branch for inspiration.
Lastly, have all your .idea
diffs in the PRs, could you make those disappear ? Thanks a lot.
Once again, good work ! Keep it up 🥇
estate/models/estate_property.py
Outdated
from odoo import models, fields | ||
from dateutil.relativedelta import relativedelta |
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.
Regarding imports, we try to order them as followed:
- Python imports
- Other imports
- Odoo imports
from odoo import models, fields | |
from dateutil.relativedelta import relativedelta | |
from dateutil.relativedelta import relativedelta | |
from odoo import fields, models |
estate/models/estate_property.py
Outdated
|
||
|
||
class EstateProperty(models.Model): | ||
_name = "estate_property" |
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.
_name = "estate_property" | |
_name = "estate.property" |
estate/models/estate_property.py
Outdated
_name = "estate_property" | ||
_description = "Create real estate properties and keep track of status" | ||
|
||
name = fields.Char(required=True) |
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.
Comment that also applies to all other fields: also add the string parameter so that the field can get translated to other languages 👍
estate/models/estate_property.py
Outdated
garden = fields.Boolean() | ||
garden_area = fields.Integer() | ||
garden_orientation = fields.Selection( | ||
string='Type', |
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.
Quote convention is
- '' for strings the users do not see
- "" for strings the users see
string='Type', | |
string="Type", |
estate/models/estate_property.py
Outdated
active = fields.Boolean('Active', default=True) | ||
state = fields.Selection( | ||
string='State', | ||
selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', "Cancelled")] |
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.
Since the selection is a bit long here, it would be nice to indent it to make it easier to read and manage in the future
selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', "Cancelled")] | |
selection=[ | |
('new', "New"), | |
('offer_received', "Offer Received"), | |
('offer_accepted', "Offer Accepted"), | |
('sold', "Sold"), | |
('cancelled', "Cancelled"), | |
] |
</field> | ||
</record> | ||
|
||
<record id="estate_property_list_view_search" model="ir.ui.view"> |
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.
<record id="estate_property_list_view_search" model="ir.ui.view"> | |
<record id="estate_property_view_search" model="ir.ui.view"> |
<filter string="Available" name="state" domain="['|', | ||
('state', '=', 'new'), | ||
('state', '=', 'offer_received')]"/> |
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.
Easier to read
<filter string="Available" name="state" domain="['|', | |
('state', '=', 'new'), | |
('state', '=', 'offer_received')]"/> | |
<filter string="Available" | |
name="state" | |
domain="[ | |
'|', | |
('state', '=', 'new'), | |
('state', '=', 'offer_received'), | |
]"/> |
<filter string="Available" name="state" domain="['|', | ||
('state', '=', 'new'), | ||
('state', '=', 'offer_received')]"/> | ||
<group expand="1" string="Group By"> |
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.
Not needed, try without and you'll see that the group_by section shows
<group> | ||
<field name="postcode"/> | ||
</group> | ||
<group> | ||
<field name="expected_price"/> | ||
</group> | ||
<group> | ||
<field name="date_availability"/> | ||
</group> | ||
<group> | ||
<field name="selling_price"/> | ||
</group> |
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.
No need to create a different group for each
<group> | |
<field name="postcode"/> | |
</group> | |
<group> | |
<field name="expected_price"/> | |
</group> | |
<group> | |
<field name="date_availability"/> | |
</group> | |
<group> | |
<field name="selling_price"/> | |
</group> | |
<group> | |
<field name="postcode"/> | |
<field name="expected_price"/> | |
<field name="date_availability"/> | |
<field name="selling_price"/> | |
</group> |
</form> | ||
</field> | ||
</record> | ||
</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.
Line break
estate/models/estate_property.py
Outdated
name = fields.Char(required=True) | ||
description = fields.Text() | ||
postcode = fields.Char() | ||
date_availability = fields.Date(default=fields.Datetime.today() + relativedelta(months=3), copy=False) |
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.
The default value here won't work, the reason being the, currently, it is compute once when you start your server and then the compute default value will remain unchanged until you next stop-start your server. That means that, currently, if you start a server today that will run forever, the default value will be 22/01 forever.
To solve the issue you need to use a lambda function
042bf07
to
37b90b6
Compare
6567d13
to
e8020f0
Compare
Problem --------- Users may want to cancel several houses and flats at once, currently this is not possible; they have to do it one by one. Objective --------- Add a way to cancel several estates in one go. Solution --------- Add a server action in the demo data so that it is available for new users. Old users can create the same server action on their database. task-123456
Problem --------- Users may want to see the total area after adding the garden area and the living area, as well as the best offer after adding multiple offers to the property. They also might want to either enter a validity number of days or a deadline, and the other should be computed automatically. Objective --------- Add a field (total area) dependant on two fields (garden and living area). Add another field which is the maximum of all offer prices. Add a relation between the validity date and the deadline fields (after creating them). Solution --------- Add a computed field for the total area depending on garden and living area fields. Add a computed field for the best price depending on the offer prices, computing the maximum of them all. Add an computed field for the date deadline and add both a compute and inverse function for it. task-xxxxxx
…de their actions. Problem --------- Users may want to modify the property status through buttons, and see their actions reflected in the data. Same applies to the offer status. Objective --------- Add a sold and cancel button for the property, modify the status based on their activity. Add accept and refuse buttons for the offers, modify the status based on their activity. Reflect the actions of offers on the property data. Solution --------- Add a sold and cancel button in the header part of the XML file, alongside their functions applied on press. Add a accept and refuse button in the list view of the offers. Based on which is clicked, modify the selling price and the buyer of the property as well as the status of the offer. task-xxxxxx
Problem --------- Users may enter incorrect data like a negative expected or offer price. Also, they may create multiple property types and tags of the same name. The selling price of a property shouldn't be less than 90% of the expected price. Objective --------- Add constraints to the field boxes (expected, selling, and offer price). The property tags and types should have constraints too. Add constraints to prevent selling prices from being lower than 90% of the expected price. Solution --------- Added an SQL constraint checking the expected, selling, and offer price to be positive. Added an SQL constraint to check the uniqueness of the property tags and types. Added a Python constraint checking the selling price to be not lower than 90% of the expected price.
a8ffcf6
to
b0c767f
Compare
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.
Could you update the PR title and add a lil description ? Thanks 🏅
Also, I have all your .idea stuff here, could you remove them from the diff ?
estate/models/__init__.py
Outdated
@@ -0,0 +1 @@ | |||
from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, users |
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.
Better indent
from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, users | |
from . import estate_property | |
from . import estate_property_type | |
from . import estate_property_tag | |
from . import estate_property_offer | |
from . import users |
property_type_id = fields.Many2one('estate.property.types', string="Property Type") | ||
salesman_id = fields.Many2one('res.users', string="Salesman", default=lambda self: self.env.user) | ||
buyer_id = fields.Many2one('res.partner', string="Buyer") | ||
tag_ids = fields.Many2many('estate.property.tag', string="Property Tags") | ||
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") | ||
total_area = fields.Float(compute="_compute_total_area", string="Total Area") | ||
best_price = fields.Float(compute="_compute_best_price", string="Best Price") |
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.
Those are missing the string option, useful for translation 👍
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.
Isn't it the second parameter in all of these? The string=""?
estate/models/estate_property.py
Outdated
for record in self: | ||
if record.buyer_id and float_compare(record.selling_price, 0.9 * record.expected_price, 5) == -1: |
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 of a meaningful variable name to ease the understanding of the method
for record in self: | |
if record.buyer_id and float_compare(record.selling_price, 0.9 * record.expected_price, 5) == -1: | |
for estate in self: | |
if estate.buyer_id and float_compare(record.selling_price, 0.9 * record.expected_price, 5) == -1: |
estate/models/estate_property.py
Outdated
def _compute_best_price(self): | ||
for record in self: | ||
prices = record.offer_ids.mapped('price') | ||
|
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.
No need of empty line here
estate/models/estate_property.py
Outdated
for record in self: | ||
prices = record.offer_ids.mapped('price') | ||
|
||
record.best_price = max(prices) if len(prices) > 0 else 0 |
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.
Lil python trick
record.best_price = max(prices) if len(prices) > 0 else 0 | |
record.best_price = max(prices, default=0) |
partner_id = fields.Many2one('res.partner', string="Partner", required=True) | ||
property_id = fields.Many2one('estate.property', string="Property", required=True) |
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.
String parameter
record.date_deadline = record.create_date + timedelta(days=record.validity) | ||
|
||
def _inverse_date_deadline(self): | ||
for record in self: |
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.
No placing it everywhere but all you looped method need this change hehe
for record in self: | |
for offer in self: |
offer_ids = One2many('estate.property.offer', 'property_type_id') | ||
offer_count = fields.Integer(compute="_compute_offer_count", string="Offer Count") |
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.
String
def action_sold(self): | ||
invoices = [] | ||
for record in self: | ||
journal = self.env['account.journal'].search([('type', '=', 'sale')]) |
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.
This could return several journal, add limit=1
to make sure you only get one 👍
invoices.append(invoice) | ||
|
||
self.env['account.move'].create(invoices) |
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.
Very nice to think of perf like that, honestly very impressed
It could be even nicer by placing journal = self.env[].search
outside of the loop, just before it so that you only search once
70679b7
to
69f7e7f
Compare
Module managing estate properties allowing addition of properties alongside their details (type, area, etc.). Keeps track of the offers for each property, prices for each offer, and the buyer.