diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..84a5e58a75a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,24 @@ +{ + 'name': "Estate", + 'version': '1.0', + 'depends': ['base'], + 'author': "Odoo", + 'category': 'Category', + 'description': """ + Just a dummy estate app + """, + 'data' : [ + 'security/ir.model.access.csv', + + 'views/estate_property_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/res_users_views.xml', + + 'views/estate_menus.xml', + ], + 'installable': True, + 'application': True, + 'license': 'LGPL-3', +} \ No newline at end of file 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..b1b1e01e5d4 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,113 @@ +from odoo import api,fields,models +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_compare, float_is_zero + + +class estate_property(models.Model): + # Private attributes + _name = "estate.property" + _description = "Estate property file" + _order = "id desc" + + _sql_constraints = [ + ('check_expected_price', 'CHECK(expected_price > 0)', + 'The expected price should be strictly positive.'), + ('check_property_selling_price','CHECK(selling_price>=0)', + 'Property selling price should be posiive') + ] + + # Default methods + def _default_date_availability(self,number_of_months): + return fields.Date.context_today(self) + relativedelta(months=number_of_months) + + # Fields declaration + name = fields.Char('Name',required=True, translate=True, default='Unknown') + description = fields.Text("Description",required=True, translate=True) + postcode = fields.Char("Post code",required=False, translate=True) + date_avaibility = fields.Date("Avaibility date",required=False, copy=False, default=lambda self: self._default_date_availability(3)) + expected_price = fields.Float("Expected Price",required=False) + selling_price = fields.Float("Selling Price",readonly=True, copy=False, required=False) + bedrooms = fields.Integer("Bedrooms numbers",required=False, default=2) + living_area = fields.Integer("Living Area",required=False) + facades = fields.Integer("Facades",required=False) + garage = fields.Boolean("Garage",required=False) + garden = fields.Boolean("Garden",required=False) + garden_area = fields.Integer("garden_area",required=False) + orientation = fields.Selection( + string="Garden Orientation", + selection=[('North','North'), + ('East','East'), + ('South','South'), + ('West','West')] + ) + estate_state = fields.Selection( + string="Estate State", + selection=[('New','New'), + ('Offer_Received','Offer Received'), + ('Offer_Accepted','Offer Accepted'), + ('Sold','Sold'), + ("Cancelled","Cancelled")], + default="New", + copy=False + ) + active = fields.Boolean("active",required=True,default=True) + total_area = fields.Float(compute="_compute_total_area") + best_offer = fields.Float(compute="_compute_best_offer") + + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + property_salesman_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) + property_buyer_id = fields.Many2one("res.partner", string="Buyer") + + property_tag_ids = fields.Many2many("estate.property.tag", string="Tags") + + property_offer_ids = fields.One2many("estate.property.offer","property_id", string="Offers") + + note = fields.Text("Special mentions about the house.") + + # compute and search fields + @api.depends("living_area","garden_area") + def _compute_total_area(self): + for prop in self: + prop.total_area = prop.living_area+prop.garden_area + + @api.depends("property_offer_ids.price") + def _compute_best_offer(self): + for prop in self: + prop.best_offer = max(prop.property_offer_ids.mapped("price")) if prop.property_offer_ids else 0.0 + + # Constraints and onchanges# Constraints and onchanges + @api.constrains("selling_price") + def _check_selling_price(self): + for record in self: + if float_compare(record.selling_price, record.expected_price * 90.0 / 100.0, precision_rounding=0.01) < 0: + raise ValidationError("The selling price is to low") + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden == True: + self.garden_area = 100 + self.orientation = "North" + else: + self.garden_area = 0 + self.orientation = False + + # CRUD methods + @api.ondelete(at_uninstall=False) + def _delete_house(self): + for record in self: + if record.estate_state not in ["New","Cancelled"]: + raise UserError("Can't delete an active house!") + + # Action methods + def action_cancel_property(self): + for record in self: + if record.estate_state == "Sold": + raise UserError('Unable to Cancel a sold estate, please change estate state before continuing.') + record.estate_state = "Cancelled" + + def action_sold_property(self): + for record in self: + if record.estate_state == "Cancelled": + raise UserError('Unable to sold a cancelled estate, please change estate state before continuing.') + record.estate_state = "Sold" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..f059fcc1246 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,73 @@ +from odoo import fields,models,api +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError + + +class estate_property_offer(models.Model): + # Private attributes + _name = "estate.property.offer" + _description = "Estate property offer file" + _order = "price desc" + + _sql_constraints = [ + ('check_offer_price', 'CHECK(price > 0)', + 'The offer price should be strictly positive.') + ] + + # Fields declaration + price = fields.Integer('Price',required=True, default=100000) + validity = fields.Integer("validity days", default=7) + date_dateline = fields.Date("Date deadline",compute="_compute_date_deadline", inverse="_inverse_date_deadline") + status = fields.Selection( + string="Offer Status", + selection=[('Accepted','Acccepted'), + ('In Waiting','In Waiting'), + ('Refused','Refused')], + default="In Waiting", + copy=False + ) + partner_id = fields.Many2one("res.partner", string="Partner",required=True) + property_id = fields.Many2one("estate.property", string="Property",required=True) + property_type_id = fields.Many2one("estate.property.type", related="property_id.property_type_id", string="Property Type", store=True) + + # compute and search fields + @api.depends("validity") + def _compute_date_deadline(self): + for record in self: + record.date_dateline=fields.Date.context_today(self)+relativedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + record.validity = (record.date_dateline - fields.Date.context_today(self)).days + + # CRUD methods + @api.model + def create(self, vals): + if vals.get("property_id") and vals.get("price"): + prop = self.env["estate.property"].browse(vals["property_id"]) + if prop.property_offer_ids: + max_offer = max(prop.property_offer_ids.mapped("price")) + print(max_offer) + if vals["price"] + + + + + + + + + + \ 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..ba611b72267 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,46 @@ + + + + estate.property.offer.view.form + estate.property.offer + +
+ + +

+ +

+ + + + + +
+
+
+
+
+ + + estate.property.offer.view.list + estate.property.offer + + + + + + + + + + + +

+ +

+
+ + + + + + + + + + + +
+ +
+
+ + + estate.property.type.view.tree + estate.property.type + + + + + + + + + + + Estate Properties Types + estate.property.type + list,form + +
\ 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..5b024d94a9a --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,140 @@ + + + + estate.property.view.tree + estate.property + + + + + + + + + + + + + + + estate.property.view.form + estate.property + +
+
+ +
+ + + +

+ +

+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.view.search + estate.property + + + + + + + + + + + + + + + + + + + + estate.property.view.kanban + estate.property + + + + +
+
+ + + +
+
+ Expected Price: +
+ + +
+ Best Offer : +
+
+ Selling Price : +
+ +
+
+
+
+
+
+ + + Estate Properties + estate.property + list,form,kanban + +
\ No newline at end of file diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..4e35277ea7d --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,18 @@ + + + + + + res.users.view.form.inherit.test + res.users + + + + + + + + + + + \ No newline at end of file diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /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 00000000000..8b3a8cc5244 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,16 @@ +{ + 'name': "Estate account", + 'version': '1.0', + 'depends': ['account','estate'], + 'author': "PRIN", + 'category': 'Category', + 'description': """ + Just a dummy estate to account app link + """, + 'data' : [ + + ], + 'installable': True, + 'application': True, + 'license': 'LGPL-3', +} \ No newline at end of file diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..5ea920dd669 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,39 @@ +from odoo import models, Command + + +class EstateProperty(models.Model): + + _inherit = "estate.property" + + def action_sold_property(self): + res = super().action_sold_property() + journal = self.env["account.journal"].search([("type", "=", "sale")], limit=1) + for record in self: + self.env["account.move"].create( + { + "partner_id": record.property_buyer_id.id, + "move_type": "out_invoice", + "journal_id": journal.id, + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": record.name, + "quantity": 1.0, + "price_unit": record.selling_price * 6.0 / 100.0, + }, + ), + ( + 0, + 0, + { + "name": "Administrative fees", + "quantity": 1.0, + "price_unit": 100.0, + }, + ), + ], + } + ) + print("ahah",journal) \ No newline at end of file