Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e8bf13e
[ADD] estate: init
hazemayman1 Nov 18, 2025
607c496
[FIX] estate: removed imports
hazemayman1 Nov 18, 2025
2a9a97f
[IMP] estate: change to installable
hazemayman1 Nov 18, 2025
44aadbb
[IMP] estate: Models and basic fields
hazemayman1 Nov 18, 2025
4ed931a
[IMP] estate: access rights and security
hazemayman1 Nov 18, 2025
e0c5d6c
[IMP] estate: UI activated and more fields added
hazemayman1 Nov 18, 2025
353fbfc
[FIX] estate: CI compliance
hazemayman1 Nov 18, 2025
cb446e9
[LINT] estate: adapting to git and code guideline
hazemayman1 Nov 19, 2025
22c397c
[IMP] estate: adjusted access rights improved views and search filters
hazemayman1 Nov 19, 2025
afa327e
[LINT] estate: remove whitespace
hazemayman1 Nov 19, 2025
7b9aad0
[FIX] estate: xml views error fix
hazemayman1 Nov 19, 2025
bc406d4
[IMP] estate : adding new foreign fields for tags, types and offers t…
hazemayman1 Nov 19, 2025
0db9a12
[IMP] estate: computed properties for garden description and offer va…
hazemayman1 Nov 20, 2025
f3df67b
[IMP] estate: ch9 actions to modify property status and accept offers…
hazemayman1 Nov 20, 2025
91c7623
[IMP] estate: ch10 Validations for property prices added
hazemayman1 Nov 20, 2025
90b99c0
[FIX] estate: resolving PR suggestions
hazemayman1 Nov 21, 2025
8e996ae
[IMP] estate : ch10 UI improvements with more filters, conditions and…
hazemayman1 Nov 24, 2025
f51b26b
[LINT] estate : remove unneccessary list comprehension
hazemayman1 Nov 24, 2025
658d11c
[IMP] estate : added display of the user's assigned properties
hazemayman1 Nov 24, 2025
d88412c
[IMP] estate : integrate account module to automate invoice creation
hazemayman1 Nov 24, 2025
4fb1641
[IMP] estate : adding kanban view to estate property
hazemayman1 Nov 24, 2025
e80be1e
[FIX] estate : fix value comparison for property state
hazemayman1 Nov 24, 2025
5fd2307
[LINT] estate: remove unneccessary invisible field
hazemayman1 Nov 25, 2025
9a2e52d
[ADD] owl_playground : ch1 tasks 1-3
hazemayman1 Nov 25, 2025
e9bbaa7
[IMP] owl_playground : ch1 tasks 4-6
hazemayman1 Nov 25, 2025
0161645
[IMP] owl_playground : ch1 rest of the tasks
hazemayman1 Nov 26, 2025
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
18 changes: 18 additions & 0 deletions awesome_owl/static/src/card/card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, useState, Slot } from "@odoo/owl";


export class Card extends Component {
static template = "awesome_owl.card";

static props = {
title: {type: String},
slots : { type: Object, optional: true },
}
setup(){
this.state = useState({ show: false });
}

toggleShow(){
this.state.show = !this.state.show;
}
}
16 changes: 16 additions & 0 deletions awesome_owl/static/src/card/card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.card">
<div class="card d-inline-block m-2" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">
<t t-out="props.title"/>
<button class="btn btn-primary" t-on-click="toggleShow">Show</button>
</h5>
<p class="card-text" t-if="state.show">
<t t-slot="default"/>
</p>
</div>
</div>
</t>
</templates>
18 changes: 18 additions & 0 deletions awesome_owl/static/src/counter/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, useState } from "@odoo/owl";


export class Counter extends Component {
static template = "awesome_owl.counter";

setup() {
this.state = useState({ counter: 1 });
}

incrementCounter() {
this.state.counter++;
this.props.callback();
}

static props = { callback: Function };

}
7 changes: 7 additions & 0 deletions awesome_owl/static/src/counter/counter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.counter">
<p>Counter: <t t-esc="state.counter"/></p>
<button t-on-click="incrementCounter">Increment</button>
</t>
</templates>
16 changes: 15 additions & 1 deletion awesome_owl/static/src/playground.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { Component } from "@odoo/owl";
import { Component, useState, markup } from "@odoo/owl";
import { Counter } from "./counter/counter";
import { Card } from "./card/card";
import { TodoList } from "./todo/TodoList";

export class Playground extends Component {
static template = "awesome_owl.playground";
static components = { Counter, Card, TodoList };
no_markup_value = "<div class='text-primary'>Testing no markup</div>";
markup_value = markup("<Counter/>");
setup(){
this.state = useState({ sum: 2 });
this.incrementSum = this.incrementSum.bind(this);
}

incrementSum(){
this.state.sum += 1;
}
}
17 changes: 13 additions & 4 deletions awesome_owl/static/src/playground.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.playground">
<div class="p-3">
hello world
Hello My Friend
<div>
<Counter callback.bind="incrementSum" />
<Counter callback.bind="incrementSum" />
</div>
<div>
<Card title="'Task 1'"/>
<Card title="'Task 2'">
<Counter callback.bind="incrementSum" />
</Card>
</div>
</t>
<p>Sum: <t t-esc="state.sum"/></p>

<TodoList/>
</t>
</templates>
20 changes: 20 additions & 0 deletions awesome_owl/static/src/todo/TodoItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Component, useRef, onMounted, useState } from "@odoo/owl";


export class TodoItem extends Component {
static template = "awesome_owl.todo_item";
static props = {
id: {type: Number},
description: {type: String},
isCompleted: {type: Boolean},
onClick : { Type: Function, optional : true },
}

toggleState(){
this.props.isCompleted = !this.props.isCompleted;
}

removeTodo() {
this.props.onClick(this.props.id);
}
}
11 changes: 11 additions & 0 deletions awesome_owl/static/src/todo/TodoItem.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.todo_item">
<p t-att-class="props.isCompleted ? 'text-muted text-decoration-line-through' : ''">
<input type="checkbox"
t-att-checked="props.isCompleted"
t-on-change="toggleState"
/> <t t-out="props.id"/>: <t t-out="props.description"/> Done: <t t-out="props.isCompleted"/> <span class="fa fa-remove" t-on-click="removeTodo"/>
</p>
</t>
</templates>
31 changes: 31 additions & 0 deletions awesome_owl/static/src/todo/TodoList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, useState, useRef, onMounted } from "@odoo/owl";
import { TodoItem } from "./TodoItem";


export class TodoList extends Component {
static template = "awesome_owl.todo_list";
static components = { TodoItem };
setup(){
this.todos = useState([]);
this.todoId = 0;
this.inputRef = useRef("new_task");
onMounted(() => {
this.inputRef.el.focus();
})
}
addTodo(e){
if (e.keyCode === 13){
if (this.inputRef.el.value){
this.todoId += 1;
this.todos.push({ id: this.todoId, description: this.inputRef.el.value, isCompleted: false });
}
this.inputRef.el.value = "";
}
}
removeTodo(id){
const index = this.todos.findIndex(todo => todo.id === id);
if (index >= 0) {
this.todos.splice(index, 1);
}
}
}
10 changes: 10 additions & 0 deletions awesome_owl/static/src/todo/TodoList.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.todo_list">
<input t-ref="new_task" placeholder="Add new task" t-on-keyup="addTodo"/>
<t t-foreach="todos" t-as="i" t-key="i.id">
<TodoItem id="i.id" description="i.description" isCompleted="i.isCompleted" onClick.bind="removeTodo"/>
<br/>
</t>
</t>
</templates>
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
18 changes: 18 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
'name': "Real Estate",
'summary': "Testing the real estate module",
'description': "Testing the description real estate module",
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/res_users_views.xml',
'views/estate_property_offer_view.xml',
'views/estate_property_view.xml',
'views/estate_property_type_view.xml',
'views/estate_property_tag_view.xml',
'views/estate_menus.xml',
],
'application': True,
'author': "Odoo",
'license': 'LGPL-3',
}
7 changes: 7 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import (
estate_property,
estate_property_offer,
estate_property_tag,
estate_property_type,
res_user,
)
121 changes: 121 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError


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

_check_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'Expected price of the property must be strictly positive.',
)

_check_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'Selling price of the property must be strictly positive.',
)

name = fields.Char("Title", required=True, default="Unknown")
description = fields.Text("Description")
postcode = fields.Char("Postcode")
expected_price = fields.Float("Expected Price", required=True)
date_availability = fields.Date(
"Available From",
default=fields.Date.add(fields.Date.today(), months=3),
copy=False,
)
selling_price = fields.Float("Selling Price", readonly=True, copy=False)
bedrooms = fields.Integer("Bedrooms", default=2)
living_area = fields.Integer("Living Area (sqm)")
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string="Orientation",
selection=[("north", "N"), ("south", "S"), ("east", "E"), ("west", "W")],
help="Orientation of the garden",
)
last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now)
active = fields.Boolean(default=True)
state = fields.Selection(
string="State",
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
default="new",
)

property_type_id = fields.Many2one(
'estate.property.type', string='Property Type', ondelete='restrict'
)

salesperson_id = fields.Many2one(
'res.users',
string='Salesman',
ondelete='restrict',
default=lambda self: self.env.user.partner_id,
)

buyer_id = fields.Many2one(
'res.partner', string='Buyer', ondelete='restrict', copy=False, readonly=True,
)

property_tags_ids = fields.Many2many("estate.property.tag", string="Tags")
color = fields.Integer('Color Index', default=7)

offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
total_area = fields.Float(compute="_compute_total_area")
best_offer = fields.Float(compute="_compute_best_offer")

@api.depends("garden_area", "living_area")
def _compute_total_area(self):
for record in self:
record.total_area = record.garden_area + record.living_area

@api.depends("offer_ids")
def _compute_best_offer(self):
for record in self:
record.best_offer = max(record.offer_ids.mapped('price'), default=0.0)

@api.onchange("garden")
def _onchange_garden(self):
if self.garden:
self.garden_area, self.garden_orientation = 10, "north"
else:
self.garden_area, self.garden_orientation = 0, None

def action_mark_as_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError(_("Canceled properties cannot be sold."))
record.state = "sold"
record.active = False
return True

def action_mark_as_cancelled(self):
for record in self:
if record.state == "sold":
raise UserError(_("Sold properties cannot be cancelled."))
record.state = "cancelled"
record.active = False
return True

@api.constrains('expected_price')
def _check_expected_price(self):
for record in self:
for offer in record.offer_ids:
if offer.status == "yes" and float_compare(offer.price, 0.9 * record.expected_price, 2) < 0:
raise ValidationError("There is an accepted offer for an amount less than 90 percent of the new selling price, action required!")

@api.ondelete(at_uninstall=False)
def _unlink_property(self):
if self.state not in ('new', 'cancelled'):
raise ValidationError("Only new and canceled properties can be deleted.")
Loading