Skip to content
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

Odoo XP 2024 - Intro to Development SmartClass #68

Open
wants to merge 94 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
6b99ab5
Begin following tutorial
mpurnell1 Sep 30, 2024
2accef3
Change name of model
mpurnell1 Sep 30, 2024
7c2cafd
Fix typo
mpurnell1 Sep 30, 2024
3e916d2
Add fields to EstateProperty
mpurnell1 Sep 30, 2024
a1b4f31
Update version
mpurnell1 Sep 30, 2024
831f529
Add attributes to properties
mpurnell1 Sep 30, 2024
2740683
Update version
mpurnell1 Sep 30, 2024
97841bf
Add access rights
mpurnell1 Sep 30, 2024
7cbfda5
Remove unnecessary spaces
mpurnell1 Sep 30, 2024
e6a472d
Add view xml
mpurnell1 Sep 30, 2024
e4e9e7d
Try to fix error
mpurnell1 Sep 30, 2024
0af462a
Update version
mpurnell1 Sep 30, 2024
81db2d5
Add odoo tag to XML
mpurnell1 Sep 30, 2024
c00025b
Fix bug
mpurnell1 Sep 30, 2024
24765b3
Add menus
mpurnell1 Sep 30, 2024
5e8a85d
Modify attributes
mpurnell1 Sep 30, 2024
d265211
Add default values to attributes
mpurnell1 Sep 30, 2024
2793b33
Correct usage of today()
mpurnell1 Sep 30, 2024
d271ae4
Add active field
mpurnell1 Sep 30, 2024
3d3da13
Add default value for active
mpurnell1 Sep 30, 2024
b1b4cc2
Add state field
mpurnell1 Sep 30, 2024
80f830f
Add better name for state field
mpurnell1 Sep 30, 2024
4d2f980
Add estate property tree view
mpurnell1 Sep 30, 2024
e092d84
Add estate property form view
mpurnell1 Sep 30, 2024
a951696
Add estate property search view
mpurnell1 Sep 30, 2024
7535afa
Add Estate Property Type model
mpurnell1 Sep 30, 2024
9290c7d
tree -> list
mpurnell1 Sep 30, 2024
40df38c
Update version
mpurnell1 Sep 30, 2024
06fca0b
Reorder views
mpurnell1 Sep 30, 2024
6fe5ca6
Add access rights for property type
mpurnell1 Sep 30, 2024
f613f27
Try to fix property type views
mpurnell1 Sep 30, 2024
9b49f9c
Update manifest to Odoo 18
mpurnell1 Sep 30, 2024
a68cc7f
Add property type to property
mpurnell1 Sep 30, 2024
53e1477
Add buyer and salesperson
mpurnell1 Sep 30, 2024
da10977
[personalization] Add name and state to property view form
mpurnell1 Sep 30, 2024
ff72331
Add property tags to property
mpurnell1 Sep 30, 2024
d501d0a
Add tag views to manifest
mpurnell1 Sep 30, 2024
b462b55
Fix copy-paste bug
mpurnell1 Sep 30, 2024
fdce7f2
Import model file in models/__init__.py
mpurnell1 Sep 30, 2024
6492cf8
Add access rights for tags
mpurnell1 Sep 30, 2024
33934f5
Refactor + add offers to properties
mpurnell1 Sep 30, 2024
be8df83
Remove unneeded field
mpurnell1 Sep 30, 2024
5e33431
Add computed fields
mpurnell1 Sep 30, 2024
cd47de2
Fix typo
mpurnell1 Sep 30, 2024
a67323f
Fix bug
mpurnell1 Sep 30, 2024
c2c0069
Add computed fields to offer
mpurnell1 Sep 30, 2024
93a2fe6
Update version
mpurnell1 Sep 30, 2024
a94b23a
Display computed offer fields
mpurnell1 Sep 30, 2024
80d7a1f
Fix bug in logic
mpurnell1 Sep 30, 2024
e297bd6
Add onchange to garden field
mpurnell1 Sep 30, 2024
5ecc4d1
Add cancel and mark as sold actions
mpurnell1 Sep 30, 2024
b7cf7a5
Import UserError before using it
mpurnell1 Sep 30, 2024
21d2185
Add accept and refuse buttons to offer
mpurnell1 Sep 30, 2024
76c82ac
Move buttons to correct view
mpurnell1 Sep 30, 2024
7b107a1
Add removed buttons back
mpurnell1 Sep 30, 2024
604241f
Remove labels from accept and refuse icons
mpurnell1 Sep 30, 2024
493990b
Only allow accepting one offer at a time
mpurnell1 Sep 30, 2024
f0c66cc
Fix logic preventing accepting multiple offers
mpurnell1 Oct 1, 2024
e555007
Fix bugs and update version
mpurnell1 Oct 1, 2024
3cfad70
Add SQL constraints
mpurnell1 Oct 1, 2024
2dcd6b9
Add missing closing square bracket
mpurnell1 Oct 1, 2024
698f161
Add python constraints
mpurnell1 Oct 1, 2024
5d46bb5
Fix bugs
mpurnell1 Oct 1, 2024
e01b2db
Update version
mpurnell1 Oct 1, 2024
bb3bbd9
Fix bug in float_compare
mpurnell1 Oct 1, 2024
1890041
Fix bug in float_is_zero
mpurnell1 Oct 1, 2024
4e85312
Add object methods
mpurnell1 Oct 1, 2024
2a199f8
Fix ondelete
mpurnell1 Oct 1, 2024
c5e20e7
Fix typo
mpurnell1 Oct 1, 2024
dcc50c0
Add debug logging
mpurnell1 Oct 1, 2024
03180fd
Fix bugs
mpurnell1 Oct 1, 2024
8921a11
Extend the users model
mpurnell1 Oct 1, 2024
7b26499
Update version
mpurnell1 Oct 1, 2024
e847208
Minor bug fixes
mpurnell1 Oct 1, 2024
361092b
Add estate_account module
mpurnell1 Oct 1, 2024
0c94b06
Override action_sold method
mpurnell1 Oct 1, 2024
e38c3b3
Create invoice when property is sold
mpurnell1 Oct 1, 2024
e1859ff
Resolve build warnings
mpurnell1 Oct 1, 2024
2dd8eb4
Add invoice lines to created invoices
mpurnell1 Oct 1, 2024
ab7f878
Add missing comma
mpurnell1 Oct 1, 2024
b86899c
TUTORIAL COMPLETE - bump subversions
mpurnell1 Oct 1, 2024
a6f7f42
[extras] begin implementing chapter 11
mpurnell1 Oct 1, 2024
31d0c16
Make newlines at EOF consistent
mpurnell1 Oct 1, 2024
c417c60
Conform to coding guidelines
mpurnell1 Oct 1, 2024
1048d45
Remove unneeded imports and improve formatting consistency
mpurnell1 Oct 1, 2024
9a07be5
Improve comment consistency in model files
mpurnell1 Oct 1, 2024
018d5a3
Refactor logic and rename file
mpurnell1 Oct 1, 2024
1adb088
[extras] implement chapter 14
mpurnell1 Oct 1, 2024
5188002
Begin implementing suggestions made during review
Nov 12, 2024
addc928
Try to use filtered in unlink function
Nov 12, 2024
0b6a3c6
Fix syntax
Nov 12, 2024
d66e09f
Use filtered correctly
Nov 12, 2024
cd710f7
Use filtered in action methods
Nov 12, 2024
d5e0b8d
Make remaining suggested changes
Nov 12, 2024
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
10 changes: 8 additions & 2 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
{
"name": "Estate", # The name that will appear in the App list
"version": "16.0.0", # Version
"version": "18.0.1.5", # Version
"application": True, # This line says the module is an App, and not a module
"depends": ["base"], # dependencies
"data": [

'security/ir.model.access.csv',
'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',
'views/res_users_views.xml',
],
"installable": True,
'license': 'LGPL-3',
Expand Down
Empty file removed estate/models.py
Empty file.
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_tag
from . import estate_property_type
from . import estate_property_offer
from . import res_users
98 changes: 98 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare, float_is_zero


class Property(models.Model):
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
_name = 'estate.property'
_description = 'Real Estate Property'
_order = 'id desc'
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved

# Custom fields
name = fields.Char(string="Title", required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(string="Available From", copy=False, default=fields.Date.add(fields.Date.today(), months=3))
expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer(string="Living Area (sqm)")
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection([('n', 'North'), ('e', 'East'), ('s', 'South'), ('w', 'West')])

# Computed fields
total_area = fields.Integer(string='Total Area (sqm)', compute='_compute_total_area')
best_price = fields.Float(string='Best Offer', compute='_compute_best_price')

@api.depends('living_area', 'garden_area')
def _compute_total_area(self):
for record in self:
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
record.total_area = record.living_area + record.garden_area

@api.depends('offer_ids.price')
def _compute_best_price(self):
for record in self:
record.best_price = max(record.offer_ids.mapped('price') or [0])

@api.onchange('garden')
def _onchange_garden(self):
if not self.garden:
self.garden_area = 0
self.garden_orientation = False
else:
self.garden_area = 10
self.garden_orientation = 'n'

# Relational fields
property_type_id = fields.Many2one('estate.property.type', string="Property Type")
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False)
salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user)
tag_ids = fields.Many2many('estate.property.tag', string="Tags")
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers")

# Constraints
_sql_constraints = [
('check_expected_price', 'CHECK(expected_price >= 0)', 'The expected price must be strictly positive.'),
('check_selling_price', 'CHECK(selling_price >= 0)', 'The selling price must be positive.'),
]

@api.constrains('selling_price', 'expected_price')
def _check_selling_price(self):
for record in self:
if not float_is_zero(record.selling_price, precision_digits=2) and \
float_compare(record.selling_price, (record.expected_price*9/10), precision_digits=2) == -1:
raise UserError("The selling price must be at least 90% of the expected price.")

# Reserved fields
active = fields.Boolean(default=True)
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
state = fields.Selection(string="Status", default='new', copy=False, selection=[
('new', 'New'),
('received', 'Offer Received'),
('accepted', 'Offer Accepted'),
('sold', 'Sold'),
('canceled', 'Canceled')])

# Object methods
@api.ondelete(at_uninstall=False)
def _unlink_if_new_or_cancelled(self):
for record in self:
if record.state not in ['new', 'canceled']:
raise UserError("You cannot delete a property that is not new or canceled.")
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved

# Action methods
def action_sold(self):
for record in self:
if record.state == 'canceled':
raise UserError("You cannot sell a canceled property.")
record.state = 'sold'
return True

def action_cancel(self):
for record in self:
if record.state == 'sold':
raise UserError("You cannot cancel a sold property.")
record.state = 'canceled'
return True
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
70 changes: 70 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare


class PropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Real Estate Property Offer'
_order = 'price desc'
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved

# Custom fields
price = fields.Float()
status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], copy=False)
partner_id = fields.Many2one('res.partner', required=True)
property_id = fields.Many2one('estate.property', required=True)

# Constraints
_sql_constraints = [
('check_price', 'CHECK(price >= 0)', 'The offer price must be positive.'),
]

# Computed fields
validity = fields.Integer(string="Validity (days)", default=7)
date_deadline = fields.Date(string="Deadline", compute='_compute_date_deadline', inverse='_inverse_date_deadline')

@api.depends('create_date', 'validity')
def _compute_date_deadline(self):
for record in self:
if record.create_date:
date = record.create_date
else:
date = fields.Date.today()
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
record.date_deadline = fields.Date.add(date, days=record.validity)

@api.depends('create_date', 'date_deadline')
def _inverse_date_deadline(self):
for record in self:
record.validity = (record.date_deadline - record.create_date.date()).days

# Object methods
@api.model_create_multi
def create(self, values_list):
for values in values_list:
property_obj = self.env['estate.property'].browse(values['property_id'])
if property_obj.state == 'new':
property_obj.state = 'received'
elif property_obj.state == 'received' and property_obj.offer_ids:
min_price = min(property_obj.offer_ids.mapped('price'))
if float_compare(values['price'], min_price, precision_digits=2) == -1:
raise UserError("You cannot make an offer with a price below the lowest offer.")
return super(PropertyOffer, self).create(values_list)
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved

# Action methods
def action_accept(self):
for record in self:
if record.property_id.buyer_id:
raise UserError("You can only accept one offer at a time.")
record.status = 'accepted'
record.property_id.state = 'accepted'
record.property_id.buyer_id = record.partner_id
record.property_id.selling_price = record.price
return True

def action_refuse(self):
for record in self:
if record.status == 'accepted':
record.property_id.buyer_id = False
record.property_id.selling_price = 0
record.status = 'refused'
return True
mpurnell1 marked this conversation as resolved.
Show resolved Hide resolved
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 PropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'Real Estate Property Tag'
_order = 'name'

# Custom fields
name = fields.Char(required=True)
color = fields.Integer()

# Constraints
_sql_constraints = [
('check_name', 'UNIQUE(name)', 'The name of the property tag must be unique.')
]
19 changes: 19 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from odoo import fields, models


class PropertyType(models.Model):
_name = 'estate.property.type'
_description = 'Real Estate Property Type'
_order = 'name'

# Custom fields
name = fields.Char(required=True)
sequence = fields.Integer(default=1)

# Relational fields
property_ids = fields.One2many('estate.property', 'property_type_id', string="Properties")

# Constraints
_sql_constraints = [
('check_name', 'UNIQUE(name)', 'The name of the property type must be unique.')
]
8 changes: 8 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from odoo import fields, models


class Users(models.Model):
_inherit = 'res.users'

# Custom fields
property_ids = fields.One2many('estate.property', 'salesperson_id', string="Properties", domain=[('state', 'not in', ('accepted', 'sold'))])
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_offer,access_estate_property_offer,estate.model_estate_property_offer,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_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1
12 changes: 12 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<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"/>
</menuitem>
<menuitem id="estate_settings_menu" name="Settings">
<menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</odoo>
35 changes: 35 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<odoo>
<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="Estate Property">
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_offer_view_list" model="ir.ui.view">
<field name="name">estate.property.offer.list</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<list string="Offer" decoration-success="status == 'accepted'" decoration-danger="status == 'refused'" editable="bottom">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="action_accept" title="Accept" type="object" icon="fa-check" invisible="status"/>
<button name="action_refuse" title="Refuse" type="object" icon="fa-times" invisible="status"/>
</list>
</field>
</record>
</odoo>
32 changes: 32 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tag_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_form" model="ir.ui.view">
<field name="name">estate.property.tag.form</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<form string="Estate Property">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_tag_view_list" model="ir.ui.view">
<field name="name">estate.property.tag.list</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<list string="Property Tag" editable="bottom">
<field name="name"/>
</list>
</field>
</record>
</odoo>
44 changes: 44 additions & 0 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_type_action" model="ir.actions.act_window">
<field name="name">Property Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list,form</field>
</record>

<record id="estate_property_type_view_form" model="ir.ui.view">
<field name="name">estate.property.type.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form string="Property Type">
<sheet>
<group>
<field name="name"/>
</group>
<notebook>
<page string="Properties">
<field name="property_ids">
<list>
<field name="name"/>
<field name="expected_price"/>
<field name="state"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

<record id="estate_property_type_view_list" model="ir.ui.view">
<field name="name">estate.property.type.list</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<list string="Property Type">
<field name="name"/>
<field name="sequence" widget="handle"/>
</list>
</field>
</record>
</odoo>
Loading