-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Up to chapter 3 #1199
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?
Up to chapter 3 #1199
Changes from 14 commits
333b72f
95f4033
ce0c008
9f0950b
81d3bb9
8715319
e3a1809
f699048
2ccccfb
48b489d
1658cf8
53b098f
0c6ac1d
26a03f6
e8dcebe
fc08cdc
08f9f31
b4b2836
41fcc51
8bb5982
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,3 @@ | ||
| { | ||
| "python.languageServer": "None" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import models |
|
leclerc-leo marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| 'name': 'Estate', | ||
| 'version': '1.0', | ||
| 'author': 'Odoo S.A.', | ||
| 'license': 'AGPL-3', | ||
| 'application': True, | ||
| 'installable': True, | ||
|
|
||
| 'depends': ['base'], | ||
|
|
||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| 'views/estate_property_offers_views.xml', | ||
| 'views/estate_property_types_views.xml', | ||
| 'views/estate_property_views.xml', | ||
| 'views/estate_menus.xml', | ||
| 'views/res_users_views.xml' | ||
| ] | ||
| } |
|
leclerc-leo marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from . import estate_property | ||
| from . import estate_property_type | ||
| from . import estate_property_tag | ||
| from . import estate_property_offer | ||
| from . import res_users |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| from odoo import api, fields, models, exceptions | ||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
|
|
||
| class EstateProperty(models.Model): | ||
| _name = "estate.property" | ||
|
abotaha23 marked this conversation as resolved.
Outdated
|
||
| _description = "Estate property" | ||
| _order = "id desc" | ||
|
|
||
| name = fields.Char(required=True, string="Title") | ||
| description = fields.Text(string="Description") | ||
| postcode = fields.Char(string="Postcode") | ||
| date_availability = fields.Date( | ||
| default=lambda self: fields.Date.today() + relativedelta(months=3), | ||
| copy=False, | ||
| string="Available From", | ||
| ) | ||
| expected_price = fields.Float(required=True, string="Expected Price") | ||
| selling_price = fields.Float( | ||
| readonly=True, copy=False, string="Selling Price") | ||
| bedrooms = fields.Integer(default=2, string="Bedrooms") | ||
| living_area = fields.Integer(string="Living Area (sqm)") | ||
| facades = fields.Integer(string="Facades") | ||
| garage = fields.Boolean(string="Garage") | ||
| garden = fields.Boolean(string="Garden") | ||
| garden_area = fields.Integer(string="Garden Area (sqm)") | ||
| garden_orientation = fields.Selection( | ||
| selection=[ | ||
| ('north', "North"), | ||
| ('south', "South"), | ||
| ('east', "East"), | ||
| ('west', "West") | ||
| ], | ||
| string="Garden Orientation", | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| selection=[ | ||
| ('new', "New"), | ||
| ('offer received', "Offer Received"), | ||
| ('offer accepted', "Offer Accepted"), | ||
| ('sold', "Sold"), | ||
| ('canceled', "Canceled") | ||
| ], | ||
| default='new', | ||
| required=True, | ||
| copy=False, | ||
| string="Status", | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| 'estate.property.type', string='Property Type') | ||
| seller_id = fields.Many2one( | ||
| 'res.users', string='Salesman', 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.Float( | ||
| compute="_compute_total_area", string="Total Area (sqm)") | ||
| best_offer = fields.Float( | ||
| compute="_compute_best_offer", string="Best Offer") | ||
|
|
||
| _check_expected_price = models.Constraint( | ||
| 'CHECK(expected_price > 0)', | ||
| 'The expected price of a property must be strictly positive!' | ||
| ) | ||
| _check_selling_price = models.Constraint( | ||
| 'CHECK(selling_price >= 0)', | ||
| 'The selling price of a property must be strictly positive!' | ||
| ) | ||
|
|
||
| @api.depends('living_area', 'garden_area') | ||
| def _compute_total_area(self): | ||
| for record in self: | ||
| record.total_area = record.living_area + record.garden_area | ||
|
|
||
| @api.depends('offer_ids') | ||
| def _compute_best_offer(self): | ||
| for record in self: | ||
| if record.offer_ids: | ||
| record.best_offer = max(record.offer_ids.mapped('price')) | ||
| else: | ||
| record.best_offer = 0 | ||
|
|
||
| @api.constrains('selling_price', 'expected_price') | ||
| def _check_price(self): | ||
| for record in self: | ||
| if float_is_zero(record.selling_price, 3): | ||
| continue | ||
| if float_compare(record.selling_price, 0.9 * record.expected_price, 3) == -1: | ||
| raise exceptions.ValidationError( | ||
| "The selling price cannot be less than 90% of the expected price") | ||
|
abotaha23 marked this conversation as resolved.
Outdated
|
||
|
|
||
| @api.onchange('offer_ids') | ||
| def _onchange_receive_offer(self): | ||
| for record in self.filtered(lambda record: record.state == 'new'): | ||
| if record.offer_ids: | ||
| record.state = 'offer received' | ||
|
|
||
| @api.onchange('garden') | ||
| def _onchange_garden(self): | ||
| if self.garden: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = 'north' | ||
| else: | ||
| self.garden_area = False | ||
| self.garden_orientation = False | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _unlike_property(self): | ||
| for record in self: | ||
| if record.state != 'new' and record.state != 'canceled': | ||
| raise exceptions.UserError( | ||
| "You cannot delete this property: only new and canceled properities can be deleted.") | ||
|
|
||
| def action_mark_as_sold(self): | ||
| for record in self: | ||
| if record.state != 'canceled': | ||
| record.state = 'sold' | ||
| else: | ||
| raise exceptions.UserError( | ||
| "Canceled properties cannot be sold!") | ||
|
|
||
| def action_mark_as_canceled(self): | ||
| for record in self: | ||
| if record.state != 'sold': | ||
| record.state = 'canceled' | ||
| else: | ||
| raise exceptions.UserError( | ||
| "Sold properties cannot be canceled!") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| from odoo import api, fields, models, exceptions | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Estate property offer" | ||
| _order = "price desc" | ||
|
|
||
| price = fields.Float(string="Price") | ||
| status = fields.Selection( | ||
| copy=False, | ||
| selection=[ | ||
| ('accepted', 'Accepted'), | ||
| ('refused', 'Refused'), | ||
| ], | ||
| string="Status", | ||
| ) | ||
| partner_id = fields.Many2one( | ||
| 'res.partner', string="Partner", required=True | ||
| ) | ||
| property_id = fields.Many2one( | ||
| 'estate.property', string="Property", required=True | ||
| ) | ||
| validity = fields.Integer(default=7, string="Validity (Days)") | ||
| date_deadline = fields.Date( | ||
| compute="_compute_date_deadline", | ||
| inverse="_inverse_date_deadline", | ||
| string="Deadline", | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| related="property_id.property_type_id", store=True | ||
| ) | ||
|
|
||
| _check_price = models.Constraint( | ||
| 'CHECK(price > 0)', | ||
| 'The offer price of must be strictly positive!' | ||
| ) | ||
|
|
||
| @api.depends('validity') | ||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| if record.create_date: | ||
| record.date_deadline = record.create_date + \ | ||
| relativedelta(days=record.validity) | ||
| else: | ||
| record.date_deadline = fields.Date.today() + relativedelta(days=record.validity) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for record in self: | ||
| record.validity = (record.date_deadline - | ||
| fields.Date.to_date(record.create_date)).days | ||
|
|
||
| @api.model | ||
| def create(self, vals_list): | ||
| for vals in vals_list: | ||
| related_property = self.env['estate.property'].browse( | ||
| vals['property_id']) | ||
| for offer in related_property.offer_ids: | ||
| if offer.price > vals['price']: | ||
| raise exceptions.UserError( | ||
| "This offer price is lower than the current ones") | ||
|
|
||
| return super().create(vals_list) | ||
|
|
||
| def action_accept_offer(self): | ||
| for record in self: | ||
| if record.status == 'accepted': | ||
| continue | ||
| for offer in record.property_id.offer_ids: | ||
| if offer.status == 'accepted': | ||
| offer.status = 'refused' | ||
| record.status = 'accepted' | ||
| record.property_id.buyer_id = record.partner_id | ||
| record.property_id.selling_price = record.price | ||
| record.property_id.state = 'offer accepted' | ||
|
|
||
| def action_refuse_offer(self): | ||
| for record in self: | ||
| record.status = 'refused' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Estate property tag" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| color = fields.Integer('Color') | ||
| _check_unique_tag_name = models.Constraint( | ||
| 'UNIQUE(name)', | ||
| 'This property tag already exists.' | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| from odoo import fields, models, api | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Estate property Tag" | ||
| _order = "sequence, name" | ||
|
|
||
| name = fields.Char(required=True) | ||
| sequence = fields.Integer('Sequence', default=1, | ||
| help="Used to order stages, Lower is better.") | ||
| property_ids = fields.One2many('estate.property', 'property_type_id') | ||
| offer_ids = fields.One2many('estate.property.offer', 'property_type_id') | ||
| offer_count = fields.Integer(compute="_compute_offer_count") | ||
|
|
||
| _check_unique_type_name = models.Constraint( | ||
| 'UNIQUE(name)', | ||
| 'This property type already exists.' | ||
| ) | ||
|
|
||
| @api.depends('offer_ids') | ||
| 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 fields, models | ||
|
|
||
|
|
||
| class ResUsers(models.Model): | ||
| _inherit = 'res.users' | ||
| _name = 'res.users' | ||
|
|
||
| property_ids = fields.One2many('estate.property', 'seller_id', | ||
| domain=['|', ('state', '=', 'new'), ('state', '=', '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_test_model,access_test_model,model_estate_property,base.group_user,1,1,1,1 | ||
|
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 should probably use use a better name and id for your access rule to make it easier when debugging.
This comment was marked as resolved.
Sorry, something went wrong. |
||
| access_test_model2,access_test_model2,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_test_model3,access_test_model3,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_test_model4,access_test_model4,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> | ||
| <menuitem id="menu_real_estate" name="Real Estate" action="estate_property_action"> | ||
| <menuitem id="menu_real_estate_settings" name="Settings"> | ||
| <menuitem id="menu_real_estate_settings_property_types" name="Property Types" action="estate_property_type_action"/> | ||
| </menuitem> | ||
| </menuitem> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| <odoo> | ||
| <record id="estate_property_offers_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 decoration-success="status == 'accepted'" decoration-danger="status == 'refused'" editable="bottom" string="Offers"> | ||
| <field name="price"/> | ||
| <field name="partner_id"/> | ||
| <field name="validity"/> | ||
| <field name="date_deadline"/> | ||
| <field name="property_id" nolabel="1"/> <!-- just to use it in the offer creation constraints--> | ||
| <button name="action_accept_offer" string=" " type="object" icon="fa-check" invisible="status"/> | ||
| <button name="action_refuse_offer" string=" " type="object" icon="fa-times" invisible="status"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_offers_view_form" model="ir.ui.view"> | ||
| <field name="name">estate.property.offerform</field> | ||
|
abotaha23 marked this conversation as resolved.
Outdated
|
||
| <field name="model">estate.property.offer</field> | ||
| <field name="arch" type="xml"> | ||
| <form string="Offers"> | ||
| <sheet> | ||
| <group> | ||
| <field name="price"/> | ||
| </group> | ||
| <group> | ||
| <field name="partner_id"/> | ||
| </group> | ||
| <group> | ||
| <field name="validity"/> | ||
| </group> | ||
| <group> | ||
| <field name="date_deadline"/> | ||
| </group> | ||
| <group> | ||
| <field name="property_id" nolabel="1"/> <!-- it's invisible because I just need to load it to accept an offer before it's saved/--> | ||
| <button name="action_accept_offer" string='accept' type="object" icon="fa-check" invisible="status"/> | ||
| <button name="action_refuse_offer" string='reject' type="object" icon="fa-times" invisible="status"/> | ||
| </group> | ||
| <group> | ||
| <field name="status" readonly="1"/> | ||
| </group> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_offer_action" model="ir.actions.act_window"> | ||
| <field name="name">Estate property 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> | ||
|
|
||
| </odoo> | ||
Uh oh!
There was an error while loading. Please reload this page.