diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d7cfa57d4c5..e669945f227 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,15 @@ { "name": "Estate", # The name that will appear in the App list - "version": "16.0.0", # Version + "version": "18.0.1.0.0", # Version "application": True, # This line says the module is an App, and not a module "depends": ["base"], # dependencies "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_menu.xml', + 'views/res_users_views.xml' ], "installable": True, 'license': 'LGPL-3', diff --git a/estate/models.py b/estate/models.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..a9459ed5906 --- /dev/null +++ b/estate/models/__init__.py @@ -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 \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..e7b81252a89 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,92 @@ +from email.policy import default + +from odoo import fields, models, api, _ +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_is_zero, float_compare + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + _order ="id desc" + + name = fields.Char(required=True) + description = fields.Text() + date_availability = fields.Date(copy=False, default=lambda self: fields.Date.add(fields.date.today(), months=3)) + expected_price = fields.Float() + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + garden = fields.Boolean() + garden_orientation = fields.Selection([("north", "North"),("south", "South"), ("east", "East"),("west", "West")]) + active= fields.Boolean(default=True) + state = fields.Selection(selection = [("new","New"),("offer_received","Offer Received"), + ("offer_accepted","Offer Accepted"),("sold","Sold"),("cancelled","Cancelled")], + default="new", + copy=False, + required=True) + postcode = fields.Char() + facades = fields.Integer() + garage = fields.Boolean() + garden_area = fields.Integer() + living_area = fields.Integer() + property_type_id = fields.Many2one("estate.property.type", string="Property type") + salesperson_id = fields.Many2one("res.users", default=lambda self: self.env.user) + buyer_id = fields.Many2one("res.partner", copy=False) + tag_ids = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many("estate.property.offer", "property_id") + best_price = fields.Float(compute="_compute_best_price") + total_area = fields.Integer(compute="_compute_total_area") + + _sql_constraints = [ + ("check_expected_price", "CHECK(expected_price > 0)", + 'The expected price must be strictly positive'), + ("check_selling_price", "CHECK(selling_price >= 0)", + 'The selling price must be positive'), + ] + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for property in self: + if property.offer_ids: + property.best_price = max (property.offer_ids.mapped("price")) + else: + property.best_price = 0 + + @api.depends('garden_area','living_area') + def _compute_total_area(self): + for property in self: + property.total_area = property.garden_area + property.living_area + + @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 + + + def action_cancel_property(self): + for property in self: + property.state = "cancelled" + + def action_sell_property(self): + for property in self: + if property.state == "cancelled": + raise UserError(_("Cancelled properties cannot be sold")) + property.state = "sold" + + + @api.constrains('selling_price','expected_price') + def _check_selling_price(self): + for property in self: + if (not float_is_zero(property.selling_price, precision_rounding = 0.01) and + float_compare(property.selling_price, 0.9 * property.expected_price, precision_rounding=0.01) < 0 + ): + raise ValidationError(_('')) + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_canceled(self): + for property in self: + if property.state not in ("new", "cancelled"): + raise UserError(_("Only new or canceiled property can be deleted")) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..3b748312e05 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,44 @@ +from odoo import fields, models, api + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + _order = "price desc" + + price = fields.Float() + status = fields.Selection([("accepted", "Accepted"), ("refused","Refused")], copy=False) + property_id = fields.Many2one("estate.property", required=True) + partner_id = fields.Many2one("res.partner", required=True) + validity = fields.Integer(default = 7) + date_deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline") + property_type_id = fields.Many2one(related="property_id.property_type_id") + + _sql_constraints = [ + ("check_offer_price", "CHECK(price > 0)", + 'The offer price must be strictly positive') + ] + + + @api.depends('validity','create_date') + def _compute_deadline(self): + for offer in self: + create_date = offer.create_date or fields.Date.today() + offer.date_deadline = fields.Date.add(create_date, days=offer.validity) + + + def _inverse_deadline(self): + for offer in self: + offer.validity = (offer.date_deadline - fields.Date.to_date(offer.create_date)).days + + + + def action_accept_offer(self): + self.status = "accepted" + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id + + + + def action_refuse_offer(self): + self.status = "refused" diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..0f260c3418b --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tag" + _order = "name desc" + + + name = fields.Char(required=True) + color = fields.Integer("Color Index") + + +_sql_constraints = [ + ("unique_name", "UNIQUE(name)", + 'Tag name should be unique.') + ] \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..797c9a831a6 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,23 @@ +from odoo import fields, models, api + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + _order = "sequence, name desc" + + + name = fields.Char(required=True) + property_ids = fields.One2many("estate.property","property_type_id") + sequence = fields.Integer('Sequence', default = 1, help="Use to order types") + offer_ids = fields.One2many("estate.property.offer", "property_type_id") + offer_count = fields.Integer(compute="_compute_offer_count") + + _sql_constraints = [ + ('unique_name', 'UNIQUE(name)', 'The Type\'s name must be unique.') + ] + + @api.depends("offer_ids") + def _compute_offer_count(self): + for record in self: + record.offer_count = len(record.offer_ids) \ No newline at end of file diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..737d01b5a55 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,6 @@ +from odoo import fields, models + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many("estate.property","salesperson_id", domain=[("state", "not in", ['sold', 'canceled'])]) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..7d830d6b132 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 +estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 +estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 + diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml new file mode 100644 index 00000000000..a650f00b4a3 --- /dev/null +++ b/estate/views/estate_menu.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..00ee6e7541a --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,24 @@ + + + + Offer List + estate.property.offer + + + + + + +