[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:
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user