Skip to content

Commit fc54c51

Browse files
committed
[ADD] estate module & estate account
1 parent 5926311 commit fc54c51

19 files changed

+558
-0
lines changed

estate/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models

estate/__manifest__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
4+
{
5+
'name': 'Real Estate Managment',
6+
'version': "16.0.1.0.0",
7+
'author': 'ADHOC SA, Odoo Community Association (OCA)',
8+
'license': 'AGPL-3',
9+
'depends': ['base'],
10+
'application' : True,
11+
'installable': True,
12+
'data': [
13+
'security/ir.model.access.csv',
14+
'views/estate_property_views.xml',
15+
'views/estate_property_type_views.xml',
16+
'views/estate_property_tag_views.xml',
17+
'views/estate_property_offer_views.xml',
18+
'views/res_users_views.xml',
19+
'views/estate_menus.xml',
20+
]
21+
}

estate/models/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from . import estate_property
2+
from . import estate_property_type
3+
from . import estate_property_tag
4+
from . import estate_property_offer
5+
from . import res_users

estate/models/estate_property.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from odoo import models, fields, api
2+
from odoo.tools.float_utils import float_compare
3+
from odoo.tools.float_utils import float_is_zero
4+
from odoo.exceptions import ValidationError
5+
6+
class EstateProperty(models.Model):
7+
_name = "estate.property"
8+
_description = 'Real Estate Properties'
9+
_order = "id desc"
10+
11+
name = fields.Char(required=True)
12+
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
13+
tag_ids = fields.Many2many("estate.property.tag")
14+
description = fields.Text()
15+
postcode = fields.Char()
16+
date_availability = fields.Date(copy=False, default=fields.Date.add(fields.Date.today(),months=3), string= "Available From")
17+
expected_price = fields.Float(required=True)
18+
selling_price = fields.Float(readonly=True,copy=False)
19+
bedrooms = fields.Integer(default="2")
20+
living_area = fields.Integer()
21+
facades = fields.Integer()
22+
garage = fields.Integer()
23+
garden = fields.Boolean()
24+
active = fields.Boolean(default=True)
25+
garden_area = fields.Integer()
26+
garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'),('east', 'East'),('west', 'West')])
27+
state = fields.Selection([('new','New'), ('offer_received','Offer Received'),('offer_accepted','Offer Accepted'),('sold','Sold'),('canceled','Canceled')], required=True, copy=False, default='new')
28+
user_id = fields.Many2one('res.users', string='Sale person', default=lambda self: self.env.user)
29+
partner_id = fields.Many2one('res.partner', string='Buyer', copy=False)
30+
offer_ids = fields.One2many('estate.property.offer', 'property_id')
31+
total_area=fields.Float(compute="_compute_total_area")
32+
amount=fields.Float()
33+
best_price=fields.Float(compute="_compute_best_price")
34+
35+
@api.depends("garden_area","living_area")
36+
def _compute_total_area(self):
37+
for rec in self:
38+
rec.total_area = rec.living_area + rec.garden_area
39+
40+
@api.depends("offer_ids.price")
41+
def _compute_best_price(self):
42+
for rec in self:
43+
rec.best_price = max(rec.offer_ids.mapped('price') or [0])
44+
45+
@api.onchange("garden")
46+
def _onchange_garden(self):
47+
if self.garden:
48+
self.garden_area = 10
49+
self.garden_orientation = 'north'
50+
else:
51+
self.garden_area = False
52+
self.garden_orientation = False
53+
54+
def action_sold(self):
55+
for record in self:
56+
if record.state == 'canceled':
57+
raise Warning('No puede vender una propiedad cancelada')
58+
record.state = 'sold'
59+
60+
def cancel_property(self):
61+
for record in self:
62+
if record.state == 'sold':
63+
raise Warning('No puede cancelar una propiedad vendida')
64+
record.state = 'canceled'
65+
66+
_sql_constraints = [
67+
('check_expected_price', 'CHECK(expected_price > 0)',
68+
'El precio esperado no puede ser negativo'),
69+
('check_selling_price', 'CHECK(selling_price >= 0)',
70+
'El precio de venta de una propiedad debe ser positivo')]
71+
72+
@api.constrains('selling_price', 'expected_price')
73+
def check_selling_price(self):
74+
for rec in self:
75+
percentage = rec.selling_price / rec.expected_price
76+
if float_is_zero(rec.selling_price, precision_digits=2) is False and percentage < 0.9:
77+
raise ValidationError('El precio de venta no puede ser inferior al 90% del precio esperado')
78+
79+
@api.model
80+
@api.ondelete(at_uninstall=False)
81+
def _check_ondelete(self):
82+
for record in self:
83+
if record.state not in ['new', 'canceled']:
84+
raise exceptions.UserError(
85+
"No se puede eliminar una propiedad que no esté en estado 'Nuevo' o 'Cancelado'.")
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from odoo import models, fields, api, exceptions
2+
from odoo.exceptions import ValidationError
3+
4+
class EstatePropertyOffer (models.Model):
5+
_name = "estate.property.offer"
6+
_description = 'Real Estate Properties Offers'
7+
_order = "price asc"
8+
9+
price = fields.Float()
10+
status = fields.Selection([('accepted', 'Accepted'),('refused', 'Refused')], copy=False)
11+
partner_id = fields.Many2one('res.partner', required=True)
12+
property_id = fields.Many2one('estate.property', required=True)
13+
validity = fields.Integer(default=7)
14+
date_deadline = fields.Date(string='Date Deadline', compute='_compute_date_deadline')
15+
# state = fields.Selection(related='property_id.state', string="Property State", readonly=True)
16+
17+
@api.depends("create_date","validity")
18+
def _compute_date_deadline(self):
19+
for rec in self:
20+
rec.date_deadline = fields.Date.add(
21+
rec.create_date, days=rec.validity) if rec.create_date else False
22+
23+
def _inverse_date_deadline(self):
24+
for rec in self:
25+
rec.validity=(rec.date_deadline - rec.create_date.date()).days
26+
27+
def action_accept_offer(self):
28+
for record in self:
29+
if 'accepted' in record.property_id.offer_ids.mapped("status"):
30+
raise Warning('No se puede aceptar más de una oferta')
31+
else:
32+
record.status = 'accepted'
33+
record.property_id.partner_id = record.partner_id
34+
record.property_id.selling_price = record.price
35+
return True
36+
37+
def action_refuse_offer(self):
38+
for record in self:
39+
record.status = 'refused'
40+
return True
41+
42+
_sql_constraints = [
43+
('check_price', 'CHECK(price > 0)','El precio de oferta no puede ser negativo')]
44+
45+
@api.model
46+
def create(self, vals):
47+
if 'property_id' in vals:
48+
property = self.env['estate.property'].browse(vals['property_id'])
49+
property.write({'state': 'offer_accepted'})
50+
existing_offers = self.search([('property_id', '=', property.id)])
51+
max_existing_amount = max(existing_offers.mapped('price'), default=0)
52+
if vals.get('price', 0) <= max_existing_amount:
53+
raise exceptions.UserError("El monto de la oferta debe ser mayor que cualquier oferta existente para esta propiedad.")
54+
return super(EstatePropertyOffer, self).create(vals)

estate/models/estate_property_tag.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from odoo import models,fields, api, exceptions
2+
from odoo.exceptions import ValidationError
3+
4+
class EstatePropertyTag (models.Model):
5+
_name = "estate.property.tag"
6+
_description = 'Real Estate Properties Tags'
7+
_order = "name desc"
8+
9+
name = fields.Char(string='Nombre')
10+
color = fields.Integer()
11+
12+
_sql_constraints = [
13+
('name_uniq', 'UNIQUE(name)', 'El nombre debe ser único.')]

estate/models/estate_property_type.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from odoo import models, fields, api, exceptions
2+
from odoo.exceptions import ValidationError
3+
4+
class EstatePropertyType (models.Model):
5+
_name = "estate.property.type"
6+
_description = 'Real Estate Properties Types'
7+
_order = "name asc"
8+
9+
name = fields.Char(required=True)
10+
sequence = fields.Integer()
11+
property_ids = fields.One2many("estate.property", "property_type_id")
12+
13+
_sql_constraints = [
14+
('name_uniq', 'UNIQUE(name)', 'El nombre debe ser único.')]

estate/models/res_users.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from odoo import models, fields
2+
3+
class InheritedResUsers(models.Model):
4+
_inherit = 'res.users'
5+
6+
property_ids = fields.One2many(
7+
comodel_name='estate.property',
8+
inverse_name='user_id',
9+
string='Properties',
10+
# domain=[('state', 'in', ['offer_accepted','offer_received', 'new'])]
11+
)

estate/security/ir.model.access.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
2+
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1
3+
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
4+
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1
5+
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1

estate/views/estate_menus.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version='1.0' encoding='utf-8'?>
2+
<odoo>
3+
<menuitem id="real_estate_menu" name="Real Estate"/>
4+
<menuitem id="advertisements_menus" name="Advertisements" parent="real_estate_menu"/>
5+
<menuitem id="settings_menu" name="Settings" sequence="99" parent="real_estate_menu"/>
6+
<menuitem id="properties_menu" action="estate_property_action" parent="advertisements_menus"/>
7+
<menuitem id="property_types_menu" name="Properties Types" action="estate_property_type_action" parent="settings_menu"/>
8+
<menuitem id="property_tags_menu" name="Properties Tags" action="estate_property_tag_action" parent="settings_menu"/>
9+
<menuitem id="property_offers_menu" name="Properties Offers" action="estate_property_offer_action" parent="settings_menu"/>
10+
</odoo>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<odoo>
2+
<record id="estate_property_offer_view_tree" model="ir.ui.view">
3+
<field name="name">estate.property.offer.tree</field>
4+
<field name="model">estate.property.offer</field>
5+
<field name="arch" type="xml">
6+
<tree editable="bottom" decoration-danger="status == 'rejected'" decoration-success="status == 'accepted'" >
7+
<field name="price"/>
8+
<field name="partner_id"/>
9+
<field name="validity"/>
10+
<field name="date_deadline"/>
11+
<button name="action_refuse_offer" type="object" icon="fa-close" title='refused' attrs="{'invisible': [('status', '!=', False)]}"/>
12+
<button name="action_accept_offer" type="object" icon="fa-check" title='accepted' attrs="{'invisible': [('status', '!=', False)]}"/>
13+
<field name="status"/>
14+
</tree>
15+
</field>
16+
</record>
17+
18+
<record id="estate_property_offer_view_form" model="ir.ui.view">
19+
<field name="name">estate.property.offer.form</field>
20+
<field name="model">estate.property.offer</field>
21+
<field name="arch" type="xml">
22+
<form>
23+
<sheet>
24+
<group>
25+
<field name="state" invisible="1"/>
26+
<field name="price" attrs="{'readonly': [('state', 'in', ['offer_accepted', 'sold', 'canceled'])]}"/>
27+
<field name="partner_id" attrs="{'readonly': [('state', 'in', ['offer_accepted', 'sold', 'canceled'])]}"/>
28+
<field name="status" attrs="{'readonly': [('state', 'in', ['offer_accepted', 'sold', 'canceled'])]}"/>
29+
<field name="validity" attrs="{'readonly': [('state', 'in', ['offer_accepted', 'sold', 'canceled'])]}"/>
30+
<field name="date_deadline" attrs="{'readonly': [('state', 'in', ['offer_accepted', 'sold', 'canceled'])]}"/>
31+
</group>
32+
</sheet>
33+
</form>
34+
</field>
35+
</record>
36+
37+
38+
<record id="estate_property_offer_action" model="ir.actions.act_window">
39+
<field name="name">Properties offers</field>
40+
<field name="res_model">estate.property.offer</field>
41+
<field name="view_mode">tree</field>
42+
</record>
43+
</odoo>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<odoo>
2+
<record id="estate_property_tag_view_tree" model="ir.ui.view">
3+
<field name="name">estate.property.tag.tree</field>
4+
<field name="model">estate.property.tag</field>
5+
<field name="arch" type="xml">
6+
<tree editable="bottom">
7+
<field name="name"/>
8+
</tree>
9+
</field>
10+
</record>
11+
12+
<record id="estate_property_tag_view_form" model="ir.ui.view">
13+
<field name="name">estate.property.tag.form</field>
14+
<field name="model">estate.property.tag</field>
15+
<field name="arch" type="xml">
16+
<form>
17+
<sheet>
18+
<group>
19+
<field name="name" class="mb16" placeholder="Title"/>
20+
<field name="color" widget="color_picker"/>
21+
</group>
22+
</sheet>
23+
</form>
24+
</field>
25+
</record>
26+
27+
<record id="estate_property_tag_view_search" model="ir.ui.view">
28+
<field name="name">estate.property.tag.search</field>
29+
<field name="model">estate.property.tag</field>
30+
<field name="arch" type="xml">
31+
<search>
32+
<field name="name"/>
33+
</search>
34+
</field>
35+
</record>
36+
37+
<record id="estate_property_tag_action" model="ir.actions.act_window">
38+
<field name="name">Properties Tags</field>
39+
<field name="res_model">estate.property.tag</field>
40+
</record>
41+
</odoo>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version='1.0' encoding='utf-8'?>
2+
<odoo>
3+
<record id="estate_property_type_view_tree" model="ir.ui.view">
4+
<field name="name">estate.property.type.tree</field>
5+
<field name="model">estate.property.type</field>
6+
<field name="arch" type="xml">
7+
<tree>
8+
<field name="sequence" widget="handle"/>
9+
<field name="name"/>
10+
</tree>
11+
</field>
12+
</record>
13+
14+
<record id="estate_property_type_view_form" model="ir.ui.view">
15+
<field name="name">estate.property.type.form</field>
16+
<field name="model">estate.property.type</field>
17+
<field name="arch" type="xml">
18+
<form>
19+
<sheet>
20+
<group>
21+
<field name="name" class="mb16" placeholder="Title"/>
22+
</group>
23+
<notebook>
24+
<page string="Properties">
25+
<field name="property_ids">
26+
<tree>
27+
<field name="name" string="Title"/>
28+
<field name="expected_price"/>
29+
<field name="state" string= "Status"/>
30+
</tree>
31+
</field>
32+
</page>
33+
</notebook>
34+
</sheet>
35+
</form>
36+
</field>
37+
</record>
38+
39+
<record id="estate_property_type_view_search" model="ir.ui.view">
40+
<field name="name">estate.property.type.search</field>
41+
<field name="model">estate.property.type</field>
42+
<field name="arch" type="xml">
43+
<search>
44+
<field name="name"/>
45+
</search>
46+
</field>
47+
</record>
48+
49+
<record id="estate_property_type_action" model="ir.actions.act_window">
50+
<field name="name">Properties Types</field>
51+
<field name="res_model">estate.property.type</field>
52+
</record>
53+
</odoo>

0 commit comments

Comments
 (0)