diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d7cfa57d4c5..25eb5f29a0d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,7 +4,14 @@ "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/property_type_views.xml", + "views/property_tag_views.xml", + "views/offers_view.xml", + + "actions/estate_property_menus.xml", ], "installable": True, 'license': 'LGPL-3', diff --git a/estate/actions/estate_property_menus.xml b/estate/actions/estate_property_menus.xml new file mode 100644 index 00000000000..2d00a766a65 --- /dev/null +++ b/estate/actions/estate_property_menus.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file 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..fbca1911712 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +from . import estate_property +from . import property_type +from . import property_tag +from . import offer \ 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..6a157e108fd --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,98 @@ +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError +from odoo.tools.float_utils import float_is_zero, float_compare + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + + name = fields.Char(required=True) + property_tag_ids = fields.Many2many("estate.property.tag", string="Tags") + + description = fields.Text() + address = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(copy=False, default=fields.Date.add(fields.Date.today(), months=3)) + expected_price = fields.Float() + selling_price = fields.Float(readonly=False, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Float(string="Living Area (sqm)") + garden = fields.Boolean() + garden_area = fields.Float(string="Garden Area (sqm)") + garden_orientation = fields.Selection([("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")]) + + total_area = fields.Float(string="Total Area (sqm)", readonly=True, compute="_compute_total_area", store=True) + best_price = fields.Float(readonly=True,compute="_compute_best_price") + + 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", + copy=False, + required=True, + ) + + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + + salesman_id = fields.Many2one("res.users", string="Salesman") + buyer_id = fields.Many2one("res.partner", string="Partner") + + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + + _sql_constraints = [ + ('check_expected_price', 'CHECK(expected_price >= 0)', 'The expected price must me be at least 100.') + ] + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = self.garden_orientation = False + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + self.total_area = self.living_area + self.garden_area + + @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 + + def action_sell_property(self): + for property in self: + if property.state == "cancelled": + raise UserError(_("Cancelled properties cannot be sold!")) + property.state = "sold" + + def action_cancel_property(self): + self.state = "canceled" + + @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(_("The selling price should not be lower than 90% of the expected price!")) + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_canceled(self): + for property in self: + if property.state not in ("new", "canceled"): + raise UserError(_("Only new or canceled property can be deleted!")) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + property = self.env["estate.property"] \ No newline at end of file diff --git a/estate/models/offer.py b/estate/models/offer.py new file mode 100644 index 00000000000..520a2b256f8 --- /dev/null +++ b/estate/models/offer.py @@ -0,0 +1,41 @@ +from odoo import api, fields, models + +class Offer(models.Model): + _name = "estate.property.offer" + _description = "Offers" + + price = fields.Float(required=True) + buyer_id = fields.Many2one("res.partner", string="Partner") + validity = fields.Integer(default=7, required=True, string="Validity (days)") + deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline", string="Deadline Date") + status = fields.Selection( + selection=[ + ("new", "New"), + ("accepted", "Accepted"), + ("refused", "Refused"), + ], + default="new", + copy=False, + required=True, + ) + + property_id = fields.Many2one("estate.property", required=True) + + @api.depends("validity", "create_date") + def _compute_deadline(self): + for estate in self: + create_date = estate.create_date or fields.Date.today() + estate.deadline = fields.Date.add(create_date, days=estate.validity) + + def _inverse_deadline(self): + for estate in self: + estate.validity = (estate.deadline - fields.Date.to_date(estate.create_date)).days + + def action_accept_offer(self): + self.status = "accepted" + for offer in self: + offer.property_id.selling_price = offer.price + + def action_refuse_offer(self): + self.status = "refused" + \ No newline at end of file diff --git a/estate/models/property_tag.py b/estate/models/property_tag.py new file mode 100644 index 00000000000..948fa5bdf4f --- /dev/null +++ b/estate/models/property_tag.py @@ -0,0 +1,9 @@ +from odoo import fields, models + +class PropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Property Tag" + + name = fields.Char(required=True) + description = fields.Text() + active = fields.Boolean(default=True) \ No newline at end of file diff --git a/estate/models/property_type.py b/estate/models/property_type.py new file mode 100644 index 00000000000..6124adb0cea --- /dev/null +++ b/estate/models/property_type.py @@ -0,0 +1,9 @@ +from odoo import fields, models + +class PropertyType(models.Model): + _name = "estate.property.type" + _description = "Property Type" + + name = fields.Char(required=True) + description = fields.Text() + active = fields.Boolean(default=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..b8bac0da463 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -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_property_type,access_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_property_tag,access_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_offers,access_offers,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..b47cdd751e9 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,123 @@ + + + + Real Estate + estate.property + list,form + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+
+
+ + +

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/estate/views/offers_view.xml b/estate/views/offers_view.xml new file mode 100644 index 00000000000..4ee1ee73491 --- /dev/null +++ b/estate/views/offers_view.xml @@ -0,0 +1,57 @@ + + + + Offers + estate.property.offer + list,form + + + + estate.property.offer.list + estate.property.offer + + + + + + + +