-
Notifications
You must be signed in to change notification settings - Fork 3.1k
19.0 belam technical training #1236
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
Open
belam-odoo
wants to merge
13
commits into
odoo:19.0
Choose a base branch
from
odoo-dev:19.0-belam-technical-training
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.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
30ee0b4
[IMP] estate: created module
belam-odoo b66316a
[IMP] estate: chapter 3 done
belam-odoo efa5693
[IMP] estate: chapter 5 - some ui and estate property model refined
belam-odoo 686f91a
[IMP] estate : chapter 6 - custom views
belam-odoo 86a346e
[IMP] estate : chapter 7 - relational models
belam-odoo 523184e
[IMP] estate : chapters 8 & 9
belam-odoo 188deb8
[IMP] estate - Chapter 10
belam-odoo f597615
[IMP] estate: add basic test cases
vandroogenbd 61e2c6d
[IMP] estate - Chapter 11
belam-odoo e3b58e0
[IMP] estate - Chapter 12
belam-odoo e4590fc
[IMP] estate - Chapter 13
belam-odoo 71f7637
[IMP] estate - Chapter 14
belam-odoo b604ccf
[FIX] estate - Try to see if CI works
belam-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,26 @@ | ||
| { | ||
| "name": "Real Estate", | ||
| "version": "19.0.0.1.0", | ||
| "category": "Custom", | ||
| "summary": "My first application", | ||
| "installable": True, | ||
| "application": True, | ||
| "author": "belam", | ||
| "depends": [ | ||
| "base", | ||
| ], | ||
| "license": "LGPL-3", | ||
| "data": [ | ||
| # Views per model | ||
| "views/estate_property_tag_views.xml", | ||
| "views/estate_property_salesman_views.xml", | ||
| "views/estate_property_type_views.xml", | ||
| "views/estate_property_views.xml", | ||
| "views/estate_property_offer_views.xml", | ||
| # Menus | ||
| "views/estate_property_menus.xml", | ||
| # Security | ||
| "security/ir.model.access.csv", | ||
| # Dqtq | ||
| ], | ||
| } |
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_offer | ||
| from . import estate_property_tag | ||
| from . import estate_property_salesman |
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,179 @@ | ||
| 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 = "Estate Property" | ||
| _order = "id desc" | ||
|
|
||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| string="State", | ||
| selection=[ | ||
| ("new", "New"), | ||
| ("offer_received", "Offer received"), | ||
| ("offer_accepted", "Offer accepted"), | ||
| ("sold", "Sold"), | ||
| ("cancelled", "Cancelled"), | ||
| ], | ||
| default="new", | ||
| copy=False, | ||
| ) | ||
| name = fields.Char( | ||
| required=True, | ||
| string="Title", | ||
| ) | ||
| description = fields.Text( | ||
| string="Description", | ||
| ) | ||
| postcode = fields.Char( | ||
| string="Postcode", | ||
| ) | ||
| date_availability = fields.Date( | ||
| default=fields.Date.add(fields.Date.today(), months=3), | ||
| copy=False, | ||
| string="Available from", | ||
| ) | ||
|
|
||
| expected_price = fields.Monetary( | ||
| string="Expected price", currency_field="currency_id", | ||
| ) | ||
|
|
||
| selling_price = fields.Monetary( | ||
| string="Selling price", | ||
| readonly=True, | ||
| copy=False, | ||
| default_export_compatible=False, | ||
| currency_field="currency_id", | ||
| ) | ||
| currency_id = fields.Many2one( | ||
| "res.currency", default=lambda self: self.env.company.currency_id, | ||
| ) | ||
|
|
||
| bedrooms = fields.Integer(default=2, string="Bedrooms") | ||
| living_area = fields.Integer(string="Living Area", default=0) | ||
| facades = fields.Integer(string="Facades") | ||
| garage = fields.Boolean(string="Garage") | ||
| has_garden = fields.Boolean(string="Garden") | ||
| garden_area = fields.Integer(string="Garden Area", default=0) | ||
| total_area = fields.Integer(compute="_compute_total_area", string="Total Area") | ||
| garden_orientation = fields.Selection( | ||
| string="Garden Orientation", | ||
| selection=[ | ||
| ("north", "North"), | ||
| ("south", "South"), | ||
| ("east", "East"), | ||
| ("west", "West"), | ||
| ], | ||
| help="If you don't know where West is, wait for the sun to go to sleep. Its bedroom lies West.", | ||
| ) | ||
|
|
||
| customer_id = fields.Many2one("res.partner", string="Customer", copy=False) | ||
|
|
||
| salesman_id = fields.Many2one( | ||
| "res.users", | ||
| string="Salesman", | ||
| default=lambda self: self.env.user, | ||
| required=True, | ||
| ) | ||
| estate_property_type_id = fields.Many2one( | ||
| "estate.property.type", | ||
| string="Property Type", | ||
| ) | ||
|
|
||
| tag_ids = fields.Many2many("estate.property.tag", string="Tags") | ||
|
|
||
| offer_ids = fields.One2many( | ||
| comodel_name="estate.property.offer", | ||
| inverse_name="estate_property_id", | ||
| ) | ||
|
|
||
| best_price = fields.Monetary( | ||
| string="Best Offer", | ||
| compute="_compute_best_price", | ||
| ) | ||
|
|
||
| #### CONSTRAINTS #### | ||
| _check_expected_price_positive = models.Constraint( | ||
| "CHECK(expected_price > 0)", | ||
| "Expected price must be positive", | ||
| ) | ||
|
|
||
| _check_selling_price = models.Constraint( | ||
| "CHECK(selling_price >= 0)", | ||
| "the selling price must be positive.", | ||
| ) | ||
|
|
||
| @api.constrains("selling_price", "expected_price") | ||
| def _check_selling_price(self): | ||
| for ep in self: | ||
| if float_is_zero(ep.selling_price, precision_digits=2): | ||
| continue | ||
|
|
||
| lowest_selling_price = ep.expected_price * 0.9 | ||
| if float_compare(ep.selling_price, lowest_selling_price, precision_digits=2) == -1: | ||
| raise ValidationError( | ||
| _( | ||
| "The selling price must be at least 90%% of the expected price! " | ||
| "(Minimum expected: %s)", lowest_selling_price, | ||
| ), | ||
| ) | ||
|
|
||
| #### COMPUTED VALUES #### | ||
| @api.depends("living_area", "garden_area") | ||
| def _compute_total_area(self): | ||
| for ep in self: | ||
| ep.total_area = ( | ||
| ep.living_area + ep.garden_area if ep.has_garden else ep.living_area | ||
| ) | ||
|
|
||
| @api.depends("offer_ids.price") | ||
| def _compute_best_price(self): | ||
| for ep in self: | ||
| ep.best_price = max(ep.offer_ids.mapped("price")) if ep.offer_ids else None | ||
|
|
||
| @api.onchange("has_garden") | ||
| def _onchange_has_garden(self): | ||
| if self.has_garden: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = "north" | ||
|
belam-odoo marked this conversation as resolved.
|
||
| else: | ||
| self.garden_area = 0 | ||
| self.garden_orientation = False | ||
|
|
||
| #### CRUD #### | ||
|
|
||
| #### ACTIONS #### | ||
| def action_set_accepted(self): | ||
| for ep in self: | ||
| # removed validation this looks a bit empty... | ||
| ep.state = "offer_accepted" | ||
|
|
||
| def action_set_sold(self): | ||
| for ep in self: | ||
| if ep.state == "cancelled": | ||
| raise UserError(_("Cancelled properties cannot be sold.")) | ||
| if ep.state == "sold": | ||
| raise UserError(_("Sold properties cannot be sold (anymore).")) | ||
|
|
||
| accepted_offer = ep.offer_ids.filtered(lambda o: o.status == "accepted") | ||
| if not accepted_offer: | ||
| raise UserError(_("You must accept an offer before selling the property.")) | ||
|
|
||
| ep.write({ | ||
| "state": "sold", | ||
| "customer_id": accepted_offer[0].partner_id.id, | ||
| "selling_price": accepted_offer[0].price, | ||
| }) | ||
| return True | ||
|
|
||
| def action_set_cancelled(self): | ||
| # todo: split action and validation (validation goes into python constraint) | ||
| for ep in self: | ||
| # todo later : add warning - dont know how to do this from the model | ||
| if ep.state == "sold": | ||
| raise UserError(_("A cancelled property cannot be sold")) | ||
| ep.state = "cancelled" | ||
| 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,114 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
| from odoo import _, api, fields, models | ||
| from odoo.exceptions import UserError, ValidationError | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Estate Property Offer" | ||
| _order = "price desc" | ||
|
|
||
| partner_id = fields.Many2one("res.partner", string="Customer", required=True) | ||
| estate_property_id = fields.Many2one( | ||
| comodel_name="estate.property", | ||
| string="Estate Property", | ||
| required=True, | ||
| ) | ||
| price = fields.Monetary(string="Price", required=True, currency_field="currency_id") | ||
| currency_id = fields.Many2one( | ||
| "res.currency", default=lambda self: self.env.company.currency_id, | ||
| ) | ||
| # STATUS AND DATE # | ||
| status = fields.Selection( | ||
| selection=[("accepted", "Accepted"), ("refused", "Refused")], | ||
| default=None, | ||
| string="Offer Status", | ||
| ) | ||
|
|
||
| validity = fields.Integer(string="Validity (days)", default=7) | ||
| date_deadline = fields.Date( | ||
| compute="_compute_date_deadline", | ||
| inverse="_inverse_date_deadline", | ||
| string="Deadline", | ||
| ) | ||
|
|
||
| # USEFUL FOR DISPLAY # | ||
| property_name = fields.Char("Property Name", related="estate_property_id.name") | ||
| property_type_name = fields.Char( | ||
| "Type Name", related="estate_property_id.estate_property_type_id.name", | ||
| ) | ||
| property_price = fields.Monetary("Expected price", related="estate_property_id.expected_price") | ||
| property_postcode = fields.Char("Postcode", related="estate_property_id.postcode") | ||
| property_type_id = fields.Many2one("estate.property.type", related="estate_property_id.estate_property_type_id", string="Property Type", store=True) | ||
|
|
||
| ### CONSTRAINTS AND VALIDATION ### | ||
| _check_expected_price_positive = models.Constraint( | ||
| "CHECK(price > 0)", | ||
| "Selling price must be positive", | ||
| ) | ||
|
|
||
| @api.constrains("status", "estate_property_id") | ||
| def _check_unique_accepted_offer(self): | ||
| for offer in self: | ||
| accepted_offers = offer.estate_property_id.offer_ids.filtered( | ||
| lambda o: o.status == "accepted", | ||
| ) | ||
| if len(accepted_offers) > 1: | ||
| raise ValidationError( | ||
| _("You cannot have multiple accepted offers for the same property."), | ||
| ) | ||
|
|
||
| ### COMPUTATED VALUES ### | ||
| @api.depends("validity") | ||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| base_date = record.create_date or fields.Date.today() | ||
| days_to_add = record.validity or 0 | ||
| record.date_deadline = base_date + relativedelta(days=days_to_add) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for o in self: | ||
| o.validity = (o.date_deadline - o.create_date.date()).days | ||
|
|
||
| #### CRUD #### | ||
| @api.model | ||
| def create(self, vals_list): | ||
| for vals in vals_list: | ||
| related_property = self.env["estate.property"].browse(vals.get("estate_property_id")) | ||
| if related_property.state in ("offer_accepted", "sold", "cancelled"): | ||
| raise UserError(_("Offers cannot be submitted for this property.")) | ||
| offered_price = vals.get("price", 0) | ||
| if related_property.offer_ids: | ||
| max_offer = max(related_property.offer_ids.mapped("price")) | ||
| if offered_price < max_offer: | ||
| raise UserError(_("The offer must be higher than %.2f", max_offer)) | ||
| related_property.state = "offer_received" | ||
| return super().create(vals_list) | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _unlink_by_user(self): | ||
| for o in self: | ||
| if o.status not in ("new", "cancelled"): | ||
| raise UserError(_("An accepted offer cannot be deleted.")) | ||
|
|
||
| ### ACTIONS ### | ||
| def action_set_refused(self): | ||
| for o in self: | ||
| if o.status: | ||
| raise UserError(_("This offer has already been %s", o.status)) | ||
| o.status = "refused" | ||
| return True | ||
|
|
||
| def action_set_accepted(self): | ||
| for record in self: | ||
| if record.estate_property_id.offer_ids.filtered( | ||
| lambda o: o.status == "accepted", | ||
| ): | ||
| raise UserError( | ||
| _("An offer has already been accepted for this property."), | ||
| ) | ||
|
|
||
| record.status = "accepted" | ||
| record.estate_property_id.action_set_accepted() | ||
| 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,12 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertySalesman(models.Model): | ||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many( | ||
| "estate.property", | ||
| "salesman_id", | ||
| string="Assigned properties", | ||
| domain=[("state", "in", ["new", "offer_received"])], | ||
| ) |
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 @@ | ||
| from random import randint | ||
|
|
||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class Tag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Estate Property Tag" | ||
| _order = "name" | ||
|
|
||
| def _get_default_color(self): | ||
| return randint(0, 11) | ||
|
|
||
| name = fields.Char(string="Tag Name", required=True) | ||
| color = fields.Integer(default=_get_default_color) | ||
|
|
||
| _name_uniq = models.Constraint( | ||
| "unique (name)", | ||
| "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 api, fields, models | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Estate Property Type" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char(required=True, string="Property Type Name") | ||
| sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") | ||
|
|
||
| property_ids = fields.One2many("estate.property", "estate_property_type_id", string="Properties") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") | ||
|
|
||
| offer_count = fields.Integer(compute="_compute_offer_count", string="Offer Count") | ||
|
|
||
| _name_uniq = 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,5 @@ | ||
| id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||
| estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 | ||
| estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 | ||
| estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 | ||
| estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,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 @@ | ||
| from . import test_estate_property |
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.
Uh oh!
There was an error while loading. Please reload this page.