-
Notifications
You must be signed in to change notification settings - Fork 2.5k
trcaz - Tutorial PR #944
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
base: 19.0
Are you sure you want to change the base?
trcaz - Tutorial PR #944
Changes from all commits
f43473a
f9f924c
5de6ad0
307ffc1
c2fe2b0
fd35e0f
f6b36d9
5a5f816
04fbf9c
458aadb
3f55e82
2a1c725
40dea79
bb8aae8
dadda3a
4d3532f
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,3 @@ | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
|
||
from . import models |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
|
||
{ | ||
'name': 'Estate', | ||
'depends': [ | ||
'base', | ||
], | ||
'version': '19.0.0.0', | ||
'installable': True, | ||
'application': True, | ||
'data': [ | ||
'views/res_users_views.xml', | ||
'views/estate_property_offer_views.xml', | ||
'views/estate_property_tag_views.xml', | ||
'views/estate_property_type_views.xml', | ||
'views/estate_property_views.xml', | ||
'views/estate_menus.xml', | ||
'security/ir.model.access.csv', | ||
], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
|
||
from . import ( | ||
estate_property, | ||
estate_property_offer, | ||
estate_property_tag, | ||
estate_property_type, | ||
res_users, | ||
) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,86 @@ | ||||||
from datetime import date, timedelta | ||||||
|
||||||
from odoo import _, api, fields, models | ||||||
from odoo.exceptions import UserError | ||||||
|
||||||
|
||||||
class Property(models.Model): | ||||||
_name = "estate.property" | ||||||
_description = "Test description for estate.property model" | ||||||
_order = "id DESC" | ||||||
|
||||||
name = fields.Char(required=True) | ||||||
expected_price = fields.Float(required=True) | ||||||
property_type_id = fields.Many2one("estate.property.type", string="Property Type") | ||||||
state = fields.Selection( | ||||||
selection=[("new", "New"), ("offer_received", "Offer Received"), ("offer_accepted", "Offer accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")], | ||||||
default="new", | ||||||
) | ||||||
description = fields.Text() | ||||||
postcode = fields.Char() | ||||||
selling_price = fields.Float(copy=False, readonly=True) | ||||||
date_availability = fields.Date(copy=False, default=lambda: date.today() + timedelta(days=90)) | ||||||
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( | ||||||
string='Orientation', | ||||||
selection=[("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")]) | ||||||
active = fields.Boolean(default=True) | ||||||
buyer_id = fields.Many2one("res.partner", string="Buyer") | ||||||
salesperson_id = fields.Many2one("res.users", string="Salesperson", copy=False, default=lambda self: self.env.user) | ||||||
tags_ids = fields.Many2many("estate.property.tag", string="Tags") | ||||||
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") | ||||||
total_area = fields.Float(compute="_compute_total_area") | ||||||
best_price = fields.Float(compute="_compute_best_price") | ||||||
|
||||||
_check_expected_price = models.Constraint( | ||||||
"CHECK(expected_price > 0)", | ||||||
"The expected price must be strictly positive", | ||||||
) | ||||||
_check_selling_price = models.Constraint( | ||||||
"CHECK (selling_price >= 0)", | ||||||
"The selling price must be positive", | ||||||
) | ||||||
|
||||||
@api.depends("garden_area", "living_area") | ||||||
def _compute_total_area(self): | ||||||
for record in self: | ||||||
record.total_area = record.living_area + record.garden_area | ||||||
|
||||||
@api.depends("offer_ids") | ||||||
def _compute_best_price(self): | ||||||
for record in self: | ||||||
record.best_price = max(o.price for o in record.offer_ids) if record.offer_ids else 0 | ||||||
|
||||||
@api.onchange("garden") | ||||||
def _onchange_garden(self): | ||||||
if self.garden: | ||||||
self.garden_area = 10 | ||||||
self.garden_orientation = "north" | ||||||
else: | ||||||
self.garden_area = None | ||||||
self.garden_orientation = None | ||||||
|
||||||
@api.ondelete(at_uninstall=False) | ||||||
def _prevent_deletion_unless_new_or_cancelled(self): | ||||||
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
couldn't find a rule for that in the guidelines but most ondelete methods start with unlink |
||||||
for record in self: | ||||||
if record.state not in ['new', 'cancelled']: | ||||||
raise UserError(_("A property can only be deleted if its state is 'New' or 'Cancelled'")) | ||||||
|
||||||
def action_property_cancel(self): | ||||||
for record in self: | ||||||
if record.state == "sold": | ||||||
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
R&D string rule 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. For all the strings or just for "values" like that? 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. I thought I explained it in your PR as well sorry. |
||||||
raise UserError(_("A sold property cannot be cancelled.")) | ||||||
record.state = "cancelled" | ||||||
return True | ||||||
|
||||||
def action_property_sold(self): | ||||||
for record in self: | ||||||
if record.state == "cancelled": | ||||||
raise UserError(_("A cancelled property cannot be sold.")) | ||||||
record.state = "sold" | ||||||
return True |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,64 @@ | ||||||
from datetime import datetime, timedelta | ||||||
|
||||||
from odoo import _, api, exceptions, fields, models | ||||||
from odoo.tools.float_utils import float_compare | ||||||
|
||||||
|
||||||
class PropertyOffer(models.Model): | ||||||
_name = "estate.property.offer" | ||||||
_description = "Test description for estate.property.offer model" | ||||||
_order = "price DESC" | ||||||
|
||||||
price = fields.Float() | ||||||
status = fields.Selection( | ||||||
string="Offer Status", | ||||||
copy=False, | ||||||
selection=[("accepted", "Accepted"), ("refused", "Refused")]) | ||||||
partner_id = fields.Many2one("res.partner", required=True) | ||||||
property_id = fields.Many2one("estate.property", required=True) | ||||||
validity = fields.Integer(default=7) | ||||||
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") | ||||||
property_type_id = fields.Many2one(related="property_id.property_type_id", store=True) | ||||||
|
||||||
_check_price = models.Constraint( | ||||||
"CHECK (price > 0)", | ||||||
"The price must be strictly positive", | ||||||
) | ||||||
|
||||||
@api.depends("validity") | ||||||
def _compute_date_deadline(self): | ||||||
for record in self: | ||||||
# record.create_date is "falsy" so if checking with `record.create_date if hasattr(record.create_date) else datetime.today()` then it's true because it hasattr but it's None so it's converted to false | ||||||
record.date_deadline = ((record.create_date or datetime.today()) + timedelta(days=record.validity)).date() | ||||||
|
||||||
def _inverse_date_deadline(self): | ||||||
for record in self: | ||||||
record.validity = (record.date_deadline - record.create_date.date()).days | ||||||
|
||||||
@api.constrains("price") | ||||||
def _check_selling_price_90_percent(self): | ||||||
for record in self: | ||||||
if float_compare(record.price, 0.9 * record.property_id.expected_price, precision_digits=2) == -1: | ||||||
raise exceptions.UserError(_("The selling price cannot be lower than 90% of the expected price")) | ||||||
|
||||||
@api.depends("property_id", "property_id.offer_ids") | ||||||
def action_offer_accept(self): | ||||||
for record in self: | ||||||
if any(o.status == "accepted" for o in record.property_id.offer_ids): | ||||||
raise exceptions.UserError(_("Cannot accept more than one offer")) | ||||||
if float_compare(record.price, 0.9 * record.property_id.expected_price, precision_digits=2) == -1: | ||||||
raise exceptions.UserError(_("The selling price cannot be lower than 90% of the expected price")) | ||||||
record.status = "accepted" | ||||||
record.property_id.buyer_id = record.partner_id | ||||||
record.property_id.state = 'offer_accepted' | ||||||
record.property_id.selling_price = record.price | ||||||
|
||||||
@api.model_create_multi | ||||||
def create(self, vals_list): | ||||||
for record in vals_list: | ||||||
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
These are not records yet so the name can be confusing |
||||||
property = self.env['estate.property'].browse(record['property_id']) | ||||||
property.state = 'offer_received' | ||||||
Comment on lines
+59
to
+60
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. Here you're executing |
||||||
return super().create(vals_list) | ||||||
|
||||||
def action_offer_refuse(self): | ||||||
self.status = "refused" # assigns the same value to all the records |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class PropertyTag(models.Model): | ||
_name = "estate.property.tag" | ||
_description = "Test description for estate.property.tag model" | ||
_order = "name" | ||
|
||
name = fields.Char(required=True) | ||
color = fields.Integer() | ||
|
||
_check_name = models.Constraint( | ||
"UNIQUE (name)", | ||
"Property tag name must be unique", | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class PropertyType(models.Model): | ||
_name = "estate.property.type" | ||
_description = "Test description for estate.property.type model" | ||
_order = "name" | ||
|
||
name = fields.Char(required=True) | ||
sequence = fields.Integer('Sequence', default=1, help="Used to order types.") | ||
property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") | ||
offers_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") | ||
offer_count = fields.Integer(compute="_compute_offer_count", default=0) | ||
|
||
_check_name = models.Constraint( | ||
"UNIQUE (name)", | ||
"Property type name must be unique", | ||
) | ||
|
||
@api.depends("offers_ids") | ||
def _compute_offer_count(self): | ||
for record in self: | ||
record.offer_count = len(record.offers_ids or []) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class ResUsers(models.Model): | ||
_inherit = 'res.users' | ||
|
||
property_ids = fields.One2many("estate.property", "salesperson_id", string="Properties") | ||
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. Properties might be the default string here. But double check |
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,13 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- the root elements of the data file --> | ||
<odoo> | ||
<menuitem id="estate_menu_root" name="Real Estate"> | ||
<menuitem id="estate_first_level_menu" name="Advertisements"> | ||
<menuitem id="estate_model_menu_action" action="estate_property_model_action"/> | ||
</menuitem> | ||
<menuitem id="estate_settings_menu" name="Settings"> | ||
<menuitem id="estate_proprty_type_model_menu_action" action="estate_property_type_model_action"/> | ||
<menuitem id="estate_proprty_tag_model_menu_action" action="estate_property_tag_model_action"/> | ||
</menuitem> | ||
</menuitem> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_offer_view_list" 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="Property offer list test string?" editable="bottom" decoration-success="status == 'accepted'" decoration-danger="status == 'refused'"> | ||
<field name="partner_id" /> | ||
<field name="property_type_id"/> | ||
<field name="price" /> | ||
<button name="action_offer_accept" string="Mark as Accepted" type="object" icon="fa-check" invisible="status"/> | ||
<button name="action_offer_refuse" string="Mark as Refused" type="object" icon="fa-xmark" invisible="status"/> | ||
<field name="validity" /> | ||
<field name="date_deadline" /> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<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="Property offer form test string?"> | ||
<sheet> | ||
<group> | ||
<field name="partner_id" /> | ||
<field name="price" /> | ||
<field name="status" /> | ||
<field name="validity" /> | ||
<field name="date_deadline" /> | ||
</group> | ||
</sheet> | ||
</form> | ||
</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
|
||
<record id="estate_property_tag_model_action" model="ir.actions.act_window"> | ||
<field name="name">Property Tags</field> | ||
<field name="res_model">estate.property.tag</field> | ||
<field name="view_mode">list,form</field> | ||
</record> | ||
|
||
|
||
<record id="estate_property_tag_view_list" model="ir.ui.view"> | ||
<field name="name">estate.property.tag.view.list</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="arch" type="xml"> | ||
<list string="Property tag list test string?" editable="bottom"> | ||
<field name="name"/> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<record id="estate_property_tag_view_form" model="ir.ui.view"> | ||
<field name="name">estate.property.tag.view.form</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="arch" type="xml"> | ||
<form string="Property tag form test string?"> | ||
<sheet> | ||
<div class="oe_title"> | ||
<div class="oe_edit_only"> | ||
<label for="name"/> | ||
</div> | ||
<h1 class="mb32"> | ||
<field name="name" class="mb16"/> | ||
</h1> | ||
</div> | ||
</sheet> | ||
</form> | ||
</field> | ||
</record> | ||
|
||
|
||
<record id="estate_property_tag_view_search" model="ir.ui.view"> | ||
<field name="name">estate.property.tag.view.search</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="arch" type="xml"> | ||
<search string="Estate property tag search string?"> | ||
<field name="name" /> | ||
</search> | ||
</field> | ||
</record> | ||
</odoo> |
Uh oh!
There was an error while loading. Please reload this page.