Skip to content

Commit de6b813

Browse files
committed
[IMP] estate : Added invoicing for sold properties and Kanban views
-Added two invoice function during invoice creation for sold properties -One for 6% commission for sales and another for fixed charges of 100. -Added a Kanban view to improve property visualization in the Real Estate module.
1 parent 6436f6c commit de6b813

14 files changed

+91
-70
lines changed

estate/estate_account/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'name': 'Estate Account',
3+
'application': True,
4+
'installable': True,
5+
'author': 'Odoo S.A.',
6+
'license': 'LGPL-3'
7+
'depends': ['estate', 'account'],
8+
}

estate/estate_account/models/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from odoo import Command, models
2+
3+
4+
class EstateProperty(models.Model):
5+
_inherit="estate.property"
6+
7+
def action_sold(self):
8+
self.env['account.move'].create({
9+
'partner_id': self.buyer_id.id,
10+
'move_type': 'out_invoice',
11+
'invoice_line_ids': [
12+
Command.create(
13+
{
14+
"name": "selling price commission (6%)",
15+
"quantity": 1,
16+
"price_unit": self.selling_price * 0.06 ,
17+
}
18+
),
19+
Command.create(
20+
{
21+
"name": "Fixed 100 fee",
22+
"quantity": 1,
23+
"price_unit": 100,
24+
}
25+
)
26+
],
27+
})
28+
return super().action_sold()

estate/models/estate_property.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,19 @@ class EstateProperty(models.Model):
99
_name = "estate.property"
1010
_description = "Real Estate Property"
1111
_order = "id desc"
12-
1312
name = fields.Char(required=True)
1413
description = fields.Text()
1514
postcode = fields.Char()
16-
1715
date_availability = fields.Date(
1816
"Availability Date",
1917
default=lambda self: fields.Date.today() + relativedelta(months=3),
2018
)
21-
2219
expected_price = fields.Float("Expected Price", required=True)
2320
selling_price = fields.Float("Selling Price", readonly=True)
24-
2521
bedrooms = fields.Integer(default=2)
2622
living_area = fields.Integer("Living Area(sqft)")
2723
facades = fields.Integer()
2824
garage = fields.Boolean()
29-
3025
garden = fields.Boolean()
3126
garden_area = fields.Integer("Garden Area(sqft)")
3227
garden_orientation = fields.Selection(
@@ -38,7 +33,6 @@ class EstateProperty(models.Model):
3833
],
3934
string="Garden Orientation",
4035
)
41-
4236
state = fields.Selection(
4337
[
4438
("new", "New"),
@@ -52,21 +46,16 @@ class EstateProperty(models.Model):
5246
copy=False,
5347
default="new",
5448
)
55-
5649
active = fields.Boolean(default=True)
57-
5850
property_type_id = fields.Many2one("estate.property.type", "Property Type")
5951
buyer_id = fields.Many2one("res.partner", "Buyer", copy=False)
6052
salesperson_id = fields.Many2one("res.users", string="Salesperson")
61-
6253
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
63-
6454
offer_ids = fields.One2many(
6555
"estate.property.offer",
6656
"property_id",
6757
string="Offers"
6858
)
69-
7059
total_area = fields.Integer("Total Area(sqm)", compute="_compute_total_area")
7160
best_price = fields.Float("Best Offer", compute="_compute_best_price")
7261

@@ -75,17 +64,16 @@ class EstateProperty(models.Model):
7564
'The expected price of a property must be strictly positive.',
7665
)
7766

78-
7967
@api.depends("living_area", "garden_area")
8068
def _compute_total_area(self):
8169
for record in self:
8270
record.total_area = (record.living_area or 0) + (record.garden_area or 0)
83-
71+
8472
@api.depends("offer_ids.price")
8573
def _compute_best_price(self):
8674
for record in self:
8775
record.best_price = max(record.offer_ids.mapped("price"), default=0)
88-
76+
8977
@api.onchange("garden")
9078
def _onchange_garden(self):
9179
if self.garden:
@@ -94,19 +82,17 @@ def _onchange_garden(self):
9482
else:
9583
self.garden_area = 0
9684
self.garden_orientation = False
97-
9885
def action_cancel(self):
9986
for record in self:
10087
if record.state == "sold":
10188
raise UserError("A sold property cannot be cancelled")
10289
record.state = "cancelled"
103-
10490
def action_sold(self):
10591
for record in self:
10692
if record.state == "cancelled":
10793
raise UserError("A cancelled property cannot be set as sold")
10894
record.state = "sold"
109-
95+
11096
@api.constrains("selling_price", "expected_price")
11197
def _check_selling_price(self):
11298
for record in self:
Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dateutil.relativedelta import relativedelta
2+
23
from odoo import fields, models, api
34
from odoo.exceptions import UserError
45

@@ -7,7 +8,6 @@ class EstatePropertyOffer(models.Model):
78
_name = "estate.property.offer"
89
_description = "Real Estate Property Offer"
910
_order = "price desc"
10-
1111
price = fields.Float()
1212
status = fields.Selection(
1313
[("accepted", "Accepted"), ("refused", "Refused")],
@@ -21,79 +21,59 @@ class EstatePropertyOffer(models.Model):
2121
compute="_compute_date_deadline",
2222
inverse="_inverse_date_deadline"
2323
)
24-
2524
property_type_id = fields.Many2one(
2625
related="property_id.property_type_id",
2726
store=True
2827
)
2928

29+
_check_offer_price = models.Constraint(
30+
'CHECK(price > 0)',
31+
'The price of an offer must be strictly positive.'
32+
)
33+
3034
@api.depends('validity')
3135
def _compute_date_deadline(self):
3236
for record in self:
3337
creation_date = record.create_date or fields.Date.today()
3438
record.date_deadline = creation_date + relativedelta(days=record.validity)
35-
3639
def _inverse_date_deadline(self):
3740
for record in self:
38-
creation_date = record.create_date or fields.Date.today()
41+
creation_date = record.create_date.date() or fields.Date.today()
3942
record.validity = (record.date_deadline - creation_date).days
40-
4143
def action_accept(self):
4244
for record in self:
4345
if record.property_id.buyer_id:
4446
raise UserError("Property already has an accepted offer.")
45-
4647
record.status = 'accepted'
4748
record.property_id.selling_price = record.price
4849
record.property_id.state = 'offer_accepted'
4950
record.property_id.buyer_id = record.partner_id
50-
5151
def action_refuse(self):
5252
self.status = 'refused'
5353
return True
54-
54+
5555
@api.model
5656
def create(self, vals):
57-
"""
58-
Handles:
59-
> avoid search inside loop
60-
> validate in bulk
61-
> update property state
62-
"""
63-
6457
vals_list = vals if isinstance(vals, list) else [vals]
65-
6658
property_offer_map = {}
6759
for v in vals_list:
6860
pid = v.get("property_id")
6961
if pid:
7062
property_offer_map.setdefault(pid, []).append(v)
71-
7263
for pid, offers in property_offer_map.items():
7364
prices = [o.get("price") for o in offers if o.get("price") is not None]
74-
7565
if prices:
7666
max_new_price = max(prices)
77-
7867
existing_offers = self.search([
7968
("property_id", "=", pid),
8069
("price", ">=", max_new_price),
8170
], limit=1)
82-
8371
if existing_offers:
8472
raise UserError(
8573
"You cannot create an offer with a lower amount than an existing offer for this property."
8674
)
87-
8875
records = super().create(vals)
89-
9076
if isinstance(records, models.Model):
9177
for offer in records:
9278
offer.property_id.state = "offer_received"
93-
9479
return records
95-
96-
_check_offer_price = models.Constraint(
97-
'CHECK(price > 0)',
98-
'The price of an offer must be strictly positive.'
99-
)

estate/models/estate_property_tag.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ class EstatePropertyTag(models.Model):
55
_name = "estate.property.tag"
66
_description = "Real Estate Property Tag"
77
_order = "name"
8-
98
name = fields.Char(required=True)
109
color = fields.Integer()
11-
10+
1211
_check_tag_name_unique = models.Constraint(
1312
'UNIQUE(name)',
1413
'The name of the property tag must be unique.'

estate/models/estate_property_type.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@ class EstatePropertyType(models.Model):
55
_name = "estate.property.type"
66
_description = "Real Estate Property Type"
77
_order = "sequence, name"
8-
98
name = fields.Char(required=True)
109
property_ids = fields.One2many("estate.property", "property_type_id", string="Properties")
1110
sequence = fields.Integer("Sequence", default=1)
1211
offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers")
1312
offer_count = fields.Integer(string="Offer Count", compute="_compute_offer_count")
1413

15-
@api.depends("offer_ids")
16-
def _compute_offer_count(self):
17-
for record in self:
18-
record.offer_count = len(record.offer_ids)
19-
2014
_check_type_name_unique = models.Constraint(
2115
'UNIQUE(name)',
2216
'The name of the property type must be unique.'
2317
)
18+
19+
@api.depends("offer_ids")
20+
def _compute_offer_count(self):
21+
for record in self:
22+
record.offer_count = len(record.offer_ids)

estate/models/res_users.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from odoo import models, fields
2+
3+
4+
class InheritedModel(models.Model):
5+
_inherit = "res.users"
6+
property_ids=fields.One2Many("estate.property","salesperson_id",domain=[("state","!=","sold")], string="Properties")

estate/views/estate_property_offer_views.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
<odoo>
2-
<!-- Action -->
32
<record id="estate_property_offer_action" model="ir.actions.act_window">
43
<field name="name">Property Offers</field>
54
<field name="res_model">estate.property.offer</field>
65
<field name="view_mode">list,form</field>
76
<field name="domain">[('property_type_id', '=', active_id)]</field>
87
</record>
9-
10-
<!-- List View -->
118
<record id="estate_property_offer_list_view" model="ir.ui.view">
129
<field name="name">estate.property.offer.list</field>
1310
<field name="model">estate.property.offer</field>
1411
<field name="arch" type="xml">
1512
<list decoration-danger="status == 'refused'"
1613
decoration-success="status == 'accepted'"
1714
editable="bottom">
18-
1915
<field name="price"/>
2016
<field name="partner_id"/>
2117
<field name="status"/>
@@ -25,8 +21,6 @@
2521
</list>
2622
</field>
2723
</record>
28-
29-
<!-- Form View -->
3024
<record id="estate_property_offer_form_view" model="ir.ui.view">
3125
<field name="name">estate.property.offer.form</field>
3226
<field name="model">estate.property.offer</field>
@@ -42,13 +36,11 @@
4236
<field name="property_type_id"/>
4337
</group>
4438
</sheet>
45-
4639
<footer>
4740
<button name="action_accept" type="object" string="Accept" class="btn-primary" />
4841
<button name="action_refuse" type="object" string="Refuse" class="btn-secondary" />
4942
</footer>
5043
</form>
5144
</field>
5245
</record>
53-
5446
</odoo>

0 commit comments

Comments
 (0)