Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
9 changes: 9 additions & 0 deletions awesome_owl/static/src/card/card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from "@odoo/owl";

export class Card extends Component {
static template = "awesome_owl.Card";
static props = {
title: String,
content: String,
}
}
11 changes: 11 additions & 0 deletions awesome_owl/static/src/card/card.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.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"/></h5>
<p class="card-text"><t t-out="props.content"/></p>
</div>
</div>
</t>
</templates>
17 changes: 17 additions & 0 deletions awesome_owl/static/src/counter/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component, useState } from "@odoo/owl";

export class Counter extends Component {
static template = "awesome_owl.Counter";
static props = {
onChange: { type: Function, optional: true }
};

setup() {
this.state = useState({ value: 0 });
}

increment() {
this.state.value = this.state.value + 1;
this.props.onChange && this.props.onChange()
}
}
10 changes: 10 additions & 0 deletions awesome_owl/static/src/counter/counter.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.Counter">
<div class="m-2 border d-inline-block">
<button class="btn btn-primary bg-success-subtle" t-on-click="increment">
Pressed <t t-esc="state.value"/> Times
</button>
</div>
</t>
</templates>
17 changes: 16 additions & 1 deletion awesome_owl/static/src/playground.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import { Component } from "@odoo/owl";
import { Component, markup, useState } from "@odoo/owl";
import { Counter } from "./counter/counter"
import { Card } from "./card/card";
import { TodoList } from "./todo_list/todo_list"

export class Playground extends Component {
static template = "awesome_owl.playground";
static components = { Counter, Card, TodoList };

setup() {
this.str1 = "<div class='text-primary'>some content</div>";
this.str2 = markup("<div class='text-primary'>some content</div>");
this.sum = useState({ value: 0 })
this.incrementSum = this.incrementSum.bind(this);
}

incrementSum() {
this.sum.value += 1
}
}
10 changes: 10 additions & 0 deletions awesome_owl/static/src/playground.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
<t t-name="awesome_owl.playground">
<div class="p-3">
hello world
<div>
<Counter onChange="incrementSum" />
<Counter onChange="incrementSum" />
<p>Sum is: <t t-esc="sum.value"/></p>
</div>
<div>
<Card title="'card 1'" content="str1"/>
<Card title="'card 2'" content="str2"/>
</div>
<TodoList/>
</div>
</t>

Expand Down
21 changes: 21 additions & 0 deletions awesome_owl/static/src/todo_list/todo_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component } from "@odoo/owl";

export class TodoItem extends Component {
static template = "awesome_owl.TodoItem";
static props = {
todo: {
type: Object,
shape: { id: Number, description: String, isCompleted: Boolean }
},
toggleState: Function,
removeTodo: Function
};

onChange() {
this.props.toggleState(this.props.todo.id)
}

onDelete() {
this.props.removeTodo(this.props.todo.id);
}
}
13 changes: 13 additions & 0 deletions awesome_owl/static/src/todo_list/todo_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.TodoItem">
<div class="form-check">
<input class="form-check-input" type="checkbox" t-att-id="props.todo.id" t-att-checked="props.todo.isCompleted" t-on-change="onChange"/>
<label t-att-for="props.todo.id" t-att-class="props.todo.isCompleted ? 'text-decoration-line-through text-muted' : '' ">
<t t-esc="props.todo.id"/>.
<t t-esc="props.todo.description"/>
</label>
<span class="fa fa-remove ms-3 text-danger" t-on-click="onDelete"/>
</div>
</t>
</templates>
36 changes: 36 additions & 0 deletions awesome_owl/static/src/todo_list/todo_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, useState, useRef } from "@odoo/owl";
import { TodoItem } from "./todo_item";
import { useAutofocus } from "../utils";

export class TodoList extends Component {
static template = "awesome_owl.TodoList";
static components = { TodoItem };

setup() {
this.todos = useState([])
this.id = 1
this.inputRef = useRef("input");
useAutofocus(this.inputRef);
this.removeTodo = this.removeTodo.bind(this)
}

addTodo(e){
if (e.keyCode === 13 && e.target.value) {
this.todos.push({
id: this.id++,
description: e.target.value,
isCompleted: false
});
e.target.value = "";
}
}

toggleState(id) {
const todo = this.todos.find(todo => todo.id === id)
todo.isCompleted = !todo.isCompleted
}

removeTodo(id) {
this.todos.splice(this.todos.findIndex((todo) => todo.id === id), 1)
}
}
12 changes: 12 additions & 0 deletions awesome_owl/static/src/todo_list/todo_list.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.TodoList">
<h2>Todo List</h2>
<div class="d-inline-block border p-2 m-2">
<input type="text" placeholder="Enter a new task" t-on-keyup="addTodo" t-ref="input" />
<t t-foreach="todos" t-as="todo" t-key="todo.id">
<TodoItem todo="todo" toggleState.bind="toggleState" removeTodo="removeTodo"/>
</t>
</div>
</t>
</templates>
9 changes: 9 additions & 0 deletions awesome_owl/static/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { onMounted } from "@odoo/owl";

export function useAutofocus(ref) {
onMounted(() => {
if (ref.el) {
ref.el.focus();
}
});
}
1 change: 1 addition & 0 deletions estate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
i18n/
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
19 changes: 19 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': 'Real Estate',
'category': 'Real Estate',
'version': '1.0',
'author': 'Radhey Detroja(RADET)',
'license': 'LGPL-3',
'summary': 'Manage real estate properties',
'depends': ['base', 'mail'],
'application': True,
'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',
]
}
5 changes: 5 additions & 0 deletions estate/models/__init__.py
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_users
111 changes: 111 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from dateutil.relativedelta import relativedelta

from odoo import api, exceptions, fields, models, _
from odoo.tools.float_utils import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Estate Property Model"
_order = "id desc"
_inherit = ["mail.thread", "mail.activity.mixin"]

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False,
default=lambda self: fields.Date.today() + relativedelta(months=3),
)
expected_price = fields.Float(required=True)
selling_price = fields.Float(
readonly=True,
copy=False,
)
best_price = fields.Float(compute="_compute_best_price", string="Best Offer")
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
active = fields.Boolean(default=True)
garden_orientation = fields.Selection(
[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]
)
total_area = fields.Integer(compute="_compute_total_area", string="Total Area (sqm)")
state = fields.Selection(
[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
],
required=True,
copy=False,
default='new',
)
property_type_id = fields.Many2one("estate.property.type")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user)
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")

_expected_price_positive = models.Constraint(
'CHECK(expected_price > 0)',
'The expected price must be strictly positive.',
)

@api.depends("living_area", "garden_area")
def _compute_total_area(self):
for record in self:
record.total_area = (record.living_area or 0) + (record.garden_area or 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.0

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

def action_sold(self):
if self.filtered(lambda r: r.state == 'cancelled'):
raise exceptions.UserError(_("Cancelled properties cannot be sold."))
self.write({'state': 'sold'})
return True

def action_cancel(self):
if self.filtered(lambda x: x.state == 'sold'):
raise exceptions.UserError(_("Sold properties cannot be cancelled."))
self.write({'state': 'cancelled'})
return True

@api.constrains("selling_price", "expected_price", "buyer_id")
def _check_selling_price(self):
for record in self:
if float_is_zero(record.selling_price, precision_digits=2):
if record.buyer_id:
raise exceptions.ValidationError("The selling price must be greater than 0.")
continue
if float_compare(record.selling_price, 0.0, precision_digits=2) <= 0:
raise exceptions.ValidationError("The selling price must be positive.")
min_selling_price = record.expected_price * 0.9
if float_compare(record.selling_price, min_selling_price, precision_digits=2) < 0:
raise exceptions.ValidationError(
f"Minimum selling price: {min_selling_price:.2f}"
)

@api.ondelete(at_uninstall=False)
def _unlink_if_not_new_or_cancelled(self):
forbidden = self.filtered(lambda r: r.state not in ("new", "cancelled"))
if forbidden:
raise exceptions.UserError(_("Only properties in 'New' or 'Cancelled' state can be deleted."))
Loading