diff --git a/.gitignore b/.gitignore index b6e47617de1..fd710c264d3 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ celerybeat.pid *.sage.py # Environments +.vscode/ .env .venv env/ diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..35cb019c772 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,30 +1,29 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Dashboard", - - 'summary': """ + "name": "Awesome Dashboard", + "summary": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'description': """ + "description": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com/", - 'category': 'Tutorials/AwesomeDashboard', - 'version': '0.1', - 'application': True, - 'installable': True, - 'depends': ['base', 'web', 'mail', 'crm'], - - 'data': [ - 'views/views.xml', + "author": "Odoo", + "website": "https://www.odoo.com/", + "category": "Tutorials/AwesomeDashboard", + "version": "0.1", + "application": True, + "installable": True, + "depends": ["base", "web", "mail", "crm"], + "data": [ + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + "assets": { + "web.assets_backend": [ + "awesome_dashboard/static/src/**/*", + ], + "awesome_dashboard.dashboard": [ + "awesome_dashboard/static/src/dashboard/**/*", ], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 637fa4bb972..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/chart/pie.js b/awesome_dashboard/static/src/dashboard/chart/pie.js new file mode 100644 index 00000000000..4bd0d2f5601 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/chart/pie.js @@ -0,0 +1,40 @@ +import { Component, useRef, onWillStart, useEffect } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.pie_chart"; + static props = { + data: { type: Object }, + }; + + setup() { + this.canvasRef = useRef('canvasRef'); + this.chart = null; + + onWillStart(async () => { + await loadJS(["/web/static/lib/Chart/Chart.js"]); + }); + + useEffect(()=> this.renderChart()) + + } + + renderChart() { + if (this.chart) { + this.chart.destroy();} + this.chart = new Chart(this.canvasRef.el, this.getChartConfig()); + } + + getChartConfig() { + return { + type: 'pie', + data: { + labels: this.props.labels, + datasets: [{ + data: Object.values(this.props.data) + }], + }, + } + } + +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard/chart/pie.xml similarity index 51% rename from awesome_dashboard/static/src/dashboard.xml rename to awesome_dashboard/static/src/dashboard/chart/pie.xml index 1a2ac9a2fed..c5447fff565 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/chart/pie.xml @@ -1,8 +1,9 @@ - - hello dashboard + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..6b8315016e2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,56 @@ +/** @odoo-module **/ + +import { Component , useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboarditem/dashboarditem"; +import { PieChart } from "./chart/pie"; +import { dashboardRegistry } from "./dashboarditem/dashboard_items"; +import { SettingsDialog } from "./dialog/dialog"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout , DashboardItem , PieChart}; + setup() { + this.action = useService("action"); + this.dialogService = useService("dialog"); + + this.statisticsService = useService("awesome_dashboard.statistics"); + this.state = useState(this.statisticsService.state.dashboardItems); + + const savedHiddenItems = JSON.parse(localStorage.getItem("hidden_dashboard_items")); + this.state.hiddenItems = new Set(savedHiddenItems); + + + } + get display() { + return {controlPanel: {} } + } + async openCustomers() { + + this.action.doAction("base.action_partner_form"); + } + + async opendialog() { + this.dialogService.add(SettingsDialog, { + onApply: (hiddenItems) => { + this.state.hiddenItems = new Set(hiddenItems); + }, + }); + } + async openCrmlead() { + this.action.doAction({ + type: 'ir.actions.act_window', + name:'crm leads', + target: 'current', + res_model: 'crm.lead', + views: [[false, 'list'],[false, 'form']], + }); + } + get visibleItems() { + return Object.values(dashboardRegistry.getAll()).filter(item => !this.state.hiddenItems.has(item.id)); + } +} + +registry.category("lazy_components").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..7c32bf3c842 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+
+
+ +
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboarditem/dashboard_items.js new file mode 100644 index 00000000000..2d33d48d056 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/dashboard_items.js @@ -0,0 +1,78 @@ +/** @odoo-module **/ +import { NumberCard } from "./number_card"; +import { PieChartCard } from "./pie_chart_card"; +import { registry } from "@web/core/registry"; + +export const dashboardRegistry = registry.category("awesome_dashboard.items"); + +dashboardRegistry.add( + "average_quantity", { + id: "average_quantity", + description: "Average amount of t-shirt by order", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "T-shirt Ordered", + value: data, + }), +}) + +dashboardRegistry.add( + "average_time", { + id: "average_time", + description: "Average Time", + Component: NumberCard, + size: 2, + props: (data) => ({ + title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + value: data, + }), +}) + +dashboardRegistry.add( + "nb_new_orders", { + id: "nb_new_orders", + description: "New orders this month", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Numbers of new orders this month", + value: data, + }), +}) + +dashboardRegistry.add( + "nb_cancelled_orders", { + id: "nb_cancelled_orders", + description: "Cancelled orders this month", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Numbers of cancelled orders this month", + value: data, + }) +}) + +dashboardRegistry.add( + "total_amount", { + id: "total_amount", + description: "Total Orders", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Total Numbers of new orders this month", + value: data, + }), +}) + +dashboardRegistry.add( + "orders_by_size", { + id: "orders_by_size", + description: "Orders by size", + Component: PieChartCard, + size: 1.5, + props: (data) => ({ + label: "Orders by size", + data: data, + }), +}) \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/dashboarditem.js b/awesome_dashboard/static/src/dashboard/dashboarditem/dashboarditem.js new file mode 100644 index 00000000000..854078553b4 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/dashboarditem.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.dashboarditem"; + static props ={ + size: { type: Number, optional: true, default: 1}, + } + +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/dashboarditem.xml b/awesome_dashboard/static/src/dashboard/dashboarditem/dashboarditem.xml new file mode 100644 index 00000000000..706c1076589 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/dashboarditem.xml @@ -0,0 +1,8 @@ + + + +
+ +
+
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/number_card.js b/awesome_dashboard/static/src/dashboard/dashboarditem/number_card.js new file mode 100644 index 00000000000..221ba45cebc --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/number_card.js @@ -0,0 +1,9 @@ +import { Component} from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard" + static props = { + title: String, + value: Number + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/number_card.xml b/awesome_dashboard/static/src/dashboard/dashboarditem/number_card.xml new file mode 100644 index 00000000000..be7bc241310 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/number_card.xml @@ -0,0 +1,11 @@ + + + + + +
+ +
+
+ +
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/dashboarditem/pie_chart_card.js new file mode 100644 index 00000000000..9459decabd9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/pie_chart_card.js @@ -0,0 +1,12 @@ +import { Component} from "@odoo/owl"; +import { PieChart } from "../chart/pie"; +export class PieChartCard extends Component { + static template= 'awesome_dashboard.PieChartCard' + + static props = { + label: String, + data: Object, + }; + + static components = { PieChart }; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboarditem/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/dashboarditem/pie_chart_card.xml new file mode 100644 index 00000000000..48d423010c0 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboarditem/pie_chart_card.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dialog/dialog.js b/awesome_dashboard/static/src/dashboard/dialog/dialog.js new file mode 100644 index 00000000000..8bf91b59039 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dialog/dialog.js @@ -0,0 +1,36 @@ +/** @odoo-module **/ +import { Component, useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { Dialog } from "@web/core/dialog/dialog"; +import { dashboardRegistry } from "../dashboarditem/dashboard_items"; + +export class SettingsDialog extends Component { + + static template = "awesome_dashboard.SettingsDialog"; + static components = { Dialog }; + setup() { + this.dialogService = useService("dialog"); + this.items = Object.values(dashboardRegistry.getAll()); + + const savedHiddenItems = JSON.parse(localStorage.getItem("hidden_dashboard_items")); + this.state = useState({ + hiddenItems: new Set(savedHiddenItems), + }); + } + + toggleItem(event) { + if (this.state.hiddenItems.has(event.target.id)) { + this.state.hiddenItems.delete(event.target.id); + } else { + this.state.hiddenItems.add(event.target.id); + } + } + + applySettings() { + localStorage.setItem("hidden_dashboard_items", JSON.stringify([...this.state.hiddenItems])); + this.props.onApply([...this.state.hiddenItems]); + + this.dialogService.closeAll(); + } +} + diff --git a/awesome_dashboard/static/src/dashboard/dialog/dialog.xml b/awesome_dashboard/static/src/dashboard/dialog/dialog.xml new file mode 100644 index 00000000000..7d1e75bba63 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dialog/dialog.xml @@ -0,0 +1,24 @@ + + + + +
+

Which cards do you wish to see?

+ +
+ + +
+
+
+ + + + +
+
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/service/awesome_dashboard_statistics.js b/awesome_dashboard/static/src/dashboard/service/awesome_dashboard_statistics.js new file mode 100644 index 00000000000..2b02e88412b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/service/awesome_dashboard_statistics.js @@ -0,0 +1,31 @@ +/** @odoo-module **/ +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; + +const REFRESH_INTERVAL = 10000; + +export const statisticsService = { + dependencies: [], + async start(env) { + const state = reactive({ dashboardItems: {} }); + + async function loadStatistics() { + const data = await rpc("/awesome_dashboard/statistics", {}); + Object.assign(state.dashboardItems, data); // Update in place + } + + // Initial load + await loadStatistics(); + + // Periodic refresh + setInterval(loadStatistics, REFRESH_INTERVAL); + + return { + state, + }; + }, +}; + +// Register the service +registry.category("services").add("awesome_dashboard.statistics", statisticsService); \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard_loader.js b/awesome_dashboard/static/src/dashboard_loader.js new file mode 100644 index 00000000000..7e997778ba5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,13 @@ +import { Component ,xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + + +export class DashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashboardLoader); \ No newline at end of file diff --git a/awesome_dashboard/views/views.xml b/awesome_dashboard/views/views.xml index 47fb2b6f258..f0341cd364c 100644 --- a/awesome_dashboard/views/views.xml +++ b/awesome_dashboard/views/views.xml @@ -5,7 +5,7 @@ awesome_dashboard.dashboard - - + + diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..5e9759dcb7b --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: {type: String}, + content: {type: String}, + slots: {type:Object} + } + + +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..672fa8b577a --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,21 @@ + + + + + +
+
+
+ +
+

+ + +

+ + +
+
+
+ +
\ 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..f1035547286 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,20 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props ={ + incrementsum:{type:Function} + } + + setup() { + this.state = useState({ value: 0 }); + } + + + increment() { + this.state.value++; + this.props.incrementsum(); + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..07965335c23 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,13 @@ + + + + +
+

Counter: +

+ +
+ +
+ +
\ No newline at end of file diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..44637bb4cf4 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,32 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import {markup, Component , useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { Todolist } from "./todo/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static props ={ + + } + value2 = markup("
some text 2
"); + static components = { Counter , Card , Todolist}; + + state= useState({ + sum:0, + card1:false, + card2:false + }) + incrementsum= ()=> { + this.state.sum++; + } + inccard1= ()=> { + this.state.card1= !this.state.card1; + + } + inccard2= ()=> { + this.state.card2= !this.state.card2; + + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..f6b70094694 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,9 +2,31 @@ +
+ + + +
+ + + + + + + + + + + + + + +
- hello world +

sum: +

+
diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..01f0be104a7 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,17 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; + +export class Todoitem extends Component { + static template = "awesome_owl.todoitem"; + static props = { + item: {type: Object + }, + delete: {type: Function + } + } + check = () =>{ + this.props.item.isCompleted = !this.props.item.isCompleted + } + +} diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..9979ee5dfd6 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,18 @@ + + + + +
+ +
  • + + + + +
  • + +
    + +
    + +
    \ No newline at end of file diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..922a318bc17 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,54 @@ +/** @odoo-module **/ + +import { Component, useState, onMounted, useRef } from "@odoo/owl"; +import { Todoitem } from "./todo_item"; +export class Todolist extends Component { + static template = "awesome_owl.todolist"; + static props ={ + + } + setup() { + this.inputRef = useRef('input'); + onMounted(() => { + this.inputRef.el.focus() + }); + this.todo= useState([{ + id:1, + name:"ch1-owl", + isCompleted: false + }, + { + id:2, + name:"ch2-owl", + isCompleted: false + }, + { + id:3, + name:"estate", + isCompleted: true + }]) + } + + addtodo = (e) => { + if(e.key=="Enter" && e.target.value){ + this.todo.push({ + id: this.todo.length +2, + name: e.target.value, + isCompleted: false + + }) + e.target.value="" + + } + } + + delete=(e)=>{ + const index = this.todo.findIndex((todo) => todo.id === e.id); + if (index >= 0) { + this.todo.splice(index, 1); + } + } + + static components = {Todoitem} + +} diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..221ae0b4648 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,17 @@ + + + + +
    + +
      + + + + +
    +
    + +
    + +
    \ No newline at end of file diff --git a/book_price/__init__.py b/book_price/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/book_price/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/book_price/__manifest__.py b/book_price/__manifest__.py new file mode 100644 index 00000000000..1f345594551 --- /dev/null +++ b/book_price/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +{ + "name": "book_price", + "summary": "Add Pricelist Price (yame)", + "description": "Add Pricelist Price (yame)", + "author": "Odoo", + "website": "https://www.odoo.com", + "category": "Tutorials/book_price", + "version": "0.1", + # any module necessary for this one to work correctly + "depends": ["base", "web", "sale", "sale_management", "account"], + "application": True, + "installable": True, + "data": [ + "views/sale_order_views.xml", + "views/account_move_views.xml", + ], + "license": "AGPL-3", +} diff --git a/book_price/models/__init__.py b/book_price/models/__init__.py new file mode 100644 index 00000000000..ea3d9579546 --- /dev/null +++ b/book_price/models/__init__.py @@ -0,0 +1,2 @@ +from . import sale_order_line +from . import account_move_line diff --git a/book_price/models/account_move_line.py b/book_price/models/account_move_line.py new file mode 100644 index 00000000000..8f3ac000322 --- /dev/null +++ b/book_price/models/account_move_line.py @@ -0,0 +1,15 @@ +from odoo import api, fields, models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + book_price = fields.Float(string="Book Price", compute="_compute_book_price") + + @api.depends("product_id.lst_price") + def _compute_book_price(self): + print(" Book Price ".center(100, "=")) + for record in self: + print(record.move_type) + print(record.product_id.lst_price) + record.book_price = record.product_id.lst_price diff --git a/book_price/models/sale_order_line.py b/book_price/models/sale_order_line.py new file mode 100644 index 00000000000..262ffd4be1c --- /dev/null +++ b/book_price/models/sale_order_line.py @@ -0,0 +1,14 @@ +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + book_price = fields.Float( + string="Book Price", readonly=True, compute="_compute_book_price" + ) + + @api.depends("product_id.lst_price") + def _compute_book_price(self): + for record in self: + record.book_price = record.product_id.lst_price diff --git a/book_price/views/account_move_views.xml b/book_price/views/account_move_views.xml new file mode 100644 index 00000000000..f838dd556b1 --- /dev/null +++ b/book_price/views/account_move_views.xml @@ -0,0 +1,20 @@ + + + + + account.move.form + account.move + + + + + + + + + + + + + + diff --git a/book_price/views/sale_order_views.xml b/book_price/views/sale_order_views.xml new file mode 100644 index 00000000000..3d1ff67dc17 --- /dev/null +++ b/book_price/views/sale_order_views.xml @@ -0,0 +1,17 @@ + + + + + sale.order.form + sale.order + + + + + + + + + + + diff --git a/budget/__init__.py b/budget/__init__.py new file mode 100644 index 00000000000..c536983e2b2 --- /dev/null +++ b/budget/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard \ No newline at end of file diff --git a/budget/__manifest__.py b/budget/__manifest__.py new file mode 100644 index 00000000000..f5020da3ea4 --- /dev/null +++ b/budget/__manifest__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +{ + "name": "budget", + "summary": "budget (yame)", + "description": "budget (yame)", + "author": "Odoo", + "website": "https://www.odoo.com", + "category": "Tutorials/budget", + "version": "0.1", + "depends": ["base", "web", "account"], + "application": True, + "installable": True, + "data": [ + "security/ir.model.access.csv", + "views/account_analytic_line.xml", + "wizard/budget_wizard_view.xml", + "views/budget_budget_views.xml", + "views/budget_line_views.xml", + "views/budget_menu.xml", + ], + "license": "AGPL-3", +} diff --git a/budget/models/__init__.py b/budget/models/__init__.py new file mode 100644 index 00000000000..ba4fc70ff89 --- /dev/null +++ b/budget/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_analytical_line +from . import budget +from . import budget_line diff --git a/budget/models/account_analytical_line.py b/budget/models/account_analytical_line.py new file mode 100644 index 00000000000..03200f57dd5 --- /dev/null +++ b/budget/models/account_analytical_line.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + budget_line_id = fields.Many2one("budget.line", ondelete="cascade") diff --git a/budget/models/budget.py b/budget/models/budget.py new file mode 100644 index 00000000000..8874c7b77dc --- /dev/null +++ b/budget/models/budget.py @@ -0,0 +1,158 @@ +from markupsafe import Markup +from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.exceptions import ValidationError + + +class BudgetBudget(models.Model): + _name = "budget.budget" + _description = "budget.budget" + _order = "duration_start_date" + _inherit = ["mail.thread", "mail.activity.mixin", "avatar.mixin"] + + name = fields.Char() + description = fields.Char(compute="_compute_description") + active = fields.Boolean("active", default=True) + warning = fields.Boolean(compute="_compute_warraning_budget_line") + + duration_start_date = fields.Date( + "duration_start_date", + ) + duration_end_date = fields.Date( + "duration_end_date", + ) + status = fields.Selection( + selection=[ + ("Draft", "Draft"), + ("Confirmed", "Confirmed"), + ("Revised", "Revised"), + ("Done", "Done"), + ], + default="Draft", + ) + over_budget = fields.Selection( + selection=[("warning", "warning"), ("restriction", "restriction")] + ) + responsible = fields.Many2one( + "res.users", ondelete="restrict", default=lambda self: self.env.user + ) + company_id = fields.Many2one( + "res.company", required=True, default=lambda self: self.env.company + ) + + revise_by = fields.Many2one("res.users", ondelete="restrict", copy=False) + budget_line_ids = fields.One2many( + "budget.line", inverse_name="budget_id", string="budget_lines", copy=True + ) + + message_ids = fields.One2many( + "mail.message", + "res_id", + domain=[("model", "=", "budget.budget"), ("model", "=", "budget.line")], + string="Messages", + ) + + @api.constrains("duration_start_date", "duration_end_date") + def _constrains_date(self): + for record in self: + if record.duration_start_date > record.duration_end_date: + raise UserError( + "Invalid duration! The start date cannot be later than the end date. Please select a valid date range." + ) + budget = self.env["budget.budget"].search( + [ + ("duration_start_date", "=", record.duration_start_date), + ("duration_end_date", "=", record.duration_end_date), + ("id", "!=", self.id), + ("active", "=", True), + ] + ) + if budget: + raise ValidationError( + "Warning: A budget already exists for this duration." + ) + + @api.depends("name", "duration_start_date", "duration_end_date") + def _compute_description(self): + for record in self: + if record.name: + record.description = ( + record.name + + ": " + + str(record.duration_start_date) + + " to " + + str(record.duration_end_date) + ) + else: + record.description = "" + + @api.depends("budget_line_ids.budget_achive") + def _compute_warraning_budget_line(self): + for record in self: + record.warning = True + if record.over_budget == "warning": + for line in record.budget_line_ids: + if line.budget_achive > line.budget_total: + record.warning = False + + def action_draft(self): + for record in self: + record.status = "Draft" + return True + + def action_confirm(self): + for record in self: + record.status = "Confirmed" + return True + + def action_done(self): + for record in self: + record.status = "Done" + return True + + def action_revise(self): + for record in self: + record.write( + {"revise_by": self.env.user, "status": "Revised", "active": False} + ) + revised_budget = record.sudo().copy( + default={ + "name": record.name, + "status": "Draft", + "duration_start_date": record.duration_start_date, + "duration_end_date": record.duration_end_date, + "active": True, + } + ) + record.message_post( + body=Markup( + "%s: %s" + ) + % ( + "new budgetid", + revised_budget.id, + record.name, + ) + ) + return True + + def action_budget_lines(self): + return { + "name": "Budget Lines", + "view_mode": "list,graph,pivot,gantt", + "res_model": "budget.line", + "type": "ir.actions.act_window", + "context": { + "default_budget_id": self.id, + }, + "domain": [("budget_id", "=", self.id)], + } + + def action_budget_form(self): + return { + "name": "Budget", + "view_mode": "form", + "res_model": "budget.budget", + "type": "ir.actions.act_window", + "res_id": self.id, + } diff --git a/budget/models/budget_line.py b/budget/models/budget_line.py new file mode 100644 index 00000000000..2816aa75aef --- /dev/null +++ b/budget/models/budget_line.py @@ -0,0 +1,52 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class BudgetLine(models.Model): + _name = "budget.line" + _description = "budget.line" + + name = fields.Char(default="budget line") + budget_total = fields.Float() + budget_achive = fields.Float(compute="_compute_achieved_amount", store=True) + analytic_account_id = fields.Many2one( + "account.analytic.account", + ) + budget_id = fields.Many2one("budget.budget", ondelete="cascade") + date_from = fields.Date(related="budget_id.duration_start_date", store=True) + date_to = fields.Date(related="budget_id.duration_end_date", store=True) + analytic_line_ids = fields.One2many("account.analytic.line", "budget_line_id") + + @api.constrains("budget_achive") + def _constrains_amount(self): + for record in self: + if record.budget_id.over_budget == "restriction": + if record.budget_achive > record.budget_total: + raise ValidationError( + "Invalid amount! The achieved budget cannot exceed the total budget. Please adjust the amount accordingly." + ) + + @api.depends("analytic_line_ids.amount") + def _compute_achieved_amount(self): + for line in self: + line.budget_achive = abs( + sum( + line.analytic_line_ids.filtered(lambda l: l.amount < 0).mapped( + "amount" + ) + ) + ) + + def action_open_account_analytic(self): + return { + "name": "Analytic Lines", + "view_mode": "list,form", + "res_model": "account.analytic.line", + "type": "ir.actions.act_window", + "context": { + "default_budget_line_id": self.id, + "default_account_id": self.analytic_account_id.id, + "default_date": self.budget_id.duration_start_date, + }, + "domain": [("budget_line_id", "=", self.id)], + } diff --git a/budget/security/ir.model.access.csv b/budget/security/ir.model.access.csv new file mode 100644 index 00000000000..15b071ed79e --- /dev/null +++ b/budget/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +budget.access_budget_budget,access_budget_budget,budget.model_budget_budget,base.group_user,1,1,1,1 +budget.access_budget_line,access_budget_line,budget.model_budget_line,base.group_user,1,1,1,1 +budget.access_budget_wizard,access_budget_wizard,budget.model_budget_wizard,base.group_user,1,1,1,1 diff --git a/budget/static/budget.png b/budget/static/budget.png new file mode 100644 index 00000000000..c14e39044dd Binary files /dev/null and b/budget/static/budget.png differ diff --git a/budget/views/account_analytic_line.xml b/budget/views/account_analytic_line.xml new file mode 100644 index 00000000000..953aa9b521f --- /dev/null +++ b/budget/views/account_analytic_line.xml @@ -0,0 +1,19 @@ + + + + + account.analytic.line.form.inherit.account + account.analytic.line + + + + + + + + + + + + + diff --git a/budget/views/budget_budget_views.xml b/budget/views/budget_budget_views.xml new file mode 100644 index 00000000000..45e2dba6892 --- /dev/null +++ b/budget/views/budget_budget_views.xml @@ -0,0 +1,129 @@ + + + + + budget + budget.budget + kanban,form + +

    + Define new budgets +

    +
    +
    + + + budget.budget.view.kanban + budget.budget + + + + + + + + + Configuration + + + +

    + +

    +
    + + + + +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + + budget.budget.form.view + budget.budget + +
    +
    +
    + + + + + + +

    + +

    + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    +
    +
    diff --git a/budget/views/budget_line_views.xml b/budget/views/budget_line_views.xml new file mode 100644 index 00000000000..50db088c2f8 --- /dev/null +++ b/budget/views/budget_line_views.xml @@ -0,0 +1,58 @@ + + + + + + budget.line + budget.line + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/estate/views/estate_property_view.xml b/estate/views/estate_property_view.xml new file mode 100644 index 00000000000..c79cb7d1f0d --- /dev/null +++ b/estate/views/estate_property_view.xml @@ -0,0 +1,233 @@ + + + + + property + estate.property + list,kanban,form + +

    + Define new property +

    +
    + {'search_default_available':True} + +
    + + + + estate.property + estate.property + + + + + + + +
    +
    + +
    +
    + This is new! +
    + +
    + expected price + +
    +
    + selling price + +
    +
    + best price + +
    + + + + +
    +
    +
    +
    +
    +
    + + + + + estate.property + estate.property + + + + + + + + + + + + + + + + + + estate.property + estate.property + +
    +
    +
    + +

    + + +

    + +

    + + + +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + invisible="not garden" + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + + + estate.property + estate.property + + + + + + + + + + + + + + + + + Make Offer + + + code + action=records.action_make_offer() + list + + + + +
    diff --git a/estate/views/inherited_user_view.xml b/estate/views/inherited_user_view.xml new file mode 100644 index 00000000000..28ce4ad7ea4 --- /dev/null +++ b/estate/views/inherited_user_view.xml @@ -0,0 +1,17 @@ + + + + + res.user.inherit.user + res.users + + + + + + + + + + + diff --git a/estate/views/property_website_list_template.xml b/estate/views/property_website_list_template.xml new file mode 100644 index 00000000000..2c53ddc94fc --- /dev/null +++ b/estate/views/property_website_list_template.xml @@ -0,0 +1,173 @@ + + + + + + + diff --git a/estate/wizard/__init__.py b/estate/wizard/__init__.py new file mode 100644 index 00000000000..c006cc17e15 --- /dev/null +++ b/estate/wizard/__init__.py @@ -0,0 +1 @@ +from . import property_offers diff --git a/estate/wizard/property_offers.py b/estate/wizard/property_offers.py new file mode 100644 index 00000000000..c49c0984750 --- /dev/null +++ b/estate/wizard/property_offers.py @@ -0,0 +1,39 @@ +from odoo import api, fields, models + + +class ProperyOffers(models.TransientModel): + _name = "propery.offers" + _description = "TransientModelmodel for offers" + + price = fields.Float("price", required=True) + # property_ids = fields.Many2many("estate.property", string="property_id") + buyer_id = fields.Many2one("res.partner", required=True) + validity = fields.Integer(default=7) + + def make_offer(self): + print(self) + failed_properties = [] + for property in self.env.context.get("active_ids"): + try: + self.env["estate.property.offer"].create( + { + "price": self.price, + "property_id": property, + "buyer_id": self.buyer_id.id, + "validity": self.validity, + } + ) + except Exception as e: + print(f"{e} exception in ", property) + failed_properties.append(str(e) + " in " + str(property)) + if failed_properties: + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "message": " ".join(failed_properties), + "type": "danger", + "next": {"type": "ir.actions.act_window_close"}, + }, + } + return {"type": "ir.actions.act_window_close"} diff --git a/estate/wizard/wizard_property_offers_view.xml b/estate/wizard/wizard_property_offers_view.xml new file mode 100644 index 00000000000..fd87021dbee --- /dev/null +++ b/estate/wizard/wizard_property_offers_view.xml @@ -0,0 +1,31 @@ + + + + property offers + propery.offers + form + + + + + propery.offers + propery.offers + +
    + + + + + + + + +
    +
    + +
    +
    +
    +
    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..60faa0228e8 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +{ + "name": "estate_account", + "author": "Odoo", + "website": "https://www.odoo.com", + "category": "Tutorials/estate_account", + "version": "0.1", + # any module necessary for this one to work correctly + "depends": ["base", "web", "estate", "account"], + "application": True, + "installable": True, + "data": [], + "license": "AGPL-3", +} 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..b4d5105e6d0 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,64 @@ +from odoo import models, Command +from datetime import datetime + + +class Property_Invoice(models.Model): + _inherit = "estate.property" + + def action_sold(self): + print(" invoice ".center(100, "=")) + self.check_access("write") + self.env["account.move"].sudo().create( + { + "partner_id": self.buyer_id.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create( + { + "name": self.name, + "quantity": 1, + "price_unit": self.selling_price, + } + ), + Command.create( + { + "name": "tax 6%", + "quantity": 1, + "price_unit": self.selling_price * 0.06, + } + ), + Command.create( + { + "name": "administrative fees", + "quantity": 1, + "price_unit": 100.00, + } + ), + ], + } + ).action_post() + # self.env["account.move"].sudo().new( + # { + # "partner_id": self.buyer_id.id, + # "move_type": "out_invoice", + # "invoice_line_ids": [ + # { + # "name": self.name, + # "quantity": 1, + # "price_unit": self.selling_price, + # }, + # { + # "name": "tax 6%", + # "quantity": 1, + # "price_unit": self.selling_price * 0.06, + # }, + # { + # "name": "administrative fees", + # "quantity": 1, + # "price_unit": 100.00, + # }, + # ], + # } + # ).action_post() + + return super().action_sold() diff --git a/product_warranty/__init__.py b/product_warranty/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/product_warranty/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/product_warranty/__manifest__.py b/product_warranty/__manifest__.py new file mode 100644 index 00000000000..e94f4e96c1b --- /dev/null +++ b/product_warranty/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +{ + "name": "product_warranty", + "summary": "Add product warranty", + "description": "Add product warranty (yame)", + "author": "Odoo", + "website": "https://www.odoo.com", + "category": "Tutorials/product_warranty", + "version": "0.1", + # any module necessary for this one to work correctly + "depends": ["base", "web", "stock", "sale_management"], + "application": True, + "installable": True, + "data": [ + "security/ir.model.access.csv", + "views/product_views.xml", + ], + "license": "AGPL-3", +} diff --git a/product_warranty/models/__init__.py b/product_warranty/models/__init__.py new file mode 100644 index 00000000000..ef24594fa25 --- /dev/null +++ b/product_warranty/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_template +from . import warranty diff --git a/product_warranty/models/product_template.py b/product_warranty/models/product_template.py new file mode 100644 index 00000000000..3daee771a98 --- /dev/null +++ b/product_warranty/models/product_template.py @@ -0,0 +1,10 @@ +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + is_warranty = fields.Boolean(default=False, string="Warranty") + warranty_configuration_ids = fields.One2many( + "product.warranty.configuration", "product_template_id", string="Add Warranty" + ) diff --git a/product_warranty/models/warranty.py b/product_warranty/models/warranty.py new file mode 100644 index 00000000000..de6c11feb03 --- /dev/null +++ b/product_warranty/models/warranty.py @@ -0,0 +1,22 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class WarrantyConfiguration(models.Model): + _name = "product.warranty.configuration" + _description = "product.warranty.configuration" + + name = fields.Char(string="name", required=True) + product_template_id = fields.Many2one( + "product.template", required=True, ondelete="cascade" + ) + percentage = fields.Float() + years = fields.Integer(default=1) + + @api.onchange("percentage") + def _onchange_percentage(self): + for record in self: + if record.percentage < 0 or record.percentage > 100: + raise ValidationError( + "The value of 'Your Float Field' must be between 0 and 100." + ) diff --git a/product_warranty/security/ir.model.access.csv b/product_warranty/security/ir.model.access.csv new file mode 100644 index 00000000000..68c6466db32 --- /dev/null +++ b/product_warranty/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +product_warranty.access_product_warranty_configuration,access_product_warranty_configuration,product_warranty.model_product_warranty_configuration,base.group_user,1,0,0,0 +product_warranty.access_product_warranty_configuration,access_product_warranty_configuration,product_warranty.model_product_warranty_configuration,product.group_product_manager,1,1,1,1 diff --git a/product_warranty/views/product_views.xml b/product_warranty/views/product_views.xml new file mode 100644 index 00000000000..b4de0819d6e --- /dev/null +++ b/product_warranty/views/product_views.xml @@ -0,0 +1,26 @@ + + + + + product.template.common.form + product.template + + + + + + + + + + + + + + + diff --git a/product_warranty/views/sale_menus.xml b/product_warranty/views/sale_menus.xml new file mode 100644 index 00000000000..b910f1a9b44 --- /dev/null +++ b/product_warranty/views/sale_menus.xml @@ -0,0 +1,8 @@ +