[IMP] hr_expense: Improve global UX (back2basics)

closes odoo/odoo#55852

Taskid: 2303951
Related: odoo/enterprise#12394
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Yannick Tivisse
2020-08-13 10:53:00 +00:00
parent a89b68e7b5
commit a37a746b4e
10 changed files with 152 additions and 33 deletions
+1
View File
@@ -48,6 +48,7 @@ This module also uses analytic accounting and is compatible with the invoice on
'qweb': [
"static/src/xml/documents_upload_views.xml",
"static/src/xml/expense_dashboard.xml",
"static/src/xml/expense_qr_modal_template.xml",
],
'installable': True,
'application': True,
@@ -0,0 +1,24 @@
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);
const qr_vals = [
'002', // Version
'1', // Character Set
action.params.url, // Download app url
];
this.url = _.str.sprintf("/report/barcode/?type=QR&value=%s&width=256&height=256&humanreadable=1", encodeURI(qr_vals.join('\n')));
},
});
core.action_registry.add('expense_qr_code_modal', QRModalAction);
});
@@ -3,13 +3,16 @@ odoo.define('hr_expense.expenses.tree', function (require) {
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;
@@ -21,6 +24,38 @@ odoo.define('hr_expense.expenses.tree', function (require) {
}),
});
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;
@@ -50,9 +85,9 @@ odoo.define('hr_expense.expenses.tree', function (require) {
}
return value;
}
}
};
var ExpenseListRenderer = ListRenderer.extend(ExpenseDashboardMixin);
var ExpenseListRenderer = ListRenderer.extend(ExpenseDashboardMixin, ExpenseQRCodeMixin);
var ExpensesListViewDashboardUpload = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
@@ -75,7 +110,7 @@ odoo.define('hr_expense.expenses.tree', function (require) {
}),
});
var ExpenseKanbanRenderer = KanbanRenderer.extend(ExpenseDashboardMixin);
var ExpenseKanbanRenderer = KanbanRenderer.extend(ExpenseDashboardMixin, ExpenseQRCodeMixin);
var ExpensesKanbanView = KanbanView.extend({
config: _.extend({}, KanbanView.prototype.config, {
@@ -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>
+1
View File
@@ -4,6 +4,7 @@
<template id="assets_backend" name="HR Expense Assets assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/hr_expense/static/src/js/expense_views.js"/>
<script type="text/javascript" src="/hr_expense/static/src/js/expense_qr_code_action.js"/>
<script type="text/javascript" src="/hr_expense/static/src/js/upload_mixin.js"/>
</xpath>
<xpath expr="link[last()]" position="after">
+72 -25
View File
@@ -105,7 +105,7 @@
class="oe_stat_button"
icon="fa-file-text-o"
type="object">
<field name="attachment_number" widget="statinfo" string="Documents" options="{'reload_on_button': true}"/>
<field name="attachment_number" widget="statinfo" string="Receipts" options="{'reload_on_button': true}"/>
</button>
</div>
<div class="oe_title">
@@ -319,7 +319,7 @@
</record>
<record id="hr_expense_actions_my_unsubmitted" model="ir.actions.act_window">
<field name="name">My Expenses</field>
<field name="name">My Expenses to Report</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,kanban,form,graph,pivot,activity</field>
<field name="search_view_id" ref="hr_expense_view_search"/>
@@ -330,12 +330,35 @@
Did you try the mobile app?
</p>
<p>Snap pictures of your receipts and let Odoo<br/> automatically create expenses for you.</p>
<p class="d-none d-md-block">
<p>
<a href="https://apps.apple.com/be/app/odoo/id1272543640" target="_blank">
<img alt="Apple App Store" class="img img-fluid h-100" src="/hr_expense/static/img/app_store.png"/>
<img alt="Apple App Store" class="img img-fluid h-100 o_expense_apple_store" src="/hr_expense/static/img/app_store.png"/>
</a>
<a href="https://play.google.com/store/apps/details?id=com.odoo.mobile" target="_blank">
<img alt="Google Play Store" class="img img-fluid h-100" src="/hr_expense/static/img/play_store.png"/>
<a href="https://play.google.com/store/apps/details?id=com.odoo.mobile" target="_blank" class="o_expense_google_store">
<img alt="Google Play Store" class="img img-fluid h-100 o_expense_google_store" src="/hr_expense/static/img/play_store.png"/>
</a>
</p>
</field>
</record>
<record id="hr_expense_actions_my_all" model="ir.actions.act_window">
<field name="name">All My Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,kanban,form,graph,pivot,activity</field>
<field name="search_view_id" ref="hr_expense_view_search"/>
<field name="view_id" ref="view_my_expenses_tree"/>
<field name="context">{'search_default_my_expenses': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_expense_receipt">
Did you try the mobile app?
</p>
<p>Snap pictures of your receipts and let Odoo<br/> automatically create expenses for you.</p>
<p>
<a href="https://apps.apple.com/be/app/odoo/id1272543640" target="_blank">
<img alt="Apple App Store" class="img img-fluid h-100 o_expense_apple_store" src="/hr_expense/static/img/app_store.png"/>
</a>
<a href="https://play.google.com/store/apps/details?id=com.odoo.mobile" target="_blank" class="o_expense_google_store">
<img alt="Google Play Store" class="img img-fluid h-100 o_expense_google_store" src="/hr_expense/static/img/play_store.png"/>
</a>
</p>
</field>
@@ -378,7 +401,7 @@
<group name="product_details">
<group string="General Information">
<field name="active" invisible="1"/>
<field name="type"/>
<field name="type" invisible="1"/>
<field name="list_price"/>
<field name="standard_price"/>
<field name="uom_id" groups="uom.group_uom" options="{'no_create': True}"/>
@@ -426,7 +449,9 @@
<field name="domain">[('can_be_expensed', '=', True)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new expense product
No expense products found.
</p><p>
Let's create one! Expense products can be reinvoiced to your customers.
</p>
</field>
</record>
@@ -455,17 +480,17 @@
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<tree string="Expense Reports" sample="1">
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="accounting_date" optional="show" groups="account.group_account_manager"/>
<field name="create_date" optional="hide"/>
<field name="name" string="Expense Report"/>
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="user_id" optional="show" widget="many2one_avatar_user"/>
<field name="user_id" optional="hide" widget="many2one_avatar_user"/>
<field name="company_id" optional="show" groups="base.group_multi_company"/>
<field name="activity_ids" widget="list_activity" optional="show"/>
<field name="total_amount" optional="show" sum="Total Amount" decoration-bf="True"/>
<field name="currency_id" optional="hide"/>
<field name="journal_id" optional="hide"/>
<field name="state" optional="show" decoration-info="state == 'draft'" decoration-success="state in ['submit', 'approve', 'post', 'done']" decoration-danger="state == 'cancel'" widget="badge"/>
<field name="activity_ids" widget="list_activity" optional="show"/>
<field name="message_unread" invisible="1"/>
</tree>
</field>
@@ -513,6 +538,7 @@
<field name="state" widget="statusbar" statusbar_visible="draft,submit,approve,post,done"/>
</header>
<sheet>
<widget name="web_ribbon" title="Paid" bg_color="bg-success" attrs="{'invisible': [('state', '!=', 'done')]}"/>
<div class="oe_button_box" name="button_box">
<button name="action_get_attachment_view"
class="oe_stat_button"
@@ -530,8 +556,8 @@
<group>
<group name="employee_details">
<field name="employee_id" context="{'default_company_id': company_id}" widget="many2one_avatar_employee"/>
<field name="payment_mode"/>
<field name="user_id" domain="[('share', '=', False)]" widget="many2one_avatar_user"/>
<field name="payment_mode"/>
<field name="address_id" invisible="1" context="{'default_company_id': company_id}"/>
<field name="department_id" invisible="1" context="{'default_company_id': company_id}"/>
<field name="company_id" groups="base.group_multi_company"/>
@@ -722,7 +748,7 @@
</record>
<record id="action_hr_expense_sheet_my_all" model="ir.actions.act_window">
<field name="name">My Expense Reports</field>
<field name="name">My Reports</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_mode">tree,kanban,form,pivot,graph,activity</field>
<field name="search_view_id" ref="hr_expense_sheet_view_search"/>
@@ -731,9 +757,9 @@
<field name="view_id" ref="view_hr_expense_sheet_dashboard_tree"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new expense report
No expense report found.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
Let's create one! Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
@@ -748,7 +774,9 @@
<field name="view_id" ref="view_hr_expense_sheet_tree"/>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No expense reports to approve
No expense reports found.
</p><p>
Approve the new expense reports submitted by the employees you manage.
</p>
</field>
</record>
@@ -762,9 +790,9 @@
<field name="context">{'search_default_to_post': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new expense report
No expense reports found.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
Post the journal entries of the new expense reports approved by the employees' manager.
</p>
</field>
</record>
@@ -778,9 +806,23 @@
<field name="context">{'search_default_approved': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new expense report
No expense reports found.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
Reimburse the employees who incurred these costs or simply register the corresponding payments.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_all" model="ir.actions.act_window">
<field name="name">All Expense Reports</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="search_view_id" ref="hr_expense_sheet_view_search"/>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No expense reports found.
</p>
</field>
</record>
@@ -905,22 +947,27 @@ if records:
<menuitem id="menu_hr_expense_root" name="Expenses" sequence="100" web_icon="hr_expense,static/description/icon.png"/>
<menuitem id="menu_hr_expense_my_expenses" name="My Expenses" sequence="1" parent="menu_hr_expense_root" groups="base.group_user"/>
<menuitem id="menu_hr_expense_my_expenses_to_submit" sequence="1" parent="menu_hr_expense_my_expenses" action="hr_expense_actions_my_unsubmitted"/>
<menuitem id="menu_hr_expense_sheet_my_reports" sequence="3" parent="menu_hr_expense_my_expenses" action="action_hr_expense_sheet_my_all"/>
<menuitem id="menu_hr_expense_my_expenses_to_submit" sequence="1" parent="menu_hr_expense_my_expenses" action="hr_expense_actions_my_unsubmitted" name="My Expenses to Report"/>
<menuitem id="menu_hr_expense_my_expenses_all" sequence="2" parent="menu_hr_expense_my_expenses" action="hr_expense_actions_my_all" name="All My Expenses"/>
<menuitem id="menu_hr_expense_sheet_my_reports" sequence="3" parent="menu_hr_expense_my_expenses" action="action_hr_expense_sheet_my_all" name="My Reports"/>
<menuitem id="menu_hr_expense_report" name="Expense Reports" sequence="2" parent="menu_hr_expense_root"/>
<menuitem id="menu_hr_expense_sheet_all_to_approve"
name="To Approve" sequence="1" parent="menu_hr_expense_report"
name="Reports to Approve" sequence="1" parent="menu_hr_expense_report"
action="action_hr_expense_sheet_all_to_approve"
groups="hr_expense.group_hr_expense_team_approver"/>
<menuitem id="menu_hr_expense_sheet_all_to_post"
name="To Post" sequence="2" parent="menu_hr_expense_report"
name="Reports to Post" sequence="2" parent="menu_hr_expense_report"
action="action_hr_expense_sheet_all_to_post"
groups="account.group_account_invoice,hr_expense.group_hr_expense_manager"/>
<menuitem id="menu_hr_expense_sheet_all_to_pay"
name="To Pay" sequence="3" parent="menu_hr_expense_report"
name="Reports to Pay" sequence="3" parent="menu_hr_expense_report"
action="action_hr_expense_sheet_all_to_pay"
groups="account.group_account_invoice,hr_expense.group_hr_expense_manager"/>
<menuitem id="menu_hr_expense_sheet_all"
name="All Reports" sequence="4" parent="menu_hr_expense_report"
action="action_hr_expense_sheet_all"
groups="account.group_account_invoice,hr_expense.group_hr_expense_manager"/>
<menuitem id="menu_hr_expense_reports" name="Reporting" sequence="4" parent="menu_hr_expense_root" groups="hr_expense.group_hr_expense_manager"/>
<menuitem id="menu_hr_expense_all_expenses" name="Expenses Analysis" sequence="0" parent="menu_hr_expense_reports" action="hr_expense_actions_all"/>
@@ -44,7 +44,7 @@
<div class="o_setting_right_pane">
<label for="module_hr_payroll_expense" string="Reimburse in Payslip"/>
<div class="text-muted">
Expenses can be reimbursed in payslips.
Reimburse expenses in payslips
</div>
</div>
</div>
+1 -1
View File
@@ -7,7 +7,7 @@ from odoo import api, fields, models
class Expense(models.Model):
_inherit = "hr.expense"
sale_order_id = fields.Many2one('sale.order', compute='_compute_sale_order_id', store=True, string='Reinvoice Customer', readonly=False, tracking=True,
sale_order_id = fields.Many2one('sale.order', compute='_compute_sale_order_id', store=True, string='Customer to Reinvoice', readonly=False, tracking=True,
states={'approved': [('readonly', True)], 'done': [('readonly', True)], 'refused': [('readonly', True)]},
# NOTE: only confirmed SO can be selected, but this domain in activated throught the name search with the `sale_expense_all_order`
# context key. So, this domain is not the one applied.
+1 -1
View File
@@ -22,7 +22,7 @@ class SaleOrder(models.Model):
@api.depends('expense_ids')
def _compute_expense_count(self):
expense_data = self.env['hr.expense'].read_group([('sale_order_id', 'in', self.ids), ('state', '=', 'done')], ['sale_order_id'], ['sale_order_id'])
expense_data = self.env['hr.expense'].read_group([('sale_order_id', 'in', self.ids)], ['sale_order_id'], ['sale_order_id'])
mapped_data = dict([(item['sale_order_id'][0], item['sale_order_id_count']) for item in expense_data])
for sale_order in self:
sale_order.expense_count = mapped_data.get(sale_order.id, 0)
@@ -18,7 +18,7 @@
<field name="inherit_id" ref="hr_expense.view_expenses_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='reference']" position="after">
<field name="sale_order_id" optional="show" attrs="{'invisible': [('can_be_reinvoiced', '=', False)]}" options="{'no_create_edit': True, 'no_create': True, 'no_open': True}" context="{'sale_show_partner_name': True, 'sale_expense_all_order': True}" readonly="1"/>
<field name="sale_order_id" optional="hide" attrs="{'invisible': [('can_be_reinvoiced', '=', False)]}" options="{'no_create_edit': True, 'no_create': True, 'no_open': True}" context="{'sale_show_partner_name': True, 'sale_expense_all_order': True}" readonly="1"/>
<field name="can_be_reinvoiced" invisible="1" readonly="1"/>
</xpath>
</field>
@@ -64,7 +64,7 @@
<field name="name">Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('sale_order_id', '=', active_id), ('state', '=', 'done')]</field>
<field name="domain">[('sale_order_id', '=', active_id)]</field>
<field name="context">{'default_sale_order_id': active_id}</field>
</record>