-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Onboarding Juica Server 101 #1391
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
Open
JuanICasareski
wants to merge
25
commits into
odoo:19.0
Choose a base branch
from
odoo-dev:19.0-onboarding-juica
base: 19.0
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
0df3fbd
[ADD] estate: real estate module
JuanICasareski 72686d0
[ADD] estate: models views & search filters
JuanICasareski 0e13e37
[ADD] estate: new state, buyer, & type fields
JuanICasareski f9b1003
[ADD] estate: tags records, tags on form & list views for estate record
JuanICasareski db402b1
[ADD] estate: property offers, offer vies, & offers tab on property view
JuanICasareski c508088
[ADD] estate: computed & onchange fields
JuanICasareski d89413f
[ADD] estate: sold & cancel property buttons, & accept & refuse offer…
JuanICasareski eeac1ab
[LINT] estate: add missing newlines & spacing, & remove extra spacing
JuanICasareski 607785f
[FIX] estate: check property_state by using referenced property on of…
JuanICasareski c44f153
[ADD] estate: models constraints
JuanICasareski ad6e728
[ADD] estate: type inline view, action buttons, & status bar on prope…
JuanICasareski ea12161
[ADD] estate: offers & properties decoration on list views, & decorat…
JuanICasareski 278b0b0
[ADD] estate: default filter for properties view
JuanICasareski 0fdba5d
[ADD] estate: stat button on property types to offers
JuanICasareski 6ca1416
[ADD] estate: set state to offer when creating an offer & prevent pro…
JuanICasareski e25bc58
[ADD] estate: show associated properties on user view
JuanICasareski 8488d14
[FIX] estate: prevent batch creation of offers lower than the best offer
JuanICasareski 35311e6
[FIX] estate: views order so that runbot runs
JuanICasareski 94b7ef5
[ADD] estate: create invoice on property sold
JuanICasareski 48dec55
[ADD] estate: kanban view on real state properties
JuanICasareski 3527b06
[REF] estate: merge `offer` & `received` property states into one
JuanICasareski 38ab99b
[IMP] estate: remove unnecessary check for property state & offer status
JuanICasareski 349e63f
[ADD] estate: test offer creation & property selling
JuanICasareski f602a6b
[ADD] estate: garden check & uncheck test case
JuanICasareski 1e4cb11
[FIX] estate: float checks on create & sell test cases
JuanICasareski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import models |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| 'name': 'Real Estate', | ||
| 'version': '0.0', | ||
| 'depends': [ | ||
| 'base', | ||
| ], | ||
| '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, | ||
| 'author': 'juica', | ||
| 'license': 'LGPL-3', | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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_user |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
| from odoo import api, fields, models | ||
| from odoo.exceptions import UserError, ValidationError | ||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
|
||
|
|
||
| class EstateProperty(models.Model): | ||
| _name = "estate.property" | ||
| _description = "Real Estate Property" | ||
| _order = "id desc" | ||
|
|
||
| name = fields.Char('Property Name', required=True, translate=True) | ||
| description = fields.Text('Description', translate=True) | ||
| postcode = fields.Char('Post Code', required=True) | ||
| date_availability = fields.Date( | ||
| 'Availability Date', | ||
| required=True, | ||
| copy=False, | ||
| default=fields.Date.today() + relativedelta(months=3), | ||
| ) | ||
| type_id = fields.Many2one("estate.property.type", string="Type", required=True) | ||
| offer_ids = fields.One2many("estate.property.offer", "property_id") | ||
| tag_ids = fields.Many2many("estate.property.tag", string="Tags") | ||
| salesperson_id = fields.Many2one( | ||
| "res.users", | ||
| string="Salesperson", | ||
| default=lambda self: self.env.user, | ||
| ) | ||
| buyer_id = fields.Many2one( | ||
| "res.partner", | ||
| string="Buyer", | ||
| copy=False, | ||
| ) | ||
| expected_price = fields.Float('Expected Price') | ||
| selling_price = fields.Float( | ||
| 'Selling Price', | ||
| readonly=True, | ||
| copy=False, | ||
| ) | ||
| bedrooms = fields.Integer( | ||
| '# Bedrooms', | ||
| default=2, | ||
| ) | ||
| facades = fields.Integer('# Facades') | ||
| garage = fields.Boolean('Garage') | ||
| garden = fields.Boolean('Garden') | ||
| living_area = fields.Integer('Living Area mt²') | ||
| garden_area = fields.Integer('Garden mt²') | ||
| garden_orientation = fields.Selection( | ||
| string='Garden Orientation', | ||
| selection=[ | ||
| ('north', 'North'), | ||
| ('south', 'South'), | ||
| ('east', 'East'), | ||
| ('west', 'West'), | ||
| ], | ||
| ) | ||
| active = fields.Boolean('Active', default=True) | ||
| state = fields.Selection( | ||
| string='State', | ||
| selection=[ | ||
| ('new', 'New'), | ||
| ('offer_received', 'Offer Received'), | ||
| ('offer_accepted', 'Offer Accepted'), | ||
| ('sold', 'Sold'), | ||
| ('cancelled', 'Cancelled'), | ||
| ], | ||
| default="new", | ||
| copy=False, | ||
| required=True, | ||
| readonly=True, | ||
| # group_expand=True | ||
| ) | ||
| total_area = fields.Integer( | ||
| "Total Area m²", | ||
| compute="_compute_total_area", | ||
| ) | ||
| best_price = fields.Float( | ||
| "Best Price", | ||
| compute="_compute_best_price", | ||
| ) | ||
|
|
||
| @api.depends("living_area", "garden_area") | ||
| def _compute_total_area(self): | ||
| for property in self: | ||
| property.total_area = property.living_area + property.garden_area | ||
|
|
||
| @api.depends("offer_ids.price") | ||
| def _compute_best_price(self): | ||
| for property in self: | ||
| property.best_price = max(property.offer_ids.mapped("price"), default=0) | ||
|
|
||
| @api.onchange("garden") | ||
| def _onchange_garden(self): | ||
| if self.garden: | ||
| self.garden_area = 10 | ||
| # Nasty magic string. I should turn the option into a variable and then reference it | ||
| self.garden_orientation = "north" | ||
| return | ||
|
|
||
| self.garden_area = 0 | ||
| self.garden_orientation = None | ||
|
|
||
| def action_sold(self): | ||
| for property in self: | ||
| if not property.selling_price: | ||
| raise UserError("You can not sell a property without accepted offers") | ||
|
|
||
| if property.state == "cancelled": | ||
| raise UserError("You can not sell a cancelled property") | ||
|
|
||
| property.state = "sold" | ||
|
|
||
| return True | ||
|
|
||
| def action_cancel(self): | ||
| for property in self: | ||
| if property.state == "sold": | ||
| raise UserError("You can not cancel a sold property") | ||
|
|
||
| property.state = "cancelled" | ||
|
|
||
| return True | ||
|
|
||
| _exp_price_positive = models.Constraint( | ||
| 'CHECK(expected_price > 0)', | ||
| 'The property expected price must be strictly positive', | ||
| ) | ||
|
|
||
| _sell_price_positive = models.Constraint( | ||
| 'CHECK(selling_price >= 0)', | ||
| 'The property selling price must be positive', | ||
| ) | ||
|
|
||
| @api.constrains("selling_price", "expected_price") | ||
| def _check_selling_price(self): | ||
| for property in self: | ||
| if float_is_zero(property.selling_price, precision_digits=2): | ||
| continue | ||
|
|
||
| limit = 0.9 * property.expected_price | ||
| if float_compare(property.selling_price, limit, precision_digits=2) == -1: | ||
| raise ValidationError("The property selling price must be at least 90% of the expected price") | ||
|
|
||
| @api.ondelete(at_uninstall=False) | ||
| def _unlink_except_not_new_or_cancelled(self): | ||
| for property in self: | ||
| if property.state not in ('new', 'cancelled'): | ||
| raise UserError("You can not delete a property that is not either 'new' or 'cancelled'") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| from dateutil.relativedelta import relativedelta | ||
|
|
||
| from odoo import api, fields, models | ||
| from odoo.exceptions import UserError | ||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Real Estate Property Offer" | ||
| _order = "price desc" | ||
|
|
||
| price = fields.Float('Price', required=True) | ||
| property_id = fields.Many2one('estate.property', 'property_id', required=True) | ||
| partner_id = fields.Many2one('res.partner', required=True) | ||
| status = fields.Selection( | ||
| string='Status', | ||
| copy=False, | ||
| selection=[ | ||
| ('accepted', 'Accepted'), | ||
| ('refused', 'Refused'), | ||
| ], | ||
| ) | ||
| property_type_id = fields.Many2one(related="property_id.type_id", store=True) | ||
| validity = fields.Integer( | ||
| "Validity (days)", | ||
| default=7, | ||
| required=True, | ||
| ) | ||
| date_deadline = fields.Date( | ||
| "Deadline", | ||
| required=True, | ||
| compute="_compute_deadline", | ||
| inverse="_inverse_deadline", | ||
| ) | ||
|
|
||
| @api.depends("validity") | ||
| def _compute_deadline(self): | ||
| for offer in self: | ||
| offer.date_deadline = fields.Date.today() + relativedelta(days=offer.validity) | ||
|
|
||
| def _inverse_deadline(self): | ||
| for offer in self: | ||
| delta = offer.date_deadline - fields.Date.today() | ||
| offer.validity = delta.days | ||
|
|
||
| def action_offer_accept(self): | ||
| for offer in self: | ||
| if offer.property_id.state in ('offer_accepted', 'sold', 'cancelled'): | ||
| raise UserError("You can not accept more offers for this property") | ||
|
|
||
| offer.status = "accepted" | ||
| offer.property_id.state = "offer_accepted" | ||
| offer.property_id.buyer_id = offer.partner_id | ||
| offer.property_id.selling_price = offer.price | ||
|
|
||
| return True | ||
|
|
||
| def action_offer_refuse(self): | ||
| for offer in self: | ||
| offer.status = "refused" | ||
|
|
||
| return True | ||
|
|
||
| _price_positive = models.Constraint( | ||
| 'CHECK(price > 0)', | ||
| 'The offer price must be strictly positive', | ||
| ) | ||
|
|
||
| @api.model_create_multi | ||
| def create(self, values): | ||
| # Get the lowest new offer per property | ||
| prop_min_offer = {} | ||
| for v in values: | ||
| pid = v['property_id'] | ||
| prop_min_offer[pid] = min(prop_min_offer.get(pid, float('inf')), v.get('price', 0)) | ||
|
|
||
| # Browse the properties referenced by the new offers | ||
| properties = self.env['estate.property'].browse(prop_min_offer.keys()) | ||
|
|
||
| for prop in properties: | ||
| if prop.state in ('sold', 'cancelled'): | ||
| raise UserError("You can not make an offer on a sold or cancelled property") | ||
|
|
||
| # No new offer may be lower than the best existing one | ||
| best_existing = prop.offer_ids[0].price if len(prop.offer_ids) else 0.0 | ||
|
|
||
| if float_is_zero(best_existing, precision_digits=2): | ||
| continue | ||
|
|
||
| if float_compare(prop_min_offer[prop.id], best_existing, precision_digits=2) == -1: | ||
| raise UserError("You can not offer less than the biggest offer") | ||
|
|
||
| properties.filtered(lambda p: p.state == 'new').state = 'offer_received' | ||
|
|
||
| return super().create(values) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Real Estate Property Tag" | ||
| _order = "name" | ||
|
|
||
| name = fields.Char('Tag Name', required=True, translate=True) | ||
| color = fields.Integer(string="Color Index") | ||
|
|
||
| _uniq_name = models.Constraint( | ||
| 'UNIQUE(name)', | ||
| 'The tag name must be unique', | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| from odoo import api, fields, models | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Real Estate Property Type" | ||
| _order = "sequence" | ||
|
|
||
| name = fields.Char('Type Name', required=True, translate=True) | ||
| sequence = fields.Integer('Sequence', default=1) | ||
|
|
||
| property_ids = fields.One2many("estate.property", "type_id") | ||
| offer_ids = fields.One2many("estate.property.offer", "property_type_id") | ||
| offer_count = fields.Integer(compute="_compute_offer_count", default=0) | ||
|
|
||
| @api.depends("offer_ids") | ||
| def _compute_offer_count(self): | ||
| for offer in self: | ||
| offer.offer_count = len(offer.offer_ids) | ||
|
|
||
| _uniq_name = models.Constraint( | ||
| 'UNIQUE(name)', | ||
| 'The type name must be unique', | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class ResUser(models.Model): | ||
| _inherit = "res.users" | ||
|
|
||
| property_ids = fields.One2many( | ||
| "estate.property", "salesperson_id", | ||
| domain="[('state', 'in', ['new', 'offer_received'])]", | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 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_user,access_estate_property_type_user,model_estate_property_type,base.group_user,1,0,0,0 | ||
| access_estate_property_type_admin,access_estate_property_type_admin,model_estate_property_type,base.group_system,1,1,1,1 | ||
| access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_user,1,0,0,0 | ||
| access_estate_property_tag_admin,access_estate_property_tag_admin,model_estate_property_tag,base.group_system,1,1,1,1 | ||
| access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import test_property |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
We normally should always have two blank lines before the definition of the class