diff --git a/.gitignore b/.gitignore
index b6e47617de1..eb45fe797be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,5 @@ dmypy.json
# Pyre type checker
.pyre/
+
+.vscode
diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js
new file mode 100644
index 00000000000..92d207206ab
--- /dev/null
+++ b/awesome_owl/static/src/card/card.js
@@ -0,0 +1,19 @@
+import { Component, useState } from "@odoo/owl";
+
+export class Card extends Component {
+ static template = "awesome_owl.card";
+
+ static props = {
+ title : String,
+ slots : { type: Object, optional: true}
+ }
+
+ setup() {
+ this.state = useState({ open: false });
+ this.changeState = this.changeState.bind(this)
+ }
+
+ changeState() {
+ this.state.open = !this.state.open
+ }
+}
diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml
new file mode 100644
index 00000000000..95d3f84033a
--- /dev/null
+++ b/awesome_owl/static/src/card/card.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js
new file mode 100644
index 00000000000..5591d195618
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.js
@@ -0,0 +1,20 @@
+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() {
+ if (this.props.onChange){
+ this.props.onChange();
+ }
+ this.state.value++;
+ }
+}
diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml
new file mode 100644
index 00000000000..56e10866159
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/global_counter/global_counter.js b/awesome_owl/static/src/global_counter/global_counter.js
new file mode 100644
index 00000000000..d7f5d8acfd7
--- /dev/null
+++ b/awesome_owl/static/src/global_counter/global_counter.js
@@ -0,0 +1,28 @@
+import { Component, useState } from "@odoo/owl";
+import { Counter } from "../counter/counter";
+
+export class GlobalCounter extends Component {
+ static template = "awesome_owl.global_counter";
+
+ static components = {
+ Counter
+ }
+
+ static props = {
+ buttons: Number
+ }
+
+ setup() {
+ this.buttons = []
+ for (let i = 0; i < this.props.buttons; i++) {
+ this.buttons.push(i)
+ }
+
+ this.state = useState({ value: 2 });
+ this.incrementSum = this.incrementSum.bind(this)
+ }
+
+ incrementSum() {
+ this.state.value++;
+ }
+}
diff --git a/awesome_owl/static/src/global_counter/global_counter.xml b/awesome_owl/static/src/global_counter/global_counter.xml
new file mode 100644
index 00000000000..5e3df1ebcef
--- /dev/null
+++ b/awesome_owl/static/src/global_counter/global_counter.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js
index 4ac769b0aa5..ba912ce86bd 100644
--- a/awesome_owl/static/src/playground.js
+++ b/awesome_owl/static/src/playground.js
@@ -1,5 +1,22 @@
-import { Component } from "@odoo/owl";
+import { Component, useState, markup } from "@odoo/owl";
+import { Counter } from "./counter/counter";
+import { Card } from "./card/card";
+import { GlobalCounter } from "./global_counter/global_counter";
+import { TodoItem } from "./todolist/todo_item";
+import { TodoList } from "./todolist/todo_list";
export class Playground extends Component {
+ setup(){
+ this.normal_string = "normal string"
+ this.html_string = markup("Visit W3Schools.com!")
+ }
+
static template = "awesome_owl.playground";
+ static components = {
+ Counter,
+ Card,
+ TodoList,
+ GlobalCounter,
+
+ }
}
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml
index 4fb905d59f9..58abf4ff77e 100644
--- a/awesome_owl/static/src/playground.xml
+++ b/awesome_owl/static/src/playground.xml
@@ -1,10 +1,16 @@
-
hello world
+
+
+
+
+
-
diff --git a/awesome_owl/static/src/todolist/todo_item.js b/awesome_owl/static/src/todolist/todo_item.js
new file mode 100644
index 00000000000..9e0cd1e4f75
--- /dev/null
+++ b/awesome_owl/static/src/todolist/todo_item.js
@@ -0,0 +1,24 @@
+import { Component, useState } from "@odoo/owl";
+
+export class TodoItem extends Component {
+ static template = "awesome_owl.todoitem";
+
+ static props = {
+ todo: Object,
+ toggleState: Function,
+ removeTodo: Function
+ }
+
+ setup() {
+ this.toggleStateItem = this.toggleStateItem.bind(this)
+ this.removeTodoItem = this.removeTodoItem.bind(this)
+ }
+
+ toggleStateItem() {
+ this.props.toggleState(this.props.todo.id)
+ }
+
+ removeTodoItem() {
+ this.props.removeTodo(this.props.todo.id)
+ }
+}
diff --git a/awesome_owl/static/src/todolist/todo_item.xml b/awesome_owl/static/src/todolist/todo_item.xml
new file mode 100644
index 00000000000..e9c2668e3d9
--- /dev/null
+++ b/awesome_owl/static/src/todolist/todo_item.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/todolist/todo_list.js b/awesome_owl/static/src/todolist/todo_list.js
new file mode 100644
index 00000000000..25b7d36ccdd
--- /dev/null
+++ b/awesome_owl/static/src/todolist/todo_list.js
@@ -0,0 +1,56 @@
+import { Component, useState, useRef, onMounted } from "@odoo/owl";
+import { TodoItem } from "./todo_item";
+
+export class TodoList extends Component {
+ static template = "awesome_owl.todolist";
+
+ static components = {
+ TodoItem,
+ }
+
+ static props = {
+ }
+
+ setup() {
+ this.state = useState({
+ text: "",
+ todos: [],
+
+ })
+ this.last_id = 0
+ this.inputRef = useRef('input')
+
+ onMounted(() => {
+ this.inputRef.el.focus()
+ });
+
+ this.toggleState = this.toggleState.bind(this)
+ this.removeTodo = this.removeTodo.bind(this)
+ }
+
+ toggleState(id) {
+ const index = this.state.todos.findIndex((elem) => elem.id === id);
+ if (index >= 0) {
+ // remove the element at index from list
+ this.state.todos[index].isCompleted = !this.state.todos[index].isCompleted
+ }
+ }
+
+ removeTodo(id) {
+ const index = this.state.todos.findIndex((elem) => elem.id === id);
+ if (index >= 0) {
+ // remove the element at index from list
+ this.state.todos.splice(index, 1)
+ }
+
+ }
+
+ addTodo(event) {
+ if (this.state.text == "") return
+ if (event.keyCode == 13) {
+ const newTodo = { id: this.last_id, description: this.state.text, isCompleted: false }
+ this.state.todos.push(newTodo)
+ this.last_id = this.last_id + 1
+ }
+ }
+}
diff --git a/awesome_owl/static/src/todolist/todo_list.xml b/awesome_owl/static/src/todolist/todo_list.xml
new file mode 100644
index 00000000000..6666f2f0e22
--- /dev/null
+++ b/awesome_owl/static/src/todolist/todo_list.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/estate/__init__.py b/estate/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/estate/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/estate/__manifest__.py b/estate/__manifest__.py
new file mode 100644
index 00000000000..3e2e73cc8c2
--- /dev/null
+++ b/estate/__manifest__.py
@@ -0,0 +1,20 @@
+{
+ 'name': 'estate-kehey',
+ 'depends': [
+ 'base',
+ ],
+ 'data': [
+ 'data/ir.model.access.csv',
+ 'views/estate_property_actions.xml',
+ 'views/estate_property_tag_view_list.xml',
+ 'views/estate_property_type_view_form.xml',
+ 'views/estate_property_type_view_list.xml',
+ 'views/estate_property_view_form.xml',
+ 'views/estate_property_view_kanban.xml',
+ 'views/estate_property_view_list.xml',
+ 'views/estate_property_view_search.xml',
+ 'views/users_extra_views.xml',
+ 'data/estate_menus.xml',
+ ],
+ 'application': True,
+}
diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml
new file mode 100644
index 00000000000..112da7f4918
--- /dev/null
+++ b/estate/data/estate_menus.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv
new file mode 100644
index 00000000000..a6a34c6144d
--- /dev/null
+++ b/estate/data/ir.model.access.csv
@@ -0,0 +1,6 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+access_estate_property_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1
+access_estate_property_type_user,access_estate_property_user,model_estate_property_type,base.group_user,1,1,1,1
+access_estate_property_tag_user,access_estate_property_user,model_estate_property_tag,base.group_user,1,1,1,1
+access_estate_property_offer_user,access_estate_property_user,model_estate_property_offer,base.group_user,1,1,1,1
+
diff --git a/estate/git-challenge.txt b/estate/git-challenge.txt
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
new file mode 100644
index 00000000000..f8ae845d7ac
--- /dev/null
+++ b/estate/models/__init__.py
@@ -0,0 +1,8 @@
+from . import (
+ estate_property,
+ estate_property_offer,
+ estate_property_tag,
+ estate_property_type,
+ inhereted_users,
+)
+
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
new file mode 100644
index 00000000000..08549809ba1
--- /dev/null
+++ b/estate/models/estate_property.py
@@ -0,0 +1,108 @@
+from dateutil.relativedelta import relativedelta
+
+from odoo import api, exceptions, fields, models
+
+
+class EstateProperty(models.Model):
+ _name = "estate.property"
+ _description = "estate model"
+ _order = "id desc"
+
+ name = fields.Char(required=True)
+ salesman_id = fields.Many2one("res.partner", string="Salesman")
+ buyer_id = fields.Many2one("res.users", default=lambda self: self.env.user, string="Buyer")
+ type_id = fields.Many2one("estate.property.type")
+ tags_id = fields.Many2many("estate.property.tag")
+ offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
+ active = fields.Boolean(default=True)
+ state = fields.Selection(
+ required=True,
+ copy=False,
+ default="new",
+ selection=[("new", "New"), ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")],
+ )
+ description = fields.Text()
+ postcode = fields.Char()
+ date_availability = fields.Datetime(copy=False, default=fields.Datetime.today() + (relativedelta(months=3)))
+ expected_price = fields.Float(required=True)
+ selling_price = fields.Float(readonly=True, copy=False)
+ bedrooms = fields.Integer(default=2)
+ living_area = fields.Integer()
+ facades = fields.Integer()
+ garage = fields.Boolean()
+ garden = fields.Boolean()
+ garden_area = fields.Integer()
+ garden_orientation = fields.Selection(
+ string='type',
+ selection=[('north', 'North'), ('south', 'South'), ('East', 'east'), ('West', 'west')],
+ )
+ total_area = fields.Integer(string="Total Area", compute="_compute_total_surface")
+ best_offer = fields.Float(string="Best Offer", compute="_compute_best_offer")
+
+ # ==========constraints===================
+ _check_positive_expected_price = models.Constraint("CHECK (expected_price > 0)", "expected price should be bigger than 0")
+ _check_positive_selling_price = models.Constraint("CHECK (selling_price > 0)", "expected price should be bigger than 0")
+
+ @api.constrains("selling_price", "expected_price")
+ def _check_enough_selling_price(self):
+ for record in self:
+ offer_made = "accepted" in record.offer_ids.mapped("status")
+ price_good_enough = record.selling_price > 0.9 * record.expected_price
+ if not price_good_enough and offer_made:
+ to_low_user_error = "selling price is too low for the expected price"
+ raise exceptions.ValidationError(to_low_user_error)
+
+ @api.constrains("state")
+ def _no_sell_without_offer(self):
+ for record in self:
+ if record.state == "sold" and "accepted" not in record.offer_ids.mapped("status"):
+ only_sold_if_accepted = "can only sell a property with an accepted offer"
+ raise exceptions.UserError(only_sold_if_accepted)
+
+ # ==========computed fields===============
+ @api.depends('garden_area', 'living_area')
+ def _compute_total_surface(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:
+ if not record.offer_ids:
+ record.best_offer = 0
+ else:
+ record.best_offer = max(record.offer_ids.mapped("price"))
+
+ # ============onchage fields==============
+ @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 = False
+
+ # ==========button functions==============
+ def action_property_sold(self):
+ for record in self:
+ if record.state == "cancelled":
+ no_sell_cancelled_error = "Can't sell a cancelled property"
+ raise exceptions.UserError(no_sell_cancelled_error)
+ record.state = "sold"
+ return True
+
+ def action_property_cancelled(self):
+ for record in self:
+ if record.state == "sold":
+ no_sell_a_sold_property = "Can't cancel a sold property"
+ raise exceptions.UserError(no_sell_a_sold_property)
+ record.state = "cancelled"
+ return True
+
+ @api.ondelete(at_uninstall=False)
+ def ondelete(self):
+ for property in self:
+ if property.state in ("new", "cancelled"):
+ no_delete_new_or_cancelled_record = "cannot delete new or cancelled record"
+ raise exceptions.UserError(no_delete_new_or_cancelled_record)
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
new file mode 100644
index 00000000000..328bd4be46b
--- /dev/null
+++ b/estate/models/estate_property_offer.py
@@ -0,0 +1,72 @@
+from dateutil.relativedelta import relativedelta
+
+from odoo import api, exceptions, fields, models
+
+
+class EstatePropertyOffer(models.Model):
+ _name = "estate.property.offer"
+ _description = "estate offer model"
+ _order = "price desc"
+
+ name = fields.Char(required=True)
+ price = fields.Float()
+ status = fields.Selection(
+ string='status',
+ copy=False,
+ selection=[('accepted', 'Accepted'), ('refused', 'Refused')],
+ )
+ partner_id = fields.Many2one("res.partner", required=True)
+ property_id = fields.Many2one("estate.property", required=True, ondelete="cascade")
+ date_deadline = fields.Datetime(string="Deadline", compute="compute_deadline", inverse="_inverse_deadline")
+ validity = fields.Integer(string="validity", default=7)
+ property_type_id = fields.Many2one("estate.property.type", related="property_id.type_id", string="Property Type", store=True)
+
+ # =========contraints============
+ _check_positive_offer_price = models.Constraint("CHECK (price > 0)", "expected price should be bigger than 0")
+
+ @api.depends('validity', "create_date")
+ def compute_deadline(self):
+ for record in self:
+ if record.create_date:
+ record.date_deadline = record.create_date + relativedelta(days=record.validity)
+ else:
+ record.date_deadline = fields.Datetime.today() + relativedelta(days=record.validity)
+
+ def _inverse_deadline(self):
+ for record in self:
+ record.validity = (record.date_deadline - record.create_date).days
+
+ # ===========button actions===========
+ def action_accept(self):
+
+ for record in self:
+ if "accepted" in record.property_id.offer_ids.mapped("status"):
+ already_accepted_exception = "already accepted an offer!"
+ raise exceptions.UserError(already_accepted_exception)
+ record.property_id.buyer_id = record.partner_id
+ record.property_id.state = "offer_accepted"
+ record.status = "accepted"
+ record.property_id.selling_price = record.price
+
+ def action_refuse(self):
+ for record in self:
+ for property in record.property_id:
+ record.status = "refused"
+
+ @api.model
+ def create(self, vals):
+ for to_create in vals:
+ property = self.env["estate.property"].browse(to_create["property_id"])
+ # If the property is already sold we can't add it
+ if property.state == "sold":
+ no_create_on_sold = "no create on already sold property"
+ raise exceptions.UserError(no_create_on_sold)
+
+ # Set a minimum bidding limit
+ new_bid = to_create["price"]
+ for offer in property.offer_ids:
+ if offer.price > float(new_bid):
+ cant_bit_lower_exception = "can't bid lower than the highest bid"
+ raise exceptions.UserError(cant_bit_lower_exception)
+ property.state = "offer_received"
+ return super().create(vals)
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
new file mode 100644
index 00000000000..de7a90154b1
--- /dev/null
+++ b/estate/models/estate_property_tag.py
@@ -0,0 +1,11 @@
+from odoo import fields, models
+
+
+class EstatePropertyTag(models.Model):
+ _name = "estate.property.tag"
+ _description = "estate tag model"
+ _order = "name"
+
+ name = fields.Char(required=True)
+
+ _check_unique_tag = models.Constraint("UNIQUE(name)", "tags should be unique")
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
new file mode 100644
index 00000000000..c44ccf2cdfc
--- /dev/null
+++ b/estate/models/estate_property_type.py
@@ -0,0 +1,20 @@
+from odoo import api, fields, models
+
+
+class EstatePropertyType(models.Model):
+ _name = "estate.property.type"
+ _description = "estate type model"
+ _order = "sequence, name"
+
+ name = fields.Char()
+ property_ids = fields.One2many("estate.property", "type_id")
+ sequence = fields.Integer(default=0)
+ offer_ids = fields.One2many("estate.property.offer", "property_type_id")
+ offer_count = fields.Integer(compute="_compute_offer_count")
+
+ _check_unique_type = models.Constraint("UNIQUE(name)", "types should be unique")
+
+ @api.depends("offer_ids")
+ def _compute_offer_count(self):
+ for property_type_record in self:
+ property_type_record.offer_count = len(property_type_record.offer_ids)
diff --git a/estate/models/inhereted_users.py b/estate/models/inhereted_users.py
new file mode 100644
index 00000000000..738f23ef0ea
--- /dev/null
+++ b/estate/models/inhereted_users.py
@@ -0,0 +1,10 @@
+from odoo import fields, models
+
+
+class InheritedModel(models.Model):
+ _inherit = "res.users"
+
+ property_ids = fields.One2many(
+ "estate.property",
+ "salesman_id",
+ domain=[("state", "in", ["new", "offer_received"])])
diff --git a/estate/tests/__init__.py b/estate/tests/__init__.py
new file mode 100644
index 00000000000..dfd37f0be11
--- /dev/null
+++ b/estate/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_estate
diff --git a/estate/tests/test_estate.py b/estate/tests/test_estate.py
new file mode 100644
index 00000000000..14b82ca5f82
--- /dev/null
+++ b/estate/tests/test_estate.py
@@ -0,0 +1,69 @@
+from odoo import Command
+from odoo.exceptions import UserError
+from odoo.tests import tagged
+from odoo.tests.common import TransactionCase
+from odoo.tests.form import Form
+
+
+# The CI will run these tests after all the modules are installed,
+# not right after installing the one defining it.
+@tagged('post_install', '-at_install')
+class EstateTestCase(TransactionCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.buyer_partner = cls.env['res.users'].create({
+ 'name': 'some guy',
+ 'login': 'some guy login',
+ })
+
+ sold_property_with_accepted_offer = {
+ "name": "property1",
+ "expected_price": "110.0",
+ "selling_price": "100.0",
+ "offer_ids": [Command.create({"name": "first offer", "price": "1100.0", "status": "accepted", "partner_id": cls.buyer_partner.partner_id.id})],
+ }
+ cls.properties = cls.env['estate.property'].create([sold_property_with_accepted_offer])
+ cls.properties.write({"state": "sold"})
+
+ property_with_no_offer = {
+ "name": "property2",
+ "expected_price": "110.0",
+ "selling_price": "100.0",
+ "offer_ids": [],
+ }
+ cls.env['estate.property'].create([property_with_no_offer])
+
+ def test_create_offer_on_sold(self):
+ sold_property_with_accepted_offer = self.properties.search([("name", "=", "property1")])
+
+ with self.assertRaises(UserError):
+ self.env["estate.property.offer"].create({
+ "name": "invalid offer",
+ "price": "1200.0",
+ "property_id": sold_property_with_accepted_offer.id,
+ "partner_id": self.buyer_partner.partner_id.id,
+ })
+
+ def test_no_sell_with_no_offer(self):
+ property_with_no_offer = self.properties.search([("name", "=", "property2")])
+ with self.assertRaises(UserError):
+ property_with_no_offer.write({
+ "state": "sold",
+ })
+
+ def test_is_sold_property_marked(self):
+ sold_property_with_accepted_offer = self.properties.search([("name", "=", "property1")])
+ self.assertRecordValues(sold_property_with_accepted_offer, [{"state": "sold"}])
+
+ property_with_no_offer = self.properties.search([("name", "=", "property2")])
+ self.assertRecordValues(property_with_no_offer, [{"state": "new"}])
+
+ def test_garden_reset(self):
+ property_with_no_offer = self.properties.search([("name", "=", "property2")])
+ self.assertRecordValues(property_with_no_offer, [{"garden_area": 0, "garden_orientation": False, "garden": False}])
+ with Form(property_with_no_offer) as property_form:
+ property_form.garden = True
+ property = property_form.save()
+ self.assertRecordValues(property, [{"garden_area": 10, "garden_orientation": "north", "garden": True}])
diff --git a/estate/views/estate_property_actions.xml b/estate/views/estate_property_actions.xml
new file mode 100644
index 00000000000..a31f2bc8571
--- /dev/null
+++ b/estate/views/estate_property_actions.xml
@@ -0,0 +1,28 @@
+
+
+
+ Property Types
+ estate.property.type
+ list,form
+
+
+
+ Property Tags
+ estate.property.tag
+ list,form
+
+
+
+ Offers
+ estate.property.offer
+ list,form
+ [("property_type_id", "==", active_id)]
+
+
+
+ Properties
+ estate.property
+ list,form,kanban
+ {'search_default_state': True}
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_tag_view_list.xml b/estate/views/estate_property_tag_view_list.xml
new file mode 100644
index 00000000000..3f4b563fcbf
--- /dev/null
+++ b/estate/views/estate_property_tag_view_list.xml
@@ -0,0 +1,13 @@
+
+
+
+ estate.property.tag.list
+ estate.property.tag
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_type_view_form.xml b/estate/views/estate_property_type_view_form.xml
new file mode 100644
index 00000000000..08bf6eef7d6
--- /dev/null
+++ b/estate/views/estate_property_type_view_form.xml
@@ -0,0 +1,29 @@
+
+
+
+ estate.property.type.form
+ estate.property.type
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_type_view_list.xml b/estate/views/estate_property_type_view_list.xml
new file mode 100644
index 00000000000..65d009a53a7
--- /dev/null
+++ b/estate/views/estate_property_type_view_list.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ estate.property.type.list
+ estate.property.type
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_view_form.xml b/estate/views/estate_property_view_form.xml
new file mode 100644
index 00000000000..3808445486f
--- /dev/null
+++ b/estate/views/estate_property_view_form.xml
@@ -0,0 +1,87 @@
+
+
+
+ estate.property.form
+ estate.property
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_view_kanban.xml b/estate/views/estate_property_view_kanban.xml
new file mode 100644
index 00000000000..2707891db08
--- /dev/null
+++ b/estate/views/estate_property_view_kanban.xml
@@ -0,0 +1,39 @@
+
+
+
+ estate.property.kanban
+ estate.property
+
+
+
+
+
+
+
+
+
+ Expected price:
+
+
+
+
+ Best Offer:
+
+
+
+
+
+ Selling price:
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_view_list.xml b/estate/views/estate_property_view_list.xml
new file mode 100644
index 00000000000..f72fc523a99
--- /dev/null
+++ b/estate/views/estate_property_view_list.xml
@@ -0,0 +1,20 @@
+
+
+
+ estate.property.list
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_view_search.xml b/estate/views/estate_property_view_search.xml
new file mode 100644
index 00000000000..af84c967570
--- /dev/null
+++ b/estate/views/estate_property_view_search.xml
@@ -0,0 +1,22 @@
+
+
+
+ estate.property.search
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/users_extra_views.xml b/estate/views/users_extra_views.xml
new file mode 100644
index 00000000000..a984be48ae4
--- /dev/null
+++ b/estate/views/users_extra_views.xml
@@ -0,0 +1,16 @@
+
+
+
+ res.users.inheret
+ res.users
+
+
+
+
+
+
+
+
+
+
+
diff --git a/estate_account/__init__.py b/estate_account/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/estate_account/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py
new file mode 100644
index 00000000000..ff5ddec2581
--- /dev/null
+++ b/estate_account/__manifest__.py
@@ -0,0 +1,15 @@
+{
+ 'name': 'estate_account-kehey',
+ 'depends': [
+ 'base',
+ 'estate',
+ 'account',
+ ],
+ 'data': [
+
+ ],
+ 'views': [
+
+ ],
+ 'application': False,
+}
diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py
new file mode 100644
index 00000000000..5e1963c9d2f
--- /dev/null
+++ b/estate_account/models/__init__.py
@@ -0,0 +1 @@
+from . import estate_property
diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py
new file mode 100644
index 00000000000..6ab5d8ad6bd
--- /dev/null
+++ b/estate_account/models/estate_property.py
@@ -0,0 +1,28 @@
+from odoo import Command, models
+
+
+class InheritedModel(models.Model):
+ _inherit = "estate.property"
+
+ def action_property_sold(self):
+ for record in self:
+ res = super().action_property_sold()
+ self.env["account.move"].sudo().create(
+ {
+ "partner_id": record.buyer_id.partner_id.id,
+ "move_type": "out_invoice",
+ "invoice_line_ids": [
+ Command.create({
+ "name": "six percent charge",
+ "quantity": "1",
+ "price_unit": record.selling_price * 0.06,
+ }),
+ Command.create({
+ "name": "administration fee",
+ "quantity": "1",
+ "price_unit": 100.0,
+ }),
+ ],
+ },
+ )
+ return res