diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py
index 77abad510e..5fa51ba737 100644
--- a/awesome_owl/__manifest__.py
+++ b/awesome_owl/__manifest__.py
@@ -38,5 +38,6 @@
'awesome_owl/static/src/**/*',
],
},
- 'license': 'AGPL-3'
+ 'license': 'AGPL-3',
+ 'auto_install': True
}
diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js
new file mode 100644
index 0000000000..14b27efb5d
--- /dev/null
+++ b/awesome_owl/static/src/card/card.js
@@ -0,0 +1,9 @@
+/** @odoo-module **/
+
+import { Component } from "@odoo/owl";
+
+export class Card extends Component {
+ static template = "awesome_owl.card";
+
+ static props = ['title', 'content'];
+}
diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml
new file mode 100644
index 0000000000..0a67b9e5f3
--- /dev/null
+++ b/awesome_owl/static/src/card/card.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ 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 0000000000..4ffec2e999
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.js
@@ -0,0 +1,21 @@
+/** @odoo-module **/
+
+import { Component, useState, xml } from "@odoo/owl";
+
+export class Counter extends Component {
+ static template = "awesome_owl.counter";
+
+ static props = {
+ onChange: { type: Function }
+ };
+
+ setup() {
+ this.state = useState({ value: 1 });
+ }
+
+ increment() {
+ this.state.value++;
+ if (this.props.onChange)
+ this.props.onChange();
+ }
+}
\ No newline at end of file
diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml
new file mode 100644
index 0000000000..1a8ec14f55
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Count:
+
+
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js
index 657fb8b07b..3ccb0115bf 100644
--- a/awesome_owl/static/src/playground.js
+++ b/awesome_owl/static/src/playground.js
@@ -1,7 +1,27 @@
/** @odoo-module **/
-import { Component } from "@odoo/owl";
+import { Component, markup, useState } from "@odoo/owl";
+
+// Custom elements
+import { Card } from "./card/card";
+import { Counter } from "./counter/counter";
+import { TodoList } from "./todo/todolist";
export class Playground extends Component {
static template = "awesome_owl.playground";
+
+ static components = { Card, Counter, TodoList };
+
+ setup() {
+ this.card_contents = [
+ { title: "Title 1", content: "
Text content 1
" },
+ { title: "Title 2", content: markup("Text content 2
") }
+ ];
+
+ this.state = useState({ total: 2});
+ }
+
+ incrementSum() {
+ this.state.total++;
+ }
}
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml
index 4fb905d59f..caf1a8ff93 100644
--- a/awesome_owl/static/src/playground.xml
+++ b/awesome_owl/static/src/playground.xml
@@ -3,8 +3,19 @@
- hello world
+
+
+
+ Total:
+
+
+
+
+
+
+
+
+
-
diff --git a/awesome_owl/static/src/todo/todoitem.js b/awesome_owl/static/src/todo/todoitem.js
new file mode 100644
index 0000000000..0c3909f8ac
--- /dev/null
+++ b/awesome_owl/static/src/todo/todoitem.js
@@ -0,0 +1,8 @@
+/** @odoo-module **/
+
+import { Component, useState, xml } from "@odoo/owl";
+
+export class TodoItem extends Component {
+ static template = "awesome_owl.todoitem";
+
+}
\ No newline at end of file
diff --git a/awesome_owl/static/src/todo/todoitem.xml b/awesome_owl/static/src/todo/todoitem.xml
new file mode 100644
index 0000000000..16c420e924
--- /dev/null
+++ b/awesome_owl/static/src/todo/todoitem.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ .
+
+
+
\ No newline at end of file
diff --git a/awesome_owl/static/src/todo/todolist.js b/awesome_owl/static/src/todo/todolist.js
new file mode 100644
index 0000000000..757f0ce7c3
--- /dev/null
+++ b/awesome_owl/static/src/todo/todolist.js
@@ -0,0 +1,32 @@
+/** @odoo-module **/
+
+import { Component, onMounted, useRef, useState } from "@odoo/owl";
+// Custom elements
+import { TodoItem } from "./todoitem";
+import { autoFocus } from "../utils";
+
+export class TodoList extends Component {
+ static template = "awesome_owl.todolist";
+ static components = { TodoItem };
+
+ nextID;
+
+ setup() {
+ this.nextID = 1;
+
+ this.todos = useState([]);
+ autoFocus("user_input");
+ }
+
+ addTodo(event) {
+ if (event.keyCode === 13 && event.target.value != '') {
+ this.todos.push({
+ id: this.nextID,
+ description: event.target.value,
+ isCompleted: false
+ });
+ this.nextID++;
+ event.target.value = "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/awesome_owl/static/src/todo/todolist.xml b/awesome_owl/static/src/todo/todolist.xml
new file mode 100644
index 0000000000..e1d2598974
--- /dev/null
+++ b/awesome_owl/static/src/todo/todolist.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js
new file mode 100644
index 0000000000..cf9c3afa5f
--- /dev/null
+++ b/awesome_owl/static/src/utils.js
@@ -0,0 +1,9 @@
+import { useRef, onMounted } from "@odoo/owl";
+
+export function autoFocus(refName) {
+ const input_ref = useRef(refName);
+
+ onMounted(() => {
+ input_ref.el.focus();
+ });
+}
\ No newline at end of file
diff --git a/estate/__init__.py b/estate/__init__.py
new file mode 100644
index 0000000000..0650744f6b
--- /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 0000000000..908d09a20c
--- /dev/null
+++ b/estate/__manifest__.py
@@ -0,0 +1,25 @@
+# A custom real estate module, created for learning purposes
+
+{
+ 'name': '[TUTO] Estate',
+ 'summary': 'Track your real estate properties',
+ 'depends': [
+ 'base_setup'
+ ],
+ 'license': "LGPL-3",
+ 'data': [
+ 'security/ir.model.access.csv',
+
+ 'views/estate_property_views.xml',
+ 'views/estate_property_offer_views.xml',
+ 'views/estate_settings_views.xml',
+ 'views/res_user_views.xml',
+ 'views/estate_menu_views.xml'
+ ],
+ 'installable': True,
+ 'application': True,
+ 'demo': [
+ "demo/estate_demo.xml",
+ ],
+ 'auto_install': True
+}
diff --git a/estate/demo/estate_demo.xml b/estate/demo/estate_demo.xml
new file mode 100644
index 0000000000..f65b321592
--- /dev/null
+++ b/estate/demo/estate_demo.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+ Appartment
+
+
+ House
+
+
+
+
+ cosy
+ 5
+
+
+ renovated
+ 3
+
+
+ renovation needed
+ 4
+
+
+
+
+ Beach House
+ new
+ A brand new house by the beach
+ 8300
+
+ 2500000
+ 0
+ 6
+ 1050
+ 4
+ True
+ True
+ 420
+ south
+
+
+
+
+
+ City Apt
+ offer_accepted
+ A shitty appartment in Brussels
+ 1090
+
+ 250000
+ 0
+ 2
+ 95
+ 1
+ False
+ False
+ 0
+
+
+
+
+
+ Small House
+ new
+ A small house in the centre of Brussels
+ 1000
+
+ 450000
+ 0
+ 2
+ 120
+ 2
+ True
+ False
+ 0
+
+
+
+
+
+
+
+ 2000000
+
+ 12
+
+
+
+ 200000
+
+ 10
+
+
+
+ 220000
+
+ 25
+
+
+
+ 260000
+ accepted
+
+
+
+
+
+
+
+ Mass cancel
+
+
+ list
+ code
+ action = records.action_cancel()
+
+
+
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
new file mode 100644
index 0000000000..adbca3c2d7
--- /dev/null
+++ b/estate/models/__init__.py
@@ -0,0 +1 @@
+from . import estate_property_infos, estate_property, estate_property_offer, res_user
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
new file mode 100644
index 0000000000..a0bcded0ab
--- /dev/null
+++ b/estate/models/estate_property.py
@@ -0,0 +1,150 @@
+from odoo import api, exceptions, fields, models
+from datetime import date
+from dateutil.relativedelta import relativedelta
+
+
+class Estate_Property(models.Model):
+ _name = "estate_property"
+ _description = "Estate properties"
+ _order = "id desc"
+ active = False
+
+ name = fields.Char(required=True, string="Title")
+
+ status = fields.Selection(
+ [
+ ("new", "New"),
+ ("offer_received", "Offer received"),
+ ("offer_accepted", "Offer Accepted"),
+ ("sold", "Sold"),
+ ("canceled", "Canceled"),
+ ],
+ default="new",
+ readonly=True,
+ copy=False,
+ string="Sale State",
+ )
+
+ description = fields.Text(copy=False, string="Description")
+
+ postcode = fields.Char(
+ help="Une adresse serait mieux mais bon...", string="Postcode"
+ )
+
+ date_availability = fields.Date(
+ default=(date.today() + relativedelta(months=+3)),
+ copy=False,
+ string="Available From",
+ )
+
+ expected_price = fields.Float(
+ default=0.0, required=True, copy=False, string="Expected Price"
+ )
+
+ selling_price = fields.Float(readonly=True, string="Selling Price")
+
+ bedrooms = fields.Integer(default=2, string="Bedrooms")
+
+ living_area = fields.Integer(string="Living Area (sqm)")
+
+ facades = fields.Integer(default=2, string="Facades")
+
+ garage = fields.Boolean(default=False, string="Garage")
+
+ garden = fields.Boolean(default=False, string="Garden")
+
+ garden_area = fields.Integer(default=0, string="Garden Area (sqm)")
+
+ garden_orientation = fields.Selection(
+ [("north", "North"), ("south", "South"), ("west", "West"), ("east", "East")],
+ string="Garden Orientation",
+ )
+
+ type_id = fields.Many2one(
+ "estate_property_type", required=True, string="Property Type"
+ )
+
+ buyer = fields.Many2one("res.partner", copy=False, string="Buyer")
+
+ salesperson = fields.Many2one(
+ "res.users", default=(lambda self: self.env.user), string="Salesman"
+ )
+
+ tag_ids = fields.Many2many("estate_property_tag", string="Tags")
+
+ offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers")
+
+ total_area = fields.Integer(compute="_compute_total_area", string="Total Area")
+
+ best_offer = fields.Float(
+ compute="_compute_best_offer", default=0.0, string="Best Offer"
+ )
+
+ _sql_constraints = [
+ (
+ "check_positive_expected_price",
+ "CHECK(expected_price >= 0.0)",
+ "Expected Price should be a positive number.",
+ ),
+ (
+ "check_positive_selling_price",
+ "CHECK(selling_price >= 0.0)",
+ "Selling Price should be a positive number.",
+ ),
+ ]
+
+ @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:
+ if record.offer_ids:
+ record.best_offer = max(record.offer_ids.mapped("price"))
+ else:
+ record.best_offer = .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 = ""
+
+ @api.constrains("selling_price", "expected_price")
+ def _check_expected_vs_selling_price(self):
+ for record in self:
+ if (record.selling_price > 0.0) and (
+ record.selling_price < 0.9 * record.expected_price
+ ):
+ raise exceptions.ValidationError(
+ r"Cannot sell for less than 90% of expected price."
+ )
+
+ @api.ondelete(at_uninstall=False)
+ def _delete_only_new_canceled(self):
+ for record in self:
+ if not (record.status == "new" or record.status == "canceled"):
+ raise exceptions.UserError("Sold properties / properties with pending offers cannot be deleted.")
+
+ def action_sold(self):
+ for record in self:
+ if record.status == "canceled":
+ raise exceptions.UserError("Canceled properties cannot be sold.")
+ elif record.status == "offer_accepted":
+ record.status = "sold"
+ else:
+ raise exceptions.UserError("An offer must be accepted for a property to be sold.")
+ return True
+
+ def action_cancel(self):
+ for record in self:
+ if record.status != "sold":
+ record.status = "canceled"
+ else:
+ raise exceptions.UserError("Sold properties cannot be canceled.")
+ return True
diff --git a/estate/models/estate_property_infos.py b/estate/models/estate_property_infos.py
new file mode 100644
index 0000000000..5626857662
--- /dev/null
+++ b/estate/models/estate_property_infos.py
@@ -0,0 +1,52 @@
+from odoo import api, fields, models
+
+
+class Estate_Property_Type(models.Model):
+ _name = "estate_property_type"
+ _description = "Estate property Types"
+ _order = "name"
+
+ sequence = fields.Integer(
+ "Sequence", default=1, help="Used to order types. Lower is better."
+ )
+
+ name = fields.Char(required=True, string="Type")
+
+ property_ids = fields.One2many(
+ "estate_property", "type_id", string="Estate Properties"
+ )
+
+ offer_ids = fields.One2many(
+ "estate_property_offer", "property_type_id", string="Offer IDs"
+ )
+
+ offer_count = fields.Integer(
+ compute="_count_offers", default=0, string="Offers"
+ )
+
+ _sql_constraints = [
+ ("check_unique_type", "UNIQUE(name)", "Property types must be unique.")
+ ]
+
+ @api.depends("offer_ids")
+ def _count_offers(self):
+ for record in self:
+ record.offer_count = len(record.offer_ids) if record.offer_ids else 0
+
+
+class Estate_Property_Tag(models.Model):
+ _name = "estate_property_tag"
+ _description = "Estate property Tags"
+ _order = "name"
+
+ name = fields.Char(required=True, string="Type")
+
+ color = fields.Integer(string="Colour")
+
+ property_estate_ids = fields.Many2many(
+ "estate_property", string="Estate Properties"
+ )
+
+ _sql_constraints = [
+ ("check_unique_tag", "UNIQUE(name)", "Property tags must be unique.")
+ ]
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
new file mode 100644
index 0000000000..fc6df35195
--- /dev/null
+++ b/estate/models/estate_property_offer.py
@@ -0,0 +1,85 @@
+from odoo import api, exceptions, fields, models
+from datetime import date
+from dateutil.relativedelta import relativedelta
+
+
+class Estate_Property_Offer(models.Model):
+ _name = "estate_property_offer"
+ _description = "Estate Property Offers"
+ _order = "price desc"
+
+ price = fields.Float(string="Price")
+
+ status = fields.Selection(
+ [("accepted", "Accepted"), ("refused", "Refused")],
+ readonly=True,
+ copy=False,
+ string="Status",
+ )
+
+ partner_id = fields.Many2one("res.partner", required=True, string="Partner")
+
+ property_id = fields.Many2one("estate_property", string="Property")
+
+ property_type_id = fields.Many2one(
+ "estate_property_type", related="property_id.type_id"
+ )
+
+ validity = fields.Integer(default=7, string="Validity (days)")
+
+ deadline = fields.Date(compute="_compute_deadline", copy=False, string="Deadline")
+
+ _sql_constraints = [
+ (
+ "check_positive_price",
+ "CHECK(price > 0.0)",
+ "Offer Price should be a positive number (higher than 0).",
+ )
+ ]
+
+ @api.depends("validity")
+ def _compute_deadline(self):
+ for record in self:
+ record.deadline = date.today() + relativedelta(days=+record.validity)
+
+ def _inverse_deadline(self):
+ for record in self:
+ record.validity = relativedelta(date.today(), record.deadline)
+
+ def action_accept(self):
+ for record in self:
+ if not any(
+ offer_status == "accepted"
+ for offer_status in record.property_id.offer_ids.mapped("status")
+ ):
+ # Set values in the Property itself
+ record.property_id.selling_price = record.price
+ record.property_id.buyer = record.partner_id
+ record.property_id.status = "offer_accepted"
+
+ record.status = "accepted"
+ else:
+ raise exceptions.UserError("An offer has already been accepted.")
+ return True
+
+ def action_refuse(self):
+ for record in self:
+ if record.status == "accepted":
+ # Set values in the Property itself
+ record.property_id.selling_price = 0.0
+ record.property_id.buyer = None
+ record.property_id.status = "offer_received"
+ record.status = "refused"
+ return True
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for record in vals_list:
+ _property = self.env["estate_property"].browse(record["property_id"])
+ if record["price"] < _property["best_offer"]:
+ raise exceptions.ValidationError(
+ r"Cannot offer less than the best pending offer."
+ )
+ if _property["status"] == "new":
+ _property["status"] = "offer_received"
+ return super().create(vals_list)
diff --git a/estate/models/res_user.py b/estate/models/res_user.py
new file mode 100644
index 0000000000..fdccb3fa94
--- /dev/null
+++ b/estate/models/res_user.py
@@ -0,0 +1,12 @@
+from odoo import fields, models
+
+
+class InheritedUser(models.Model):
+ _inherit = "res.users"
+
+ property_ids = fields.One2many(
+ "estate_property",
+ "salesperson",
+ domain=["|", ("status", "=", "new"), ("status", "=", "offer_received")],
+ string="Properties",
+ )
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv
new file mode 100644
index 0000000000..0db13e578e
--- /dev/null
+++ b/estate/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1
+estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1
+estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1
+estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/estate/static/description/icon.webp b/estate/static/description/icon.webp
new file mode 100644
index 0000000000..9758ad2b58
Binary files /dev/null and b/estate/static/description/icon.webp differ
diff --git a/estate/views/estate_menu_views.xml b/estate/views/estate_menu_views.xml
new file mode 100644
index 0000000000..a4cdc3455e
--- /dev/null
+++ b/estate/views/estate_menu_views.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml
new file mode 100644
index 0000000000..d9704d34cc
--- /dev/null
+++ b/estate/views/estate_property_offer_views.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ estate_property_offer_list
+ estate_property_offer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Offers
+ estate_property_offer
+ list
+ [('property_type_id', '=', active_id)]
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
new file mode 100644
index 0000000000..397a82fbeb
--- /dev/null
+++ b/estate/views/estate_property_views.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+ estate_property_search
+ estate_property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate_property_form
+ estate_property
+
+
+
+
+
+
+
+
+
+ estate_property_list
+ estate_property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate_property_kanban
+ estate_property
+
+
+
+
+
+
+
+
+
Expected price:
+
+
+
+
+ Best offer:
+
+
+
+ Selling price:
+
+
+
+
+
+
+
+
+
+
+
+ Estate Properties
+ estate_property
+ list,form,kanban
+ {'search_default_availability': True}
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_settings_views.xml b/estate/views/estate_settings_views.xml
new file mode 100644
index 0000000000..f31d88e563
--- /dev/null
+++ b/estate/views/estate_settings_views.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+ estate_property_type_form
+ estate_property_type
+
+
+
+
+
+
+
+
+ estate_property_type_list
+ estate_property_type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate_property_tag_list
+ estate_property_tag
+
+
+
+
+
+
+
+
+
+
+ Property Types
+ estate_property_type
+ list,form
+
+
+
+ Property Tags
+ estate_property_tag
+ list
+
+
+
\ No newline at end of file
diff --git a/estate/views/res_user_views.xml b/estate/views/res_user_views.xml
new file mode 100644
index 0000000000..cdfabcad56
--- /dev/null
+++ b/estate/views/res_user_views.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ user_properties
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate_account/__init__.py b/estate_account/__init__.py
new file mode 100644
index 0000000000..0650744f6b
--- /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 0000000000..2ef66d2e19
--- /dev/null
+++ b/estate_account/__manifest__.py
@@ -0,0 +1,16 @@
+# A custom real estate module, created for learning purposes
+
+{
+ 'name': '[TUTO] Estate_Account',
+ 'summary': 'Module to invoice sold real estate properties',
+ 'depends': [
+ 'base_setup', 'estate', 'account'
+ ],
+ 'license': "LGPL-3",
+ 'data': [
+ ],
+ 'installable': True,
+ 'demo': [
+ ],
+ 'auto_install': True
+}
diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py
new file mode 100644
index 0000000000..5e1963c9d2
--- /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 0000000000..9e3282f4ad
--- /dev/null
+++ b/estate_account/models/estate_property.py
@@ -0,0 +1,41 @@
+from odoo import Command, models
+from datetime import date
+
+
+class Estate_Property(models.Model):
+ _inherit = "estate_property"
+
+ def action_sold(self):
+ super().action_sold()
+
+ for record in self:
+ record.env["account.move"].create(
+ {
+ "partner_id": record.buyer.id,
+ "move_type": "out_invoice",
+ "invoice_date": date.today(),
+ "journal_id": (
+ record.env["account.journal"].search(
+ [("type", "=", "sale")], limit=1
+ )
+ ).id,
+ "line_ids": [
+ Command.create(
+ {
+ "name": record.name,
+ "quantity": 1,
+ "price_unit": 0.06 * record.selling_price,
+ }
+ ),
+ Command.create(
+ {
+ "name": "Administrative fee",
+ "quantity": 1,
+ "price_unit": 100,
+ }
+ ),
+ ],
+ }
+ )
+
+ return True