-
Notifications
You must be signed in to change notification settings - Fork 2k
[ADD] estate: added a real estate module #732
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?
Conversation
Created the estate module with its necassary folder structure, and created the model with all its properties (at that moment).
You can mark this PR as ready to review (Click on the gray button: Ready to Review) |
For your second commit, the header is too long: You can use |
d7613ef
to
b0dbb14
Compare
Added custom forms and search views for the estate.property model, and established the baseline for the estate.property.type model, linking it to the estate.property model.
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.
Ok, you can move on :)
0afd9ac
to
c497d5c
Compare
Added the offers, types and tags models for the estate.property model and created the necessary views in order to create the records for these entities. In addition to adding the models, I added the seller_id field to the property and the partner_id to the offer.
c497d5c
to
0a1066e
Compare
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.
Some remarks :)
"views/estate_property_type_actions.xml", | ||
"views/estate_property_tag_actions.xml", | ||
"views/estate_property_menus.xml", | ||
], |
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.
Be aware, your commit header doesn't follow the guidelines.
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.
Some remarks :)
6a34048
to
9b3000b
Compare
estate/models/estate_property.py
Outdated
@api.depends("deadline_date") | ||
def _compute_validity_date(self): | ||
for record in self: | ||
record.validity_date = (record.deadline.date - datetime.date.today()).days |
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.
Is this working? I'm confused with record.deadline.date
... It should be record.deadline_date
. Did you test? 🙄
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.
It's a mistake indeed, it should be deadline_date. Sorry for the inconvenience.
In this commit, I added a couple of computed fields, like best_offer_price to the estate.property model to easily visualize the best offer and modified the model class names to follow the naming convention.
9b3000b
to
e8881a1
Compare
@vava-odoo This one for you. Cheers! :) |
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.
Quick review passing by. Good luck for next chapters 💪
estate/__manifest__.py
Outdated
], | ||
"application": True, | ||
"installable": True, | ||
"auto_install": False, |
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.
This is the default value (obviously, you don't want all modules to be auto-installed) so you don't have to define it
estate/models/estate_property.py
Outdated
selection=[("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")] | ||
) | ||
status = fields.Selection( | ||
selection=[("new", "New"), ("offer_receieved", "Offer Recieved"), ("sold", "Sold"), ("cancelled", "Cancelled")], |
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 use American English at Odoo (no need to edit the technical name now)
selection=[("new", "New"), ("offer_receieved", "Offer Recieved"), ("sold", "Sold"), ("cancelled", "Cancelled")], | |
selection=[("new", "New"), ("offer_receieved", "Offer Recieved"), ("sold", "Sold"), ("cancelled", "Canceled")], |
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.
I didn't know that the convention was American English and I didn't know too that 'cancelled' might be with a single 'L' 😂.
5323dd0
to
59e95a7
Compare
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.
Good, continue like this 💪
[("accepted", "Accepted"), ("rejected", "Rejected"), ("pending", "Pending")], | ||
default="pending", |
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.
Good to add a third option. But be aware that, since it is not required, it is possible to have a NULL value here.
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.
Initially, offers get created with the 'pending' option to avoid checking for 'falsy' or 'None' values. My guess on the possibility of having None values will be that we won't have null values inserted in the database unless the default constraint was a constraint that is being enforced on the API level not the database level (in this case we might insert records escaping the API validation for an example), if it was a constraint on the database level, then in this case the database will inject the default attribute anyways (I guess 😂).
), | ||
] | ||
|
||
@api.model_create_single |
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 not multi?
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.
I had multiple options in this case, either use the decorator used above or use the one suffixed with '_multi', but because I only tend to create single offer per save, then I used the single option, but it's okay I can decorate with the create_multi one and then change the implementation of the function to interpret the 'vals' as an array of records instead of being interpreted as a single record.
@api.model_create_single | ||
def create(self, vals): | ||
record = super().create(vals) | ||
record.property_id.status = "offer_receieved" |
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.
typo
@api.depends("validity_days") | ||
def _compute_deadline_date(self): | ||
for record in self: | ||
record.deadline_date = datetime.date.today() + datetime.timedelta(days=record.validity_days) |
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.
Shouldn't this depend on create_date?
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.
My bad 😂. Yes indeed, it should. I think in this case it will always update the deadline_date to be 7 days from the present time. So, this must be modified.
record.validity_days = (record.deadline_date - datetime.date.today()).days | ||
|
||
def action_accept_offer(self): | ||
self.ensure_one() |
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 tend to avoid this and instead handle multiple-records cases
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.
My justification in this case, that if we are allowed to accept multiple offers, then there might be a high possibility that someone might choose multiple offers related to a single property and try to accept them, so that's why I did this. I might raise an error in this case and accept multiple offers only if there are no 2 or more offers related to a single property.
<field name="arch" type="xml"> | ||
<form> | ||
<sheet> | ||
<group col="2" colspan="2"> |
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.
Don't. Use nested groups instead
885675d
to
570c339
Compare
60839fa
to
d6c3801
Compare
In this commit, I've added button actions to easily accept and reject offer, and added the necessary business logic to handle the different scenarios that might happen while modifying the status of both the offer and the property model. I've modified some of the code to follow the guidelines.
In this commit, I've added api and sql constraints on some of the fields in the property model and the offers model to make sure we don't create a property with an expected_price less than zero and users don't submit an offer with a price less than zero as well, and added business logic to not accept multiple offers on the same property at the same time and not to submit an offer on a property that is already sold.
In this commit, I've added a smart button to the form view of the property_type to display all the offers linked to this property_type and added property_offer_count field to be able to count the offers on this specific type. I've also added decoration styling to offer items to easily diffrentiate between accepted and rejected offers (accepted to be viewed in green color and rejected to be in red ), the same goes for the properties in the list_view.
I've added extension classes in this commit, to extend the functionality of the base user model and be able to list all of the properties he has listed in our module in the profile form view of the user. In addition to this, I've established the baseline of the estate_account model to handle issuing an invoice for the buyers once a property has been sold.
In this commit, I've created an invoicing module for the properties and defined the estate_property extension model to bridge between the estate.property module and the invoicing module.
I've modified the estate.property actions to add a kanban view.
d17d272
to
342d258
Compare
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.
Really good on this! Good luck with next tutorials
copy=False, | ||
) | ||
expected_price = fields.Float() | ||
selling_price = fields.Float(readonly=True, copyright=False, copy=False, precision_rounding=0.01) |
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.
copyright? 👀
[("status", "=", "accepted"), ("property_id", "=", record.id)], ["price", "id"], limit=1 | ||
) | ||
|
||
if not accepted_offer.id: |
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.
id is not necessary. The search will return an empty recordset if it fails (if I'm not mistaken).
if not accepted_offer.id: | |
if not accepted_offer: |
if property_id.status in ["cancelled", "sold"]: | ||
raise UserError("Cannot submit an offer on a cancelled or a sold property.") | ||
|
||
if property_id.best_offer_price > val["price"]: |
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.
Pay attention that price is not required
"name": "estate_account", | ||
"license": "AGPL-3", | ||
"depends": ["base", "estate", "account"], | ||
"data": [], |
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.
No need if no data
"data": [], |
"license": "AGPL-3", | ||
"depends": ["base", "estate", "account"], | ||
"data": [], | ||
} |
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.
Would be worth to automatically install this module if estate and account are installed, wouldn't it?
accepted_offer = self.env["estate.property.offer"].search_fetch( | ||
[("property_id", "=", record.id), ("status", "=", "accepted")], ["partner_id"], limit=1 | ||
) | ||
self.env["account.move"].create( |
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.
You could create invoices in batch
"partner_id": accepted_offer.partner_id.id, | ||
"move_type": "out_invoice", | ||
"invoice_line_ids": [ | ||
Command.create({"name": record.name, "quantity": 1, "price_unit": accepted_offer.price}), |
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.
Well, you as a real estate agent don't receive this money, right? 😄
Created the estate module with a property model to be able to represent all the real estate listings that will be created in our system, so that the users will have the ability to submit offers on them.