-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[ADD] estate: Created Estate Module #1049
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
Draft
haman-odoo
wants to merge
11
commits into
odoo:19.0
Choose a base branch
from
odoo-dev:19.0-real-estate-tutorial-haman
base: 19.0
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+544
−0
Draft
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
0ce5e44
[ADD] estate: Created base module structure and manifest
haman-odoo 742a492
[IMP] estate: Added EstateProperty model with all required fields
haman-odoo e197d7f
[IMP] estate: Added access control for EstateProperty model
haman-odoo 22d2d17
[IMP] estate: Added actions, menus and first list/form views
haman-odoo c260137
[IMP] estate: Added custom list, form and search views
haman-odoo a6feab6
[IMP] estate: Added relations between property, type and offer models
haman-odoo b048a7d
[IMP] estate: Compute total area, best price and add onchange for gar…
haman-odoo f21c9e7
[IMP] estate: Added action buttons to manage property and offer status
haman-odoo dfc0ad5
[IMP] estate: Added SQL and Python constraints for data validation
haman-odoo d59cb9b
[IMP] estate: Added Inline Views, Widgets, List Order, Attributes an…
haman-odoo e577a81
[IMP] estate: Added User Inheritance and property listing
haman-odoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import models |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| 'name': "Real Estate", | ||
| 'description': """Real estate management tutorial module with properties, offers, types and tags.""", | ||
| 'version': '1.0', | ||
| 'license': 'LGPL-3', | ||
| 'summary': 'Real Estate advertisement tutorial module', | ||
| 'depends': ['base'], | ||
| 'author': "Harsh Maniya", | ||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| 'views/estate_property_offer_views.xml', | ||
| 'views/estate_property_type_views.xml', | ||
| 'views/estate_property_tag_views.xml', | ||
| 'views/estate_property_views.xml', | ||
| 'views/res_users_views.xml', | ||
| 'views/estate_menus.xml' | ||
| ], | ||
| 'category': 'Sales/Real Estate', | ||
| 'installable': True, | ||
| 'auto_install': False, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from . import estate_property | ||
| from . import estate_property_offer | ||
| from . import estate_property_type | ||
| from . import estate_property_tag | ||
| from . import res_users |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
| from datetime import date | ||
|
|
||
| from odoo import models, fields, api, exceptions, _ | ||
| from odoo.tools.float_utils import float_compare | ||
|
|
||
|
|
||
| class EstateProperty(models.Model): | ||
| _name = "estate.property" | ||
| _description = "Real Estate Property" | ||
| _order = "id desc" | ||
|
|
||
| name = fields.Char(required=True) | ||
| description = fields.Text() | ||
| postcode = fields.Char() | ||
| date_availability = fields.Date( | ||
| default=lambda self: date.today() + relativedelta(months=3), | ||
| copy=False | ||
| ) | ||
| 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'), | ||
| ], | ||
| required=True, | ||
| copy=False, | ||
| default='new', | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| "estate.property.type", | ||
| ) | ||
| salesperson_id = fields.Many2one( | ||
| "res.users", | ||
| default=lambda self: self.env.user, | ||
| ) | ||
| buyer_id = fields.Many2one( | ||
| "res.partner", | ||
| copy=False, | ||
| ) | ||
| tag_ids = fields.Many2many( | ||
| "estate.property.tag", | ||
| ) | ||
| offer_ids = fields.One2many( | ||
| "estate.property.offer", | ||
| "property_id", | ||
| ) | ||
| total_area = fields.Float( | ||
| compute="_compute_total_area" | ||
| ) | ||
| best_price = fields.Float( | ||
| compute="_compute_best_price", | ||
| ) | ||
| _expected_price = models.Constraint( | ||
| 'CHECK(expected_price >= 0)', | ||
| 'The expected price must be positive.', | ||
| ) | ||
| _selling_price = models.Constraint( | ||
| 'CHECK(selling_price >= 0)', | ||
| 'The selling price 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_price(self): | ||
| for record in self: | ||
| prices = record.offer_ids.mapped('price') | ||
| record.best_price = max(prices) if prices else 0.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 action_cancel_property(self): | ||
| if self.filtered(lambda rec: rec.state == "sold"): | ||
| raise exceptions.UserError(_("Sold properties cannot be cancelled.")) | ||
| self.write({"state": "cancelled"}) | ||
| return True | ||
|
|
||
| def action_set_sold(self): | ||
| for rec in self: | ||
| if rec.state == "cancelled": | ||
| raise exceptions.UserError(_("Canceled properties cannot be sold.")) | ||
| else: | ||
| rec.state = "sold" | ||
| return True | ||
|
|
||
| @api.constrains("selling_price", "expected_price") | ||
| def _check_selling_price(self): | ||
| for rec in self: | ||
| if rec.selling_price == 0: | ||
| return False | ||
| if float_compare(rec.selling_price, rec.expected_price * 0.9, precision_digits=2) < 0: | ||
| raise exceptions.ValidationError(_( | ||
| "The selling price must be at least 90% of the expected price!\n" | ||
| "You must reduce the expected price if you want to accept this offer." | ||
| )) | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _check_property_deletion(self): | ||
| for rec in self: | ||
| if rec.state not in ("new", "cancelled"): | ||
| raise exceptions.UserError(_( | ||
| "You can only delete properties in New or Cancelled state." | ||
| )) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
| from odoo import models, fields, api, exceptions, _ | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Real Estate Property Offer" | ||
| _order = "price desc" | ||
|
|
||
| price = fields.Float(string="Price") | ||
| status = fields.Selection( | ||
| selection=[ | ||
| ("accepted", "Accepted"), | ||
| ("refused", "Refused"), | ||
| ], | ||
| copy=False, | ||
| ) | ||
| partner_id = fields.Many2one( | ||
| "res.partner", | ||
| required=True, | ||
| ) | ||
| property_id = fields.Many2one( | ||
| "estate.property", | ||
| required=True, | ||
| ) | ||
| validity = fields.Integer( | ||
| default=7, | ||
| ) | ||
| date_deadline = fields.Date( | ||
| compute="_compute_date_deadline", | ||
| inverse="_inverse_date_deadline", | ||
| store=True, | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| related="property_id.property_type_id", store=True | ||
| ) | ||
| _offer_price = models.Constraint( | ||
| 'CHECK (price > 0)', | ||
| 'Offer price must be greater than 0', | ||
| ) | ||
|
|
||
| @api.depends("validity") | ||
| def _compute_date_deadline(self): | ||
| for rec in self: | ||
| create = rec.create_date or fields.Date.today() | ||
| rec.date_deadline = (create + relativedelta(days=rec.validity)) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for rec in self: | ||
| create = rec.create_date or fields.Date.today() | ||
| rec.validity = (rec.date_deadline - fields.Date.today(create)).days | ||
|
|
||
| def action_accept(self): | ||
| for offer in self: | ||
| if offer.property_id.buyer_id: | ||
| raise UserError('Only one offer can be accepted for a property.') | ||
| offer.status = 'accepted' | ||
| offer.property_id.selling_price = offer.price | ||
| offer.property_id.buyer_id = offer.partner_id | ||
| offer.property_id.state = 'offer_accepted' | ||
| return True | ||
|
|
||
| def action_refuse(self): | ||
| for offer in self: | ||
| offer.status = 'refused' | ||
| return True | ||
|
|
||
| @api.model | ||
| def create(self, vals): | ||
| for rec in vals: | ||
| property_id = rec.get('property_id') | ||
| price = rec.get('price', 0.0) | ||
| if property_id: | ||
| property_obj = self.env['estate.property'].browse(property_id) | ||
| best_offer = property_obj.best_price or 0.0 | ||
| if price < best_offer: | ||
| raise exceptions.UserError(_( | ||
| 'Offer price must be greater than or equal to the best offer price.')) | ||
| return super().create(vals) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Real Estate Property Tag" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| color = fields.Integer() | ||
|
|
||
| _unique_name = models.Constraint( | ||
| 'unique(name)', | ||
| 'The tag name must be unique.', | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Real Estate Property Type" | ||
| _order = "sequence,name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| property_ids = fields.One2many( | ||
| "estate.property", | ||
| "property_type_id", | ||
| ) | ||
| sequence = fields.Integer(default=1, help="Used to order stages. Lower is better.") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_type_id") | ||
| offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
|
||
| _unique_name = models.Constraint( | ||
| 'unique(name)', | ||
| 'The property type name must be unique.', | ||
| ) | ||
|
|
||
| def _compute_offer_count(self): | ||
| for rec in self: | ||
| rec.offer_count = len(rec.offer_ids) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from odoo import models, fields | ||
|
|
||
| class ResUsers(models.Model): | ||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many( | ||
| "estate.property", | ||
| "salesperson_id", | ||
| domain=[("state", "=", "new")] | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| access_estate_property_type_user,access_estate_property_type_user,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_estate_property_offer_user,access_estate_property_offer_user,model_estate_property_offer,base.group_user,1,1,1,1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <odoo> | ||
| <menuitem id="menu_estate_root" name="Real Estate" /> | ||
| <menuitem id="menu_estate_advertisements" name="Advertisements" parent="menu_estate_root" | ||
| sequence="10" /> | ||
| <menuitem id="menu_estate_properties" name="Properties" parent="menu_estate_advertisements" | ||
| action="action_estate_property" sequence="10" /> | ||
| <menuitem id="menu_estate_settings" name="Settings" parent="menu_estate_root" sequence="20" /> | ||
| <menuitem id="menu_estate_property_types" name="Property Types" | ||
| parent="menu_estate_settings" | ||
| action="action_estate_property_type" | ||
| sequence="10" /> | ||
| <menuitem id="menu_estate_property_tags" name="Property Tags" | ||
| parent="menu_estate_settings" | ||
| action="action_estate_property_tag" /> | ||
| </odoo> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <odoo> | ||
| <record id="action_estate_property_offer" model="ir.actions.act_window"> | ||
| <field name="name">Property Offers</field> | ||
| <field name="res_model">estate.property.offer</field> | ||
| <field name="view_mode">list,form</field> | ||
| <field name="domain">[('property_type_id', '=', active_id)]</field> | ||
| </record> | ||
| <record id="view_estate_property_offer_list" 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="Offers" editable="bottom" decoration-danger="status=='refused'" decoration-success="status=='accepted'"> | ||
| <field name="price"/> | ||
| <field name="partner_id"/> | ||
| <field name="validity"/> | ||
| <field name="date_deadline"/> | ||
| <button name="action_accept" string="Accept" type="object" icon="fa-check" invisible="status"/> | ||
| <button name="action_refuse" string="Refuse" type="object" icon="fa-close" invisible="status"/> | ||
| <field name="status"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </odoo> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <odoo> | ||
| <record id="action_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,form</field> | ||
| <field name="help" type="html"> | ||
| <p>Create and manage tags for your properties.</p> | ||
| </field> | ||
| </record> | ||
| <record id="view_estate_property_tag_list" model="ir.ui.view"> | ||
| <field name="name">estate.property.tag.list</field> | ||
| <field name="model">estate.property.tag</field> | ||
| <field name="arch" type="xml"> | ||
| <list string="Tag" editable="bottom"> | ||
| <field name="name"/> | ||
| <field name="color"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </odoo> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Should be above one empty line.