Skip to content

[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

Open
wants to merge 11 commits into
base: 18.0
Choose a base branch
from

Conversation

mohannedah
Copy link

@mohannedah mohannedah commented Apr 22, 2025

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.

Created the estate module with its necassary folder structure, and created the model with all its properties (at that moment).
@robodoo
Copy link

robodoo commented Apr 22, 2025

Pull request status dashboard

@mohannedah mohannedah changed the title [ADD] estate: adding estate module [ADD] estate: added a real estate module Apr 23, 2025
@SergeBayet
Copy link

You can mark this PR as ready to review (Click on the gray button: Ready to Review)

@mohannedah mohannedah marked this pull request as ready for review April 23, 2025 08:38
@SergeBayet
Copy link

For your second commit, the header is too long:
Try to limit the header length to about 50 characters for readability.
src: https://www.odoo.com/documentation/18.0/contributing/development/git_guidelines.html#commit-message-header

You can use git commit --amend to correct it.

@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch from d7613ef to b0dbb14 Compare April 23, 2025 08:57
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.
Copy link

@SergeBayet SergeBayet left a 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 :)

@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch from 0afd9ac to c497d5c Compare April 23, 2025 20:29
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.
@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch from c497d5c to 0a1066e Compare April 24, 2025 07:19
Copy link

@SergeBayet SergeBayet left a 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",
],

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.

Copy link

@SergeBayet SergeBayet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some remarks :)

@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch 4 times, most recently from 6a34048 to 9b3000b Compare April 24, 2025 09:37
@api.depends("deadline_date")
def _compute_validity_date(self):
for record in self:
record.validity_date = (record.deadline.date - datetime.date.today()).days

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? 🙄

Copy link
Author

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.
@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch from 9b3000b to e8881a1 Compare April 24, 2025 10:56
@SergeBayet
Copy link

@vava-odoo This one for you. Cheers! :)

Copy link

@vava-odoo vava-odoo left a 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 💪

],
"application": True,
"installable": True,
"auto_install": False,

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

selection=[("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")]
)
status = fields.Selection(
selection=[("new", "New"), ("offer_receieved", "Offer Recieved"), ("sold", "Sold"), ("cancelled", "Cancelled")],

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)

Suggested change
selection=[("new", "New"), ("offer_receieved", "Offer Recieved"), ("sold", "Sold"), ("cancelled", "Cancelled")],
selection=[("new", "New"), ("offer_receieved", "Offer Recieved"), ("sold", "Sold"), ("cancelled", "Canceled")],

Copy link
Author

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' 😂.

@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch 4 times, most recently from 5323dd0 to 59e95a7 Compare April 28, 2025 09:39
@mohannedah mohannedah requested a review from vava-odoo April 28, 2025 09:42
@mohannedah mohannedah requested a review from SergeBayet April 28, 2025 09:42
Copy link

@vava-odoo vava-odoo left a 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 💪

Comment on lines +14 to +15
[("accepted", "Accepted"), ("rejected", "Rejected"), ("pending", "Pending")],
default="pending",

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.

Copy link
Author

@mohannedah mohannedah Apr 28, 2025

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not multi?

Copy link
Author

@mohannedah mohannedah Apr 28, 2025

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"

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)

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?

Copy link
Author

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()

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

Copy link
Author

@mohannedah mohannedah Apr 28, 2025

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">

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

@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch from 885675d to 570c339 Compare April 29, 2025 14:32
@mohannedah mohannedah requested a review from vava-odoo April 29, 2025 15:21
@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch 3 times, most recently from 60839fa to d6c3801 Compare April 29, 2025 19:39
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.
@mohannedah mohannedah force-pushed the 18.0-adding_estate_module-admd branch from d17d272 to 342d258 Compare April 30, 2025 08:43
Copy link

@vava-odoo vava-odoo left a 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)

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:

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).

Suggested change
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"]:

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": [],

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

Suggested change
"data": [],

"license": "AGPL-3",
"depends": ["base", "estate", "account"],
"data": [],
}

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(

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}),

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? 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants