Skip to content

First commit #823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to always add a EOL in each file so that when someone adds lines in the future, the last line of the previous devs is not in the diff

24 changes: 24 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
'name': "Estate",
'version': '1.0',
'depends': ['base'],
'author': "Odoo",
'category': 'Category',
'description': """
Just a dummy estate app

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not nice to call it dummy ... It has feeling too :/

""",
'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',
}
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
113 changes: 113 additions & 0 deletions estate/models/estate_property.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind that the convention for model ordering is:

  1. Private attributes (_name, _description, _inherit, _sql_constraints, …)
  2. Default method and default_get
  3. Field declarations
  4. Compute, inverse and search methods in the same order as field declaration
  5. Selection method (methods used to return computed values for selection fields)
  6. Constrains methods (@api.constrains) and onchange methods (@api.onchange)
  7. CRUD methods (ORM overrides)
  8. Action methods
  9. And finally, other business methods.

Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from odoo import api,fields,models

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from odoo import api,fields,models
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')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think the translate does here ?

description = fields.Text("Description",required=True, translate=True)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here ?

postcode = fields.Char("Post code",required=False, translate=True)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here ?

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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expected_price = fields.Float("Expected Price",required=False)
expected_price = fields.Float("Expected Price", required=False)

selling_price = fields.Float("Selling Price",readonly=True, copy=False, required=False)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
selling_price = fields.Float("Selling Price",readonly=True, copy=False, required=False)
selling_price = fields.Float("Selling Price", readonly=True, copy=False, required=False)

And so on in other definitions

bedrooms = fields.Integer("Bedrooms numbers",required=False, default=2)
living_area = fields.Integer("Living Area",required=False)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, strings parameter default to the python definition minus the _id(s) suffix. So,

living_area -> "Living Area"
partner_id -> "Partner"

facades = fields.Integer("Facades",required=False)
garage = fields.Boolean("Garage",required=False)
garden = fields.Boolean("Garden",required=False)
Comment on lines +33 to +35

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required defaults to False for basic fields, so it is not needed. There is no harm in having a field definition empty like garden = fields.Boolean()

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")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@api.depends("living_area","garden_area")
@api.depends("living_area", "garden_area")

def _compute_total_area(self):
for prop in self:
prop.total_area = prop.living_area+prop.garden_area

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
prop.total_area = prop.living_area+prop.garden_area
prop.total_area = prop.living_area + prop.garden_area


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra tab

@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"
73 changes: 73 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from odoo import fields,models,api

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from odoo import fields,models,api
from odoo import api, fields, models

The imports should be

  1. External libraries (one per line sorted and split in python stdlib)
  2. Imports of odoo
  3. Imports from Odoo modules (rarely, and only if necessary)

Inside these 3 groups, the imported lines are alphabetically sorted. You can use RUFF to help you sort them, it's quite handy

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"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file ? Your description what this model is, not the 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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the validity be computed from the creation date ? I mean, you create an offer today with a validity of 7 days. It should expire next Monday. Currently, it always expires 7 days after today so, if I look tomorrow, it will expire next Tuesday


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"]<max_offer:
raise UserError("offer with better price already exist")
else:
prop.estate_state="Offer_Received"
return super().create(vals)

#Action methods
def action_refuse_offer(self):
for record in self:
if record.status == "Accepted":
raise UserError('Unable to refuse offer, please change offer status before continuing.')
record.status = "Refused"

def action_accept_offer(self):
for record in self:
if record.property_id.estate_state=="Cancelled" or record.property_id.estate_state=="Sold":
raise UserError("Unable to accept offer (estate can't be sold), please change estate status before continuing.")
if record.status == "Refused":
raise UserError('Unable to accept offer, please change offer status before continuing.')
record.status = "Accepted"
record.property_id.estate_state="Offer_Accepted"
record.property_id.property_buyer_id = record.partner_id
record.property_id.selling_price =record.price
16 changes: 16 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields,models


class estate_property_tag(models.Model):
# Private attributes
_name = "estate.property.tag"
_description = "Estate property tag file"
_order = "name"

_sql_constraints = [
("check_name", "UNIQUE(name)", "The name must be unique"),
]

# Fields declaration
name = fields.Char('Name',required=True, translate=True, default='Unknown')
color = fields.Integer("couleur")
26 changes: 26 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from odoo import fields,models,api


class estate_property_type(models.Model):
# Private attributes
_name = "estate.property.type"
_description = "Estate property type file"
_order = "sequence, name"

_sql_constraints = [
("check_name", "UNIQUE(name)", "The name must be unique"),
]

# Fields declaration
name = fields.Char('Name',required=True, translate=True, default='Unknown')
property_ids = fields.One2many("estate.property","property_type_id",string="Property")
offer_ids = fields.One2many("estate.property.offer","property_type_id",string="Property")
offer_count = fields.Integer("Offer counted",compute="_compute_offer_count")

sequence = fields.Integer('Sequence', default=1)

# compute and search fields
@api.depends("offer_ids.price")
def _compute_offer_count(self):
for record in self:
record.offer_count=len(record.offer_ids.mapped("price")) if record.offer_ids else 0
14 changes: 14 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from odoo import api,fields,models

class res_users(models.Model):
# Private attributes
_inherit = "res.users"

# Fields declaration
property_ids = fields.One2many("estate.property","property_salesman_id",string="Properties",domain=[("estate_state","in",["New","Offer_Received"])])

# @api.onchange("property_ids")
# def _onchange_garden(self):
# print("####################################################")
# for record in self:
# print(record.property_ids)
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
43 changes: 43 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem
id="estate_menu_root"
name="Estate"
sequence="5"
/>

<menuitem
id="estate_menu_advertissement"
name="Advertissement"
parent="estate_menu_root"
sequence="10"
/>
<menuitem
id="estate_menu_advertissement_property"
name="Property"
parent="estate_menu_advertissement"
action="estate_property_view"
sequence="15"
/>

<menuitem
id="estate_menu_settings"
name="Settings"
parent="estate_menu_root"
sequence="10"
/>
<menuitem
id="estate_menu_settings_type"
name="Property Types"
parent="estate_menu_settings"
action="estate_property_type_view"
sequence="15"
/>
<menuitem
id="estate_menu_settings_tag"
name="Property Tags"
parent="estate_menu_settings"
action="estate_property_tag_view"
sequence="15"
/>
</odoo>
46 changes: 46 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_offer_view_form" model="ir.ui.view">
<field name="name">estate.property.offer.view.form</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form string="Estate Property Offers">
<sheet>
<group>
<h1>
<field name="price"/>
</h1>
<field name="status"/>
<field name="partner_id"/>
<field name="property_id"/>
<field name="date_dateline"/>
<field name="validity"/>
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_offer_view_tree" model="ir.ui.view">
<field name="name">estate.property.offer.view.list</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<list string="Channel" editable ='bottom' decoration-success="status=='Accepted'" decoration-danger="status=='Refused'">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_dateline" optional="True"/>
<field name="property_type_id"/>
<button name="action_accept_offer" type="object" string="Accept" icon="fa-check" invisible="status!='In Waiting'"/>
<button name="action_refuse_offer" type="object" string="Refuse" icon="fa-times" invisible="status!='In Waiting'"/>
</list>
</field>
</record>

<record id="estate_property_offer_view" model="ir.actions.act_window">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're creating an action not a view here. The convention is <model_name>_action

<field name="name">Estate Properties Offers</field>
<field name="res_model">estate.property.offer</field>
<field name="domain">[('property_type_id','=', active_id)]</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
Loading