-
Notifications
You must be signed in to change notification settings - Fork 2.6k
HAZEI Onboarding #1000
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?
HAZEI Onboarding #1000
Changes from all commits
c2f4e01
ae40a80
2ebfca3
c451738
e7f8afa
500f0fc
c85b983
85903f1
26e9cb3
dd4f93c
dca37b2
5bee9d6
fbe8107
b78a704
cdbf0e2
ae6c1ab
4d8c35d
791378e
e66f5d0
7e58d88
71ed848
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,20 @@ | ||
| { | ||
| 'name': 'Odoo Tutorial Real Estate', | ||
| 'category': 'Real Estate', | ||
| 'version': '19.0.1.0', | ||
| 'author': 'Hazei', | ||
| 'license': 'LGPL-3', | ||
| 'summary': 'Real Estate Management Tutorial', | ||
| 'depends': [ | ||
| 'base', | ||
| ], | ||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| 'views/estate_property_views.xml', | ||
| 'views/estate_property_type.xml', | ||
| 'views/estate_property_tags_view.xml', | ||
| 'views/menu_views.xml', | ||
| ], | ||
| 'installable': True, | ||
| 'application': True, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| from . import estate_property | ||
| from . import estate_property_offer | ||
| from . import estate_property_tags | ||
| from . import estate_property_type |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||||||||||||||||||||
| from datetime import date | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| from dateutil.relativedelta import relativedelta | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| from odoo import api, fields, models | ||||||||||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||||||||||
| from odoo.tools import float_compare | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class EstateProperty(models.Model): | ||||||||||||||||||||||||
| _name = "estate.property" | ||||||||||||||||||||||||
| _description = "Real Estate Property" | ||||||||||||||||||||||||
| _order = "id desc" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| name = fields.Char(string="Title", required=True) | ||||||||||||||||||||||||
| property_type = fields.Selection( | ||||||||||||||||||||||||
| string='Property Type', | ||||||||||||||||||||||||
| selection=[('house', 'House'), ('apartment', 'Apartment')]) | ||||||||||||||||||||||||
| description = fields.Text() | ||||||||||||||||||||||||
| postcode = fields.Char() | ||||||||||||||||||||||||
| date_availability = fields.Date(copy=False, readonly=True, default=lambda self: date.today() + relativedelta(months=3)) | ||||||||||||||||||||||||
| expected_price = fields.Float("Expected Price", required=True) | ||||||||||||||||||||||||
| selling_price = fields.Float("Selling Price", readonly=True, copy=False) | ||||||||||||||||||||||||
| bedrooms = fields.Integer(default=2) | ||||||||||||||||||||||||
| living_area = fields.Integer(string='Living Area (m2)') | ||||||||||||||||||||||||
| facades = fields.Integer() | ||||||||||||||||||||||||
| garage = fields.Boolean() | ||||||||||||||||||||||||
| garden = fields.Boolean() | ||||||||||||||||||||||||
| garden_area = fields.Integer(string='Garden Area (m2)') | ||||||||||||||||||||||||
| garden_orientation = fields.Selection( | ||||||||||||||||||||||||
| string='Garden Orientation', | ||||||||||||||||||||||||
| selection=[('north', 'North'), ('south', 'South'), | ||||||||||||||||||||||||
| ('east', 'East'), ('west', 'West')]) | ||||||||||||||||||||||||
| active = fields.Boolean(default=True) | ||||||||||||||||||||||||
| state = fields.Selection( | ||||||||||||||||||||||||
| copy=False, | ||||||||||||||||||||||||
| readonly=True, | ||||||||||||||||||||||||
| default='new', | ||||||||||||||||||||||||
| string='States', | ||||||||||||||||||||||||
| selection=[ | ||||||||||||||||||||||||
| ('new', 'New'), | ||||||||||||||||||||||||
| ('offer_received', 'Offer Received'), | ||||||||||||||||||||||||
| ('offer_accepted', 'Offer Accepted'), | ||||||||||||||||||||||||
| ('sold', 'Sold'), | ||||||||||||||||||||||||
| ('cancelled', 'Cancelled')] | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
Comment on lines
+40
to
+46
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 is a constant, so it should go outside the class and be given a more consistent name like PROPERTY_STATES |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def action_set_sold(self): | ||||||||||||||||||||||||
| if self.state != "cancelled": | ||||||||||||||||||||||||
| self.state = "sold" | ||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||
| raise UserError("A cancelled property can not be sold") | ||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||
|
Comment on lines
+48
to
+53
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
More concise and extendable 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. Also what would happen here if self contains more than 1 property ? |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def action_set_cancelled(self): | ||||||||||||||||||||||||
| if (self.state != "sold"): | ||||||||||||||||||||||||
| self.state = "cancelled" | ||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||
| raise UserError("A sold property can not be cancelled") | ||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||
|
Comment on lines
+48
to
+60
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 per attribute ordering (coding guidelines), this should go lower in the file
Comment on lines
+55
to
+60
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
No parentheses around if, and more concise 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. Also what would happen here if self contains more than 1 property ? |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| property_type_id = fields.Many2one('estate.property.type', string='Type') | ||||||||||||||||||||||||
| buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) | ||||||||||||||||||||||||
| salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) | ||||||||||||||||||||||||
| tags_ids = fields.Many2many('estate.property.tags', string='Tags') | ||||||||||||||||||||||||
| offer_ids = fields.One2many("estate.property.offer", "property_id", string="offer") | ||||||||||||||||||||||||
| total_area = fields.Integer(string='Total Area(m2)', compute='_compute_total_area', store=True) | ||||||||||||||||||||||||
| best_price = fields.Float(string='Best Offer', compute='_compute_best_price', store=True) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @api.depends('living_area', 'garden_area') | ||||||||||||||||||||||||
| def _compute_total_area(self): | ||||||||||||||||||||||||
| for property in self: | ||||||||||||||||||||||||
| property.total_area = property.living_area + (property.garden_area or 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.
Suggested change
garden_area is an integer, it cannot be False |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @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')) if record.offer_ids else 0 | ||||||||||||||||||||||||
|
Comment on lines
+76
to
+79
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
First check not needed |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @api.onchange('garden') | ||||||||||||||||||||||||
| def _onchange_garden(self): | ||||||||||||||||||||||||
| if self.garden: | ||||||||||||||||||||||||
| self.garden_area = 10 | ||||||||||||||||||||||||
| self.garden_orientation = 'north' | ||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||
| self.garden_area = 0 | ||||||||||||||||||||||||
| self.garden_orientation = False | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| _check_expected_price = models.Constraint( | ||||||||||||||||||||||||
| 'CHECK(expected_price >= 0)', 'The expected price must be strictly 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_expected_price(self): | ||||||||||||||||||||||||
| for record in self: | ||||||||||||||||||||||||
| if record.selling_price == 0: | ||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||
| if float_compare(record.selling_price, 0.9 * record.expected_price, precision_digits=2) == -1: | ||||||||||||||||||||||||
| raise UserError("The selling price must be at least 90% of the expected price.") | ||||||||||||||||||||||||
|
Comment on lines
+90
to
+102
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 per attribute ordering this needs to be higher |
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||||||||||||||||
| from odoo import models, fields, api | ||||||||||||||||||||||||||
| from dateutil.relativedelta import relativedelta | ||||||||||||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| class EstatePropertyOffer(models.Model): | ||||||||||||||||||||||||||
| _name = 'estate.property.offer' | ||||||||||||||||||||||||||
| _description = 'Estate Property Offer' | ||||||||||||||||||||||||||
| _order = 'price desc' | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| price = fields.Float(string='price', required=True) | ||||||||||||||||||||||||||
| state = fields.Selection(selection=[('accepted', 'Accepted'), | ||||||||||||||||||||||||||
| ('refused', 'Refused')], | ||||||||||||||||||||||||||
| string="Status", | ||||||||||||||||||||||||||
| copy=False, | ||||||||||||||||||||||||||
| default='accepted', | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| validity = fields.Integer(string='Validity(days)', default=7) | ||||||||||||||||||||||||||
| partner_id = fields.Many2one('res.partner', string='Partner', required=True) | ||||||||||||||||||||||||||
| property_id = fields.Many2one('estate.property', required=True) | ||||||||||||||||||||||||||
| date_deadline = fields.Date(string='Deadline', compute='_compute_date_deadline', store=True) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @api.depends('validity', 'create_date') | ||||||||||||||||||||||||||
| def _compute_date_deadline(self): | ||||||||||||||||||||||||||
| for offer in self: | ||||||||||||||||||||||||||
| if not offer.create_date: | ||||||||||||||||||||||||||
| offer.date_deadline = fields.Date.today() + relativedelta(days=offer.validity) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def action_accept(self): | ||||||||||||||||||||||||||
| for offer in self: | ||||||||||||||||||||||||||
| if offer.state != 'refused': | ||||||||||||||||||||||||||
| offer.state = 'accepted' | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| raise UserError("A refused offer cannot be accepted.") | ||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def action_refuse(self): | ||||||||||||||||||||||||||
| for offer in self: | ||||||||||||||||||||||||||
| if offer.state != 'accepted': | ||||||||||||||||||||||||||
| offer.state = 'refused' | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| raise UserError("An accepted offer cannot be refused.") | ||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||
|
Comment on lines
+37
to
+43
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
More concise |
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class PropertyTypeTags(models.Model): | ||
| _name = 'estate.property.tags' | ||
| _description = 'Estate Property Tags' | ||
| _order = 'name' | ||
|
|
||
| name = fields.Char(required=True) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class PropertyType(models.Model): | ||
| _name = 'estate.property.type' | ||
| _description = 'Estate Property Type' | ||
| _order = 'sequence, name' | ||
|
|
||
| name = fields.Char(required=True) | ||
| property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties') | ||
| sequence = fields.Integer(string="Sequence", default=10) |
| 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_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_property_tags,access_estate_property_tags,model_estate_property_tags,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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <odoo> | ||
| <record id="estate_property_tags_action" model="ir.actions.act_window"> | ||
| <field name="name">Property Tags</field> | ||
| <field name="res_model">estate.property.tags</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_property_type_action" model="ir.actions.act_window"> | ||
| <field name="name">Property Type</field> | ||
| <field name="res_model">estate.property.type</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
| <record id="estate_property_type_list" model="ir.ui.view"> | ||
| <field name="name">estate.property.list</field> | ||
| <field name="model">estate.property.type</field> | ||
| <field name="arch" type="xml"> | ||
| <list string="Property_type"> | ||
| <field name ="sequence" widget="handle"/> | ||
| <field name="name"/> | ||
| <field name="property_ids" widget="one2many_list" mode="list"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| <record id="estate_property_type_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 string="Property Type"> | ||
| <sheet> | ||
| <group> | ||
| <field name="name"/> | ||
| <field name="property_ids" widget="one2many_list" mode="list,form"> | ||
| <list editable="bottom"> | ||
| <field name="name"/> | ||
| <field name="expected_price"/> | ||
| <field name="state"/> | ||
| </list> | ||
| </field> | ||
| </group> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_property_action" model="ir.actions.act_window"> | ||
| <field name="name">Property</field> | ||
| <field name="res_model">estate.property</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_view_list" model="ir.ui.view"> | ||
| <field name="name">estate.property.list</field> | ||
| <field name="model">estate.property</field> | ||
| <field name="arch" type="xml"> | ||
| <list string="Property"> | ||
| <field name="name"/> | ||
| <field name="bedrooms"/> | ||
| <field name="expected_price"/> | ||
| <field name="selling_price"/> | ||
| <field name="date_availability"/> | ||
| <field name="garden_area"/> | ||
| <field name="living_area"/> | ||
| <field name="total_area"/> | ||
| <field name="active"/> | ||
| <field name="property_type_id"/> | ||
| <field name="salesman_id"/> | ||
| <field name="best_price"/> | ||
| <field name="state"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_view_form" model="ir.ui.view"> | ||
| <field name="name">estate.property.form</field> | ||
| <field name="model">estate.property</field> | ||
| <field name="arch" type="xml"> | ||
| <form string="Property Form"> | ||
| <sheet> | ||
| <header> | ||
| <button name="action_set_sold" type="object" string="Sold"/> | ||
| <button name="action_set_cancelled" type="object" string="Cancel"/> | ||
| <field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/> | ||
| </header> | ||
| <div class="oe_title"> | ||
| <h1> | ||
| <field name="name"/> | ||
| </h1> | ||
| </div> | ||
| <seperator/> | ||
| <group> | ||
| <group> | ||
| <field name="property_type"/> | ||
| <field name="postcode"/> | ||
| <field name="date_availability"/> | ||
| </group> | ||
| <group> | ||
| <field name="expected_price"/> | ||
| <field name="best_price"/> | ||
| <field name="selling_price"/> | ||
| </group> | ||
| </group> | ||
| <notebook> | ||
| <page string="Description"> | ||
| <group> | ||
| <field name="description"/> | ||
| <field name="bedrooms"/> | ||
| <field name="living_area"/> | ||
| <field name="facades"/> | ||
| <field name="garage"/> | ||
| <field name="garden"/> | ||
| <field name="garden_area"/> | ||
| <field name="garden_orientation"/> | ||
| <field name="active"/> | ||
| <field name="tags_ids" widget="many2many_tags"/> | ||
| <field name="total_area" readonly="1"/> | ||
| <field name="best_price" readonly="1"/> | ||
| </group> | ||
| </page> | ||
| <page string="Offers"> | ||
| <field name ="offer_ids" widget="one2many_list" mode="list,form"> | ||
| <list editable="bottom"> | ||
| <field name="partner_id"/> | ||
| <field name="price"/> | ||
| <field name="state"/> | ||
| <field name="validity"/> | ||
| <field name="date_deadline"/> | ||
| <button name="action_accept" type="object" icon="fa-check" title="Accept"/> | ||
| <button name="action_refuse" type="object" icon="fa-times" title="Refuse"/> | ||
| </list> | ||
| <form> | ||
| <field name="partner_id"/> | ||
| <field name="price"/> | ||
| <field name="state"/> | ||
| <field name="validity"/> | ||
| <field name="date_deadline"/> | ||
| </form> | ||
| </field> | ||
| </page> | ||
| <page string="Other Info"> | ||
| <group> | ||
| <field name="salesman_id"/> | ||
| <field name="buyer_id"/> | ||
| </group> | ||
| </page> | ||
| </notebook> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_view_search" model="ir.ui.view"> | ||
| <field name="name">estate.property.views.search</field> | ||
| <field name="model">estate.property</field> | ||
| <field name="arch" type="xml"> | ||
| <search string="Estate Property"> | ||
| <field name="name"/> | ||
| <field name="living_area"/> | ||
| <separator/> | ||
| <filter string="Archived" name="active" domain="[('active', '=', False)]"/> | ||
| <filter string="state" name="state" domain="[('state', 'in', ('new', 'offer_received'))]"/> | ||
| <filter string="active" name="group_active" context="{'group_by':'active'}"/> | ||
| <filter string="postcode" name="group_active" context="{'group_by':'postcode'}"/> | ||
| <filter string="bedrooms" name="group_bedrooms" context="{'group_by':'bedrooms'}"/> | ||
| </search> | ||
| </field> | ||
| </record> | ||
| </odoo> |
| 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_first_level_menu" name="Advertisements"> | ||
| <menuitem id="estate_model_menu_action" action="estate_property_action"/> | ||
| </menuitem> | ||
| <menuitem id="estate_sec_level_menu" name="Settings"> | ||
| <menuitem id="estate_type_model_menu_action" action="estate_property_type_action"/> | ||
| <menuitem id="estate_tags_model_menu_action" action="estate_property_tags_action"/> | ||
| </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.
default value for installable is already True