-
Notifications
You must be signed in to change notification settings - Fork 3.3k
ollet - Technical Training #1414
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
b592011
9282f17
339767a
433fc25
7a55a96
703a614
5ec1f59
b365a35
2dd8a20
8badc9b
18d1370
162db65
0afb8b8
7d4e7ce
a835310
4f8b1c9
ea8305e
c3845d7
e7eabbf
dd0f613
b24f6c7
c8db864
774457f
cf79289
a7f50eb
ef52fd9
991989f
de922c4
c0d2543
bf627c7
9f5e770
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,16 @@ | ||
| # noinspection statement-effect | ||
| { | ||
| "name": "estate", | ||
| "depends": ["base"], | ||
| "data": [ | ||
| "security/ir.model.access.csv", | ||
| "views/estate_property_views.xml", | ||
| "views/estate_property_type_views.xml", | ||
| "views/estate_property_tag_views.xml", | ||
| "views/estate_property_offer_views.xml", | ||
| "views/res_users_views.xml", | ||
| "views/estate_menus.xml" | ||
| ], | ||
| "author": "Odoo S.A.", | ||
| "license": "AGPL-3" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from . import property | ||
| from . import property_type | ||
| from . import property_tag | ||
| from . import property_offer | ||
| from . import res_users |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| from odoo import models, fields, api | ||
| from odoo.exceptions import UserError, ValidationError | ||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
|
||
|
|
||
| class Property(models.Model): | ||
| _name = "estate.property" | ||
| _description = "Real estate property" | ||
| _order = "id desc" | ||
|
|
||
| name = fields.Char(string="Title", required=True) | ||
| description = fields.Text() | ||
| postcode = fields.Char() | ||
| date_availability = fields.Date( | ||
| string="Available From", | ||
| copy=False, | ||
| default=lambda x: fields.Date.add(fields.Date.today(), months=3) | ||
| ) | ||
| expected_price = fields.Float(required=True) | ||
| selling_price = fields.Float(readonly=True, copy=False) | ||
| bedrooms = fields.Integer(default=2) | ||
| living_area = fields.Integer(string="Living Area (sqm)") | ||
| faces = fields.Integer(string="Facades") | ||
| has_garage = fields.Boolean(string="Garage") | ||
| has_garden = fields.Boolean(string="Garden") | ||
| garden_area = fields.Integer(string="Garden Area (sqm)") | ||
| garden_orientation = fields.Selection( | ||
| selection=[ | ||
| ("north", "North"), | ||
| ("east", "East"), | ||
| ("south", "South"), | ||
| ("west", "West") | ||
| ] | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| default="new", | ||
| required=True, | ||
| copy=False, | ||
| selection=[ | ||
| ("new", "New"), | ||
| ("offer_received", "Offer Received"), | ||
| ("offer_accepted", "Offer Accepted"), | ||
| ("sold", "Sold"), | ||
| ("cancelled", "Cancelled") | ||
| ] | ||
| ) | ||
| property_type_id = fields.Many2one("estate.property.type", string="Type") | ||
|
ollet-odoo marked this conversation as resolved.
|
||
| buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) | ||
| salesperson_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) | ||
| tag_ids = fields.Many2many("estate.property.tag", string="Tags") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") | ||
|
|
||
| total_area = fields.Float(compute="_compute_total_area") | ||
| best_price = fields.Float(string="Best Offer", compute="_compute_best_price") | ||
|
|
||
| # Constraints | ||
| _check_expected_price = models.Constraint( | ||
| "CHECK(expected_price > 0)", | ||
| "Expected price must be strictly positive", | ||
| ) | ||
| _check_selling_price = models.Constraint( | ||
| "CHECK(selling_price >= 0)", | ||
| "Expected price must be positive", | ||
| ) | ||
|
|
||
| @api.depends("garden_area", "living_area") | ||
| def _compute_total_area(self): | ||
| for record in self: | ||
| record.total_area = record.garden_area + record.living_area | ||
|
|
||
| @api.depends("offer_ids.price") | ||
| def _compute_best_price(self): | ||
| for record in self: | ||
| record.best_price = max(record.offer_ids.mapped("price")) if record.offer_ids else 0.0 | ||
|
|
||
| @api.constrains("expected_price", "selling_price") | ||
| def _check_expected_price_selling_price(self): | ||
| for record in self: | ||
| if not float_is_zero(record.selling_price, precision_digits=2) and \ | ||
| float_compare(record.expected_price * 0.90, record.selling_price, precision_digits=2) > 0: | ||
| raise ValidationError("Selling price must be above 90% of expected price") | ||
|
|
||
| @api.onchange("has_garden") | ||
| def _onchange_has_garden(self): | ||
| if self.has_garden: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = "north" | ||
| else: | ||
| self.garden_area = 0 | ||
| self.garden_orientation = False | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def prevent_unwanted_deletion(self): | ||
| if self.state not in ("new", "cancelled"): | ||
| raise UserError("Can only delete Property that are New or Cancelled") | ||
| return super().unlink() | ||
|
|
||
| def action_do_sold(self): | ||
| for record in self: | ||
| if record.state == "cancelled": | ||
| raise UserError("Canceled property cannot be sold") | ||
| else: | ||
| record.state = "sold" | ||
| return True | ||
|
|
||
| def action_do_cancel(self): | ||
| for record in self: | ||
| if record.state == "sold": | ||
| raise UserError("Sold property cannot be canceled") | ||
| else: | ||
| record.state = "cancelled" | ||
| return True | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| from odoo import models, fields, api | ||
| from odoo.exceptions import UserError | ||
|
|
||
|
|
||
| class PropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Property Offer" | ||
| _order = "price desc" | ||
|
|
||
| price = fields.Float() | ||
| status = fields.Selection( | ||
| copy=False, | ||
| selection=[ | ||
| ("accepted", "Accepted"), | ||
| ("refused", "Refused"), | ||
| ], | ||
| ) | ||
| partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||
| property_id = fields.Many2one("estate.property", required=True) | ||
| property_id_state = fields.Selection(related="property_id.state") | ||
| validity = fields.Integer(default=7) | ||
| date_deadline = fields.Date( | ||
| compute="_compute_date_deadline", inverse="_inverse_date_deadline" | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| "estate.property.type", related="property_id.property_type_id", store=True | ||
| ) | ||
|
|
||
| _check_price = models.Constraint( | ||
| "CHECK(price > 0)", | ||
| "Offer price must be strictly positive", | ||
| ) | ||
|
|
||
| @api.depends("validity", "create_date") | ||
|
ollet-odoo marked this conversation as resolved.
|
||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| base_date = record.create_date or fields.Date.today() | ||
| record.date_deadline = fields.Date.add(base_date, days=record.validity) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for record in self: | ||
| base_date = ( | ||
| fields.Date.to_date(record.create_date) | ||
| if record.create_date | ||
| else fields.Date.today() | ||
| ) | ||
| record.validity = (record.date_deadline - base_date).days | ||
|
|
||
| @api.model | ||
| def create(self, vals_list): | ||
| for vals in vals_list: | ||
| property_id = self.env['estate.property'].browse(vals['property_id']) | ||
| property_id.state = 'offer_received' | ||
| for record in property_id.offer_ids: | ||
| if record.price > vals['price']: | ||
| raise UserError("Cannot create an offer with a lower value than an existing one") | ||
|
Comment on lines
+54
to
+56
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. It's correct of course, you can also use |
||
| return super().create(vals_list) | ||
|
|
||
| def action_accept(self): | ||
| # TODO Investigate using write() to update records | ||
| for record in self: | ||
| if record.property_id.state in ("offer_accepted", "sold", "cancelled"): | ||
| err_msg = f"Cannot accept offer on property that is {record.property_id.state}" | ||
| raise UserError(err_msg) | ||
| record.status = "accepted" | ||
| record.property_id.buyer_id = record.partner_id | ||
| record.property_id.selling_price = record.price | ||
| record.property_id.state = "offer_accepted" | ||
| return True | ||
|
|
||
| def action_refuse(self): | ||
| for record in self: | ||
| record.status = "refused" | ||
| return True | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class PropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Property Tag" | ||
| _order = "name asc" | ||
|
|
||
| name = fields.Char(string="Name", required=True) | ||
| color = fields.Integer() | ||
|
|
||
| _name_uniq = models.Constraint( | ||
| "UNIQUE (name)", | ||
| "The name of the tag must be unique!", | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class PropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Property Type" | ||
| _order = "sequence asc" | ||
|
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. I think the type also wants order by name |
||
|
|
||
| name = fields.Char(string="Name", required=True) | ||
| property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") | ||
| sequence = fields.Integer(default=1, help="Used to order type. Lower is better.") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_type_id") | ||
| offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
|
||
| _name_uniq = models.Constraint( | ||
| "UNIQUE (name)", | ||
| "The name of the type must be unique!", | ||
| ) | ||
|
|
||
| def _compute_offer_count(self): | ||
| for record in self: | ||
| record.offer_count = len(record.offer_ids) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| from odoo import models, fields, api | ||
| from odoo.exceptions import UserError | ||
|
|
||
|
|
||
| class ResUser(models.Model): | ||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many('estate.property', 'salesperson_id', name='Properties', | ||
| domain=[('state', 'in', ['offer_received', 'new'])]) |
| 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,model_estate_property,base.group_user,1,1,1,1 | ||
| access_estate_property_type_user,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_property_tag_user,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_estate_property_offer_user,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <menuitem id="estate_menu_root" name="Real Estate"> | ||
| <menuitem id="estate_advertisements_menu" name="Advertisements"> | ||
| <menuitem id="estate_property_menu_action" action="estate_property_action"/> | ||
| </menuitem> | ||
| <menuitem id="estate_settgins_menu" name="Settings"> | ||
| <menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/> | ||
| <menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action"/> | ||
| </menuitem> | ||
| </menuitem> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||||
| <?xml version="1.0"?> | ||||||||||||
| <odoo> | ||||||||||||
| <record id="estate_property_offer_view_tree" 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 editable="bottom" | ||||||||||||
| decoration-success="status == 'accepted'" | ||||||||||||
| decoration-danger="status == 'refused'" | ||||||||||||
| > | ||||||||||||
|
Comment on lines
+7
to
+10
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. We don't like a very long line but this one is fine, it makes it easier to read the file :)
Suggested change
|
||||||||||||
| <field name="price"/> | ||||||||||||
| <field name="partner_id"/> | ||||||||||||
| <field name="validity"/> | ||||||||||||
| <field name="date_deadline"/> | ||||||||||||
| <button name="action_accept" title="Accept" type="object" icon="fa-check" | ||||||||||||
| invisible="property_id_state in ('offer_accepted', 'sold', 'cancelled')" | ||||||||||||
|
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. surely it's correct, but usually teams avoid adding new fields to the database unless absolutely necessary. It's no big issue here, it's a related field already but thought to mention it |
||||||||||||
| /> | ||||||||||||
| <button name="action_refuse" title="Refuse" type="object" | ||||||||||||
| icon="oi-close" | ||||||||||||
| invisible="property_id_state in ('offer_accepted', 'sold', 'cancelled')" | ||||||||||||
| /> | ||||||||||||
| </list> | ||||||||||||
| </field> | ||||||||||||
| </record> | ||||||||||||
| <record id="estate_property_offer_view_form" model="ir.ui.view"> | ||||||||||||
| <field name="name">estate.property.offer.form</field> | ||||||||||||
| <field name="model">estate.property.offer</field> | ||||||||||||
| <field name="arch" type="xml"> | ||||||||||||
| <form> | ||||||||||||
| <group> | ||||||||||||
| <field name="price"/> | ||||||||||||
| <field name="partner_id"/> | ||||||||||||
| <field name="date_deadline"/> | ||||||||||||
| <field name="validity"/> | ||||||||||||
| <field name="status"/> | ||||||||||||
| </group> | ||||||||||||
| </form> | ||||||||||||
| </field> | ||||||||||||
| </record> | ||||||||||||
| </odoo> | ||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_property_tag_action" 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> | ||
| </record> | ||
| <record id="estate_property_tag_view_tree" 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 editable="top"> | ||
| <field name="name"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| <record id="estate_property_tag_view_form" model="ir.ui.view"> | ||
| <field name="name">estate.property.tag.form</field> | ||
| <field name="model">estate.property.tag</field> | ||
| <field name="arch" type="xml"> | ||
| <form> | ||
| <sheet> | ||
| <div class="oe_title"> | ||
| <h1> | ||
| <group> | ||
| <field name="name"/> | ||
| </group> | ||
| </h1> | ||
| </div> | ||
| <group> | ||
| <field name="color"/> | ||
| </group> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
| <record id="estate_property_tag_view_search" model="ir.ui.view"> | ||
| <field name="name">estate.property.tag.search</field> | ||
| <field name="model">estate.property.tag</field> | ||
| <field name="arch" type="xml"> | ||
| <search> | ||
| <field name="name"/> | ||
| </search> | ||
| </field> | ||
| </record> | ||
| </odoo> |
Uh oh!
There was an error while loading. Please reload this page.