-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[ADD] Estate Module (Python Framework Training) #1068
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?
Changes from all commits
e6f8dbd
4a69d2f
e47cf30
bf9de48
ce471ef
0225b68
cb0c70a
1a772bd
17ffc1d
657bfe4
0f04b0e
2d074dc
a3610be
9e6806c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import models |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "name": "Real Estate", | ||
| "description": "Real Estate Management System", | ||
| "category": "Tutorials", | ||
| "version": "1.1", | ||
| "application": True, | ||
| "data": [ | ||
| "security/ir.model.access.csv", | ||
| "views/views.xml", | ||
| "views/menus.xml" | ||
| ], | ||
| "author": "Odoo S.A.", | ||
| "license": "LGPL-3", | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from . import building | ||
| from . import building_type | ||
| from . import tag | ||
| from . import offer | ||
| from . import salesperson |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| from odoo import models, fields, api | ||
| from odoo.exceptions import UserError | ||
| from datetime import timedelta | ||
|
|
||
|
|
||
| class Building(models.Model): | ||
| _name = "estate.buildings" | ||
| _description = "Buildings" | ||
| _order = "id desc" | ||
|
|
||
| name = fields.Char() | ||
| description = fields.Text() | ||
| value = fields.Integer(copy=False) | ||
| availability_date = fields.Date( | ||
| default=lambda self: fields.Date.today() + timedelta(days=90), copy=False | ||
| ) | ||
| number_of_rooms = fields.Integer(default=2) | ||
| garden_area = fields.Integer() | ||
| building_area = fields.Integer() | ||
| garden_orientation = fields.Selection( | ||
| [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], | ||
| "garden Orientation", | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| [ | ||
| ("new", "New"), | ||
| ("offer_received", "Offer Received"), | ||
| ("offer_accepted", "Offer Accepted"), | ||
| ("sold", "Sold"), | ||
| ("canceled", "Canceled"), | ||
| ], | ||
| default="new", | ||
| ) | ||
| post_code = fields.Integer(default=1000) | ||
| building_type_id = fields.Many2one("estate.building_type", string="Building Type") | ||
| buyer_id = fields.Many2one("res.partner", string="Buyer") | ||
| salesperson_id = fields.Many2one( | ||
| "res.users", string="Salesperson", default=lambda self: self.env.user | ||
| ) | ||
| tag_ids = fields.Many2many("estate.building_tags", string="Tags") | ||
| offer_ids = fields.One2many("estate.offers", "building_id", string="Offers") | ||
|
|
||
| total_area = fields.Integer(string="Total Area", compute="_compute_total_area") | ||
|
|
||
| best_price = fields.Integer( | ||
| string="Best Offer Price", | ||
| compute="_compute_best_price", | ||
| ) | ||
| has_garden = fields.Boolean(string="Has Garden", default=False) | ||
|
|
||
| _price_constraint = models.Constraint( | ||
| "CHECK (value > 0)", "Price must be POSITIVE." | ||
| ) | ||
| _name_constraint = models.Constraint( | ||
| "UNIQUE(name)", "Building name must be UNIQUE." | ||
| ) | ||
|
|
||
| @api.depends("building_area", "garden_area") | ||
| def _compute_total_area(self): | ||
| for record in self: | ||
| record.total_area = record.building_area + record.garden_area | ||
|
|
||
| @api.depends("offer_ids.price") | ||
| def _compute_best_price(self): | ||
| for record in self: | ||
| if record.offer_ids: | ||
| record.best_price = max(record.offer_ids.mapped("price")) | ||
| else: | ||
| record.best_price = 0 | ||
|
|
||
| @api.onchange("has_garden") | ||
| def _onchange_garden_area(self): | ||
| if self.has_garden: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = "north" | ||
| else: | ||
| self.garden_area = 0 | ||
| self.garden_orientation = False | ||
|
|
||
| def action_set_sold(self): | ||
| for record in self: | ||
| if record.state == "canceled": | ||
| raise UserError(self.env._("Canceled buildings cannot be sold.")) | ||
| record.state = "sold" | ||
|
|
||
| def action_set_canceled(self): | ||
| for record in self: | ||
| if record.state == "sold": | ||
| raise UserError(self.env._("Sold buildings cannot be canceled.")) | ||
| record.state = "canceled" | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _check_if_sold(self): | ||
| for record in self: | ||
| if record.state not in ("new", "canceled"): | ||
| raise UserError(self.env._("This building cannot be deleted.")) | ||
| return self |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class BuildingType(models.Model): | ||
| _name = "estate.building_type" | ||
| _description = "Building Type" | ||
| _order = "sequence, name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| building_ids = fields.One2many( | ||
| "estate.buildings", "building_type_id", string="Buildings" | ||
| ) | ||
| offer_ids = fields.One2many("estate.offers", "property_type_id", string="Offers") | ||
| offers_count = fields.Integer( | ||
| string="Offers Count", | ||
| compute="_compute_offers_count", | ||
| ) | ||
|
|
||
| _name_uniqueness_constraint = models.Constraint( | ||
| "UNIQUE (name)", "Building type name must be UNIQUE." | ||
| ) | ||
|
|
||
| sequence = fields.Integer( | ||
| default=1, | ||
| help="Gives the sequence order when displaying a list of building types.", | ||
| ) | ||
|
|
||
| def _compute_offers_count(self): | ||
| for record in self: | ||
| record.offers_count = self.env["estate.offers"].search_count( | ||
| [("property_type_id", "=", record.id)] | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,97 @@ | ||||||||||||||||
| from odoo import models, fields, api | ||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||
| from datetime import timedelta | ||||||||||||||||
| from odoo.tools.float_utils import float_compare | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class Offer(models.Model): | ||||||||||||||||
| _name = "estate.offers" | ||||||||||||||||
| _description = "Offers" | ||||||||||||||||
| _order = "price desc" | ||||||||||||||||
|
|
||||||||||||||||
| price = fields.Integer(required=True) | ||||||||||||||||
| status = fields.Selection( | ||||||||||||||||
| [("accepted", "Accepted"), ("refused", "Refused")], | ||||||||||||||||
| string="Status", | ||||||||||||||||
| required=False, | ||||||||||||||||
| ) | ||||||||||||||||
| building_id = fields.Many2one("estate.buildings", string="Building") | ||||||||||||||||
| partner_id = fields.Many2one("res.partner", string="Partner") | ||||||||||||||||
| validity = fields.Integer(string="Validity (days)", default=7) | ||||||||||||||||
| date_deadline = fields.Date( | ||||||||||||||||
| string="Deadline", | ||||||||||||||||
| compute="_compute_date_deadline", | ||||||||||||||||
| inverse="_inverse_date_deadline", | ||||||||||||||||
| ) | ||||||||||||||||
| property_type_id = fields.Many2one( | ||||||||||||||||
| related="building_id.building_type_id", string="Property Type", store=True | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| _price_positive_constraint = models.Constraint( | ||||||||||||||||
| "CHECK (price > 0)", "Offer price must be positive." | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| @api.depends("validity") | ||||||||||||||||
| def _compute_date_deadline(self): | ||||||||||||||||
| for record in self: | ||||||||||||||||
| record.date_deadline = fields.Date.today() + timedelta(days=record.validity) | ||||||||||||||||
|
|
||||||||||||||||
| def _inverse_date_deadline(self): | ||||||||||||||||
| for record in self: | ||||||||||||||||
| record.validity = (record.date_deadline - fields.Date.today()).days | ||||||||||||||||
|
|
||||||||||||||||
| def action_accept_offer(self): | ||||||||||||||||
| for record in self: | ||||||||||||||||
| if record.status != "accepted" and record.building_id.state not in [ | ||||||||||||||||
| "sold", | ||||||||||||||||
| "canceled", | ||||||||||||||||
| ]: | ||||||||||||||||
|
Comment on lines
+45
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO this is more readable
Suggested change
|
||||||||||||||||
| record.status = "accepted" | ||||||||||||||||
| record.building_id.state = "offer_accepted" | ||||||||||||||||
| record.building_id.buyer_id = record.partner_id | ||||||||||||||||
| record.building_id.value = record.price | ||||||||||||||||
| other_offers = self.search( | ||||||||||||||||
| [ | ||||||||||||||||
| ("building_id", "=", record.building_id.id), | ||||||||||||||||
| ("id", "!=", record.id), | ||||||||||||||||
| ] | ||||||||||||||||
| ) | ||||||||||||||||
| other_offers.write({"status": "refused"}) | ||||||||||||||||
| elif record.building_id.state in ["sold", "canceled"]: | ||||||||||||||||
| raise UserError( | ||||||||||||||||
| self.env._("Cannot accept offers for sold or canceled buildings.") | ||||||||||||||||
| ) | ||||||||||||||||
|
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
| else: | ||||||||||||||||
| raise UserError(self.env._("Offer is already accepted.")) | ||||||||||||||||
|
|
||||||||||||||||
| def action_refuse_offer(self): | ||||||||||||||||
| for record in self: | ||||||||||||||||
| if record.status != "refused": | ||||||||||||||||
| record.status = "refused" | ||||||||||||||||
| record.building_id.state = "offer_received" | ||||||||||||||||
| record.building_id.buyer_id = False | ||||||||||||||||
| else: | ||||||||||||||||
| raise UserError(self.env._("Offer is already refused.")) | ||||||||||||||||
|
|
||||||||||||||||
| @api.constrains("building_id", "price") | ||||||||||||||||
| def _check_price(self): | ||||||||||||||||
| for record in self: | ||||||||||||||||
| if ( | ||||||||||||||||
| float_compare( | ||||||||||||||||
| 0.9 * record.building_id.value, record.price, precision_digits=2 | ||||||||||||||||
| ) | ||||||||||||||||
| == 1 | ||||||||||||||||
| ): | ||||||||||||||||
|
Comment on lines
+79
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we know
Suggested change
|
||||||||||||||||
| raise UserError( | ||||||||||||||||
| self.env._( | ||||||||||||||||
| "Offer price must be at least 90% of the building's value." | ||||||||||||||||
| ) | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| @api.model | ||||||||||||||||
| def create(self, vals): | ||||||||||||||||
| for val in vals: | ||||||||||||||||
| self.env["estate.buildings"].browse( | ||||||||||||||||
| val["building_id"] | ||||||||||||||||
| ).state = "offer_received" | ||||||||||||||||
| return super().create(vals) | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class ResUsers(models.Model): | ||
| _inherit = ["res.users"] | ||
|
|
||
| building = fields.One2many("estate.buildings", "salesperson_id", string="Listings") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class BuildingTag(models.Model): | ||
| _name = "estate.building_tags" | ||
| _description = "Building Tags" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| color = fields.Integer() | ||
|
|
||
| _name_uniqueness_constraint = models.Constraint( | ||
| "UNIQUE (name)", "Building tag name must be UNIQUE." | ||
| ) |
| 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_first_model,access_first_model,model_estate_buildings,base.group_user,1,1,1,1 | ||
| access_building_type_model,access_building_type_model,model_estate_building_type,base.group_user,1,1,1,1 | ||
| access_building_tags_model,access_building_tags_model,model_estate_building_tags,base.group_user,1,1,1,1 | ||
| access_offers_model,access_offers_model,model_estate_offers,base.group_user,1,1,1,1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <menuitem id="test_menu_root" name="Estate FelBeit"> | ||
| <menuitem id="test_first_level_menu" name="First Level"> | ||
| <menuitem id="test_model_menu_action" action="some_model_action_1"/> | ||
| <menuitem id="test_building_type_menu_action" action="some_model_action_2"/> | ||
| </menuitem> | ||
| </menuitem> | ||
| </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.
Nit here