diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..508f7064eff --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,23 @@ +{ + 'name': 'RealEstate', + 'version': '1.0', + 'category': 'Real Estate/Brokerage', + 'summary': 'A module to manage real estate advertisements and property offers', + 'description': """A simple module to manage real estate ads.List your properties, track details like bedrooms and garden,let buyers make offers, and accept or reject them.""", + 'author': 'Pranjali Sangavekar(prsan)', + 'license': 'LGPL-3', + 'depends': ['base'], + 'data': [ + 'security/security.xml', + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tags_views.xml', + 'views/estate_property_offers_views.xml', + 'views/estate_menus.xml', + ], + 'demo': [ + 'demo/estate_property_data.xml', + ], + 'application': True, +} diff --git a/estate/demo/estate_property_data.xml b/estate/demo/estate_property_data.xml new file mode 100644 index 00000000000..8ea006ac286 --- /dev/null +++ b/estate/demo/estate_property_data.xml @@ -0,0 +1,153 @@ + + + + Beautiful Villa in Downtown + A stunning 3-bedroom villa with modern amenities + 382421 + 2026-06-15 + 6767676.00 + 3 + 9234 + 2 + True + True + 5000 + south + new + + + Cozy House with Garden + Perfect starter home with large garden + 400605 + 2026-07-01 + 100000.00 + 2 + 500 + 1 + False + True + 2000 + north + new + + + Modern Apartment + Newly built apartment in city center + 382421 + 2026-05-20 + 250000.00 + 2 + 750 + 3 + True + False + 0 + east + new + + + Traditional Cottage + Charming countryside cottage + 400605 + 2026-08-10 + 180000.00 + 3 + 650 + 2 + True + True + 3000 + west + new + + + Luxury Penthouse + High-end penthouse with panoramic views + 382421 + 2026-04-01 + 500000.00 + 4 + 1200 + 2 + True + True + 1000 + south + new + + + Seaside Bungalow + Relaxing bungalow with sea view + 600001 + 2026-10-01 + 420000.00 + 2 + 800 + 2 + False + True + 1500 + east + new + + + Mountain Cabin + Cozy cabin in the mountains + 700002 + 2026-11-15 + 210000.00 + 3 + 600 + 1 + False + True + 1200 + west + new + + + City Studio + Compact studio apartment for singles + 800003 + 2026-12-01 + 95000.00 + 1 + 350 + 1 + False + False + 0 + north + new + + + Family Home + Spacious home perfect for families + 900004 + 2027-01-10 + 300000.00 + 4 + 1100 + 2 + True + True + 2000 + south + new + + + Downtown Office Space + Modern office space in business district + 100005 + 2027-02-01 + 800000.00 + 0 + 2000 + 4 + True + False + 0 + east + new + + diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..2f1821a39c1 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..73fe80f2f7c --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,68 @@ +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real estate system" + + def _get_default_date_calculation(self): + return fields.Date.today() + relativedelta(months=3) + + name = fields.Char(string="Property Name", required=True) + description = fields.Text() + postcode = fields.Char(string="Postal Code") + date_availability = fields.Date(copy=False, default=_get_default_date_calculation) + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer(string="Living Area", help="Living area in square meters") + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean(string="Garden", help="Has garden") + garden_area = fields.Integer() + garden_orientation = fields.Selection([ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ]) + active = fields.Boolean(default=True, help="Uncheck to archive this property") + + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], required=True, copy=False, default='new') + + property_type_id = fields.Many2one('estate.property.type', string="Property Type", ondelete='cascade') + buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) + seller_id = fields.Many2one('res.users', string="Seller", default=lambda self: self.env.user) + + tag_ids = fields.Many2many('estate.property.tag', 'pranjali', 'property_id', 'tag_id', string="Tags") + offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + + total_area = fields.Float(compute='_compute_total_area', store=True) + best_price = fields.Float(compute='_compute_best_price', readonly=True, store=True) + + @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.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price'), default=0.0) + + @api.onchange('garden') + def _onchange_garden(self): + if not self.garden: + self.garden_area = 0 + self.garden_orientation = False + else: + self.garden_area = 10 + self.garden_orientation = 'north' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..3e2fd5bad5c --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,35 @@ +from datetime import timedelta + +from odoo import api, fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real estate system - Property Offer" + + price = fields.Float(string="Offer Price") + status = fields.Selection([ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], copy=False) + + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate.property', required=True) + + validity = fields.Integer(default=7) + + date_deadline = fields.Date( + compute='_compute_date_deadline', + inverse='_inverse_date_deadline' + ) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for offer in self: + date = offer.create_date or fields.Date.today() + offer.date_deadline = date + timedelta(days=offer.validity) + + def _inverse_date_deadline(self): + for record in self: + start = record.create_date.date() if record.create_date else fields.Date.today() + record.validity = (record.date_deadline - start).days diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..6e4099899cf --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real estate system - Property Tag" + + name = fields.Char(string="Tag Name", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..1dd588218ff --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real estate system - Property Type" + + name = fields.Char(string="Property Type Name", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..6905974b938 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property_manager,estate.property manager,model_estate_property,estate.estate_group_manager,1,1,1,1 +access_estate_property_user,estate.property user,model_estate_property,estate.estate_group_user,1,1,0,0 +access_estate_property_type_manager,estate.property.type manager,model_estate_property_type,estate.estate_group_manager,1,1,1,1 +access_estate_property_type_user,estate.property.type user,model_estate_property_type,estate.estate_group_user,1,0,0,0 +access_estate_property_tag_manager,estate.property.tag manager,model_estate_property_tag,estate.estate_group_manager,1,1,1,1 +access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,estate.estate_group_user,1,0,0,0 +access_estate_property_offer_manager,estate.property.offer manager,model_estate_property_offer,estate.estate_group_manager,1,1,1,1 +access_estate_property_offer_user,estate.property.offer user,model_estate_property_offer,estate.estate_group_user,1,1,1,0 diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 00000000000..aceaae91826 --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,29 @@ + + + + + Real Estate + Real Estate Management + + + + Real Estate + + + + + Agent + + + + + Manager + + + + + + + + + \ No newline at end of file diff --git a/estate/static/description/icon.png b/estate/static/description/icon.png new file mode 100644 index 00000000000..6fc6561a21c Binary files /dev/null and b/estate/static/description/icon.png differ diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..53f245deb28 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml new file mode 100644 index 00000000000..c8bb1198f92 --- /dev/null +++ b/estate/views/estate_property_offers_views.xml @@ -0,0 +1,34 @@ + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + + + +
+
+
+ + + estate.property.offer.list + estate.property.offer + + + + + + + + + + +
diff --git a/estate/views/estate_property_tags_views.xml b/estate/views/estate_property_tags_views.xml new file mode 100644 index 00000000000..6a3adee6972 --- /dev/null +++ b/estate/views/estate_property_tags_views.xml @@ -0,0 +1,42 @@ + + + + estate.property.tag.form + estate.property.tag + +
+ + + + + +
+
+
+ + + estate.property.tag.list + estate.property.tag + + + + + + + + + estate.property.tag.search + estate.property.tag + + + + + + + + + Property Tags + estate.property.tag + list,form + +
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..6fab9d23e29 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,42 @@ + + + + estate.property.type.form + estate.property.type + +
+ + + + + +
+
+
+ + + estate.property.type.list + estate.property.type + + + + + + + + + estate.property.type.search + estate.property.type + + + + + + + + + Property Types + estate.property.type + list,form + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..446a1b6dfce --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,121 @@ + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.list + estate.property + + + + + + + + + + + + + + + + + + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + + + + + Properties + estate.property + list,form + {'search_default_properties_with_offers': 1} + +