-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
18.0 training aele #232
base: 18.0
Are you sure you want to change the base?
18.0 training aele #232
Changes from 8 commits
f30dfaf
e667c01
9d7fa57
aa0fcde
9fcc3e8
4fc5606
6cd46b9
26a0a2b
6afea5a
841b073
62be2b9
a7c9212
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 |
---|---|---|
@@ -1,11 +1,10 @@ | ||
# Odoo tutorials | ||
|
||
This repository hosts the code for the bases of the modules used in the | ||
[official Odoo tutorials](https://www.odoo.com/documentation/latest/developer/tutorials.html). | ||
This repository hosts the code for the bases and solutions of the | ||
[official Odoo tutorials](https://www.odoo.com/documentation/17.0/developer/tutorials.html). | ||
|
||
It has 3 branches for each Odoo version: one for the bases, one for the | ||
[Discover the JS framework](https://www.odoo.com/documentation/latest/developer/tutorials/discover_js_framework.html) | ||
tutorial's solutions, and one for the | ||
[Master the Odoo web framework](https://www.odoo.com/documentation/latest/developer/tutorials/master_odoo_web_framework.html) | ||
tutorial's solutions. For example, `17.0`, `17.0-discover-js-framework-solutions` and | ||
`17.0-master-odoo-web-framework-solutions`. | ||
It has 3 branches for each Odoo version: one for the bases, one for | ||
[Discover the JS framework](https://www.odoo.com/documentation/17.0/developer/tutorials/discover_js_framework.html) solutions and one for [Master the Odoo web framework](https://www.odoo.com/documentation/17.0/developer/tutorials/master_odoo_web_framework.html) solutions. For example, `17.0`, `17.0-discover-js-framework-solutions` and `17.0-master-odoo-web-framework-solutions`. | ||
The first contains the code of the modules that serve as base for the tutorials, | ||
and the others contains the code of each chapter with the complete | ||
solution. |
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': "Real Estate", | ||
'version': '1.0', | ||
'depends': ['base', 'mail'], | ||
'author': "Ahmed Elamery", | ||
'category': 'Training', | ||
|
||
'data' :[ | ||
'security/ir.model.access.csv', | ||
|
||
# views | ||
'views/estate_property_views.xml', | ||
'views/estate_property_type_views.xml', | ||
'views/estate_property_tag_views.xml', | ||
'views/estate_property_offer_views.xml', | ||
|
||
|
||
# menu | ||
'views/estate_menus.xml', | ||
|
||
], | ||
'installable': True, | ||
'application': True, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from . import estate_property | ||
from . import estate_property_type | ||
from . import estate_property_tag | ||
from . import estate_property_offer |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,113 @@ | ||||||||||||||||||||||||
from odoo import models, fields, api | ||||||||||||||||||||||||
from odoo.exceptions import UserError, ValidationError | ||||||||||||||||||||||||
from odoo.tools.float_utils import float_compare, float_is_zero, float_round | ||||||||||||||||||||||||
from datetime import timedelta | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
class EstateProperty(models.Model): | ||||||||||||||||||||||||
_name = "estate.property" | ||||||||||||||||||||||||
_description = "Property data model" | ||||||||||||||||||||||||
_inherit = ['mail.thread'] | ||||||||||||||||||||||||
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. Why are you inheriting from mail.threads? |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
#region Constraint | ||||||||||||||||||||||||
_sql_constraints = [ | ||||||||||||||||||||||||
('check_expected_price', 'CHECK(expected_price >= 0)', | ||||||||||||||||||||||||
'The expected price of a property MUST be postive.'), | ||||||||||||||||||||||||
('check_selling_price', 'CHECK(selling_price >= 0)', | ||||||||||||||||||||||||
'The selling price of a property MUST be postive.'), | ||||||||||||||||||||||||
] | ||||||||||||||||||||||||
@api.constrains('expected_price','selling_price') | ||||||||||||||||||||||||
def _check_selling_price(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
if float_is_zero(record.selling_price, precision_digits=3): | ||||||||||||||||||||||||
continue | ||||||||||||||||||||||||
if float_compare(value1=record.selling_price, value2=(0.9*record.expected_price),precision_digits=3) == -1: | ||||||||||||||||||||||||
raise ValidationError("Selling price must be at least 90% of the expected price!") | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
name = fields.Char(required=True) | ||||||||||||||||||||||||
description = fields.Text() | ||||||||||||||||||||||||
postcode = fields.Char() | ||||||||||||||||||||||||
date_availability = fields.Date(default=fields.Date.today()+ timedelta(days=90),copy=False) | ||||||||||||||||||||||||
expected_price = fields.Float(required=True) | ||||||||||||||||||||||||
selling_price = fields.Float(copy=False, readonly=True) | ||||||||||||||||||||||||
bedrooms = fields.Integer(default=2) | ||||||||||||||||||||||||
living_area = fields.Integer() | ||||||||||||||||||||||||
facades = fields.Integer() | ||||||||||||||||||||||||
garage = fields.Boolean() | ||||||||||||||||||||||||
garden = fields.Boolean() | ||||||||||||||||||||||||
garden_area = fields.Integer() | ||||||||||||||||||||||||
garden_orientation = fields.Selection( | ||||||||||||||||||||||||
selection=[('n', 'North'), | ||||||||||||||||||||||||
('s', 'South'), | ||||||||||||||||||||||||
('e', 'East'), | ||||||||||||||||||||||||
('w', 'West'), | ||||||||||||||||||||||||
], | ||||||||||||||||||||||||
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
what you did is not wrong but this is the name that will be used in the database so it's better to be more that one character |
||||||||||||||||||||||||
) | ||||||||||||||||||||||||
active = fields.Boolean(default=True) | ||||||||||||||||||||||||
state = fields.Selection(selection=[ | ||||||||||||||||||||||||
('new','New'), | ||||||||||||||||||||||||
('offer_received','Offer Received'), | ||||||||||||||||||||||||
('offer_accepted','Offer Accepted'), | ||||||||||||||||||||||||
('sold','Sold'), | ||||||||||||||||||||||||
('cancelled','Cancelled'), | ||||||||||||||||||||||||
], | ||||||||||||||||||||||||
default = 'new', | ||||||||||||||||||||||||
copy = False | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# Relations | ||||||||||||||||||||||||
property_type_id = fields.Many2one(comodel_name="estate.property.type") | ||||||||||||||||||||||||
property_tag_ids = fields.Many2many(comodel_name='estate.property.tag') | ||||||||||||||||||||||||
property_offer_ids = fields.One2many(comodel_name='estate.property.offer',inverse_name='property_id') | ||||||||||||||||||||||||
user_id = fields.Many2one(comodel_name='res.users', string='Salesperson', default=lambda self: self.env.uid) | ||||||||||||||||||||||||
partner_id = fields.Many2one(comodel_name='res.partner', string='Buyer', copy=False) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# computed | ||||||||||||||||||||||||
total_area = fields.Integer(compute="_compute_total_area") | ||||||||||||||||||||||||
best_price = fields.Float(compute="_compute_best_price") | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
#region Compute methodes | ||||||||||||||||||||||||
@api.depends('living_area','garden_area') | ||||||||||||||||||||||||
def _compute_total_area(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
record.total_area = record.living_area + record.garden_area | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@api.depends('property_offer_ids.price') | ||||||||||||||||||||||||
def _compute_best_price(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
record.best_price = max(record.property_offer_ids.mapped('price') or [0]) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
#endregion | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
#region onchange | ||||||||||||||||||||||||
@api.onchange('garden') | ||||||||||||||||||||||||
def _onchange_garden(self): | ||||||||||||||||||||||||
self.garden_area = 10 if self.garden else False | ||||||||||||||||||||||||
self.garden_orientation = 'n' if self.garden else False | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
#endregion | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# region actions | ||||||||||||||||||||||||
def action_set_cancelled(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
if record.state == 'sold': | ||||||||||||||||||||||||
raise UserError("Sold properties can not be cancelled!") | ||||||||||||||||||||||||
record.state = 'cancelled' | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def action_set_sold(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
if record.state == 'cancelled': | ||||||||||||||||||||||||
raise UserError("Cancelled properties can not be sold!") | ||||||||||||||||||||||||
record.state = 'sold' | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def action_set_new(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
record.state = 'new' | ||||||||||||||||||||||||
self.selling_price = False | ||||||||||||||||||||||||
self.partner_id = False | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def action_offer_accepted(self, offer): | ||||||||||||||||||||||||
if self.state == 'offer_accepted': | ||||||||||||||||||||||||
raise UserError("this property has already an accepted offer!!") | ||||||||||||||||||||||||
self.state = 'offer_accepted' | ||||||||||||||||||||||||
self.selling_price = offer.price | ||||||||||||||||||||||||
self.partner_id = offer.partner_id |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from odoo import models, fields, api | ||
from odoo.exceptions import UserError | ||
|
||
from datetime import timedelta, date | ||
|
||
|
||
class EstatePropertyOffer(models.Model): | ||
_name = 'estate.property.offer' | ||
_description = '' | ||
_sql_constraints = [ | ||
('check_price', 'CHECK(price >= 0)', | ||
'The price of an offer MUST be postive.'), | ||
] | ||
|
||
price = fields.Float() | ||
status = fields.Selection([ | ||
('accepted', 'Accepted'), | ||
('refused', 'Refused'), | ||
], | ||
copy=False, | ||
) | ||
validity = fields.Integer(string="Validity (Days)", default=7) | ||
|
||
# Relations | ||
partner_id = fields.Many2one(comodel_name='res.partner', required=True) | ||
property_id = fields.Many2one(comodel_name='estate.property', required=True) | ||
|
||
# computed | ||
date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline') | ||
|
||
# region Compute methodes | ||
@api.depends('validity') | ||
def _compute_date_deadline(self): | ||
for record in self: | ||
if not record.create_date: | ||
record.create_date = fields.Date.today() | ||
record.date_deadline = record.create_date + timedelta(days=record.validity) | ||
|
||
def _inverse_date_deadline(self): | ||
for record in self: | ||
if not record.create_date: | ||
record.create_date = fields.Date.today() | ||
record.validity = (record.date_deadline - record.create_date.date()).days | ||
|
||
# endregion | ||
|
||
# region actions | ||
def action_set_accepted(self): | ||
for record in self: | ||
try: | ||
record.property_id.action_offer_accepted(self) | ||
record.status = 'accepted' | ||
except UserError as e: | ||
raise e | ||
|
||
def action_set_refused(self): | ||
for record in self: | ||
record.status = 'refused' | ||
|
||
def action_reset(self): | ||
for record in self: | ||
if record.status == 'accepted': | ||
record.property_id.action_set_new() | ||
record.status = False |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from odoo import models, fields | ||
|
||
class EstatePropertyTag(models.Model): | ||
_name = 'estate.property.tag' | ||
_description = 'Tags for our properties' | ||
_sql_constraints = [ | ||
('check_name', 'UNIQUE(name)', | ||
'The property tag names MUST be unique.'), | ||
] | ||
|
||
name = fields.Char(requird=True) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from odoo import models, fields, api | ||
|
||
class EstatePropertyType(models.Model): | ||
_name = 'estate.property.type' | ||
_description = 'Types for our properties' | ||
_order = "sequence, name" | ||
|
||
name = fields.Char(requird=True) | ||
|
||
sequence = fields.Integer() | ||
# Relations | ||
property_ids = fields.One2many(comodel_name='estate.property', inverse_name='property_type_id') | ||
offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_type_id') | ||
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. There is no field 'property_type_id' in model 'estate.property.offer' |
||
# computed | ||
offer_count = fields.Integer(compute='_compute_offer_count') | ||
|
||
# region Compute methodes | ||
@api.depends('offer_ids') | ||
def _compute_offer_count(self): | ||
for record in self: | ||
record.offer_count = len(self.offer_ids) | ||
|
||
|
||
_sql_constraints = [ | ||
('check_name', 'UNIQUE(name)', | ||
'The property type names MUST be unique.'), | ||
] |
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 | ||
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 | ||
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<odoo> | ||
<menuitem id="estate_menu_root" name="Real Estate"> | ||
<menuitem id="estate_advertisements_menu" name="Advertisements"> | ||
<menuitem id="estate_property_menu_action" action="estate_property_action" name="Properties"/> | ||
</menuitem> | ||
<menuitem id="estate_settings_menu" name="Settings"> | ||
<menuitem id="estate_property_type_menu_action" action="estate_property_type_action" name="Property Types"/> | ||
<menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action" name="Property Tags"/> | ||
|
||
</menuitem> | ||
</menuitem> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<odoo> | ||
<record id="estate_property_offer_view_tree" model="ir.ui.view"> | ||
<field name="name">estate.property.offer.tree</field> | ||
<field name="model">estate.property.offer</field> | ||
<field name="arch" type="xml"> | ||
<list string="Offers"> | ||
<field name="price" string="Price"/> | ||
<field name="partner_id" string="Partner"/> | ||
<field name="validity" /> | ||
<field name="date_deadline" string="Deadline"/> | ||
<field name="status" string="Status"/> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<record id="estate_property_offer_view_form" model="ir.ui.view"> | ||
<field name="name">estate.property.offer.form</field> | ||
<field name="model">estate.property.offer</field> | ||
<field name="arch" type="xml"> | ||
<form string="Properties"> | ||
<sheet> | ||
<group> | ||
<field name="price" string="Price"/> | ||
<field name="partner_id" string="Partner"/> | ||
<field name="validity" /> | ||
<field name="date_deadline" string="Deadline"/> | ||
<button name="action_set_accepted" string="Accept" type="object" icon="fa-check" invisible="status"/> | ||
<button name="action_set_refused" string="Reject" type="object" icon="fa-times" invisible="status"/> | ||
<field name="status" string="Status"/> | ||
</group> | ||
</sheet> | ||
|
||
</form> | ||
</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<odoo> | ||
|
||
|
||
<record id="estate_property_tag_action" model="ir.actions.act_window"> | ||
<field name="name">Estate Property Tag</field> | ||
<field name="res_model">estate.property.tag</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.
why are you depending on mail?!!