-
Notifications
You must be signed in to change notification settings - Fork 3.2k
[ADD] estate: started the server framework, completed the coding guidelines, setup guide, and chapter 1 #1348
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
abkus-odoo
wants to merge
24
commits into
odoo:19.0
Choose a base branch
from
odoo-dev:19.0-tutorial-abkus
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.
+651
−0
Draft
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
c899377
[ADD] estate: Create estate property model
abkus-odoo 50755fd
[IMP] estate: Initialize Estate application module
abkus-odoo aab3a9c
[IMP] estate: Add estate property model with essential fields
abkus-odoo cf4f109
[IMP] estate: add security access rights and model permissions
abkus-odoo 70dfa93
[ADD] estate: add UI action and views structure
abkus-odoo 6c947d1
[ADD] estate: add menu, action, list and form views
abkus-odoo a9b9310
[ADD] estate: add form and search views for properties
abkus-odoo d320f73
[IMP] estate: revise previous chapters and begin relational fields
abkus-odoo 7992afd
[IMP] estate: improve code formatting and begin Many2one relations
abkus-odoo 85beace
[IMP] estate: learn and implement Many2one relations
abkus-odoo 5da3deb
[IMP] estate: Understand Many2many field behavior and usage
abkus-odoo cc5d490
[ADD] estate: implement Many2many tags for properties
abkus-odoo bb1821b
[ADD] estate: implement One2many offers for properties
abkus-odoo abd8b8f
[ADD] estate: implement computed total_area field
abkus-odoo 61ddca8
[ADD] estate: compute best offer using mapped()
abkus-odoo 6e0b7f1
[ADD] estate: implement offer validity and property onchange logic
abkus-odoo 531e2aa
[ADD] estate: implement property and offer state actions
abkus-odoo fc9fa2c
[ADD] estate: implement SQL constraints for data integrity
abkus-odoo db0c1ba
[ADD] estate: add property type One2many inline view and statusbar wi…
abkus-odoo 42d4b15
[ADD] estate: add ordering, sequence, and tag widget options
abkus-odoo 5a37613
[ADD] estate: improve views, filters and business rules
abkus-odoo e608aae
[ADD] estate: implement stat button and offer filtering by property type
abkus-odoo a2e2802
[ADD] estate: implement offer validation and property state logic
abkus-odoo e6d6e48
[ADD] estate_account: generate invoice on property sold
abkus-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
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", | ||
| "version": "1.0", | ||
| "summary": "Estate management", | ||
| "description": "Estate management", | ||
| "author": "Odoo S.A.", | ||
| "category": "tutorials", | ||
| "depends": ["base"], | ||
| "data": [ | ||
| "security/ir.model.access.csv", | ||
| "views/estate_property_views.xml", | ||
| "views/estate_property_offer_views.xml", | ||
| "views/estate_property_type_views.xml", | ||
| "views/estate_property_tag_views.xml", | ||
| "views/estate_menus.xml", | ||
| "views/res_users_views.xml", | ||
| ], | ||
| "license": "LGPL-3", | ||
| "application": True, | ||
| "installable": True | ||
| } |
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_type | ||
| from . import estate_property_tag | ||
| from . import estate_property_offer | ||
| 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,143 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
| from odoo import api, fields, models, _ | ||
| from odoo.exceptions import UserError, ValidationError | ||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
|
||
|
|
||
| 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: ( | ||
| fields.Date.today() + relativedelta(months=3) | ||
| ) | ||
| ) | ||
| expected_price = fields.Float() | ||
| 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() | ||
| total_area = fields.Integer(compute="_compute_total") | ||
| best_offer = fields.Float( | ||
| string="Best Offer", compute="_compute_best_offer") | ||
| garden_orientation = fields.Selection( | ||
| [ | ||
| ('north', "North"), | ||
| ('south', "South"), | ||
| ('east', "East"), | ||
| ('west', "West"), | ||
| ] | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| [ | ||
| ('new', "New"), | ||
| ('offer_received', "Offer Received"), | ||
| ('offer_accepted', "Offer Accepted"), | ||
| ('sold', "Sold"), | ||
| ('canceled', "Canceled"), | ||
| ], | ||
| required=True, | ||
| copy=False, | ||
| default="new", | ||
| ) | ||
| buyer_id = fields.Many2one( | ||
| "res.partner", | ||
| string="Buyer", | ||
| copy=False, | ||
| ) | ||
| salesperson_id = fields.Many2one( | ||
| "res.users", | ||
| string="Salesperson", | ||
| default=lambda self: self.env.user, | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| "estate.property.type", | ||
| string="Property Type", | ||
| ) | ||
| tag_ids = fields.Many2many( | ||
| "estate.property.tag", | ||
| string="Tags", | ||
| ) | ||
| offer_ids = fields.One2many( | ||
| "estate.property.offer", | ||
| "property_id", | ||
| string="Offer", | ||
| ) | ||
|
|
||
| _check_expected_price = models.Constraint( | ||
| "CHECK(expected_price>0)", "Expected price must be strictly positive" | ||
| ) | ||
|
|
||
| _check_selling_price = models.Constraint( | ||
| "CHECK(selling_price>0)", "Selling price must be positive" | ||
| ) | ||
|
|
||
| @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.price") | ||
| def _compute_best_offer(self): | ||
| for record in self: | ||
| if record.offer_ids: | ||
| record.best_offer = max(record.offer_ids.mapped("price")) | ||
| else: | ||
| record.best_offer = 0 | ||
|
|
||
| @api.constrains("expected_price", "selling_price") | ||
| def _check_selling_price(self): | ||
| for record in self: | ||
| if float_is_zero(record.selling_price, precision_digits=2): | ||
| continue | ||
| if ( | ||
| float_compare( | ||
| record.selling_price, | ||
| record.expected_price * 0.9, | ||
| precision_digits=2, | ||
| ) | ||
| < 0 | ||
| ): | ||
| raise ValidationError( | ||
| _("Selling price cannot be lower than 90% of the expected price")) | ||
|
|
||
| @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 = False | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _unlink_check_state(self): | ||
| for record in self: | ||
| if record.state not in ('new', 'canceled'): | ||
| raise UserError( | ||
| _("Only New or Cancelled properties can be deleted")) | ||
| return True | ||
|
|
||
| def action_sold(self): | ||
| for record in self: | ||
| if record.state == "canceled": | ||
| raise UserError(_("Canceled Property cannot be sold")) | ||
| record.state = "sold" | ||
| return True | ||
|
|
||
| def action_cancel(self): | ||
| for record in self: | ||
| if record.state == "sold": | ||
| raise UserError(_("Sold Property cannot be canceled")) | ||
| record.state = "canceled" | ||
| return True |
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,94 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
| from odoo import api, fields, models, _ | ||
| from odoo.exceptions import UserError | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Property Offers" | ||
| _order = "price desc" | ||
|
|
||
| price = fields.Float() | ||
| status = fields.Selection( | ||
| [ | ||
| ('accepted', "Accepted"), | ||
| ('refused', "Refused"), | ||
| ], | ||
| copy=False, | ||
| ) | ||
| partner_id = fields.Many2one( | ||
| 'res.partner', | ||
| required=True, | ||
| ) | ||
| property_id = fields.Many2one( | ||
| 'estate.property', | ||
| required=True, | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| related="property_id.property_type_id", store=True) | ||
| validity = fields.Integer(default=7) | ||
| date_deadline = fields.Date( | ||
| compute='_compute_date_deadline', | ||
| inverse='_inverse_date_deadline', | ||
| store=True, | ||
| ) | ||
|
|
||
| _check_price = models.Constraint( | ||
| 'CHECK(price>0)', 'Price must be positive') | ||
|
|
||
| @api.depends('create_date', 'validity') | ||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| if record.create_date: | ||
| record.date_deadline = ( | ||
| record.create_date.date() + relativedelta(days=record.validity) | ||
| ) | ||
| else: | ||
| record.date_deadline = ( | ||
| fields.Date.today() + relativedelta(days=record.validity) | ||
| ) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for record in self: | ||
| if record.date_deadline: | ||
| if record.create_date: | ||
| delta = ( | ||
| record.date_deadline - record.create_date.date() | ||
| ) | ||
| record.validity = delta.days | ||
| else: | ||
| delta = ( | ||
| record.date_deadline - fields.Date.today() | ||
| ) | ||
| record.validity = delta.days | ||
|
|
||
| @api.model_create_multi | ||
| def create(self, vals_list): | ||
| for vals in vals_list: | ||
| current_price = vals.get('price') | ||
| property_id = self.env['estate.property'].browse( | ||
| vals['property_id']) | ||
| for offer in property_id.offer_ids: | ||
| if current_price < offer.price: | ||
| raise UserError(_( | ||
| "The offer must be higher than the current highest offer.")) | ||
| if property_id.state == 'new': | ||
| property_id.state = 'offer_received' | ||
|
|
||
| return super().create(vals_list) | ||
|
|
||
| def action_confirm(self): | ||
| for offer in self.property_id.offer_ids: | ||
| if self != offer and offer.status == 'accepted': | ||
| raise UserError(_("An offer is already accepted.")) | ||
| self.status = "accepted" | ||
| self.property_id.state = "offer_accepted" | ||
| self.property_id.selling_price = self.price | ||
| self.property_id.buyer_id = self.partner_id | ||
| return True | ||
|
|
||
| def action_refuse(self): | ||
| for record in self: | ||
| record.status = "refused" | ||
| return True |
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,13 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Property Tags" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| color = fields.Integer() | ||
|
|
||
| _unique_price = models.Constraint( | ||
| "UNIQUE(name)", "Property tag 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,28 @@ | ||
| from odoo import api, fields, models | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Property Types" | ||
| _order = "sequence, name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| sequence = fields.Integer(string="Sequence", default=1) | ||
| property_ids = fields.One2many( | ||
| "estate.property", | ||
| "property_type_id", | ||
| ) | ||
| offer_ids = fields.One2many( | ||
| "estate.property.offer", | ||
| "property_type_id", | ||
| ) | ||
| offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
|
||
| _unique_name = models.Constraint( | ||
| "UNIQUE(name)", "Property type name must be unique." | ||
| ) | ||
|
|
||
| @api.depends('offer_ids') | ||
| def _compute_offer_count(self): | ||
| for record in self: | ||
| record.offer_count = len(record.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,12 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class ResUsers(models.Model): | ||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many( | ||
| "estate.property", | ||
| "salesperson_id", | ||
| string="My Properties", | ||
| domain=[('state', '!=', 'sold')] | ||
| ) |
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,access_estate_property,model_estate_property,base.group_user,1,1,1,1 | ||
| access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_estate_property_offer,access_estate_property_offer,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,39 @@ | ||
| <odoo> | ||
|
|
||
| <menuitem | ||
| id="estate_menu_root" | ||
| name="Real Estate" | ||
| /> | ||
|
|
||
| <menuitem | ||
| id="estate_property_menu" | ||
| name="Advertisement" | ||
| parent="estate_menu_root" | ||
| /> | ||
|
|
||
| <menuitem | ||
| id="estate_property_action_menu" | ||
| name="Property List" | ||
| parent="estate_property_menu" | ||
| action="estate_property_action" | ||
| /> | ||
|
|
||
| <menuitem | ||
| id="estate_property_type_menu" | ||
| name="Settings" | ||
| parent="estate_menu_root" | ||
| /> | ||
|
|
||
| <menuitem | ||
| id="estate_property_type_list_menu" | ||
| name="Property Type" | ||
| parent="estate_property_type_menu" | ||
| action="estate_property_type_action" | ||
| /> | ||
| <menuitem | ||
| id="estate_property_tag_menu" | ||
| name="Property Tags" | ||
| parent="estate_property_type_menu" | ||
| action="estate_property_tag_action" | ||
| /> | ||
| </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 one empty line before it.