-
Notifications
You must be signed in to change notification settings - Fork 2.9k
[ADD] estate: implement core estate management features #1109
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?
Conversation
- Real Estate module created - Basic structure and required fields added - CHAPTER 1, 2, and 3
- Added basic security access for estate records - Fixed missing whitespace and formatting issues - Covers CHAPTER 4
- Added form and list views for estate records - Added menu and action to access the module - Covers CHAPTER 5
- Added list, form, and search views - Added domains and group by options - Covers CHAPTER 6
mash-odoo
left a comment
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.
Hello @jakan-odoo,
Good start on the PR,
Please view the comments and apply the needed changes.
estate/models/estate_property.py
Outdated
| from odoo import fields, models | ||
| 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.
For ordering of imports, refer this: https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html#imports
| <record id="estate_property_list_view" model="ir.ui.view"> | ||
| <field name="name">estate.property.list</field> |
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.
For naming a view: <model_name>_view_<view_type>, where view_type is kanban, form, list, search, …
You can refer this guideline: https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html#xml-ids-and-naming
| <record id="estate_property_form_view" model="ir.ui.view"> | ||
| <field name="name">estate.property.form</field> |
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.
Do same as suggested above
- Added Many2one relation between models - Improves data linking between records - Covers CHAPTER 7
- Added One2many and Many2many relations between models - Improves linking and management of related records - Covers CHAPTER 7
- Added computed fields for automatic values - Added onchange methods to update fields - Covers CHAPTER 8
- Created Sell, Cancel, Accept, and Refuse buttons - Buttons call related methods on the estate models - Buyer and selling price are set when an offer is accepted - Covers CHAPTER 9
mash-odoo
left a comment
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.
Hello,
Good going on the task..
Here are some suggestions..
| <field name="bedrooms"/> | ||
| <field name="living_area"/> | ||
| <field name="selling_price"/> | ||
| <filter string="Available Properties" name="available properties" 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.
Can we write this filter in any other way?
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.
Yes ma'am,
We can write filter like this
<filter string="Available Properties" name="available_properties" domain="[('state', 'in', ('new', 'offer_received'))]"/>
| </form> | ||
| </field> | ||
| </record> | ||
| </odoo> No newline at end of file |
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.
Leave a line at the end of file
estate/models/estate_property.py
Outdated
| ("north", "North"), | ||
| ("south", "South"), | ||
| ("east", "East"), | ||
| ("west", "West")]) |
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.
| ("north", "North"), | |
| ("south", "South"), | |
| ("east", "East"), | |
| ("west", "West")]) | |
| ('north', "North"), | |
| ('south', "South"), | |
| ('east', "East"), | |
| ('west', "West")]) |
estate/models/estate_property.py
Outdated
| ('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.
| ('new', 'New'), | |
| ('offer_received', 'Offer Received'), | |
| ('offer_accepted', 'Offer Accepted'), | |
| ('sold', 'Sold'), | |
| ('cancelled', 'Cancelled'), | |
| ('new', "New"), | |
| ('offer_received', "Offer Received"), | |
| ('offer_accepted', "Offer Accepted"), | |
| ('sold', "Sold"), | |
| ('cancelled', '"Cancelled"), |
estate/__manifest__.py
Outdated
| @@ -0,0 +1,20 @@ | |||
| { | |||
| 'name': 'Real Estate', | |||
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': 'Real Estate', | |
| 'name': "Real Estate", |
estate/__manifest__.py
Outdated
| 'version': '1.0', | ||
| 'category': 'Real Estate', | ||
| 'summary': 'Manage real estate properties', | ||
| 'description': 'This module allows managing properties.', |
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.
| 'description': 'This module allows managing properties.', | |
| 'description': "This module allows managing properties.", |
estate/models/estate_property.py
Outdated
| from dateutil.relativedelta import relativedelta | ||
| from odoo import fields, models, api |
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.
For ordering of imports, refer this: https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html#imports
- Added SQL constraints to ensure data validity - Added Python constraints for business rules - Covers CHAPTER 10
- Added inline views for related records - Applied widgets to improve user interface - Set default ordering using _order - CHAPTER 11
- Added maintenance request model with title, cost, and status - Ensured approved requests have a positive cost - Displayed total maintenance cost on the property - Prevented property sale when maintenance is not completed
- Replaced direct cost check with float_is_zero - Ensures correct validation for decimal precision
- Added manual ordering and widget options - Applied conditional button visibility using invisible - Made list views editable and some fields optional - Added decorations, default filters, and stat buttons - Covers CHAPTER 11
mash-odoo
left a comment
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.
Hello!
Thank you for your work..
Here are some suggestions and questions..
| offer.status = "refused" | ||
| return True | ||
|
|
||
| _check_offer_price_positive = models.Constraint( |
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.
Follow this conventions while declaring fields, constraints, methods or CRUD operations.
| _order = 'name' | ||
|
|
||
| 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.
estate/models/estate_property.py
Outdated
| property.state = 'cancelled' | ||
| return True | ||
|
|
||
| _check_expected_price_positive = models.Constraint( |
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.
Follow this conventions while declaring fields, constraints, methods or CRUD operations.
| <header> | ||
| <field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/> | ||
| </header> | ||
| <sheet> | ||
| <header> | ||
| <button name="action_sold" type="object" string="Sold" class="btn-primary" invisible="state in ('sold', 'cancelled')"/> | ||
| <button name="action_cancel" type="object" string="Cancel" class="btn-secondary" invisible="state in ('sold', 'cancelled')"/> | ||
| </header> |
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.
| <header> | |
| <field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/> | |
| </header> | |
| <sheet> | |
| <header> | |
| <button name="action_sold" type="object" string="Sold" class="btn-primary" invisible="state in ('sold', 'cancelled')"/> | |
| <button name="action_cancel" type="object" string="Cancel" class="btn-secondary" invisible="state in ('sold', 'cancelled')"/> | |
| </header> | |
| <header> | |
| <field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/> | |
| <button name="action_sold" type="object" string="Sold" class="btn-primary" invisible="state in ('sold', 'cancelled')"/> | |
| <button name="action_cancel" type="object" string="Cancel" class="btn-secondary" invisible="state in ('sold', 'cancelled')"/> | |
| </header> | |
| <sheet> |
You can add these buttons directly in the header.
- Used Python inheritance to extend existing logic - Applied model inheritance to add fields and behavior - Used view inheritance to update and extend UI - Covers CHAPTER 12
- Added translatable strings for the module - Updated constraint names for clarity - Applied small user interface changes - Added archived filter for records
- Created new estate_account module - Linked estate module with account module - Added invoice creation for sold properties - Covers CHAPTER 13
- Added investor profile linked to partner data - Deleting partner removes investor, investor delete keeps partner - Computed total unsold property value on res.partner - Sum based on expected price of non-sold properties
- Added a Kanban view for estate properties - Grouped properties by type by default - Covers CHAPTER 14
- Implemented a Counter component to show reactive state using useState - Split logic into reusable child components and used them in Playground - Add Card component to showcase props, markup rendering, and validation - Added offer accept button to automatically accept the highest offer price CHAPTER 1
- Added props validation to Owl components - Implemented logic to calculate the sum of two counters CHAPTER 1
- Added a basic todo list component - Used dynamic attributes for state-based updates - Implemented event handler to add new todos CHAPTER 1
mash-odoo
left a comment
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.
Hello!
Thank you for your work.
I have added some questions and suggestions..
| <field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold" options="{'clickable': '1'}"/> | ||
| <button name="action_sold" type="object" string="Sold" class="btn-primary" invisible="state in ('sold', 'cancelled')"/> | ||
| <button name="action_cancel" type="object" string="Cancel" class="btn-secondary" invisible="state in ('sold', 'cancelled')"/> | ||
| <button name="offer_accepted" type="object" string="Offer Accept" class="btn-primary"/> |
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.
| <button name="offer_accepted" type="object" string="Offer Accept" class="btn-primary"/> | |
| <button name="action_best_offer" type="object" string="Best Offer" class="btn-primary"/> |
Give meaningful names
estate/models/estate_property.py
Outdated
| def offer_accepted(self): | ||
| for record in self: | ||
| if not record.offer_ids: | ||
| raise UserError("There are no offers to accept") | ||
|
|
||
| best_offer = max(record.offer_ids, key=lambda p: p.price) | ||
| best_offer.action_accept() |
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.
Try optimizing this. Instead of throwing user error, maybe handle the visibility of the button directly on the state where it should be shown.
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.
estate_property.py
def action_best_offer(self):
for record in self:
best_offer = max(record.offer_ids, key=lambda p: p.price)
best_offer.action_accept()
return True
estate_property_views.xml
<button name="action_best_offer" type="object" string="Best Offer" class="btn-primary" invisible="not offer_ids"/>
| ('new', "New"), | ||
| ('approved', "Approved"), | ||
| ('done', "Done"), | ||
| ('cancle', "Cancle") |
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.
| ('cancle', "Cancle") | |
| ('cancel', "Cancel") |
| _description = 'Estate Property maintenance' | ||
|
|
||
| title = fields.Char(required=True) | ||
| cost = fields.Float(string="Cost") |
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 give the string
| <list> | ||
| <field name="title"/> | ||
| <field name="cost"/> | ||
| <button name="maintenance_accept" type="object" string="Confirm" icon="fa-check"/> |
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.
| <button name="maintenance_accept" type="object" string="Confirm" icon="fa-check"/> | |
| <button name="action_accept_maintenance" type="object" string="Confirm" icon="fa-check"/> |
.gitignore
Outdated
|
|
||
| i18n No newline at end of file |
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.
unnecessary diff
| # Set property state | ||
| property_rec.state = "offer_received" | ||
|
|
||
| return super().create(vals_list) |
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.
Why do we call super()?
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.
We call super because it calls to parent create() method and the parent method is used for saving the record in database.
estate/models/estate_property.py
Outdated
| seller_id = fields.Many2one("res.users", string="Seller", default=lambda self: self.env.user) | ||
| tag_ids = fields.Many2many("estate.property.tag", string="Property Tags") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_id", string="Property Offers") | ||
| total_area = fields.Float(string="Total Area", compute="_compute_total_area", store=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.
Can you try implementing searching/filtering of data without using store=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.
Without using store=True i can use search method for searching data
estate_property.py
total_area = fields.Float(string="Total Area", compute="_compute_total_area", search="_search_total_area")
@api.depends('living_area', 'garden_area')
def _compute_total_area(self):
for property in self:
property.total_area = property.living_area + property.garden_area
def _search_total_area(self, operator, value):
if operator == '>' and value == 500:
records = self.search([])
ids = []
for rec in records:
if (rec.living_area) + (rec.garden_area) > 500:
ids.append(rec.id)
return [('id', 'in', ids)]
return []
estate_property_views.xml
<filter string="Total Area > 500" name="total_area_gt_500" domain="[('total_area', '>', 500)]"/>
| def action_cancel(self): | ||
| for property in self: | ||
| if property.state == 'sold': | ||
| raise UserError("Cancelled property cannot be sold") | ||
| property.state = 'cancelled' | ||
| return 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.
Rewrite this without using for loop.
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.
def action_cancel(self):
if self.filtered(lambda p: p.state == 'sold'):
raise UserError("Cancelled property cannot be sold")
self.write({'state': 'cancelled'})
return True
- Added default properties, property types, tags, and offers - Automatically loads sample records when a new database is created - Helps users see estate records on first login

Uh oh!
There was an error while loading. Please reload this page.