diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6..263ceab8c 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,3 @@ from . import models +from . import controllers +from . import wizard \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4456713b6..cad362d07 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,15 +4,27 @@ 'summary': 'Manage real estate properties', 'description': 'Module to manage real estate properties', 'author': 'Akya', - 'depends': ['base', 'mail'], + 'category': 'Real Estate/Brokerage', + 'sequence': '15', + 'depends': ['base', 'mail','website'], 'data': [ + 'security/estate_security.xml', 'security/ir.model.access.csv', + 'data/estate.property.type.csv', + 'wizard/estate_property_offer_wizard.xml', + 'wizard/estate_property_event_view.xml', 'views/estate_property_views.xml', + 'views/estate_property_controller_template.xml', 'views/estate_property_offer_view.xml', 'views/estate_property_tag_view.xml', 'views/estate_property_type_view.xml', 'views/res_users_view.xml', 'views/estate_menus.xml', + 'report/estate_property_reports.xml', + 'report/estate_property_templates.xml', + ], + 'demo':[ + 'demo/estate_property_demo.xml' ], 'installable': True, 'application': True, diff --git a/estate/controllers/__init__.py b/estate/controllers/__init__.py new file mode 100644 index 000000000..d321c5f19 --- /dev/null +++ b/estate/controllers/__init__.py @@ -0,0 +1 @@ +from . import estate_property_controller \ No newline at end of file diff --git a/estate/controllers/estate_property_controller.py b/estate/controllers/estate_property_controller.py new file mode 100644 index 000000000..a6d4da110 --- /dev/null +++ b/estate/controllers/estate_property_controller.py @@ -0,0 +1,22 @@ +from odoo import http + + +class WebsiteProperty(http.Controller): + + @http.route("/properties", auth="public", website=True) + def display_properties(self, page=1, **kwargs): + page = int(page) + items_per_page = 6 + offset = (page - 1) * items_per_page + total_properties = http.request.env["estate.property"].search_count([('state', '!=', 'sold'), ('state', '!=', 'canceled')]) + total_page = (total_properties + items_per_page - 1) // items_per_page + properties = http.request.env["estate.property"].search([('state', '!=', 'sold'), ('state', '!=', 'canceled')], limit=items_per_page, offset=offset) + return http.request.render( + "estate.property_page", {"properties": properties, 'page': page, 'total_page': total_page} + ) + + @http.route('/property/', type='http', auth='public', website=True) + def property_details(self, each_property, **kwargs): + return http.request.render('estate.property_detail_page', { + 'property': each_property, + }) \ No newline at end of file diff --git a/estate/data/estate.property.type.csv b/estate/data/estate.property.type.csv new file mode 100644 index 000000000..2b87635b2 --- /dev/null +++ b/estate/data/estate.property.type.csv @@ -0,0 +1,5 @@ +id,name +id1,Residential +id2,Commercial +id3,Industrial +id4,Land diff --git a/estate/demo/estate_property_demo.xml b/estate/demo/estate_property_demo.xml new file mode 100644 index 000000000..968d740cf --- /dev/null +++ b/estate/demo/estate_property_demo.xml @@ -0,0 +1,84 @@ + + + + + Big villa + new + A nice and big villa + 12345 + 2020-02-02 + 100 + 6 + 100 + True + True + 100000 + 4 + south + + + + + BTrailer home + canceled + home in a trailer park + 54321 + 1970-01-01 + 100 + 1 + 10 + False + 4 + + + + + Luxury villa + new + A very nice villa + 12345 + 2020-02-02 + 100 + 6 + 100 + True + True + 100000 + south + 4 + + + + + + + + 100 + 14 + + + + + + + 150 + 14 + + + + + + + 151 + 14 + + + + \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 79238789c..9a2189b63 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,5 @@ -from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, res_users +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 10bef7223..d61b638e5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -43,6 +43,13 @@ class EstateProperty(models.Model): seller_id = fields.Many2one('res.users', string="Salesperson", ondelete='set null') tag_ids = fields.Many2many('estate.property.tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + property_image = fields.Binary(string="Property Image") + company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company) + + _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('living_area', 'garden_area') def _compute_total(self): @@ -74,6 +81,7 @@ def action_cancel(self): return True def action_sold(self): + print("Old method called ############") if self.state != "canceled": if self.state == 'offer_accepted': self.state = "sold" @@ -83,11 +91,6 @@ def action_sold(self): raise UserError("This property can't be sold as it is canceled already") return True - _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.constrains('selling_price', 'expected_price') def _check_selling_price(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index bbcd29b93..7667531b6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -16,6 +16,10 @@ class EstatePropertyOffer(models.Model): property_id = fields.Many2one('estate.property', 'Property', required=True, ondelete="cascade") 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('create_date', 'validity') def _compute_date_deadline(self): for record in self: @@ -49,10 +53,6 @@ def action_refuse(self): self.status = 'refused' return True - _sql_constraints = [ - ('check_offer_price', 'CHECK(price > 0)', 'The offer price must be strictly positive.') - ] - @api.model def create(self, vals): record = super().create(vals) diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 7cd489678..dc96f330e 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -8,5 +8,5 @@ class ResUsers(models.Model): 'estate.property', 'seller_id', string='Properties', - domain=[('state', '=', 'available')] + domain="['|', ('state', '=', 'new'), ('state', '=', 'offer received')]" ) diff --git a/estate/report/estate_property_reports.xml b/estate/report/estate_property_reports.xml new file mode 100644 index 000000000..bca11a351 --- /dev/null +++ b/estate/report/estate_property_reports.xml @@ -0,0 +1,24 @@ + + + + + Real Estate Property + estate.property + qweb-pdf + estate.report_property_offers + estate.report_property_offers + + report + + + + User Real Estate Property + res.users + qweb-pdf + estate.report_property_offers_users + estate.report_property_offers_users + + report + + + \ No newline at end of file diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml new file mode 100644 index 000000000..05c6036ad --- /dev/null +++ b/estate/report/estate_property_templates.xml @@ -0,0 +1,100 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/security/estate_security.xml b/estate/security/estate_security.xml new file mode 100644 index 000000000..8820437dd --- /dev/null +++ b/estate/security/estate_security.xml @@ -0,0 +1,37 @@ + + + + Agent + + + + + + Manager + + + + + + + Estate Property User Access Rule + + + ['|', ('seller_id', '=', user.id), ('seller_id', '=', False)] + + + + Estate Property User Access Rule + + + + + + Restricted Record: multi-company + + + + [('company_id', 'in', company_ids)] + + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 5f2c0485b..7e120f7f5 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,7 +1,12 @@ 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 -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 - - +access_estate_property_public,access_estate_property,model_estate_property,base.group_public,1,0,0,0 +access_estate_property_manager,access_estate_property,model_estate_property,estate.estate_group_manager,1,1,1,1 +access_estate_property_type_manager,access_estate_property_type,model_estate_property_type,estate.estate_group_manager,1,1,1,1 +access_estate_property_tag_manager,access_estate_property_tag,model_estate_property_tag,estate.estate_group_manager,1,1,1,1 +access_estate_property_offer_manager,access_estate_property_offer,model_estate_property_offer,estate.estate_group_manager,1,1,1,1 +access_estate_property_user,access_estate_property,model_estate_property,estate.estate_group_user,1,1,1,0 +access_estate_property_type_user,access_estate_property_type,model_estate_property_type,estate.estate_group_user,1,0,0,0 +access_estate_property_tag_user,access_estate_property_tag,model_estate_property_tag,estate.estate_group_user,1,0,0,0 +access_estate_property_offer_user,access_estate_property_offer,model_estate_property_offer,estate.estate_group_user,1,1,1,1 +access_estate_property_offer_wizard_user,access_estate_property_offer_wizard_user,model_estate_property_offer_wizard,base.group_user,1,1,1,0 +access_estate_property_event_wizard_user,access_estate_property_event_wizard_user,model_estate_property_event_wizard,base.group_user,1,1,1,0 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index e7b2da9a9..b9262664d 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,10 +4,9 @@ - + - diff --git a/estate/views/estate_property_controller_template.xml b/estate/views/estate_property_controller_template.xml new file mode 100644 index 000000000..5ace21d1f --- /dev/null +++ b/estate/views/estate_property_controller_template.xml @@ -0,0 +1,89 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml index 1c40e47f1..c95e5ab5c 100644 --- a/estate/views/estate_property_offer_view.xml +++ b/estate/views/estate_property_offer_view.xml @@ -1,17 +1,12 @@ - - Property Offers estate.property.offer tree,form [('property_type_id', '=', active_id)] - - - - - + + estate.property.offer.form estate.property.offer @@ -23,7 +18,6 @@ - @@ -34,18 +28,16 @@ estate.property.offer.tree estate.property.offer - - - - - - - + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f0a1fb03b..8d3a83c7e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,13 +1,14 @@ - + + Estate Properties estate.property {'search_default_available': True} - tree,form + tree,form,kanban

Create a new property @@ -17,16 +18,56 @@ + + estate.property.kanban + estate.property + + + + + +

+
+ + + +
+
+ Expected Price: + +
+
+ Best Offer: + +
+
+ Selling Price: + +
+
+ +
+
+ + + +
+
+ + estate.property.tree estate.property - +
+
@@ -54,14 +95,18 @@
+
+

+ + +

-

diff --git a/estate/views/res_users_view.xml b/estate/views/res_users_view.xml index fcc82d0e9..6cec69357 100644 --- a/estate/views/res_users_view.xml +++ b/estate/views/res_users_view.xml @@ -7,25 +7,23 @@ tree,form
- + res.users.form.inherit.property_ids res.users - - - - - - - - - - - - - - - - + + + + + + + + + + + + +
+
diff --git a/estate/wizard/__init__.py b/estate/wizard/__init__.py new file mode 100644 index 000000000..57ff4c8db --- /dev/null +++ b/estate/wizard/__init__.py @@ -0,0 +1,2 @@ +from . import estate_property_offer_wizard +from . import estate_property_event_wizard \ No newline at end of file diff --git a/estate/wizard/estate_property_event_view.xml b/estate/wizard/estate_property_event_view.xml new file mode 100644 index 000000000..9cf6cb0d0 --- /dev/null +++ b/estate/wizard/estate_property_event_view.xml @@ -0,0 +1,30 @@ + + + + estate.property.event.wizard.form + estate.property.event.wizard + + + + + + + + +
+
+ +
+
+ + + Create Event + estate.property.event.wizard + form + + new + + +
diff --git a/estate/wizard/estate_property_event_wizard.py b/estate/wizard/estate_property_event_wizard.py new file mode 100644 index 000000000..216de65f3 --- /dev/null +++ b/estate/wizard/estate_property_event_wizard.py @@ -0,0 +1,21 @@ +from odoo import models, fields, api + +class EstatePropertyEventWizard(models.TransientModel): + _name = 'estate.property.event.wizard' + _description = 'Property Event Wizard' + + event_name = fields.Char(string="Event Name", required=True) + date = fields.Datetime(string="Date", required=True) + attendee_ids = fields.Many2many('res.partner', string="Attendees") + organizer_id = fields.Many2one('res.users', string="Organizer", default=lambda self: self.env.user) + + def create_event(self): + self.env['calendar.event'].create({ + 'name': self.event_name, + 'start': self.date, + 'stop': self.date, + 'allday': True, + 'user_id': self.organizer_id.id, + 'partner_ids': [(6, 0, self.attendee_ids.ids)], + }) + return {'type': 'ir.actions.act_window_close'} diff --git a/estate/wizard/estate_property_offer_wizard.py b/estate/wizard/estate_property_offer_wizard.py new file mode 100644 index 000000000..7549374a4 --- /dev/null +++ b/estate/wizard/estate_property_offer_wizard.py @@ -0,0 +1,23 @@ +from odoo import models, fields + + +class EstatePropertyOfferWizard(models.TransientModel): + _name = 'estate.property.offer.wizard' + _description = 'Estate Property offer wizard' + + price = fields.Float(string='Offer Price', required=True) + validity = fields.Integer(default=7) + partner_id = fields.Many2one('res.partner', string='Partner', required=True) + + def make_offer(self): + active_ids = self.env.context.get('active_ids') + properties = self.env['estate.property'].browse(active_ids) + for prop in properties: + prop.state = "offer_received" + self.env['estate.property.offer'].create({ + 'property_id': prop.id, + 'price': self.price, + 'validity': self.validity, + 'partner_id': self.partner_id.id, + }) + return {'type': 'ir.actions.act_window_close'} diff --git a/estate/wizard/estate_property_offer_wizard.xml b/estate/wizard/estate_property_offer_wizard.xml new file mode 100644 index 000000000..660b01ea7 --- /dev/null +++ b/estate/wizard/estate_property_offer_wizard.xml @@ -0,0 +1,28 @@ + + + + Add Property Offer + estate.property.offer.wizard + form + new + {'active_ids': active_ids} + + + + estate.property.offer.wizard.form + estate.property.offer.wizard + +
+ + + + + +
+
+
+
+
+
diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 000000000..0fd6284c6 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Estate Account', + 'version': '1.0', + 'category': 'Real Estate', + 'summary': 'Accounting Integration for Real Estate', + 'description': """ + This module integrates the Real Estate module with the Accounting module. + """, + 'author': 'Akya', + 'depends': ['estate', 'account'], + 'data': [ + 'report/estate_property_report_invoice.xml' + ], + 'installable': True, + 'application': True, + 'license': 'AGPL-3' +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 000000000..5e1963c9d --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 000000000..4bf38bfcf --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,31 @@ +from odoo import Command, models + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_sold(self): + self.env["account.move"].create( + { + "move_type": "out_invoice", + "partner_id": self.buyer_id.id, + "invoice_line_ids": [ + Command.create( + { + "name": self.title, + "quantity": 1, + "price_unit": 0.06 * self.selling_price, + } + ), + Command.create( + { + "name": "administrative_fees", + "quantity": 1, + "price_unit": 100, + } + ), + + ], + } + ) + return super().action_sold() diff --git a/estate_account/report/estate_property_report_invoice.xml b/estate_account/report/estate_property_report_invoice.xml new file mode 100644 index 000000000..434e335ec --- /dev/null +++ b/estate_account/report/estate_property_report_invoice.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/estate_event/__init__.py b/estate_event/__init__.py new file mode 100644 index 000000000..81f1166fe --- /dev/null +++ b/estate_event/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import controllers + diff --git a/estate_event/__manifest__.py b/estate_event/__manifest__.py new file mode 100644 index 000000000..f942de023 --- /dev/null +++ b/estate_event/__manifest__.py @@ -0,0 +1,21 @@ +{ + 'name': "estate_event", + 'summary': "Short (1 phrase/line) summary of the module's purpose", + 'description': """ +Long description of module's purpose + """, + 'author': "My Company", + 'website': "https://www.yourcompany.com", + 'category': 'Uncategorized', + 'version': '0.1', + 'depends': ['base'], + 'data': [ + # 'security/ir.model.access.csv', + 'views/views.xml', + 'views/templates.xml', + ], + 'demo': [ + 'demo/demo.xml', + ], +} + diff --git a/estate_event/controllers/__init__.py b/estate_event/controllers/__init__.py new file mode 100644 index 000000000..b0f26a9a6 --- /dev/null +++ b/estate_event/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers diff --git a/estate_event/controllers/controllers.py b/estate_event/controllers/controllers.py new file mode 100644 index 000000000..209717fea --- /dev/null +++ b/estate_event/controllers/controllers.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# from odoo import http + + +# class EstateEvent(http.Controller): +# @http.route('/estate_event/estate_event', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/estate_event/estate_event/objects', auth='public') +# def list(self, **kw): +# return http.request.render('estate_event.listing', { +# 'root': '/estate_event/estate_event', +# 'objects': http.request.env['estate_event.estate_event'].search([]), +# }) + +# @http.route('/estate_event/estate_event/objects/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('estate_event.object', { +# 'object': obj +# }) + diff --git a/estate_event/demo/demo.xml b/estate_event/demo/demo.xml new file mode 100644 index 000000000..10e06bcbf --- /dev/null +++ b/estate_event/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/estate_event/models/__init__.py b/estate_event/models/__init__.py new file mode 100644 index 000000000..5b65b9068 --- /dev/null +++ b/estate_event/models/__init__.py @@ -0,0 +1 @@ +from . import estate_event diff --git a/estate_event/models/estate_event.py b/estate_event/models/estate_event.py new file mode 100644 index 000000000..53a80322a --- /dev/null +++ b/estate_event/models/estate_event.py @@ -0,0 +1,22 @@ +from odoo import models, fields, api + + +class EstateEvent(models.Model): + _name = 'estate.event' + _description = 'estate_event' + + date = fields.Datetime(string="Date", required=True) + attendee_ids = fields.Many2many('res.partner', string="Attendees") + organizer_id = fields.Many2one('res.users', string="Organizer", default=lambda self: self.env.user.id) + event_name = fields.Char(string="Event Name", required=True) + + def create_event(self): + self.env['calendar.event'].create({ + 'name': self.event_name, + 'start': self.date, + 'stop': self.date, + 'allday': False, + 'user_id': self.organizer_id.id, + 'partner_ids': [(6, 0, self.attendee_ids.ids)], + }) + return {'type': 'ir.actions.act_window_close'} diff --git a/estate_event/security/ir.model.access.csv b/estate_event/security/ir.model.access.csv new file mode 100644 index 000000000..97dd8b917 --- /dev/null +++ b/estate_event/security/ir.model.access.csv @@ -0,0 +1 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink diff --git a/estate_event/views/templates.xml b/estate_event/views/templates.xml new file mode 100644 index 000000000..2f37b71f8 --- /dev/null +++ b/estate_event/views/templates.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/estate_event/views/views.xml b/estate_event/views/views.xml new file mode 100644 index 000000000..309c2d1bf --- /dev/null +++ b/estate_event/views/views.xml @@ -0,0 +1,30 @@ + + + + estate.event.form + estate.event + +
+ + + + + + +
+
+
+
+
+ + + Create Event + estate.event + form + + new + + +