diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b1cf30486..69ca83821 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,6 +9,7 @@ 'data': [ 'security/ir.model.access.csv', + 'view/res_user_views.xml', 'view/estate_property_views.xml', 'view/estate_property_offer_views.xml', 'view/estate_property_type_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 449b55018..8f914bbb5 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,5 +1,5 @@ - from . import estate_property from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_user diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index cdb4fb636..eac2a8c60 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,10 +8,11 @@ class estate_property(models.Model): _name = "estate_property" _description = "Estate Property" _order = "sequence, id desc" - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ["mail.thread", "mail.activity.mixin"] + name = fields.Char(required=True) description = fields.Text() - sequence = fields.Integer('Sequence') + sequence = fields.Integer("Sequence") postcode = fields.Char() date_availability = fields.Date(default=date.today() + relativedelta(month=3)) expected_price = fields.Float(required=True) @@ -23,19 +24,48 @@ class estate_property(models.Model): garden = fields.Boolean() garden_area = fields.Integer() active = fields.Boolean(default=True) - state = fields.Selection(default='new', selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('canceled', 'Canceled')], tracking=True) - garden_orientation = fields.Selection(selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) + state = fields.Selection( + default="new", + selection=[ + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("canceled", "Canceled"), + ], + tracking=True, + ) + garden_orientation = fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] + ) property_type = fields.Many2one("estate_property_type", string="Property Type") - salesperson_id = fields.Many2one('res.users', string='Selesperson', default=lambda self: self.env.user) - buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False, ondelete='cascade') + salesperson_id = fields.Many2one( + "res.users", string="Selesperson", default=lambda self: self.env.user + ) + buyer_id = fields.Many2one( + "res.partner", string="Buyer", copy=False, ondelete="cascade" + ) tag_ids = fields.Many2many("estate_property_tag", string="Tags") - offer_ids = fields.One2many('estate_property_offer', 'property_id') + offer_ids = fields.One2many("estate_property_offer", "property_id") total_area = fields.Float(compute="_compute_total") best_offer = fields.Float(compute="_best_offer") _sql_constraints = [ - ('check_Expected_price', 'CHECK(expected_price > 0)', 'Expected Price must be strictly positive'), - ('check_selling_price', 'CHECK(selling_price >= 0)', 'Selling Price selling price must be positive'), - ('unique_name', 'UNIQUE(name)', 'Property type name must be unique') + ( + "check_Expected_price", + "CHECK(expected_price > 0)", + "Expected Price must be strictly positive", + ), + ( + "check_selling_price", + "CHECK(selling_price >= 0)", + "Selling Price selling price must be positive", + ), + ("unique_name", "UNIQUE(name)", "Property type name must be unique"), ] @api.depends() @@ -43,7 +73,7 @@ def _compute_total(self): for record in self: record.total_area = record.garden_area + record.livingArea - @api.depends('offer_ids') + @api.depends("offer_ids") def _best_offer(self): maximum_price = 0 for record in self.offer_ids: @@ -51,14 +81,10 @@ def _best_offer(self): maximum_price = record.price self.best_offer = maximum_price - @api.onchange('garden') + @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 = '' + self.garden_area = 0.00 def button_action_sold(self): if self.state == "canceled": @@ -72,11 +98,19 @@ def button_action_cancled(self): else: self.state = "canceled" - @api.constrains('selling_price', 'expected_price') + @api.constrains("selling_price", "expected_price") def _price_constrains(self): for record in self: if record.selling_price > 0: if record.selling_price < 0.9 * record.expected_price: - raise ValidationError("Selling price cannot be lower than 90% of the expected price") + raise ValidationError( + "Selling price cannot be lower than 90% of the expected price" + ) else: pass + + @api.ondelete(at_uninstall=False) + def _delete_state(self): + for record in self: + if record.state != "new" and record.state != "canceled": + raise ValidationError("can't be deleted") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b3269f3d5..57a5e846c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,7 +1,7 @@ from odoo import models, fields, api from datetime import date from datetime import timedelta -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError class estate_property_offer(models.Model): @@ -9,18 +9,22 @@ class estate_property_offer(models.Model): _description = "Estate Property offer" price = fields.Float() _order = "sequence, price desc" - sequence = fields.Integer('Sequence') + sequence = fields.Integer("Sequence") _sql_constraints = [ - ('offer_price', 'CHECK(price > 0)', 'offer price must be strictly positive') + ("offer_price", "CHECK(price > 0)", "offer price must be strictly positive") ] - status = fields.Selection(copy=False, selection=[('accepted', 'Accepted'), ('refused', 'Refused')]) + status = fields.Selection( + copy=False, selection=[("accepted", "Accepted"), ("refused", "Refused")] + ) 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_deadline", inverse="_inverse_validity", string="Deadline") + date_deadline = fields.Date( + compute="_compute_deadline", inverse="_inverse_validity", string="Deadline" + ) property_type_id = fields.Many2one(related="property_id.property_type", store=True) - @api.depends('validity') + @api.depends("validity") def _compute_deadline(self): for record in self: today = record.create_date @@ -29,14 +33,16 @@ def _compute_deadline(self): else: today = date.today() if record.date_deadline: - record.date_deadline = today + (record.validity - (record.date_deadline - today).days) + record.date_deadline = today + ( + record.validity - (record.date_deadline - today).days + ) else: record.date_deadline = timedelta(days=record.validity) + today @api.ondelete(at_uninstall=False) def _deletion_check(self): for record in self: - if record.status == 'accepted': + if record.status == "accepted": record.property_id.buyer_id = None record.property_id.selling_price = 0 @@ -52,14 +58,35 @@ def action_confirm(self): else: self.property_id.selling_price = self.price self.property_id.buyer_id = self.partner_id - self.status = 'accepted' - self.property_id.state = 'offer_accepted' + self.status = "accepted" + self.property_id.state = "offer_accepted" def action_refused(self): - if self.status == 'accepted' and self.property_id.buyer_id == self.partner_id: - self.status = 'refused' + if self.status == "accepted" and self.property_id.buyer_id == self.partner_id: + self.status = "refused" self.property_id.buyer_id = None self.property_id.selling_price = 0 - self.property_id.state = 'new' + self.property_id.state = "new" else: self.status = "refused" + + @api.model + def create(self, vals): + property_id = vals.get("property_id") + offer_amount = vals.get("price") + property_record = self.env["estate_property"].browse(property_id) + if not property_record: + raise ValidationError("record not found") + existing_offers = self.search([("property_id", "=", property_id)]) + highest_offer = 0 + for offer in existing_offers: + if offer.price >= highest_offer: + highest_offer = offer.price + # Check if the new offer amount is lower than the highest existing offer + if offer_amount < highest_offer: + raise ValidationError( + "The new offer amount must be higher than the existing highest offer." + ) + if property_record.state != "offer_received": + property_record.state = "offer_received" + return super(estate_property_offer, self).create(vals) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 9ace8f6d0..663f6e121 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,9 +6,9 @@ class estate_property_tag(models.Model): _description = "Estate Property Tag" name = fields.Char(required=True) _order = " sequence, name" - sequence = fields.Integer('Sequence') + sequence = fields.Integer("Sequence") color = fields.Integer() _sql_constraints = [ - ('unique_tag', 'UNIQUE(name)', 'Property Tag name must be unique') + ("unique_tag", "UNIQUE(name)", "Property Tag name must be unique") ] diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 765bf102f..6265112d7 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,12 +6,16 @@ class estate_property_type(models.Model): _description = "Estate Property Type" name = fields.Char(required=True) _order = "sequence, name" - sequence = fields.Integer('Sequence') - property_list_id = fields.One2many('estate_property', 'property_type') - offer_ids = fields.One2many("estate_property_offer", "property_type_id", string="offers") - offer_count = fields.Integer(string="Numbers of offer", compute='_compute_offer_count', store=True) + sequence = fields.Integer("Sequence") + property_list_id = fields.One2many("estate_property", "property_type") + offer_ids = fields.One2many( + "estate_property_offer", "property_type_id", string="offers" + ) + offer_count = fields.Integer( + string="Numbers of offer", compute="_compute_offer_count", store=True + ) - @api.depends('offer_ids') + @api.depends("offer_ids") def _compute_offer_count(self): for record in self: record.offer_count = len(record.offer_ids) diff --git a/estate/models/res_user.py b/estate/models/res_user.py new file mode 100644 index 000000000..91b9a977e --- /dev/null +++ b/estate/models/res_user.py @@ -0,0 +1,12 @@ +from odoo import models, fields + + +class resUser(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + "estate_property", + "salesperson_id", + string="Properties", + domain="['|',('state', '=', 'new'),('state', '=', 'offer_received')]", + ) diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index d4fde175f..45c45a818 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -3,7 +3,7 @@ Property estate_property - tree,form + tree,form,kanban {'search_default_available': True, 'search_default_current': True} @@ -22,18 +22,20 @@ estate_property_tree estate_property - - + + - - + + - + - + @@ -48,24 +50,28 @@
-

- +

- +
- - + + @@ -89,11 +95,12 @@ - + - + - + @@ -106,17 +113,18 @@ - - + +
- - -
+ + + @@ -136,14 +144,14 @@ + filter_domain="[('livingArea', '>=', self)]" /> - + @@ -151,8 +159,48 @@
- - + + + action_kanban_view + estate_property + + + + + +
+ + + +
+ Expected Price: + +
+
+ +
+
+ + + Best Price: + + +
+
+ + + Selling Price: + + +
+ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/estate/view/res_user_views.xml b/estate/view/res_user_views.xml new file mode 100644 index 000000000..9cd33e081 --- /dev/null +++ b/estate/view/res_user_views.xml @@ -0,0 +1,31 @@ + + + + + res.users.form.inherit.properties + 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 000000000..0650744f6 --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 000000000..98581c913 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,9 @@ +{ + 'name': 'estate_account', + 'version': '0.1', + 'license': 'LGPL-3', + 'depends': ['estate', 'account'], + 'description': "Technical Training", + 'installable': True, + 'application': True, +} 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..3dd1f2a08 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,31 @@ +from odoo import models, api, Command + + +class estate_property(models.Model): + _inherit = "estate_property" + + def button_action_sold(self): + result = super(estate_property, self).button_action_sold() + for property in self: + commission = 0.06 * property.selling_price + administrative_fee = 100 + invoice_vals = { + "partner_id": property.buyer_id.id, + "move_type": "out_invoice", + "line_ids": [ + Command.create( + { + "name": "6% Commission on Selling Price", + "price_unit": commission, + } + ), + Command.create( + { + "name": "administrative fees", + "price_unit": administrative_fee, + } + ), + ], + } + invoice = self.env["account.move"].create(invoice_vals) + return result