-
Notifications
You must be signed in to change notification settings - Fork 2.3k
18.0 training rvar #848
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: 18.0
Are you sure you want to change the base?
18.0 training rvar #848
Changes from all commits
1db8870
447c426
cdb6fa8
45baaf4
718660d
8235948
f96eeb3
b6e2c1e
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,20 @@ | ||
{ | ||
"name": "Real Estate", | ||
"category": "All", | ||
"summary": "Demo app for estate", | ||
"description": "This is the demo app ", | ||
Comment on lines
+4
to
+5
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. Add a proper summary and description |
||
"installable": True, | ||
"depends": ["base"], | ||
"application": True, | ||
"auto_install": 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. If the value is false, then no need to define it anyways |
||
"license": "AGPL-3", | ||
"data": [ | ||
"security/ir.model.access.csv", | ||
"views/estate_property_offer_views.xml", | ||
"views/estate_property_views.xml", | ||
"views/estate_settings_views.xml", | ||
"views/estate_property_tags_views.xml", | ||
"views/res_users_views.xml", | ||
"views/estate_menus.xml", | ||
], | ||
} |
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 inherited_model |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
from dateutil.relativedelta import relativedelta | ||
from odoo import fields, models, api, _ | ||
from odoo.exceptions import UserError, ValidationError | ||
from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
||
|
||
class RecurringPlan(models.Model): | ||
_name = "estate.property" | ||
_description = "estate property revenue plans" | ||
Comment on lines
+7
to
+9
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. Mismatch class name and model name |
||
_order = "id desc" # For ordering in ascending opr descending order one more way to do so is from view like: <list default_order="date desc"> | ||
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. Comment is not required here |
||
|
||
name = fields.Char(required=True) | ||
description = fields.Text(string="Description") | ||
postcode = fields.Char(string="Postcode") | ||
date_availability = fields.Date( | ||
string="Available From", | ||
copy=False, | ||
default=lambda self: fields.Date.today() + relativedelta(months=3), | ||
) | ||
expected_price = fields.Float(string="Expected Price", required=True) | ||
selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) | ||
bedrooms = fields.Integer(string="Bedrooms", default=2) | ||
living_area = fields.Integer(string="Living Area (sqm)") | ||
facades = fields.Integer(string="Number of Facades") | ||
garage = fields.Boolean(string="Garage") | ||
garden = fields.Boolean(string="Garden") | ||
garden_area = fields.Integer(string="Garden Area (sqm)") | ||
garden_orientation = fields.Selection( | ||
selection=[ | ||
("north", "North"), | ||
("south", "South"), | ||
("east", "East"), | ||
("west", "West"), | ||
], | ||
string="Garden Orientation", | ||
help="Orientation of the garden relative to the property", | ||
) | ||
active = fields.Boolean(default=True) | ||
state = fields.Selection( | ||
selection=[ | ||
("new", "NEW"), | ||
("offer_received", "Offer Received"), | ||
("offer_accepted", "Offer Accepted"), | ||
("sold", "Sold"), | ||
("cancelled", "Cancelled"), | ||
], | ||
string="Status", | ||
required=True, | ||
default="new", | ||
copy=False, | ||
# readonly=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. If not required, please remove it. |
||
) | ||
|
||
total_area = fields.Integer(string="Total Area", compute="_compute_total_area") | ||
best_price = fields.Float(string="Best Offer", compute="_compute_best_price") | ||
|
||
property_type_id = fields.Many2one("estate.property.type", string="Property Type") | ||
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) | ||
user_id = fields.Many2one( | ||
"res.users", string="Salesman", copy=False, default=lambda self: self.env.user | ||
) | ||
|
||
tag_id = fields.Many2many("estate.property.tag", string="Tags", copy=False) | ||
|
||
offer_id = fields.One2many("estate.property.offer", "property_id", string="Offer") | ||
|
||
_sql_constraints = [ | ||
( | ||
"check_expected_price", | ||
"CHECK(expected_price > 0)", | ||
"The Expected price of a property should be strictly positive.", | ||
), | ||
( | ||
"check_selling_price", | ||
"CHECK(selling_price IS NULL OR selling_price >= 0)", | ||
"Selling price must be positive when set.", | ||
), | ||
] | ||
|
||
@api.depends("living_area", "garden_area") | ||
def _compute_total_area(self): | ||
for area in self: | ||
area.total_area = area.living_area + area.garden_area | ||
|
||
@api.depends("offer_id.price") | ||
def _compute_best_price(self): | ||
for record in self: | ||
# Get all prices from offer_ids | ||
offer_prices = record.offer_id.mapped("price") | ||
record.best_price = max(offer_prices) if offer_prices else 0.0 | ||
|
||
@api.onchange("garden") | ||
def _onchange_garden(self): | ||
if self.garden: | ||
self.garden_orientation = "north" | ||
self.garden_area = 10 | ||
else: | ||
self.garden_orientation = False | ||
self.garden_area = 0 | ||
|
||
def action_property_sold(self): | ||
for prop in self: | ||
if prop.state == "cancelled": | ||
raise UserError(_("Cancelled properties cannot be sold.")) | ||
prop.state = "sold" | ||
return True | ||
|
||
def action_property_cancel(self): | ||
for prop in self: | ||
if prop.state == "sold": | ||
raise UserError(_("Sold properties cannot be cancelled.")) | ||
prop.state = "cancelled" | ||
return True | ||
|
||
@api.constrains("selling_price", "expected_price") | ||
def _check_selling_price(self): | ||
for record in self: | ||
if float_is_zero(record.selling_price, precision_digits=2): | ||
continue | ||
min_allowed_price = record.expected_price * 0.9 | ||
|
||
if ( | ||
float_compare( | ||
record.selling_price, min_allowed_price, precision_digits=2 | ||
) | ||
< 0 | ||
): | ||
Comment on lines
+122
to
+127
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. this could be inlined |
||
raise ValidationError( | ||
"Selling price cannot be lower than 90% of the expected price. " | ||
f"(Minimum allowed: {min_allowed_price:.2f})" | ||
) | ||
|
||
@api.ondelete(at_uninstall=False) | ||
def _unlink_except_state_not_new(self): | ||
for rec in self: | ||
if rec.state not in ["new", "cancelled"]: | ||
raise UserError( | ||
_( | ||
"You cannot delete a property unless its state is 'New' or 'Cancelled'." | ||
) | ||
) | ||
Comment on lines
+137
to
+141
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. this could be inlined |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from odoo import fields, models, api, exceptions, _ | ||
from dateutil.relativedelta import relativedelta | ||
from odoo.exceptions import UserError | ||
|
||
|
||
class EstatePropertyOffer(models.Model): | ||
_name = "estate.property.offer" | ||
_description = "Offers of Estate property " | ||
_order = "price desc" | ||
|
||
price = fields.Float(string="Price") | ||
status = fields.Selection( | ||
selection=[("accepted", "Accepted"), ("refused", "Refused")], | ||
copy=False, | ||
string="Status", | ||
) | ||
|
||
validity = fields.Integer(string="Validity", default=7) | ||
date_deadline = fields.Date( | ||
string="Deadline", | ||
compute="_compute_deadline_date", | ||
inverse="_inverse_deadline_date", | ||
) | ||
|
||
partner_id = fields.Many2one( | ||
"res.partner", string="Partner", copy=False, required=True | ||
) | ||
property_id = fields.Many2one("estate.property", copy=False, required=True) | ||
property_type_id = fields.Many2one( | ||
"estate.property.type", | ||
related="property_id.property_type_id", | ||
store=True, | ||
readonly=False, | ||
) | ||
_sql_constraints = [ | ||
( | ||
"check_offer_price", | ||
"CHECK(price > 0)", | ||
"The Offer price must be strictly positive", | ||
) | ||
] | ||
|
||
@api.depends("create_date", "validity") | ||
def _compute_deadline_date(self): | ||
for offer in self: | ||
create_dt = offer.create_date or fields.Date.today() | ||
offer.date_deadline = create_dt + relativedelta(days=offer.validity) | ||
|
||
def _inverse_deadline_date(self): | ||
for offer in self: | ||
if offer.date_deadline: | ||
create_dt = (offer.create_date or fields.Datetime.now()).date() | ||
delta = (offer.date_deadline - create_dt).days | ||
offer.validity = delta | ||
else: | ||
offer.validity = 0 | ||
|
||
def action_accept(self): | ||
for offer in self: | ||
# Allow only if property has no accepted offer | ||
if offer.property_id.buyer_id: | ||
raise UserError( | ||
_("An offer has already been accepted for this property.") | ||
) | ||
# Set buyer and selling price | ||
offer.property_id.buyer_id = offer.partner_id | ||
offer.property_id.selling_price = offer.price | ||
offer.status = "accepted" | ||
offer.property_id.state = "offer_accepted" | ||
# Setting remaining Offer as refused | ||
other_offers = offer.property_id.offer_id - offer | ||
# for other in other_offers: # -----> Normal for loop logic | ||
# other.status = 'refused' | ||
other_offers.write({"status": "refused"}) # -----> Odoo ORM Method | ||
Comment on lines
+60
to
+74
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. Comment are unnecesary |
||
|
||
return True | ||
|
||
def action_refused(self): | ||
for offer in self: | ||
offer.status = "refused" | ||
return True | ||
|
||
@api.model_create_multi | ||
def create(self, vals_list): | ||
new_records = [] | ||
for vals in vals_list: | ||
property_id = vals.get("property_id") | ||
|
||
property_rec = self.env["estate.property"].browse(property_id) | ||
# Business logic: set property state | ||
if property_rec.state == "new": | ||
property_rec.state = "offer_received" | ||
|
||
# Business logic: validate price | ||
if "price" in vals and vals["price"] < property_rec.best_price: | ||
raise exceptions.ValidationError( | ||
"Offer price must be higher than existing offers." | ||
) | ||
|
||
new_records.append(vals) | ||
|
||
return super().create(new_records) | ||
|
||
# vals_list---------> [{'partner_id': 23, 'price': 100, 'validity': 7, 'date_deadline': '2025-07-16', 'property_id': 19}] | ||
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. Unnecessary |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class EstatePropertyTag(models.Model): | ||
_name = "estate.property.tag" | ||
_description = "Tags of Estate property " | ||
_order = "name asc" | ||
|
||
name = fields.Char(required=True, string="Name") | ||
color = fields.Integer(default=3) | ||
|
||
_sql_constraints = [ | ||
( | ||
"unique_property_tag_name", | ||
"UNIQUE(name)", | ||
"A property tag name must be unique.", | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from odoo import fields, models, api | ||
|
||
|
||
class EstatePropertyType(models.Model): | ||
_name = "estate.property.type" | ||
_description = "Types of Estate property " | ||
_order = "sequence, name asc" # Order by sequence first | ||
|
||
name = fields.Char(required=True) | ||
sequence = fields.Integer( | ||
"Sequence", default=10, help="Used to order property types." | ||
) | ||
|
||
property_ids = fields.One2many("estate.property", "property_type_id") | ||
offer_ids = fields.One2many( | ||
"estate.property.offer", "property_type_id", string="Offers" | ||
) | ||
offer_count = fields.Integer( | ||
string="Offer Count", compute="_compute_offer_count", store=True | ||
) | ||
|
||
_sql_constraints = [ | ||
( | ||
"unique_property_type_name", | ||
"UNIQUE(name)", | ||
"A property type name must be unique.", | ||
), | ||
] | ||
|
||
@api.depends("offer_ids") | ||
def _compute_offer_count(self): | ||
for rec in self: | ||
rec.offer_count = len(rec.offer_ids) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class ResUsers(models.Model): | ||
_inherit = "res.users" | ||
|
||
property_ids = fields.One2many( | ||
comodel_name="estate.property", | ||
inverse_name="user_id", | ||
string="Properties", | ||
domain=[("state", "in", ["new", "offer_received"])], | ||
) |
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_model,estate.property,model_estate_property,base.group_user,1,1,1,1 | ||
access_estate_model_property,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 | ||
access_estate_model_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
access_estate_model_offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 | ||
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. End of file line is missing |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<odoo> | ||
<menuitem id="test_menu_root" | ||
name="Real Estate"> <!-- Root Level = which is displayed in the App switcher --> | ||
|
||
<menuitem id="Advertisement_menu" | ||
name="Advertisement"> <!-- First Level = Top Bar--> | ||
|
||
<menuitem id="estate_property_menu_action" | ||
action="estate_property_action"/> <!-- The action menus = Drop Down in topo bar --> | ||
|
||
</menuitem> | ||
|
||
<menuitem id="Settings_menu" | ||
name="Settings"> <!-- First Level = Top Bar--> | ||
|
||
<menuitem id="estate_property_menu_action_settings" | ||
action="estate_property_action_settings"/> | ||
|
||
<menuitem id="estate_property_menu_action_settings_tag" | ||
action="estate_property_action_tag"/> | ||
|
||
</menuitem> | ||
|
||
|
||
|
||
|
||
</menuitem> | ||
|
||
</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.
Missing licensing