summaryrefslogtreecommitdiff
path: root/addons/hr_expense/static
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/hr_expense/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hr_expense/static')
-rw-r--r--addons/hr_expense/static/description/icon.pngbin0 -> 9064 bytes
-rw-r--r--addons/hr_expense/static/description/icon.svg24
-rw-r--r--addons/hr_expense/static/img/air_ticket-image.jpgbin0 -> 4805 bytes
-rw-r--r--addons/hr_expense/static/img/app_store.pngbin0 -> 4682 bytes
-rw-r--r--addons/hr_expense/static/img/car_travel-image.jpgbin0 -> 3739 bytes
-rw-r--r--addons/hr_expense/static/img/nocontent.pngbin0 -> 161157 bytes
-rw-r--r--addons/hr_expense/static/img/play_store.pngbin0 -> 8171 bytes
-rw-r--r--addons/hr_expense/static/src/img/expense.pngbin0 -> 26896 bytes
-rw-r--r--addons/hr_expense/static/src/js/expense_qr_code_action.js19
-rw-r--r--addons/hr_expense/static/src/js/expense_views.js167
-rw-r--r--addons/hr_expense/static/src/js/tours/hr_expense.js65
-rw-r--r--addons/hr_expense/static/src/js/upload_mixin.js77
-rw-r--r--addons/hr_expense/static/src/scss/hr_expense.scss47
-rw-r--r--addons/hr_expense/static/src/xml/documents_upload_views.xml31
-rw-r--r--addons/hr_expense/static/src/xml/expense_dashboard.xml23
-rw-r--r--addons/hr_expense/static/src/xml/expense_qr_modal_template.xml11
-rw-r--r--addons/hr_expense/static/tests/tours/expense_upload_tours.js80
17 files changed, 544 insertions, 0 deletions
diff --git a/addons/hr_expense/static/description/icon.png b/addons/hr_expense/static/description/icon.png
new file mode 100644
index 00000000..a0a1ba81
--- /dev/null
+++ b/addons/hr_expense/static/description/icon.png
Binary files differ
diff --git a/addons/hr_expense/static/description/icon.svg b/addons/hr_expense/static/description/icon.svg
new file mode 100644
index 00000000..b72a86f2
--- /dev/null
+++ b/addons/hr_expense/static/description/icon.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70">
+ <defs>
+ <path id="icon-a" d="M4,5.35309892e-14 C36.4160122,9.87060235e-15 58.0836068,-3.97961823e-14 65,5.07020818e-14 C69,6.733808e-14 70,1 70,5 C70,43.0488877 70,62.4235458 70,65 C70,69 69,70 65,70 C61,70 9,70 4,70 C1,70 7.10542736e-15,69 7.10542736e-15,65 C7.25721566e-15,62.4676575 3.83358709e-14,41.8005206 3.60818146e-14,5 C-1.13686838e-13,1 1,5.75716207e-14 4,5.35309892e-14 Z"/>
+ <linearGradient id="icon-c" x1="100%" x2="0%" y1="0%" y2="98.616%">
+ <stop offset="0%" stop-color="#797C79"/>
+ <stop offset="100%" stop-color="#545554"/>
+ </linearGradient>
+ <path id="icon-d" d="M29.2777778,17.2348485 C34.4714536,17.2348485 38.681713,21.506348 38.681713,26.7755682 C38.681713,32.0447884 34.4714536,36.3162879 29.2777778,36.3162879 C24.0841019,36.3162879 19.8738426,32.0447884 19.8738426,26.7755682 C19.8738426,21.506348 24.0841019,17.2348485 29.2777778,17.2348485 Z M42.7933813,37.84729 C42.7933813,41.2291455 53.1142387,40.370791 53.1141828,46.7993457 C53.1141828,49.8785938 50.8543359,52.5088721 47.1832578,53.0575293 L47.1832578,55.6054688 C47.1832578,55.9614111 46.8831254,56.25 46.5129453,56.25 L44.2785703,56.25 C43.9083902,56.25 43.6082578,55.9614111 43.6082578,55.6054688 L43.6082578,53.0156885 C41.4017008,52.6548584 39.4354508,51.6754395 38.0920887,50.4390137 C37.8475922,50.2139648 37.8167578,49.8485693 38.0193598,49.5878564 L39.7168703,47.4035937 C39.944609,47.1106006 40.3787481,47.0596289 40.6753613,47.2886523 C42.0671535,48.3632471 43.8647641,49.2161768 45.5824957,49.2161768 C47.5829875,49.2161768 48.4941098,48.0702539 48.4941098,47.005542 C48.4941098,43.8578662 38.1732523,44.5412305 38.1732523,37.9061572 C38.1732523,35.0744092 40.3334461,32.784873 43.6083137,32.0710547 L43.6083137,29.3945312 C43.6083137,29.0385889 43.9084461,28.75 44.2786262,28.75 L46.5130012,28.75 C46.8831813,28.75 47.1833137,29.0385889 47.1833137,29.3945312 L47.1833137,31.9317822 C48.979416,32.1313721 50.9263387,32.8088281 52.2826602,33.9623779 C52.5156496,34.1605176 52.5744695,34.4877783 52.4264981,34.7508545 L51.1108981,37.0899121 C50.9184625,37.4321045 50.4576227,37.5328125 50.1291695,37.3056689 C48.8809918,36.4425342 47.3510035,35.783877 45.8581059,35.783877 C43.9963688,35.783877 42.7933813,36.5938379 42.7933813,37.84729 Z M35.2962963,36.7272727 C35.2962963,36.3141183 26.1175854,38.5736818 22.6967864,36.0773525 L18.5053937,37.1404272 C15.9936026,37.7775087 14.2314815,40.0671622 14.2314815,42.6939608 L14.2314815,44.9029356 C14.2314815,46.4837136 15.4945475,47.7651515 17.052662,47.7651515 C29.017554,47.7651515 35,47.7651515 35,47.7651515 C33.1481481,41.4242424 35.2962963,37.1404272 35.2962963,36.7272727 Z"/>
+ <path id="icon-e" d="M29.2777778,15.2348485 C34.4714536,15.2348485 38.681713,19.506348 38.681713,24.7755682 C38.681713,30.0447884 34.4714536,34.3162879 29.2777778,34.3162879 C24.0841019,34.3162879 19.8738426,30.0447884 19.8738426,24.7755682 C19.8738426,19.506348 24.0841019,15.2348485 29.2777778,15.2348485 Z M42.7933813,35.84729 C42.7933813,39.2291455 53.1142387,38.370791 53.1141828,44.7993457 C53.1141828,47.8785938 50.8543359,50.5088721 47.1832578,51.0575293 L47.1832578,53.6054688 C47.1832578,53.9614111 46.8831254,54.25 46.5129453,54.25 L44.2785703,54.25 C43.9083902,54.25 43.6082578,53.9614111 43.6082578,53.6054688 L43.6082578,51.0156885 C41.4017008,50.6548584 39.4354508,49.6754395 38.0920887,48.4390137 C37.8475922,48.2139648 37.8167578,47.8485693 38.0193598,47.5878564 L39.7168703,45.4035937 C39.944609,45.1106006 40.3787481,45.0596289 40.6753613,45.2886523 C42.0671535,46.3632471 43.8647641,47.2161768 45.5824957,47.2161768 C47.5829875,47.2161768 48.4941098,46.0702539 48.4941098,45.005542 C48.4941098,41.8578662 38.1732523,42.5412305 38.1732523,35.9061572 C38.1732523,33.0744092 40.3334461,30.784873 43.6083137,30.0710547 L43.6083137,27.3945312 C43.6083137,27.0385889 43.9084461,26.75 44.2786262,26.75 L46.5130012,26.75 C46.8831813,26.75 47.1833137,27.0385889 47.1833137,27.3945312 L47.1833137,29.9317822 C48.979416,30.1313721 50.9263387,30.8088281 52.2826602,31.9623779 C52.5156496,32.1605176 52.5744695,32.4877783 52.4264981,32.7508545 L51.1108981,35.0899121 C50.9184625,35.4321045 50.4576227,35.5328125 50.1291695,35.3056689 C48.8809918,34.4425342 47.3510035,33.783877 45.8581059,33.783877 C43.9963688,33.783877 42.7933813,34.5938379 42.7933813,35.84729 Z M35.2962963,34.7272727 C35.2962963,34.3141183 26.1175854,36.5736818 22.6967864,34.0773525 L18.5053937,35.1404272 C15.9936026,35.7775087 14.2314815,38.0671622 14.2314815,40.6939608 L14.2314815,42.9029356 C14.2314815,44.4837136 15.4945475,45.7651515 17.052662,45.7651515 C29.017554,45.7651515 35,45.7651515 35,45.7651515 C33.1481481,39.4242424 35.2962963,35.1404272 35.2962963,34.7272727 Z"/>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <mask id="icon-b" fill="#fff">
+ <use xlink:href="#icon-a"/>
+ </mask>
+ <g mask="url(#icon-b)">
+ <rect width="70" height="70" fill="url(#icon-c)"/>
+ <path fill="#FFF" fill-opacity=".383" d="M4,1.8 L65,1.8 C67.6666667,1.8 69.3333333,1.13333333 70,-0.2 C70,2.46666667 70,3.46666667 70,2.8 L1.10547097e-14,2.8 C-1.65952376e-14,3.46666667 -2.9161925e-14,2.46666667 -2.66453526e-14,-0.2 C0.666666667,1.13333333 2,1.8 4,1.8 Z" transform="matrix(1 0 0 -1 0 2.8)"/>
+ <path fill="#393939" d="M40.2790698,48 L4,48 C2,48 -7.10542736e-15,47.8556793 0,43.9590217 L2.0734159e-16,21.6246135 L20.6658658,0.0963982452 L37.783693,7.00604553 L34.121375,14.2273604 L43.799671,6.01962237 L50.8814479,14.2273604 L46.6827849,19.2273136 L52.6062041,25.9680455 L40.2790698,48 Z" opacity=".324" transform="translate(0 21)"/>
+ <path fill="#000" fill-opacity=".383" d="M4,4 L65,4 C67.6666667,4 69.3333333,3 70,1 C70,3.66666667 70,5 70,5 L1.77635684e-15,5 C1.77635684e-15,5 1.77635684e-15,3.66666667 1.77635684e-15,1 C0.666666667,3 2,4 4,4 Z" transform="translate(0 65)"/>
+ <use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#icon-d"/>
+ <use fill="#FFF" fill-rule="nonzero" xlink:href="#icon-e"/>
+ </g>
+ </g>
+</svg>
diff --git a/addons/hr_expense/static/img/air_ticket-image.jpg b/addons/hr_expense/static/img/air_ticket-image.jpg
new file mode 100644
index 00000000..04ade769
--- /dev/null
+++ b/addons/hr_expense/static/img/air_ticket-image.jpg
Binary files differ
diff --git a/addons/hr_expense/static/img/app_store.png b/addons/hr_expense/static/img/app_store.png
new file mode 100644
index 00000000..4d79d760
--- /dev/null
+++ b/addons/hr_expense/static/img/app_store.png
Binary files differ
diff --git a/addons/hr_expense/static/img/car_travel-image.jpg b/addons/hr_expense/static/img/car_travel-image.jpg
new file mode 100644
index 00000000..fad495b0
--- /dev/null
+++ b/addons/hr_expense/static/img/car_travel-image.jpg
Binary files differ
diff --git a/addons/hr_expense/static/img/nocontent.png b/addons/hr_expense/static/img/nocontent.png
new file mode 100644
index 00000000..b8745170
--- /dev/null
+++ b/addons/hr_expense/static/img/nocontent.png
Binary files differ
diff --git a/addons/hr_expense/static/img/play_store.png b/addons/hr_expense/static/img/play_store.png
new file mode 100644
index 00000000..73dd393c
--- /dev/null
+++ b/addons/hr_expense/static/img/play_store.png
Binary files differ
diff --git a/addons/hr_expense/static/src/img/expense.png b/addons/hr_expense/static/src/img/expense.png
new file mode 100644
index 00000000..04e43790
--- /dev/null
+++ b/addons/hr_expense/static/src/img/expense.png
Binary files differ
diff --git a/addons/hr_expense/static/src/js/expense_qr_code_action.js b/addons/hr_expense/static/src/js/expense_qr_code_action.js
new file mode 100644
index 00000000..5de326d0
--- /dev/null
+++ b/addons/hr_expense/static/src/js/expense_qr_code_action.js
@@ -0,0 +1,19 @@
+odoo.define('hr_expense.qr_code_action', function (require) {
+"use strict";
+
+const AbstractAction = require('web.AbstractAction');
+const core = require('web.core');
+const config = require('web.config');
+
+const QRModalAction = AbstractAction.extend({
+ template: 'hr_expense_qr_code',
+ xmlDependencies: ['/hr_expense/static/src/xml/expense_qr_modal_template.xml'],
+
+ init: function(parent, action){
+ this._super.apply(this, arguments);
+ this.url = _.str.sprintf("/report/barcode/?type=QR&value=%s&width=256&height=256&humanreadable=1", action.params.url);
+ },
+});
+
+core.action_registry.add('expense_qr_code_modal', QRModalAction);
+});
diff --git a/addons/hr_expense/static/src/js/expense_views.js b/addons/hr_expense/static/src/js/expense_views.js
new file mode 100644
index 00000000..a031f647
--- /dev/null
+++ b/addons/hr_expense/static/src/js/expense_views.js
@@ -0,0 +1,167 @@
+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 PivotView = require('web.PivotView');
+ 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 PivotRenderer = require('web.PivotRenderer');
+ var session = require('web.session');
+ const config = require('web.config');
+
+ var QWeb = core.qweb;
+
+ var ExpensesListController = ListController.extend(DocumentUploadMixin, {
+ buttons_template: 'ExpensesListView.buttons',
+ events: _.extend({}, ListController.prototype.events, {
+ 'click .o_button_upload_expense': '_onUpload',
+ 'change .o_expense_documents_upload .o_form_binary_form': '_onAddAttachment',
+ }),
+ });
+
+ const ExpenseQRCodeMixin = {
+ async _renderView() {
+ const self = this;
+ await this._super(...arguments);
+ const google_url = "https://play.google.com/store/apps/details?id=com.odoo.mobile";
+ const apple_url = "https://apps.apple.com/be/app/odoo/id1272543640";
+ const action_desktop = {
+ name: 'Download our App',
+ type: 'ir.actions.client',
+ tag: 'expense_qr_code_modal',
+ params: {'url': "https://apps.apple.com/be/app/odoo/id1272543640"},
+ target: 'new',
+ };
+ this.$el.find('img.o_expense_apple_store').on('click', function(event) {
+ event.preventDefault();
+ if (!config.device.isMobile) {
+ self.do_action(_.extend(action_desktop, {params: {'url': apple_url}}));
+ } else {
+ self.do_action({type: 'ir.actions.act_url', url: apple_url});
+ }
+ });
+ this.$el.find('img.o_expense_google_store').on('click', function(event) {
+ event.preventDefault();
+ if (!config.device.isMobile) {
+ self.do_action(_.extend(action_desktop, {params: {'url': google_url}}));
+ } else {
+ self.do_action({type: 'ir.actions.act_url', url: google_url});
+ }
+ });
+ },
+ };
+
+ const ExpenseDashboardMixin = {
+ _render: async function () {
+ var self = this;
+ await this._super(...arguments);
+ const result = await this._rpc({
+ model: 'hr.expense',
+ method: 'get_expense_dashboard',
+ context: this.context,
+ });
+
+ self.$el.parent().find('.o_expense_container').remove();
+ const elem = QWeb.render('hr_expense.dashboard_list_header', {
+ expenses: result,
+ render_monetary_field: self.render_monetary_field,
+ });
+ self.$el.before(elem);
+ },
+ render_monetary_field: function (value, currency_id) {
+ value = value.toFixed(2);
+ var currency = session.get_currency(currency_id);
+ if (currency) {
+ if (currency.position === "after") {
+ value += currency.symbol;
+ } else {
+ value = currency.symbol + value;
+ }
+ }
+ return value;
+ }
+ };
+
+ // Expense List Renderer
+ var ExpenseListRenderer = ListRenderer.extend(ExpenseQRCodeMixin);
+
+ // Expense List Renderer with the Header
+ // Used in "My Expenses to Report", "All My Expenses" & "My Reports"
+ var ExpenseListRendererHeader = ExpenseListRenderer.extend(ExpenseDashboardMixin);
+
+ var ExpensesListViewDashboardUpload = ListView.extend({
+ config: _.extend({}, ListView.prototype.config, {
+ Renderer: ExpenseListRenderer,
+ Controller: ExpensesListController,
+ }),
+ });
+
+ // Used in "My Expenses to Report" & "All My Expenses"
+ var ExpensesListViewDashboardUploadHeader = ExpensesListViewDashboardUpload.extend({
+ config: _.extend({}, ExpensesListViewDashboardUpload.prototype.config, {
+ Renderer: ExpenseListRendererHeader,
+ }),
+ });
+
+ // The dashboard view of the expense module
+ var ExpensesListViewDashboard = ListView.extend({
+ config: _.extend({}, ListView.prototype.config, {
+ Renderer: ExpenseListRenderer,
+ Controller: ExpensesListController,
+ }),
+ });
+
+ // The dashboard view of the expense module with an header
+ // Used in "My Expenses"
+ var ExpensesListViewDashboardHeader = ExpensesListViewDashboard.extend({
+ config: _.extend({}, ExpensesListViewDashboard.prototype.config, {
+ Renderer: ExpenseListRendererHeader,
+ })
+ });
+
+ 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"
+ viewRegistry.add('hr_expense_tree_dashboard_upload_header', ExpensesListViewDashboardUploadHeader);
+ viewRegistry.add('hr_expense_tree_dashboard', ExpensesListViewDashboard);
+ // 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/js/tours/hr_expense.js b/addons/hr_expense/static/src/js/tours/hr_expense.js
new file mode 100644
index 00000000..0422d687
--- /dev/null
+++ b/addons/hr_expense/static/src/js/tours/hr_expense.js
@@ -0,0 +1,65 @@
+odoo.define('hr_expense.tour', function(require) {
+"use strict";
+
+var core = require('web.core');
+var tour = require('web_tour.tour');
+
+var _t = core._t;
+
+tour.register('hr_expense_tour' , {
+ url: "/web"
+}, [tour.stepUtils.showAppsMenuItem(), {
+ trigger: '.o_app[data-menu-xmlid="hr_expense.menu_hr_expense_root"]',
+ content: _t("Want to manage your expenses? It starts here."),
+ position: 'right',
+ edition: 'community'
+}, {
+ trigger: '.o_app[data-menu-xmlid="hr_expense.menu_hr_expense_root"]',
+ content: _t("Want to manage your expenses? It starts here."),
+ position: 'bottom',
+ edition: 'enterprise'
+}, {
+ trigger: '.o_form_button_save',
+ content: _t("<p>Once your <b> Expense </b> is ready, you can save it.</p>"),
+ position: 'bottom',
+}, {
+ trigger: '.o_attach_document',
+ content: _t("Attach your receipt here."),
+ position: 'bottom',
+}, {
+ trigger: '.o_expense_submit',
+ extra_triggger: ".o_expense_form",
+ content: _t('<p>Click on <b> Create Report </b> to create the report.</p>'),
+ position: 'right',
+}, {
+ trigger: '.o_expense_tree input[type=checkbox]',
+ content: _t('<p>Select expenses to submit them to your manager</p>'),
+ position: 'bottom'
+}, {
+ trigger: '.o_dropdown_toggler_btn',
+ extra_trigger: ".o_expense_tree",
+ content: _t('<p>Click on <b> Action Create Report </b> to submit selected expenses to your manager</p>'),
+ position: 'right',
+}, {
+ trigger: '.o_expense_sheet_submit',
+ content: _t('Once your <b>Expense report</b> is ready, you can submit it to your manager and wait for the approval from your manager.'),
+ position: 'bottom',
+}, {
+ trigger: '.o_expense_sheet_approve',
+ content: _t("<p>Approve the report here.</p><p>Tip: if you refuse, don’t forget to give the reason thanks to the hereunder message tool</p>"),
+ position: 'bottom',
+}, {
+ trigger: '.o_expense_sheet_post',
+ content: _t("<p>The accountant receive approved expense reports.</p><p>He can post journal entries in one click if taxes and accounts are right.</p>"),
+ position: 'bottom',
+}, {
+ trigger: '.o_expense_sheet_pay',
+ content: _t("The accountant can register a payment to reimburse the employee directly."),
+ position: 'bottom',
+}, {
+ trigger: 'li a[data-menu-xmlid="hr_expense.menu_hr_expense_sheet_my_all"], div[data-menu-xmlid="hr_expense.menu_hr_expense_sheet_my_all"]',
+ content: _t("Managers can get all reports to approve from this menu."),
+ position: 'bottom',
+}]);
+
+});
diff --git a/addons/hr_expense/static/src/js/upload_mixin.js b/addons/hr_expense/static/src/js/upload_mixin.js
new file mode 100644
index 00000000..f5fbd74c
--- /dev/null
+++ b/addons/hr_expense/static/src/js/upload_mixin.js
@@ -0,0 +1,77 @@
+odoo.define('hr_expense.documents.upload.mixin', function (require) {
+"use strict";
+
+var core = require('web.core');
+var config = require('web.config');
+var _t = core._t;
+var qweb = core.qweb;
+
+/**
+* Mixin for uploading single or multiple documents.
+*/
+var DocumentUploadMixin = {
+ start: function () {
+ // define a unique uploadId and a callback method
+ this.fileUploadID = _.uniqueId('hr_expense_document_upload');
+ $(window).on(this.fileUploadID, this._onFileUploaded.bind(this));
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @private
+ */
+ _onAddAttachment: function (ev) {
+ // Auto submit form once we've selected an attachment
+ var $input = $(ev.currentTarget).find('input.o_input_file');
+ if ($input.val() !== '') {
+ var $binaryForm = this.$('.o_expense_documents_upload form.o_form_binary_form');
+ $binaryForm.submit();
+ }
+ },
+ /**
+ * @private
+ */
+ _onFileUploaded: function () {
+ // Callback once attachment have been created, create an expense with attachment ids
+ var self = this;
+ var attachments = Array.prototype.slice.call(arguments, 1);
+ // Get id from result
+ var attachent_ids = attachments.reduce(function(filtered, record) {
+ if (record.id) {
+ filtered.push(record.id);
+ }
+ return filtered;
+ }, []);
+ if (!attachent_ids.length) {
+ return self.do_notify(false, _t("An error occurred during the upload"));
+ }
+ var myContext = this.initialState.context
+ myContext['isMobile'] = config.device.isMobile
+ return this._rpc({
+ model: 'hr.expense',
+ method: 'create_expense_from_attachments',
+ args: ["", attachent_ids, this.viewType],
+ context: myContext,
+ }).then(function(result) {
+ self.do_action(result);
+ });
+ },
+ /**
+ * @private
+ * @param {Event} event
+ */
+ _onUpload: function (event) {
+ var self = this;
+ // If hidden upload form don't exists, create it
+ var $formContainer = this.$('.o_content').find('.o_expense_documents_upload');
+ if (!$formContainer.length) {
+ $formContainer = $(qweb.render('hr.expense.DocumentsHiddenUploadForm', {widget: this}));
+ $formContainer.appendTo(this.$('.o_content'));
+ }
+ // Trigger the input to select a file
+ this.$('.o_expense_documents_upload .o_input_file').click();
+ },
+};
+
+return DocumentUploadMixin;
+
+});
diff --git a/addons/hr_expense/static/src/scss/hr_expense.scss b/addons/hr_expense/static/src/scss/hr_expense.scss
new file mode 100644
index 00000000..5c0d1e84
--- /dev/null
+++ b/addons/hr_expense/static/src/scss/hr_expense.scss
@@ -0,0 +1,47 @@
+.o_content {
+ .o_expense_card {
+ border-bottom: 1px solid darken($o-control-panel-background-color, 20%);
+ border-right: 1px solid darken($o-control-panel-background-color, 20%);
+ text-align: center;
+ padding: 10px;
+ .content_center {
+ margin-top: auto;
+ margin-bottom: auto;
+ }
+ div {
+ line-height: 1;
+ }
+ }
+
+ .o_expense_container {
+ @include o-position-sticky($left: 0px);
+ }
+
+ .o_expense_card_last {
+ border-right: 0px;
+ }
+
+ .o_expense_purple {
+ color: $o-enterprise-color;
+ }
+}
+.hr_expense {
+ &.o_list_view, &.o_kanban_view {
+ height: auto;
+ min-height: auto;
+ }
+}
+.hr_expense .o_view_nocontent {
+ top: 10%;
+}
+.o_view_nocontent {
+ .o_nocontent_help {
+ .o_view_nocontent_expense_receipt:before {
+ @extend %o-nocontent-init-image;
+ @include size(300px, 230px);
+ background: transparent url(/hr_expense/static/img/nocontent.png) no-repeat center;
+ background-size: 300px 230px;
+ margin-bottom: 1rem;
+ }
+ }
+}
diff --git a/addons/hr_expense/static/src/xml/documents_upload_views.xml b/addons/hr_expense/static/src/xml/documents_upload_views.xml
new file mode 100644
index 00000000..a491d1a7
--- /dev/null
+++ b/addons/hr_expense/static/src/xml/documents_upload_views.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates>
+ <t t-name="hr.expense.DocumentsHiddenUploadForm">
+ <div class="d-none o_expense_documents_upload">
+ <t t-call="HiddenInputFile">
+ <t t-set="multi_upload" t-value="true"/>
+ <t t-set="fileupload_id" t-value="widget.fileUploadID"/>
+ <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
+ <input type="hidden" name="model" t-att-value="'hr.expense'"/>
+ <input type="hidden" name="id" t-att-value="0"/>
+ </t>
+ </div>
+ </t>
+
+ <t t-extend="ListView.buttons" t-name="ExpensesListView.buttons">
+ <t t-jquery="button.o_list_button_add" t-operation="after">
+ <button type="button" class="btn btn-primary o_button_upload_expense">
+ Upload
+ </button>
+ </t>
+ </t>
+
+ <t t-extend="KanbanView.buttons" t-name="ExpensesKanbanView.buttons">
+ <t t-jquery="button" t-operation="after">
+ <button type="button" class="btn btn-primary o_button_upload_expense">
+ Upload
+ </button>
+ </t>
+ </t>
+</templates>
diff --git a/addons/hr_expense/static/src/xml/expense_dashboard.xml b/addons/hr_expense/static/src/xml/expense_dashboard.xml
new file mode 100644
index 00000000..ba1a10c8
--- /dev/null
+++ b/addons/hr_expense/static/src/xml/expense_dashboard.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates id="template">
+ <t t-name="hr_expense.dashboard_list_header">
+ <div class="o_expense_container d-flex o_form_statusbar">
+ <t t-foreach="expenses" t-as="expense">
+ <div t-attf-class="o_expense_card o_arrow_button flex-grow-1 d-flex flex-column border-right-0">
+ <div class="content_center">
+ <div>
+ <span t-esc="render_monetary_field(expenses[expense]['amount'], expenses[expense]['currency'])" class="h2 o_expense_purple"/>
+ </div>
+ <b class="m-2"><span t-esc="expenses[expense]['description']"/></b>
+ </div>
+ </div>
+ <div t-if="expense !== 'approved'" t-attf-class="o_expense_card o_arrow_button flex-grow-1 d-flex flex-column border-right-0">
+ <div class="content_center">
+ <span class="fa fa-angle-right fa-3x"/>
+ </div>
+ </div>
+ </t>
+ </div>
+ </t>
+</templates>
diff --git a/addons/hr_expense/static/src/xml/expense_qr_modal_template.xml b/addons/hr_expense/static/src/xml/expense_qr_modal_template.xml
new file mode 100644
index 00000000..00a59f41
--- /dev/null
+++ b/addons/hr_expense/static/src/xml/expense_qr_modal_template.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+<t t-name="hr_expense_qr_code">
+ <div style="text-align:center;" class="o_expense_modal">
+ <t t-if="widget.url">
+ <h3>Scan this QR code to get the Odoo app:</h3><br/><br/>
+ <img class="border border-dark rounded" t-att-src="widget.url"/>
+ </t>
+ </div>
+</t>
+</templates>
diff --git a/addons/hr_expense/static/tests/tours/expense_upload_tours.js b/addons/hr_expense/static/tests/tours/expense_upload_tours.js
new file mode 100644
index 00000000..e9cd036d
--- /dev/null
+++ b/addons/hr_expense/static/tests/tours/expense_upload_tours.js
@@ -0,0 +1,80 @@
+odoo.define('hr_expense.tests.tours', function (require) {
+ "use strict";
+
+ var core = require('web.core');
+ var tour = require('web_tour.tour');
+
+ tour.register('hr_expense_test_tour', {
+ test: true,
+ url: "/web",
+ }, [tour.stepUtils.showAppsMenuItem(),
+ {
+ content: "Go to Expense",
+ trigger: '.o_app[data-menu-xmlid="hr_expense.menu_hr_expense_root"]',
+ },
+ {
+ content: "Go to My Expenses",
+ trigger: 'a[data-menu-xmlid="hr_expense.menu_hr_expense_my_expenses"]',
+ },
+ {
+ content: "Go to My Expenses to Report",
+ trigger: 'a[data-menu-xmlid="hr_expense.menu_hr_expense_my_expenses_to_submit"]',
+ },
+ {
+ content: "Check Upload Button",
+ trigger: '.o_button_upload_expense',
+ run() {
+ const button = document.querySelector('.o_button_upload_expense');
+ if(!button) {
+ console.error('Missing Upload button in My Expenses to Report > List View');
+ }
+ }
+ },
+ {
+ content: "Go to kanban view",
+ trigger: "button.o_switch_view.o_kanban",
+ },
+ {
+ content: "Check Upload Button",
+ trigger: "button.o_switch_view.o_kanban.active",
+ run() {
+ const button = document.querySelector('.o_button_upload_expense');
+ if(!button) {
+ console.error('Missing Upload button in My Expenses to Report > Kanban View');
+ }
+ }
+ },
+ {
+ content: "Go to Reporting",
+ trigger: 'a[data-menu-xmlid="hr_expense.menu_hr_expense_reports"]',
+ },
+ {
+ content: "Go to Expenses Analysis",
+ trigger: 'a[data-menu-xmlid="hr_expense.menu_hr_expense_all_expenses"]',
+ },
+ {
+ content: "Check Upload Button",
+ trigger: 'li.breadcrumb-item:contains("Expenses Analysis")',
+ run() {
+ const button = document.querySelector('.o_button_upload_expense');
+ if(!button) {
+ console.error('Missing Upload button in Expenses Analysis > List View');
+ }
+ }
+ },
+ {
+ content: "Go to kanban view",
+ trigger: "button.o_switch_view.o_kanban",
+ },
+ {
+ content: "Check Upload Button",
+ trigger: "button.o_switch_view.o_kanban.active",
+ run() {
+ const button = document.querySelector('.o_button_upload_expense');
+ if(!button) {
+ console.error('Missing Upload button in Expenses Analysis > Kanban View');
+ }
+ }
+ },
+ ]);
+});