Skip to content

Commit

Permalink
[ADD] Estate Module
Browse files Browse the repository at this point in the history
  • Loading branch information
fres-sudo committed Oct 28, 2024
1 parent b16e643 commit 94d5319
Show file tree
Hide file tree
Showing 27 changed files with 702 additions and 3 deletions.
16 changes: 16 additions & 0 deletions awesome_owl/static/src/card/card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/** @odoo-module */

import { Component } from "@odoo/owl";

export class Card extends Component {}

Card.template = "awesome_owl.Card";
Card.props = {
slots: {
type: Object,
shape: {
default: Object,
title: { type: Object, optional: true },
},
},
};
13 changes: 13 additions & 0 deletions awesome_owl/static/src/card/card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.Card" owl="1">
<div class="card" style="width: 18rem;">
<div class="card-body">
<t t-if="props.slots.title">
<h5 class="card-title"><t t-slot="title"/></h5>
</t>
<p class="card-text"><t t-slot="default"/></p>
</div>
</div>
</t>
</templates>
15 changes: 15 additions & 0 deletions awesome_owl/static/src/counter/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @odoo-module **/

import { Component, useState } from "@odoo/owl";

export class Counter extends Component {
static template = "awesome_owl.Counter";

setup() {
this.state = useState({ value: 0 });
}

increment() {
this.state.value++;
}
}
7 changes: 7 additions & 0 deletions awesome_owl/static/src/counter/counter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.Counter" owl="1">
<p>Counter: <t t-esc="state.value"/></p>
<button class="btn btn-primary" t-on-click="increment">Increment</button>
</t>
</templates>
3 changes: 3 additions & 0 deletions awesome_owl/static/src/playground.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/** @odoo-module **/

import { Component } from "@odoo/owl";
import { Counter } from "./counter/counter";
import { Card } from "./card/card"

export class Playground extends Component {
static template = "awesome_owl.playground";
static components = { Counter, Card }
}
8 changes: 6 additions & 2 deletions awesome_owl/static/src/playground.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.playground">
<t t-name="awesome_owl.playground" owl="1">
<div class="p-3">
hello world
<Counter />
<Card>
<t t-set-slot="title">awesome card</t>
awesome text
</Card>
</div>
</t>

Expand Down
2 changes: 1 addition & 1 deletion awesome_owl/views/templates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
</html>
</template>
</data>
</odoo>
</odoo>
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
24 changes: 24 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

{
'name': 'Estate',
'category': 'Tutorials/Estate',
'summary': 'Real estate application',
'description': "",
'depends': [
'base_setup',
],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_offer_views.xml',
'views/res_users_views.xml',
'views/estate_menu.xml',
],
'installable': True,
'application': True,
'auto_install': False,
'license': 'OEEL-1'
}
13 changes: 13 additions & 0 deletions estate/demo/demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="model_estate_property_action_cancel" model="ir.actions.server">
<field name="name">Mass cancel</field>
<field name="model_id" ref="estate.model_estate_property"/>
<field name="binding_model_id" ref="estate.model_estate_property"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">action = records.action_cancel()</field>
</record>
</data>
</odoo>
5 changes: 5 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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
111 changes: 111 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from odoo import api, models, fields
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"

name = fields.Char("Estate Name", required=True)
description = fields.Text("Description")
postcode = fields.Char("Postalcode")
date_availability = fields.Date('Date Availability', default=lambda self: date.today() + relativedelta(months=3))
expected_price = fields.Float('Expected Price', required=True)
selling_price = fields.Float('Selling Price', readonly=True)
bedrooms = fields.Integer("Bedrooms", default=2)
living_area = fields.Integer("Living Area (sqm)")
facades = fields.Integer("Facades")
garage = fields.Boolean("Garage")
garden = fields.Boolean("Garden")
garden_area = fields.Integer("Garden Area (sqm)")
garden_orientation = fields.Selection(selection=[
('north', 'NORTH'),
('south', 'SOUTH'),
('west', 'WEST'),
('east', 'EAST')
])
active = fields.Boolean("Active", default=True)
state = fields.Selection(selection=[
('new', 'NEW'),
('offer_received', 'OFFER RECEIVED'),
('offer_accepted', 'OFFER ACCEPTED'),
('sold', 'SOLD'),
('cancelled', 'CANCELLED')
], default='new')
property_type_id = fields.Many2one('estate.property.type', string='Real Estate Type')
buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False)
salesperson_id = fields.Many2one('res.partner', string='Salesperson', default=lambda self: self.env.user)
tag_ids = fields.Many2many('estate.property.tag', string='Real Estate Tag')
offer_ids = fields.One2many('estate.property.offer', inverse_name='property_id')
total_area = fields.Float('Total Area (sqm)', compute='_compute_total_area', readonly=True)
best_offer = fields.Float('Best Offer', compute='_compute_best_offer', readonly=True)
_order = 'id desc'

_sql_constraints = [
('expected_price', 'CHECK(expected_price > 0)',
'The expected price should be strictly greater than 0.'),
('selling_price', 'CHECK(selling_price >= 0)',
'The selling price should be zero or strictly greater than 0 if an offer is accepted.'),
]

def action_reset_to_draft(self):
for record in self:
if record.state == 'sold':
raise UserError("You cannot reset a sold property to draft.")
record.state = 'new'

def action_open_offers(self):
self.ensure_one()
return {
'name': 'Property Offer',
'views': [(self.env.ref('estate.estate_property_offer_view_list').id, 'list')],
'type': 'ir.actions.act_window',
'domain': [('id', 'in', self.offer_ids.ids)],
'res_model': 'estate.property.offer'
}

def action_cancel(self):
if self.state != 'cancelled':
self.state = 'cancelled'

def action_sold(self):
if self.state == 'cancelled':
raise UserError("You can't sold an estate marked as CANCELLED")
self.state = 'sold'

@api.ondelete(at_uninstall=False)
def on_delete(self, vals_list):
for val in vals_list:
if val and val['state'] == 'new' or val['state'] == 'cancelled':
raise UserError('You can not delete a new or cancelled property')

@api.constrains('expected_price', 'selling_price')
def _check_expected_price(self):
for record in self:
if float_compare(record.expected_price * 0.9, record.selling_price, 3) == 1 and record.selling_price != 0:
raise ValidationError("The selling price must be at least the 90% of the expected price.")

@api.depends("living_area", "garden_area")
def _compute_total_area(self):
for record in self:
record.total_area = record.living_area + (record.garden_area or 0)

@api.depends("offer_ids.price")
def _compute_best_offer(self):
for record in self:
if record.offer_ids:
record.best_offer = max(offer.price for offer in record.offer_ids)
else:
record.best_offer = 0

@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 = None
74 changes: 74 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from odoo import api, models, fields
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Real Estate Property Offer"

price = fields.Float("Offer Price", required=True)
status = fields.Selection(selection=[
('accepted', 'ACCEPTED'),
('refused', 'REFUSED')
])
partner_id = fields.Many2one('res.partner', required=True)
property_id = fields.Many2one('estate.property', required=True)
validity = fields.Integer('Validity', default=7)
date_deadline = fields.Date('Date Deadline', compute='_compute_date_deadline', inverse='_inverse_date_deadline', store=True)
property_type_id = fields.Many2one(
"estate.property.type", related="property_id.property_type_id", string="Property Type", store=True
)
_order = 'price desc'

_sql_constraints = [
('price', 'CHECK(price > 0)',
'The price of an offer should be strictly grater than 0.')
]

@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if val.get("property_id") and val.get("price"):
prop = self.env["estate.property"].browse(val["property_id"])

if prop.offer_ids:
max_offer = max(prop.mapped("offer_ids.price"))
if float_compare(val["price"], max_offer, precision_rounding=0.01) <= 0:
raise UserError("The offer must be higher than %.2f" % max_offer)
prop.state = "offer_received"
return super().create(vals_list)

def action_accept(self):
for offer in self.property_id.offer_ids:
if offer.status == 'accepted':
raise UserError("An offer has already been accepted for this property.")
if self.status != 'accepted':
if self.property_id.state == 'sold':
raise UserError("You can't accept an offer for a property already sold")
self.status = 'accepted'
self.property_id.state = 'offer_accepted'
self.property_id.selling_price = self.price
self.property_id.buyer_id = self.partner_id.id
else:
raise UserError("You can't accept an offer already accepted")

def action_refuse(self):
if not self.status:
self.status = 'refused'
else:
raise UserError("You can't refuse an offer already refused or already accepted")

@api.depends("create_date", "validity")
def _compute_date_deadline(self):
for record in self:
create_date = record.create_date or fields.Date.context_today(record)
record.date_deadline = create_date + relativedelta(days=record.validity)

def _inverse_date_deadline(self):
for record in self:
if record.create_date:
record.validity = (record.date_deadline - record.create_date.date()).days
else:
record.validity = 0
15 changes: 15 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import models, fields


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Real Estate Property Tag"

name = fields.Char("Estate Tag", required=True)
color = fields.Integer('Color')
_order = 'name desc'

_sql_constraints = [
('name', 'UNIQUE(name)',
'The name of the tag should be unique.')
]
27 changes: 27 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from odoo import models, fields


class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Real Estate Property Type"

name = fields.Char("Estate Type", required=True)
property_ids = fields.One2many('estate.property', inverse_name='property_type_id')
sequence = fields.Integer('Sequence', default=1, help="Used to order types on the business needs")
offer_ids = fields.One2many('estate.property.offer', inverse_name='property_type_id')
_order = 'name desc'

_sql_constraints = [
('name', 'UNIQUE(name)',
'The name of the type should be unique.')
]

def action_open_offers(self):
self.ensure_one()
return {
'name': 'Property Offer',
'views': [(False, 'list')],
'type': 'ir.actions.act_window',
'domain': [('id', 'in', self.offer_ids.ids)],
'res_model': 'estate.property.offer'
}
9 changes: 9 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from odoo import fields, models


class ResUsers(models.Model):
_inherit = "res.users"

property_ids = fields.One2many(
"estate.property", inverse_name="salesperson_id", domain=[("state", "in", ["new", "offer_received"])]
)
5 changes: 5 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.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
Loading

0 comments on commit 94d5319

Please sign in to comment.