-
Notifications
You must be signed in to change notification settings - Fork 3.1k
[ADD] estate,estate_account: technical onboarding commit #1285
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
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 |
|---|---|---|
| @@ -1,5 +1,13 @@ | ||
| import { Component } from "@odoo/owl"; | ||
| import { Component, useState } from "@odoo/owl"; | ||
|
|
||
| export class Playground extends Component { | ||
| static template = "awesome_owl.playground"; | ||
|
|
||
| setup() { | ||
| this.state = useState({ value: 0 }) | ||
| } | ||
|
|
||
| increment() { | ||
| this.state.value++ | ||
| } | ||
| } |
| 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,18 @@ | ||
| { | ||
| "name": "Estate", | ||
| "summary": "Track your real estate business", | ||
| "depends": ["base"], | ||
| "data": [ | ||
| "security/ir.model.access.csv", | ||
| "views/estate_property_views.xml", | ||
| "views/estate_property_offer_views.xml", | ||
| "views/estate_property_tag_views.xml", | ||
| "views/estate_property_type_views.xml", | ||
| "views/res_users_views.xml", | ||
| "views/estate_property_menus.xml", | ||
| ], | ||
| "installable": True, | ||
| "application": True, | ||
| "author": "Julien (jupir)", | ||
| "license": "LGPL-3", | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from . import estate_property | ||
| from . import estate_property_tag | ||
| from . import estate_property_type | ||
| from . import estate_property_offer | ||
| from . import res_users |
|
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. In a Model attribute order should be:
|
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||
| from dateutil.relativedelta import relativedelta | ||||||
| from datetime import datetime | ||||||
|
|
||||||
| from odoo import api, fields, models | ||||||
| from odoo.exceptions import UserError, ValidationError | ||||||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||||||
|
|
||||||
|
|
||||||
| class EstatePropery(models.Model): | ||||||
| _name = "estate.property" | ||||||
|
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. There is a tacit convention that strings viewed by the users are surrounded by
Suggested change
|
||||||
| _description = "Estate property" | ||||||
| _order = "id desc" | ||||||
|
|
||||||
| name = fields.Char(required=True) | ||||||
| description = fields.Text() | ||||||
| postcode = fields.Char() | ||||||
| date_availability = fields.Date( | ||||||
| copy=False, default=datetime.today() + relativedelta(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=[("N", "North"), ("E", "East"), ("S", "South"), ("W", "West")], | ||||||
|
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. Consider selection and a kind of dictionary, for dictionaries, you would write the keys in lower letters
Suggested change
Also, don't hesitate to be exhaustive in your name; |
||||||
| ) | ||||||
| total_area = fields.Integer(compute="_compute_total_area") | ||||||
| best_price = fields.Float(compute="_compute_best_price") | ||||||
| property_type_id = fields.Many2one("estate.property.type") | ||||||
| tag_ids = fields.Many2many("estate.property.tag") | ||||||
| salesperson_id = fields.Many2one("res.users", default=lambda self: self.env.user) | ||||||
| buyer_id = fields.Many2one("res.partner", copy=False) | ||||||
| offer_ids = fields.One2many("estate.property.offer", "property_id") | ||||||
| active = fields.Boolean(default=True) | ||||||
| state = fields.Selection( | ||||||
| selection=[ | ||||||
| ("new", "New"), | ||||||
| ("offer_received", "Offer Received"), | ||||||
| ("offer_accepted", "Offer Accepted"), | ||||||
| ("sold", "Sold"), | ||||||
| ("cancelled", "Cancelled"), | ||||||
| ], | ||||||
| copy=False, | ||||||
| default="new", | ||||||
| ) | ||||||
|
|
||||||
| _expected_price_constraint = models.Constraint( | ||||||
| "CHECK(expected_price >= 0)", "Expected price must be positive." | ||||||
| ) | ||||||
| _selling_price_constraint = models.Constraint( | ||||||
| "CHECK(selling_price >= 0)", "Selling price must be positive." | ||||||
| ) | ||||||
|
|
||||||
| @api.depends("living_area", "garden_area") | ||||||
| def _compute_total_area(self): | ||||||
| for property in self: | ||||||
| property.total_area = property.living_area + property.garden_area | ||||||
|
|
||||||
| @api.depends("offer_ids.price") | ||||||
| def _compute_best_price(self): | ||||||
| for property in self: | ||||||
| property.best_price = ( | ||||||
| max(property.offer_ids.mapped("price")) if property.offer_ids else 0 | ||||||
| ) | ||||||
|
|
||||||
| @api.constrains("selling_price", "expected_price") | ||||||
| def _check_selling_price_and_expected_price(self): | ||||||
| for property in self: | ||||||
| if ( | ||||||
| not float_is_zero(property.selling_price, 0) | ||||||
| and float_compare( | ||||||
| property.expected_price * 0.9, property.selling_price, 0 | ||||||
| ) | ||||||
| > 0 | ||||||
| ): | ||||||
| raise ValidationError( | ||||||
| "You cannot accept an offer lower than 90% of the expected price. Lower the expected price if you want to accept it." | ||||||
| ) | ||||||
|
|
||||||
| @api.onchange("garden") | ||||||
| def _onchange_garden(self): | ||||||
| if self.garden: | ||||||
| self.garden_area = 10 | ||||||
| self.garden_orientation = "N" | ||||||
| else: | ||||||
| self.garden_area = None | ||||||
| self.garden_orientation = None | ||||||
|
|
||||||
| @api.ondelete(at_uninstall=False) | ||||||
| def _unlink_except_valid_state(self): | ||||||
| if any(property.state not in ["new", "cancelled"] for property in 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. This works well! An alternative could be
Suggested change
|
||||||
| raise UserError("Can't delete a property if status is not New or Cancelled") | ||||||
|
|
||||||
| def action_cancel_property(self): | ||||||
| if self.state == "sold": | ||||||
| raise UserError("Sold properties cannot be cancelled") | ||||||
| self.state = "cancelled" | ||||||
|
|
||||||
| def action_mark_sold_property(self): | ||||||
| if self.state == "cancelled": | ||||||
| raise UserError("Cancelled properties cannot be sold") | ||||||
| self.state = "sold" | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,71 @@ | ||||||||||||||||
| from datetime import timedelta | ||||||||||||||||
| from dateutil.utils import today | ||||||||||||||||
|
|
||||||||||||||||
| from odoo import api, fields, models | ||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||
| from odoo.tools.float_utils import float_compare | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class EstatePropertyOffer(models.Model): | ||||||||||||||||
| _name = "estate.property.offer" | ||||||||||||||||
| _description = "Offer for a property" | ||||||||||||||||
| _order = "price desc" | ||||||||||||||||
|
|
||||||||||||||||
| price = fields.Float() | ||||||||||||||||
| status = fields.Selection( | ||||||||||||||||
| 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 | ||||||||||||||||
|
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. Why do you need to store this related field ? |
||||||||||||||||
| ) | ||||||||||||||||
| validity = fields.Integer(default=7) | ||||||||||||||||
| date_deadline = fields.Date( | ||||||||||||||||
| compute="_compute_deadline", inverse="_inverse_deadline" | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| _check_price = models.Constraint("CHECK(price >= 0)", "Price must be positive") | ||||||||||||||||
|
|
||||||||||||||||
| @api.depends("create_date", "validity") | ||||||||||||||||
| def _compute_deadline(self): | ||||||||||||||||
| for offer in self: | ||||||||||||||||
| delta = timedelta(days=offer.validity) | ||||||||||||||||
| offer.date_deadline = ( | ||||||||||||||||
| offer.create_date.date() + delta | ||||||||||||||||
| if offer.create_date | ||||||||||||||||
| else today() + delta | ||||||||||||||||
| ) | ||||||||||||||||
|
Comment on lines
+33
to
+38
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. Alternatively (one-liner)
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| def _inverse_deadline(self): | ||||||||||||||||
| for offer in self: | ||||||||||||||||
| offer.validity = ( | ||||||||||||||||
| (offer.date_deadline - offer.create_date.date()).days | ||||||||||||||||
| if offer.create_date | ||||||||||||||||
| else (offer.date_line - today()).days | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| @api.model | ||||||||||||||||
| def create(self, vals): | ||||||||||||||||
| for record in vals: | ||||||||||||||||
| property = self.env["estate.property"].browse(record["property_id"]) | ||||||||||||||||
|
|
||||||||||||||||
| if float_compare(property.best_price, record["price"], 2) == 1: | ||||||||||||||||
| raise UserError("You already have a higher offer") | ||||||||||||||||
|
|
||||||||||||||||
| if hasattr(property, "state"): | ||||||||||||||||
| property.state = "offer_received" | ||||||||||||||||
|
|
||||||||||||||||
| return super().create(vals) | ||||||||||||||||
|
|
||||||||||||||||
| def action_accept(self): | ||||||||||||||||
| if self.property_id.state == "sold": | ||||||||||||||||
| raise UserError("This property is already sold") | ||||||||||||||||
|
|
||||||||||||||||
| self.status = "accepted" | ||||||||||||||||
| self.property_id.state = "offer_accepted" | ||||||||||||||||
| self.property_id.buyer_id = self.partner_id | ||||||||||||||||
| self.property_id.selling_price = self.price | ||||||||||||||||
|
|
||||||||||||||||
| def action_refuse(self): | ||||||||||||||||
| self.status = "refused" | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Property tag" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| color = fields.Integer() | ||
|
|
||
| _name_constraint = models.UniqueIndex("(name)", "Tag names must be unique.") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from odoo import api, models, fields, _ | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Property type" | ||
| _order = "name" | ||
|
|
||
| sequence = fields.Integer(default=1) | ||
| name = fields.Char(required=True) | ||
| property_ids = fields.One2many("estate.property", "property_type_id") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_type_id", store=True) | ||
| offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
|
||
| @api.depends("property_ids", "offer_ids") | ||
| def _compute_offer_count(self): | ||
| for property_type in self: | ||
| property_type.offer_count = len(property_type.offer_ids) | ||
|
|
||
| @api.readonly | ||
| def action_view_offers(self): | ||
| return { | ||
| "name": _("Offer()"), | ||
| "type": "ir.actions.act_window", | ||
| "res_model": "estate.property.offer", | ||
| "target": "current", | ||
| "view_mode": "list,form", | ||
| "domain": [("id", "in", self.property_ids.ids)], | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstateUser(models.Model): | ||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many( | ||
| "estate.property", | ||
| "salesperson_id", | ||
| domain=[("state", "in", ["new", "offer_received"])], | ||
| ) |
| 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_offer,access_estate_property_offer,model_estate_property_offer,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_type,access_estate_property_type,model_estate_property_type,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" encoding="utf-8"?> | ||
| <odoo> | ||
| <menuitem id="estate_menu_root" name="Real Estate"> | ||
| <menuitem id="estate_menu_advertisements" name="Advertisements"> | ||
| <menuitem id="estate_property_menu_action" action="estate_property_action" /> | ||
| </menuitem> | ||
| <menuitem id="estate_menu_settings" 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,17 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <data> | ||
| <record id="estate_property_offer_view_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="Property types" editable="bottom"> | ||
| <field name="price" /> | ||
| <field name="partner_id" /> | ||
| <field name="status"/> | ||
| <field name="property_type_id"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </data> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <data> | ||
| <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_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" string="Title" /> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </data> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <data> | ||
| <record id="estate_property_type_action" model="ir.actions.act_window"> | ||
| <field name="name">Property Types</field> | ||
| <field name="res_model">estate.property.type</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
| <record id="estate_property_type_view_list" model="ir.ui.view"> | ||
| <field name="name">estate.property.type.list</field> | ||
| <field name="model">estate.property.type</field> | ||
| <field name="arch" type="xml"> | ||
| <list string="Property types"> | ||
| <field name="sequence" widget="handle"/> | ||
| <field name="name" string="Title" /> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| <record id="estate_property_type_view_form" model="ir.ui.view"> | ||
| <field name="name">estate.property.type.form</field> | ||
| <field name="model">estate.property.type</field> | ||
| <field name="arch" type="xml"> | ||
| <form> | ||
| <sheet> | ||
| <div class="oe_button_box" name="button_box"> | ||
| <button name="action_view_offers" type="object" class="oe_stat_button" icon="fa-money"> | ||
| <field name="offer_count" widget="statinfo" string=" Offer(s)" /> | ||
| </button> | ||
| </div> | ||
| <h1> | ||
| <field name="name" /> | ||
| </h1> | ||
| <notebook> | ||
| <page name="Properties"> | ||
| <field name="property_ids"> | ||
| <list> | ||
| <field name="name" string="Title"/> | ||
| <field name="expected_price" /> | ||
| <field name="state" string="Status"/> | ||
| </list> | ||
| </field> | ||
| </page> | ||
| </notebook> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
| </data> | ||
| </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.
Everything you create for Odoo belongs to "Odoo S.A." hehe