Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
603691a
[ADD] estate,estate_account: add initial modules
waqasahmed-gif Apr 24, 2026
ba79e3d
Added estate and estate account module.
waqasahmed-gif Apr 24, 2026
7875976
[IMP] estate: add agent/manager record rules
waqasahmed-gif Apr 24, 2026
27199c6
Added record rules to restrict agents to own/unassigned properties an…
waqasahmed-gif Apr 24, 2026
f192b33
Added restricted access to data for companies and agents.
muhammadqasimshabbir3-art Apr 28, 2026
f55732d
[IMP] estate: restrict access by company and role
muhammadqasimshabbir3-art Apr 28, 2026
9c18533
Added Pharma control Dashboard module.
muhammadqasimshabbir3-art Apr 29, 2026
44693f4
[ADD] pharma_control_center: add dashboard and base models
muhammadqasimshabbir3-art Apr 29, 2026
25977b5
Added cart feature.
muhammadqasimshabbir3-art Apr 30, 2026
2f5dafe
[IMP] pharma_control_center: add cart checkout flow
muhammadqasimshabbir3-art Apr 30, 2026
1ebdf76
Added remaining file.
muhammadqasimshabbir3-art May 4, 2026
da1ebb5
[IMP] pharma_control_center: add remaining views and security
muhammadqasimshabbir3-art May 4, 2026
7bd3671
Added Sales anylytics feature for graphs.
muhammadqasimshabbir3-art May 5, 2026
32ce200
[IMP] pharma_control_center: add sales analytics views
muhammadqasimshabbir3-art May 5, 2026
e7f5460
Merge pull request #1 from MuhammadQasimShabeer/main
MuhammadQasimShabeer May 5, 2026
06e4d9c
[IMP] pharma_control_center: add drug interaction checks
muhammadqasimshabbir3-art May 6, 2026
a83c07b
[IMP] pharma_control_center: update README
muhammadqasimshabbir3-art May 6, 2026
798fcc4
[IMP] pharma_control_center: split documentation files
muhammadqasimshabbir3-art May 7, 2026
545201c
[LINT] pharma_control_center: fix python style
muhammadqasimshabbir3-art May 7, 2026
b11ed10
[FIX] Apply drug interaction validation in cart checkout
muhammadqasimshabbir3-art May 7, 2026
878dd44
Fixed odoo pipelines issue.
muhammadqasimshabbir3-art May 7, 2026
640f352
[FIX] Code style issues: missing newlines, indentation, and ambiguous…
muhammadqasimshabbir3-art May 7, 2026
863b631
Added estate and estate account module.
waqasahmed-gif Apr 24, 2026
0b315e7
Added record rules to restrict agents to own/unassigned properties an…
waqasahmed-gif Apr 24, 2026
00581a7
Merge branch '19.0' into main
MuhammadQasimShabeer May 7, 2026
dfd1bd2
[FIX] estate/estate_account: Fix code style issues (newlines, indenta…
muhammadqasimshabbir3-art May 7, 2026
05e70fc
Merge branch 'main' of https://github.com/MuhammadQasimShabeer/odoo-t…
muhammadqasimshabbir3-art May 7, 2026
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
2 changes: 2 additions & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models

46 changes: 46 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'estate',
'version': '1.0',
'summary': 'Added Real estate module',
'description': """
Real Estate Management Module
=============================
Manage properties, offers, and real estate transactions.
""",
'depends': [
'base_setup',
],
'author': 'Muhammad Qasim Shabbir',
'license': 'LGPL-3',
'category': 'Real Estate/Brokerage',

# Required for Odoo 18
'installable': True,
'application': True,
'auto_install': False,

# Add these later as you create files
'data': [
'security/security.xml',
'security/ir.model.access.csv',

# 1. FIRST: property views (defines actions)
'views/estate_property_views.xml',
'data/property_type_data.xml',

# 2. SECOND: type + tag views (define actions used by menu)
'views/estate_property_type.xml',
'views/estate_property_tag_views.xml',

# 3. LAST: menus (depends on actions above)
'views/estate_menus.xml',
],
'demo': [
'demo/property_offer_demo.xml',

]
}


20 changes: 20 additions & 0 deletions estate/data/property_type_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="estate_property_type_residential" model="estate.property.type">
<field name="name">Residential</field>
</record>

<record id="estate_property_type_commercial" model="estate.property.type">
<field name="name">Commercial</field>
</record>

<record id="estate_property_type_industrial" model="estate.property.type">
<field name="name">Industrial</field>
</record>

<record id="estate_property_type_land" model="estate.property.type">
<field name="name">Land</field>
</record>

</odoo>
49 changes: 49 additions & 0 deletions estate/demo/property_offer_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<!-- ===================== -->
<!-- PROPERTIES -->
<!-- ===================== -->

<record id="estate_property_big_villa" model="estate.property">
<field name="name">Big Villa</field>
<field name="expected_price">1000000</field>
<field name="selling_price">0</field>
<field name="property_type_id" ref="estate.estate_property_type_residential"/>
</record>

<record id="estate_property_small_house" model="estate.property">
<field name="name">Small House</field>
<field name="expected_price">200000</field>
<field name="selling_price">0</field>
<field name="property_type_id" ref="estate.estate_property_type_residential"/>
</record>

<!-- ===================== -->
<!-- OFFERS -->
<!-- ===================== -->

<!-- Azure Interior -->
<record id="estate_offer_1" model="estate.property.offer">
<field name="partner_id" ref="base.res_partner_2"/>
<field name="property_id" ref="estate.estate_property_big_villa"/>
<field name="price">10000</field>
<field name="validity">14</field>
</record>

<record id="estate_offer_2" model="estate.property.offer">
<field name="partner_id" ref="base.res_partner_2"/>
<field name="property_id" ref="estate.estate_property_big_villa"/>
<field name="price">1500000</field>
<field name="validity">14</field>
</record>

<!-- Deco Addict -->
<record id="estate_offer_3" model="estate.property.offer">
<field name="partner_id" ref="base.res_partner_3"/>
<field name="property_id" ref="estate.estate_property_big_villa"/>
<field name="price">1500001</field>
<field name="validity">14</field>
</record>

</odoo>
4 changes: 4 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import estate_property
from . import property_offer
from . import property_tag
from . import property_type
183 changes: 183 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from odoo import models, fields, api
from datetime import timedelta

from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_is_zero, float_compare


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"
_order = "id desc"

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False,
default=lambda self: fields.Date.today() + timedelta(days=90)
)


living_area = fields.Integer(string='Living Area (sqm)',default=120)

active = fields.Boolean(default=True)
state = fields.Selection([
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
], required=True, copy=False, default='new')


def unlink(self):
for record in self:
if record.state not in ("new", "cancelled"):
raise UserError("You can only delete properties in New or Cancelled state.")
return super().unlink()


def action_cancel(self):
for record in self:
if record.state == 'sold':
raise UserError("A sold property cannot be cancelled.")
record.state = 'cancelled'
return True

def action_sold(self):
for record in self:
if record.state == 'cancelled':
raise UserError("A cancelled property cannot be set as sold.")
record.state = 'sold'
return True

property_type_id = fields.Many2one("estate.property.type")
buyer_id = fields.Many2one("res.partner", copy=False)
seller_id = fields.Many2one("res.users", default=lambda self: self.env.user)
tag_ids = fields.Many2many("estate.property.tag")
offer_ids = fields.One2many("estate.property.offer", "property_id")


expected_price = fields.Integer(default=230)
selling_price = fields.Float(copy=False)
bedrooms = fields.Integer(default=2)
facades = fields.Integer(string="Facades",default=130)
garage = fields.Boolean()

garden = fields.Boolean()
garden_area = fields.Integer(string='Garden Area (sqm)',default=220)
garden_orientation = fields.Selection([
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West')
])


@api.onchange('garden')
def _onchange_garden(self):
for record in self:
if record.garden:
record.garden_area = 10
record.garden_orientation = 'north'
else:
record.garden_area = 0
record.garden_orientation = False

# Add this computed field
total_area = fields.Integer(
string='Total Area (sqm)',
compute='_compute_total_area',
store=True # Optional: stores in database for better performance
)

best_price = fields.Float(
string="Best Offer is 123210 when we computed it to zero.",
compute="_compute_best_price",
store=True,
default=123
)

price_per_sqm = fields.Float(
compute="_compute_price_per_sqm",
store=True
)

@api.depends('living_area', 'garden_area','facades')
def _compute_total_area(self):
for record in self:
record.total_area = (record.living_area or 0) + (record.garden_area or 0) + (record.facades or 0)


@api.depends('expected_price', 'total_area')
def _compute_price_per_sqm(self):
for record in self:
if record.total_area:
record.price_per_sqm = record.expected_price / record.total_area
else:
record.price_per_sqm = 0

@api.depends('offer_ids.price')
def _compute_best_price(self):
for record in self:
prices = record.offer_ids.mapped('price')
record.best_price = max(prices) if prices else 0



@api.constrains('expected_price', 'selling_price')
def _check_selling_price(self):
for record in self:

# If selling price is not set yet (0 or False), skip validation
if float_is_zero(record.selling_price, precision_digits=2):
continue

# Ensure selling price >= 90% of expected price
min_price = record.expected_price * 0.90

comparison = float_compare(
record.selling_price,
min_price,
precision_digits=2
)

if comparison < 0:
raise ValidationError(
"Selling price cannot be lower than 90% of expected price."
)


@api.constrains('date_availability')
def _check_date_end(self):
for record in self:
if record.date_availability < fields.Date.today():
raise ValidationError("The end date cannot be set in the past")

@api.constrains('expected_price')
def _check_expected_price(self):
for record in self:
if record.expected_price < 220:
raise ValidationError("Expected price cannot be lower than 220")


@api.model
def create(self, vals):

property_id = vals.get("property_id")
amount = vals.get("price")

if property_id and amount:
property_rec = self.env["estate.property"].browse(property_id)

existing_offers = property_rec.offer_ids.mapped("price")

if existing_offers and amount < max(existing_offers):
raise UserError("Offer must be higher than existing offers.")

# update property state
property_rec.state = "offer_received"

return super().create(vals)

Loading