-
Notifications
You must be signed in to change notification settings - Fork 3.1k
vibad - technical trainning #1239
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 15 commits
11a9e65
e8c4542
ebe26b3
760eba1
86dbba3
842b9fb
d3a2e67
0eeb0b0
45320c8
b2e44d5
7ab1515
c4f1032
0895297
e5985e4
bce8978
bf2cafe
867cbfe
9061548
8d99e23
5dd56af
a43be70
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,21 @@ | ||
| { | ||
| 'name': 'estate', | ||
| 'depends': [ | ||
| 'base' | ||
| ], | ||
| 'installable': True, | ||
| 'application': True, | ||
| 'author': 'vibad', | ||
| 'license': 'LGPL-3', | ||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| 'view/estate_property_offer_views.xml', | ||
| 'view/estate_property_views.xml', | ||
| 'view/estate_property_type_views.xml', | ||
| 'view/estate_property_tag_views.xml', | ||
| 'view/estate_inherit_view.xml', | ||
| 'view/estate_action.xml', | ||
| ], | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, inherited_model | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| from odoo import models, fields, api | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| from odoo.exceptions import UserError, ValidationError | ||
|
|
||
|
|
||
| class Estate_property(models.Model): | ||
|
nausicaa73 marked this conversation as resolved.
|
||
| _name = "estate_property" | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| _description = "APP super mega trop bien" | ||
| _order = "id desc" | ||
|
|
||
| name = fields.Char(required=True) | ||
|
nausicaa73 marked this conversation as resolved.
|
||
| description = fields.Text() | ||
| postcode = fields.Char() | ||
| date_availability = fields.Date(copy=False, default=lambda self: 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() | ||
| facades = fields.Integer() | ||
| garage = fields.Boolean() | ||
| garden = fields.Boolean() | ||
| garden_area = fields.Integer() | ||
| garden_orientation = fields.Selection( | ||
| string='Orientation', | ||
| selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], | ||
| help="The garden orientation", | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| string="Status", | ||
| selection=[('new', 'New'), ('offer Received', 'Offer Received'), ('offer Accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')], | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| required=True, | ||
| copy=False, | ||
| default="new", | ||
| compute="_compute_state", | ||
| store=True, | ||
| ) | ||
|
nausicaa73 marked this conversation as resolved.
|
||
| property_type_id = fields.Many2one("estate_property_type", string="Property Type") | ||
| salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) | ||
| buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) | ||
| tag_ids = fields.Many2many("estate_property_tag", string="Tags") | ||
| offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers") | ||
| total_area = fields.Integer(compute="_compute_area") | ||
| best_price = fields.Float(compute="_compute_best_price") | ||
|
|
||
| _check_expected_price = models.Constraint( | ||
|
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. Order of the elements of the file is wrong, constraints should come after computes (see doc) |
||
| "CHECK(expected_price > 0)", | ||
| message="The expected price must be strictly positive", | ||
| ) | ||
|
|
||
| _check_selling_price = models.Constraint( | ||
| "CHECK(selling_price >= 0)", | ||
| message="The selling price cannot be negative", | ||
| ) | ||
|
|
||
| @api.depends("living_area", "garden_area") | ||
| def _compute_area(self): | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| 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: | ||
| if record.offer_ids: | ||
| record.best_price = max(record.offer_ids.mapped("price")) | ||
| else: | ||
| record.best_price = 0 | ||
|
|
||
| @api.onchange("garden") | ||
| def _onchange_garden(self): | ||
|
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. Tricky (?) question time! At first glance, this method could also be an Answer
It doesn't make much sense to set arbitrary default values when programmatically updating records, so |
||
| if self.garden: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = "north" | ||
| else: | ||
| self.garden_area = 0 | ||
| self.garden_orientation = False | ||
|
|
||
| @api.depends("offer_ids", "offer_ids.state") | ||
| def _compute_state(self): | ||
| if self.state in ["sold", "cancelled"]: | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| return | ||
| if self.offer_ids: | ||
| for offer in self.offer_ids: | ||
| if offer.state == "accepted": | ||
| self.state = "offer Accepted" | ||
| break | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| else: | ||
| self.state = "new" | ||
| self.selling_price = 0 | ||
|
|
||
| def action_sold(self): | ||
| for record in self: | ||
| if record.state != "cancelled" and record.state != "sold": | ||
| record.state = "sold" | ||
| else: | ||
| raise UserError("A property that is cancelled or already sold cannot be sold.") | ||
|
|
||
| def action_cancel(self): | ||
| for record in self: | ||
| if record.state != "sold" and record.state != "cancelled": | ||
| record.state = "cancelled" | ||
| else: | ||
| raise UserError("A property that is sold or already cancelled cannot be cancelled.") | ||
|
|
||
| @api.constrains("selling_price", "expected_price") | ||
| def _check_enough_selling_price(self): | ||
| for record in self: | ||
| if record.selling_price and record.selling_price < record.expected_price * 0.9: | ||
| raise ValidationError("The selling price cannot be less than 90% of the expected price.") | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _ondelete_cancel_new(self): | ||
| for record in self: | ||
| if record.state not in ["new", "cancelled"]: | ||
| raise UserError("You can only delete offers that are new or cancelled.") | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||
| from odoo import models, fields, api | ||||||||||||||||||
|
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. Same remark |
||||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class Estate_property_offer(models.Model): | ||||||||||||||||||
| _name = "estate_property_offer" | ||||||||||||||||||
|
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. Same remark |
||||||||||||||||||
| _description = "Offer for estate properties" | ||||||||||||||||||
| _order = "price desc" | ||||||||||||||||||
|
|
||||||||||||||||||
| price = fields.Float(required=True) | ||||||||||||||||||
| partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||||||||||||||||||
| property_id = fields.Many2one("estate_property", string="Property", required=True) | ||||||||||||||||||
| state = fields.Selection([ | ||||||||||||||||||
| ('new', 'New'), | ||||||||||||||||||
| ("accepted", "Accepted"), | ||||||||||||||||||
| ("refused", "Refused"), | ||||||||||||||||||
| ], default="new", string="State", copy=False) | ||||||||||||||||||
| validaty = fields.Integer(string="Offer Validity (days)", default=7) | ||||||||||||||||||
| date_deadline = fields.Date(string="Offer Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline") | ||||||||||||||||||
| property_type_id = fields.Many2one(related="property_id.property_type_id", store=True) | ||||||||||||||||||
|
|
||||||||||||||||||
| _check_price = models.Constraint( | ||||||||||||||||||
| "CHECK(price > 0)", | ||||||||||||||||||
| message="The price must be strictly positive", | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| @api.depends("validaty", "create_date") | ||||||||||||||||||
| def _compute_date_deadline(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| date = record.create_date | ||||||||||||||||||
| if not date: | ||||||||||||||||||
| date = fields.Date.today() | ||||||||||||||||||
| if record.validaty: | ||||||||||||||||||
| record.date_deadline = fields.Date.add(date, days=record.validaty) | ||||||||||||||||||
| else: | ||||||||||||||||||
| record.date_deadline = False | ||||||||||||||||||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||||||||||||||||||
|
|
||||||||||||||||||
| def _inverse_date_deadline(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| if record.date_deadline and record.create_date: | ||||||||||||||||||
| create_date = fields.Date.to_date(record.create_date) | ||||||||||||||||||
| record.validaty = (record.date_deadline - create_date).days | ||||||||||||||||||
| else: | ||||||||||||||||||
| record.validaty = 0 | ||||||||||||||||||
|
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. Shouldn't you default to 7? |
||||||||||||||||||
|
|
||||||||||||||||||
| def accept_offer(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| if record.state == "accepted" or record.state == "refused": | ||||||||||||||||||
| raise UserError("This offer has already been accepted or refused.") | ||||||||||||||||||
| for offer in record.property_id.offer_ids: | ||||||||||||||||||
| if offer.state == "accepted": | ||||||||||||||||||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||||||||||||||||||
| raise UserError("Another offer has already been accepted for this property.") | ||||||||||||||||||
| record.state = "accepted" | ||||||||||||||||||
|
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. When an action modifies values on a record, it usually explicitly calls a
Suggested change
|
||||||||||||||||||
| record.property_id.selling_price = record.price | ||||||||||||||||||
| record.property_id.buyer_id = record.partner_id | ||||||||||||||||||
| return True | ||||||||||||||||||
|
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. You can
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| def refuse_offer(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| if record.state != "new": | ||||||||||||||||||
| raise UserError("This offer has already been accepted or refused.") | ||||||||||||||||||
| record.state = "refused" | ||||||||||||||||||
| return True | ||||||||||||||||||
|
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. Same remark |
||||||||||||||||||
|
|
||||||||||||||||||
| @api.model_create_multi | ||||||||||||||||||
| def create(self, vals_list): | ||||||||||||||||||
|
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. Same remark for order of methods |
||||||||||||||||||
| for vals in vals_list: | ||||||||||||||||||
| if self.env["estate_property"].browse(vals["property_id"]).state == "new": | ||||||||||||||||||
| self.env["estate_property"].browse(vals["property_id"]).state = "offer Received" | ||||||||||||||||||
| max_price = max(self.env["estate_property_offer"].search([("property_id", "=", vals["property_id"])]).mapped("price") or [0]) | ||||||||||||||||||
| if vals["price"] <= max_price: | ||||||||||||||||||
| raise UserError("The price must be higher than the current highest offer.") # Error for one offer blocks all offers in the list | ||||||||||||||||||
| return super().create(vals_list) | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||
| from odoo import models, fields | ||||||
|
|
||||||
|
|
||||||
| class Estate_property_tag(models.Model): | ||||||
| _name = "estate_property_tag" | ||||||
|
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. Same remark |
||||||
| _description = "tag super mega trop bien" | ||||||
| _order = "name" | ||||||
|
|
||||||
| name = fields.Char(required=True) | ||||||
| color = fields.Integer() | ||||||
|
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. American english is ugly, too bad it's the default most of the time 😢
Suggested change
(don't apply this, it's just me ranting)
Author
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. 🇺🇸🇺🇸🇺🇸 |
||||||
|
|
||||||
| _check_name = models.Constraint( | ||||||
| "UNIQUE(name)", | ||||||
| message="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 Estate_property_type(models.Model): | ||
| _name = "estate_property_type" | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| _description = "APP super mega trop bien" | ||
| _order = "sequence, name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| sequence = fields.Integer() | ||
| offer_ids = fields.One2many("estate_property_offer", "property_type_id", string="Offers") | ||
| offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
|
||
| _check_name = models.Constraint( | ||
|
nausicaa73 marked this conversation as resolved.
|
||
| "UNIQUE(name)", | ||
| message="The name of the property type must be unique", | ||
| ) | ||
| property_ids = fields.One2many("estate_property", "property_type_id", string="Properties") | ||
|
|
||
| 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,7 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class Estate_inherited_model(models.Model): | ||
|
nausicaa73 marked this conversation as resolved.
Outdated
|
||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many("estate_property", "salesperson_id", string="Properties") | ||
| 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 | ||
| acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,1 | ||
| acces_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,0 | ||
| acces_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,0 | ||
| acces_estate_property_offer,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_root" name="Real Estate"> | ||
| <menuitem id="advertisements_level_menu" name="Advertisements"> | ||
| <menuitem id="estate_property_menu_action" action="estate_property_action"/> | ||
|
nausicaa73 marked this conversation as resolved.
|
||
| </menuitem> | ||
| <menuitem id="property_types_level_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,15 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_inherit_view" model="ir.ui.view"> | ||
| <field name="name">estate.inherit.view</field> | ||
| <field name="model">res.users</field> | ||
| <field name="inherit_id" ref="base.view_users_form"/> | ||
| <field name="arch" type="xml"> | ||
| <xpath expr="//page[@name='preferences']" position="after"> | ||
| <page string="Real Estate" name="real_estate"> | ||
| <field name="property_ids" options="{'no_create': True, 'no_open': True}"/> | ||
| </page> | ||
| </xpath> | ||
| </field> | ||
| </record> | ||
| </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_action" model="ir.actions.act_window"> | ||
| <field name="name">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="estate_property_offer_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 string="Offer"> | ||
| <sheet> | ||
| <group> | ||
| <field name="price" string="Price"/> | ||
| <field name="state" string="Status"/> | ||
| <field name="partner_id" string="Partner"/> | ||
| <field name="validaty" string="Validity (days)"/> | ||
| <field name="date_deadline" string="Offer Deadline"/> | ||
| </group> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="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-success="state == 'accepted'" decoration-danger="state == 'refused'"> | ||
| <field name="price" string="Price"/> | ||
| <button name="accept_offer" type="object" string="Accept" icon="fa-check" invisible="state == 'accepted' or state == 'refused'"/> | ||
| <button name="refuse_offer" type="object" string="Refuse" icon="fa-close" invisible="state == 'accepted' or state == 'refused'"/> | ||
| <field name="partner_id" string="Partner"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?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_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="Property Tags" editable="bottom"> | ||
| <field name="name"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </odoo> |
Uh oh!
There was an error while loading. Please reload this page.