-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
First commit #823
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
} |
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 |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mind that the convention for model ordering is:
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,113 @@ | ||||||
from odoo import api,fields,models | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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') | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here ? |
||||||
postcode = fields.Char("Post code",required=False, translate=True) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And so on in other definitions |
||||||
bedrooms = fields.Integer("Bedrooms numbers",required=False, default=2) | ||||||
living_area = fields.Integer("Living Area",required=False) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, strings parameter default to the python definition minus the
|
||||||
facades = fields.Integer("Facades",required=False) | ||||||
garage = fields.Boolean("Garage",required=False) | ||||||
garden = fields.Boolean("Garden",required=False) | ||||||
Comment on lines
+33
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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_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") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
def _compute_total_area(self): | ||||||
for prop in self: | ||||||
prop.total_area = prop.living_area+prop.garden_area | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,73 @@ | ||||||
from odoo import fields,models,api | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The imports should be
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" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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") |
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 |
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) |
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 |
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> |
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"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're creating an action not a view here. The convention is |
||
<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> |
There was a problem hiding this comment.
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