diff --git a/addons/hr_expense/__manifest__.py b/addons/hr_expense/__manifest__.py
index d8c57f06faf..60e5c5c2672 100644
--- a/addons/hr_expense/__manifest__.py
+++ b/addons/hr_expense/__manifest__.py
@@ -52,6 +52,8 @@ This module also uses analytic accounting and is compatible with the invoice on
'application': True,
'assets': {
'web.assets_backend': [
+ 'hr_expense/static/src/mixins/*.js',
+ 'hr_expense/static/src/views/*.js',
'hr_expense/static/src/js/expense_views.js',
'hr_expense/static/src/js/expense_form_view.js',
'hr_expense/static/src/js/expense_qr_code_action.js',
@@ -63,6 +65,7 @@ This module also uses analytic accounting and is compatible with the invoice on
'hr_expense/static/tests/tours/expense_upload_tours.js',
],
'web.assets_qweb': [
+ 'hr_expense/static/src/views/*.xml',
'hr_expense/static/src/xml/**/*',
],
},
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 995ca762cc3..c7ebbc6170a 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -335,7 +335,7 @@ class HrExpense(models.Model):
expense=expense.name, product=expense.product_id.name,
))
- def create_expense_from_attachments(self, attachment_ids=None, view_type='tree'):
+ def create_expense_from_attachments(self, attachment_ids=None, view_type='list'):
''' Create the expenses from files.
:return: An action redirecting to hr.expense tree view.
'''
diff --git a/addons/hr_expense/static/src/js/expense_views.js b/addons/hr_expense/static/src/js/expense_views.js
index a5ba179fe16..043edae5921 100644
--- a/addons/hr_expense/static/src/js/expense_views.js
+++ b/addons/hr_expense/static/src/js/expense_views.js
@@ -1,14 +1,11 @@
odoo.define('hr_expense.expenses.tree', function (require) {
"use strict";
var DocumentUploadMixin = require('hr_expense.documents.upload.mixin');
- var KanbanController = require('web.KanbanController');
- var KanbanView = require('web.KanbanView');
var ListController = require('web.ListController');
var ListView = require('web.ListView');
var viewRegistry = require('web.view_registry');
var core = require('web.core');
var ListRenderer = require('web.ListRenderer');
- var KanbanRenderer = require('web.KanbanRenderer');
var session = require('web.session');
const config = require('web.config');
@@ -129,34 +126,6 @@ odoo.define('hr_expense.expenses.tree', function (require) {
})
});
- var ExpensesKanbanController = KanbanController.extend(DocumentUploadMixin, {
- buttons_template: 'ExpensesKanbanView.buttons',
- events: _.extend({}, KanbanController.prototype.events, {
- 'click .o_button_upload_expense': '_onUpload',
- 'change .o_expense_documents_upload .o_form_binary_form': '_onAddAttachment',
- }),
- });
-
- var ExpenseKanbanRenderer = KanbanRenderer.extend(ExpenseQRCodeMixin);
-
- var ExpenseKanbanRendererHeader = ExpenseKanbanRenderer.extend(ExpenseDashboardMixin);
-
- // The kanban view
- var ExpensesKanbanView = KanbanView.extend({
- config: _.extend({}, KanbanView.prototype.config, {
- Controller: ExpensesKanbanController,
- Renderer: ExpenseKanbanRenderer,
- }),
- });
-
- // The kanban view with the Header
- // Used in "My Expenses to Report", "All My Expenses" & "My Repo
- var ExpensesKanbanViewHeader = ExpensesKanbanView.extend({
- config: _.extend({}, ExpensesKanbanView.prototype.config, {
- Renderer: ExpenseKanbanRendererHeader,
- })
- });
-
viewRegistry.add('hr_expense_tree_dashboard_upload', ExpensesListViewDashboardUpload);
// Tree view with the header.
// Used in "My Expenses to Report" & "All My Expenses"
@@ -165,8 +134,4 @@ odoo.define('hr_expense.expenses.tree', function (require) {
// Tree view with the header.
// Used in "My Reports"
viewRegistry.add('hr_expense_tree_dashboard_header', ExpensesListViewDashboardHeader);
- viewRegistry.add('hr_expense_kanban', ExpensesKanbanView);
- // Kanban view with the header.
- // Used in "My Expenses to Report", "All My Expenses" & "My Reports"
- viewRegistry.add('hr_expense_kanban_header', ExpensesKanbanViewHeader);
});
diff --git a/addons/hr_expense/static/src/mixins/document_upload.js b/addons/hr_expense/static/src/mixins/document_upload.js
new file mode 100644
index 00000000000..9bc62313c1c
--- /dev/null
+++ b/addons/hr_expense/static/src/mixins/document_upload.js
@@ -0,0 +1,50 @@
+/** @odoo-module */
+
+import { useService } from '@web/core/utils/hooks';
+
+const { useRef } = owl;
+
+export const ExpenseDocumentUpload = {
+ setup() {
+ this._super();
+ this.actionService = useService('action');
+ this.notification = useService('notification');
+ this.orm = useService('orm');
+ this.http = useService('http');
+
+ this.fileInput = useRef('fileInput');
+ },
+
+ uploadDocument() {
+ this.fileInput.el.click();
+ },
+
+ async onChangeFileInput() {
+ const params = {
+ csrf_token: odoo.csrf_token,
+ ufile: [...this.fileInput.el.files],
+ model: 'hr.expense',
+ id: 0,
+ };
+
+ const fileData = await this.http.post('/web/binary/upload_attachment', params, "text");
+ const attachments = JSON.parse(fileData);
+ if (attachments.error) {
+ throw new Error(attachments.error);
+ }
+ this.onUpload(attachments);
+ },
+
+ async onUpload(attachments) {
+ const attachmentIds = attachments.map((a) => a.id);
+ if (!attachmentIds.length) {
+ this.notification.add(
+ this.env._t('An error occurred during the upload')
+ );
+ return;
+ }
+
+ const action = await this.orm.call('hr.expense', 'create_expense_from_attachments', ["", attachmentIds]);
+ this.actionService.doAction(action);
+ },
+};
diff --git a/addons/hr_expense/static/src/mixins/expense_dashboard.js b/addons/hr_expense/static/src/mixins/expense_dashboard.js
new file mode 100644
index 00000000000..d631f8b53c3
--- /dev/null
+++ b/addons/hr_expense/static/src/mixins/expense_dashboard.js
@@ -0,0 +1,36 @@
+/** @odoo-module */
+
+import { useService } from '@web/core/utils/hooks';
+import session from 'web.session';
+
+const { onWillStart, useState } = owl;
+
+
+export const ExpenseDashboardMixin = {
+ setup() {
+ this._super();
+ this.orm = useService('orm');
+
+ this.state = useState({
+ expenses: {}
+ });
+
+ onWillStart(async () => {
+ const expense_states = await this.orm.call("hr.expense", 'get_expense_dashboard', []);
+ this.state.expenses = expense_states;
+ });
+ },
+
+ renderMonetaryField(value, currency_id) {
+ value = value.toFixed(2);
+ const currency = session.get_currency(currency_id);
+ if (currency) {
+ if (currency.position === "after") {
+ value += currency.symbol;
+ } else {
+ value = currency.symbol + value;
+ }
+ }
+ return value;
+ }
+}
diff --git a/addons/hr_expense/static/src/mixins/qrcode.js b/addons/hr_expense/static/src/mixins/qrcode.js
new file mode 100644
index 00000000000..8d7bd395f0f
--- /dev/null
+++ b/addons/hr_expense/static/src/mixins/qrcode.js
@@ -0,0 +1,46 @@
+/** @odoo-module */
+
+import { useService } from "@web/core/utils/hooks";
+
+const { onMounted, onPatched, useRef } = owl;
+
+export const ExpenseMobileQRCode = {
+ setup() {
+ this._super();
+ this.root = useRef('root');
+ this.actionService = useService('action');
+
+ onMounted(this.bindAppsIcons);
+ onPatched(this.bindAppsIcons);
+ },
+
+ bindAppsIcons() {
+ const apps = this.root.el.querySelectorAll('.o_expense_mobile_app');
+ if (!apps) {
+ return;
+ }
+
+ const handler = this.handleClick.bind(this);
+ for (const app of apps) {
+ app.addEventListener('click', handler);
+ }
+ },
+
+ handleClick(ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ const url = ev.currentTarget && ev.currentTarget.href;
+ if (!this.env.isSmall) {
+ this.actionService.doAction({
+ name: this.env._t("Download our App"),
+ type: "ir.actions.client",
+ tag: 'expense_qr_code_modal',
+ target: "new",
+ params: { url },
+ });
+ } else {
+ this.actionService.doAction({ type: "ir.actions.act_url", url });
+ }
+ }
+};
diff --git a/addons/hr_expense/static/src/scss/hr_expense.scss b/addons/hr_expense/static/src/scss/hr_expense.scss
index 8ea31cc9c80..da4481e753f 100644
--- a/addons/hr_expense/static/src/scss/hr_expense.scss
+++ b/addons/hr_expense/static/src/scss/hr_expense.scss
@@ -1,6 +1,6 @@
.hr_expense {
@include media-breakpoint-up(md) {
- &.o_list_view, &.o_legacy_kanban_view {
+ &.o_list_view, &.o_kanban_renderer {
min-height: auto;
}
}
diff --git a/addons/hr_expense/static/src/views/kanban.js b/addons/hr_expense/static/src/views/kanban.js
new file mode 100644
index 00000000000..747ed0efc34
--- /dev/null
+++ b/addons/hr_expense/static/src/views/kanban.js
@@ -0,0 +1,36 @@
+/** @odoo-module */
+
+import { registry } from '@web/core/registry';
+import { patch } from '@web/core/utils/patch';
+
+import { ExpenseDashboardMixin } from '../mixins/expense_dashboard';
+import { ExpenseMobileQRCode } from '../mixins/qrcode';
+import { ExpenseDocumentUpload } from '../mixins/document_upload';
+
+import { kanbanView } from '@web/views/kanban/kanban_view';
+import { KanbanController } from '@web/views/kanban/kanban_controller';
+import { KanbanRenderer } from '@web/views/kanban/kanban_renderer';
+
+export class ExpenseKanbanController extends KanbanController {}
+patch(ExpenseKanbanController.prototype, 'expense_kanban_controller_upload', ExpenseDocumentUpload);
+
+export class ExpenseKanbanRenderer extends KanbanRenderer {}
+patch(ExpenseKanbanRenderer.prototype, 'expense_kanban_renderer_qrcode', ExpenseMobileQRCode);
+ExpenseKanbanRenderer.template = 'hr_expense.KanbanRenderer';
+
+export class ExpenseDashboardKanbanRenderer extends ExpenseKanbanRenderer {}
+patch(ExpenseDashboardKanbanRenderer.prototype, 'expense_kanban_renderer_dashboard', ExpenseDashboardMixin);
+
+registry.category('views').add('hr_expense_kanban', {
+ ...kanbanView,
+ buttonTemplate: 'hr_expense.KanbanButtons',
+ Controller: ExpenseKanbanController,
+ Renderer: ExpenseKanbanRenderer,
+});
+
+registry.category('views').add('hr_expense_dashboard_kanban', {
+ ...kanbanView,
+ buttonTemplate: 'hr_expense.KanbanButtons',
+ Controller: ExpenseKanbanController,
+ Renderer: ExpenseDashboardKanbanRenderer,
+});
diff --git a/addons/hr_expense/static/src/views/kanban.xml b/addons/hr_expense/static/src/views/kanban.xml
new file mode 100644
index 00000000000..28adb7e02df
--- /dev/null
+++ b/addons/hr_expense/static/src/views/kanban.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/hr_expense/static/src/xml/documents_upload_views.xml b/addons/hr_expense/static/src/xml/documents_upload_views.xml
index ab16011ad68..af7771eeccc 100644
--- a/addons/hr_expense/static/src/xml/documents_upload_views.xml
+++ b/addons/hr_expense/static/src/xml/documents_upload_views.xml
@@ -32,18 +32,4 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index 1663a74026d..dc9cd645fd6 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -290,7 +290,7 @@
primary
- hr_expense_kanban_header
+ hr_expense_dashboard_kanban
@@ -429,10 +429,10 @@
Snap pictures of your receipts and let Odoo
automatically create expenses for you.
-
+
-
+
@@ -466,10 +466,10 @@
Snap pictures of your receipts and let Odoo
automatically create expenses for you.
-
+
-
+
@@ -868,7 +868,7 @@
primary
- hr_expense_kanban_header
+ hr_expense_dashboard_kanban
diff --git a/addons/hr_holidays/static/src/scss/accrual_plan_level.scss b/addons/hr_holidays/static/src/scss/accrual_plan_level.scss
index 3682ca9be8b..32425fa17b2 100644
--- a/addons/hr_holidays/static/src/scss/accrual_plan_level.scss
+++ b/addons/hr_holidays/static/src/scss/accrual_plan_level.scss
@@ -30,6 +30,7 @@ $o-hr-holidays-border-color: map-get($grays, '300');
border-radius: 25px;
}
+ .o_legacy_kanban_view.o_kanban_ungrouped .o_kanban_record,
.o_kanban_renderer.o_kanban_ungrouped .o_kanban_record {
counter-increment: o-hr-holidays-accrual-plan-level-counter;
display: flex;
diff --git a/addons/hr_holidays/views/hr_leave_accrual_views.xml b/addons/hr_holidays/views/hr_leave_accrual_views.xml
index c58f2245bc6..ce71bf98166 100644
--- a/addons/hr_holidays/views/hr_leave_accrual_views.xml
+++ b/addons/hr_holidays/views/hr_leave_accrual_views.xml
@@ -105,7 +105,6 @@