summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorshreya thakrar <sht@odoo.com>2019-08-02 08:43:36 +0000
committerYannick Tivisse <yti@odoo.com>2019-08-16 15:00:13 +0000
commitbf347e7727b477575c4a2230ac01e25f9d08270e (patch)
treeb03382dc6007997820efc96bcd11d5e95eba7dca /addons
parentd3a6330b2b314d670744ca7dd2e78be5b963e505 (diff)
[IMP] hr_expense: Improve general UX (back2basics)
For better user experience, there should be a 'UPLOAD' button on'hr.expense' through which user can directly upload a single or multiple documents and according to the number of documents, blank expense record with attached document should be generated. These records should be in 'draft' state. TaskID: 2040887 closes odoo/odoo#35400 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/hr_expense/__manifest__.py3
-rw-r--r--addons/hr_expense/models/hr_expense.py42
-rw-r--r--addons/hr_expense/static/src/js/documents_upload.js23
-rw-r--r--addons/hr_expense/static/src/js/upload_mixin.js74
-rw-r--r--addons/hr_expense/static/src/xml/documents_upload_views.xml28
-rw-r--r--addons/hr_expense/views/assets.xml6
-rw-r--r--addons/hr_expense/views/hr_expense_views.xml11
-rw-r--r--addons/sale_expense/models/hr_expense.py5
8 files changed, 185 insertions, 7 deletions
diff --git a/addons/hr_expense/__manifest__.py b/addons/hr_expense/__manifest__.py
index fc59b584152..428ec51806d 100644
--- a/addons/hr_expense/__manifest__.py
+++ b/addons/hr_expense/__manifest__.py
@@ -44,6 +44,9 @@ This module also uses analytic accounting and is compatible with the invoice on
'views/account_journal_dashboard.xml',
],
'demo': ['data/hr_expense_demo.xml'],
+ 'qweb': [
+ "static/src/xml/documents_upload_views.xml",
+ ],
'installable': True,
'application': True,
}
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 83ed84cbc52..af4a154e0a4 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -157,6 +157,46 @@ class HrExpense(models.Model):
if self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id:
raise UserError(_('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.'))
+ def create_expense_from_attachments(self, attachment_ids=None):
+ ''' Create the expenses from files.
+ :return: An action redirecting to hr.expense tree/form view.
+ '''
+ if attachment_ids is None:
+ attachment_ids = []
+ attachments = self.env['ir.attachment'].browse(attachment_ids)
+ if not attachments:
+ raise UserError(_("No attachment was provided"))
+ expenses = self.env['hr.expense']
+
+ if any(attachment.res_id or attachment.res_model != 'hr.expense' for attachment in attachments):
+ raise UserError(_("Invalid attachments!"))
+
+ for attachment in attachments:
+ expense = self.env['hr.expense'].create({
+ 'name': attachment.name.split('.')[0],
+ 'unit_amount': 0,
+ })
+ attachment.write({'res_id': expense.id})
+ expense.message_post(body=_('Uploaded Attachment'), attachment_ids=[attachment.id])
+ expenses += expense
+
+ if len(expenses) == 1:
+ return {
+ 'name': _('Generated Expense'),
+ 'view_mode': 'form',
+ 'res_model': 'hr.expense',
+ 'type': 'ir.actions.act_window',
+ 'views': [[False, 'form']],
+ 'res_id': expenses[0].id,
+ }
+ return {
+ 'name': _('Generated Expenses'),
+ 'domain': [('id', 'in', expenses.ids)],
+ 'res_model': 'hr.expense',
+ 'type': 'ir.actions.act_window',
+ 'views': [[False, "tree"], [False, "form"]],
+ }
+
# ----------------------------------------
# ORM Overrides
# ----------------------------------------
@@ -209,6 +249,8 @@ class HrExpense(models.Model):
raise UserError(_("You cannot report twice the same line!"))
if len(self.mapped('employee_id')) != 1:
raise UserError(_("You cannot report expenses for different employees in the same report."))
+ if any(not expense.product_id for expense in self):
+ raise UserError(_("You can not create report without product."))
todo = self.filtered(lambda x: x.payment_mode=='own_account') or self.filtered(lambda x: x.payment_mode=='company_account')
return {
diff --git a/addons/hr_expense/static/src/js/documents_upload.js b/addons/hr_expense/static/src/js/documents_upload.js
new file mode 100644
index 00000000000..ef7fef75601
--- /dev/null
+++ b/addons/hr_expense/static/src/js/documents_upload.js
@@ -0,0 +1,23 @@
+odoo.define('hr_expense.expenses.tree', function (require) {
+"use strict";
+ var ListController = require('web.ListController');
+ var ListView = require('web.ListView');
+ var DocumentUploadMixin = require('hr_expense.documents.upload.mixin');
+ var viewRegistry = require('web.view_registry');
+
+ 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',
+ }),
+ });
+
+ var ExpensesListView = ListView.extend({
+ config: _.extend({}, ListView.prototype.config, {
+ Controller: ExpensesListController,
+ }),
+ });
+
+ viewRegistry.add('hr_expense_tree', ExpensesListView);
+});
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 00000000000..f3c1718201c
--- /dev/null
+++ b/addons/hr_expense/static/src/js/upload_mixin.js
@@ -0,0 +1,74 @@
+odoo.define('hr_expense.documents.upload.mixin', function (require) {
+"use strict";
+
+var core = require('web.core');
+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(_t("Error"), _t("An error occurred during the upload"));
+ }
+ return this._rpc({
+ model: 'hr.expense',
+ method: 'create_expense_from_attachments',
+ args: ["", attachent_ids],
+ context: this.initialState.context,
+ }).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/xml/documents_upload_views.xml b/addons/hr_expense/static/src/xml/documents_upload_views.xml
new file mode 100644
index 00000000000..38c2ba0bda1
--- /dev/null
+++ b/addons/hr_expense/static/src/xml/documents_upload_views.xml
@@ -0,0 +1,28 @@
+<?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">
+ <t t-js="ctx">
+ if (window.odoo._modules.indexOf("base_import") >= 0) {
+ r.push(context.engine.tools.call(context, 'ImportView.import_button', dict));
+ };
+ </t>
+ <button type="button" class="btn btn-secondary o_button_upload_expense">
+ Upload
+ </button>
+ </t>
+ </t>
+</templates>
diff --git a/addons/hr_expense/views/assets.xml b/addons/hr_expense/views/assets.xml
index 010318f80bf..7d0872f858f 100644
--- a/addons/hr_expense/views/assets.xml
+++ b/addons/hr_expense/views/assets.xml
@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
+ <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/documents_upload.js"/>
+ <script type="text/javascript" src="/hr_expense/static/src/js/upload_mixin.js"/>
+ </xpath>
+ </template>
<template id="assets_tests" name="HR Expense Assets Tests" inherit_id="web.assets_tests">
<xpath expr="." position="inside">
<script type="text/javascript" src="/hr_expense/static/src/js/tours/hr_expense.js"></script>
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index 6958e26f4a4..e72faf0c602 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -27,7 +27,7 @@
<field name="name">hr.expense.tree</field>
<field name="model">hr.expense</field>
<field name="arch" type="xml">
- <tree string="Expenses" decoration-bf="message_unread == True" class="o_expense_tree">
+ <tree string="Expenses" decoration-bf="message_unread == True" class="o_expense_tree" js_class="hr_expense_tree" decoration-info="state == 'draft'" decoration-muted="state == 'cancel'">
<field name="currency_id" invisible="True"/>
<field name="attachment_number" invisible="True"/>
<field name="date"/>
@@ -70,7 +70,7 @@
</button>
</div>
<div class="oe_title">
- <label for="name"/>
+ <label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Lunch with Customer"/>
</h1>
@@ -90,6 +90,7 @@
<field name="product_uom_id" required="1" widget="selection" class="oe_inline" groups="uom.group_uom"/>
</div>
<field name="tax_ids" domain="[('type_tax_use', '=', 'purchase')]" widget="many2many_tags" groups="account.group_account_user" attrs="{'readonly': [('is_editable', '=', False)]}"/>
+ <field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
</group><group>
<field name="reference" attrs="{'readonly': [('is_ref_editable', '=', False)]}"/>
<field name="date"/>
@@ -101,13 +102,11 @@
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags" attrs="{'readonly': [('is_editable', '=', False)]}"/>
<field name="company_id" groups="base.group_multi_company"/>
</group><group>
- <field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
<label for="payment_mode"/>
<div>
<field name="payment_mode" widget="radio"/>
</div>
</group>
-
</group>
<div>
<field name="description" class="oe_inline" placeholder="Notes..."/>
@@ -373,7 +372,7 @@
<field name="domain">[('can_be_expensed', '=', True)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
- Create a new expense category
+ Create a new expense product
</p>
</field>
</record>
@@ -828,7 +827,7 @@
</record>
<record id="hr_expense_submit_action_server" model="ir.actions.server">
- <field name="name">Submit Report</field>
+ <field name="name">Create Report</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="binding_model_id" ref="model_hr_expense"/>
diff --git a/addons/sale_expense/models/hr_expense.py b/addons/sale_expense/models/hr_expense.py
index a7a2c983b55..057381815ab 100644
--- a/addons/sale_expense/models/hr_expense.py
+++ b/addons/sale_expense/models/hr_expense.py
@@ -7,7 +7,10 @@ from odoo import api, fields, models
class Expense(models.Model):
_inherit = "hr.expense"
- sale_order_id = fields.Many2one('sale.order', string='Sale Order', readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)]}, domain="[('state', '=', 'sale'), ('company_id', '=', company_id)]")
+ sale_order_id = fields.Many2one(
+ 'sale.order', string='Reinvoice Customer', readonly=True,
+ states={'draft': [('readonly', False)], 'reported': [('readonly', False)]},
+ domain="[('state', '=', 'sale'), ('company_id', '=', company_id)]")
@api.onchange('sale_order_id')
def _onchange_sale_order(self):