diff options
| author | shreya thakrar <sht@odoo.com> | 2019-08-02 08:43:36 +0000 |
|---|---|---|
| committer | Yannick Tivisse <yti@odoo.com> | 2019-08-16 15:00:13 +0000 |
| commit | bf347e7727b477575c4a2230ac01e25f9d08270e (patch) | |
| tree | b03382dc6007997820efc96bcd11d5e95eba7dca /addons | |
| parent | d3a6330b2b314d670744ca7dd2e78be5b963e505 (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__.py | 3 | ||||
| -rw-r--r-- | addons/hr_expense/models/hr_expense.py | 42 | ||||
| -rw-r--r-- | addons/hr_expense/static/src/js/documents_upload.js | 23 | ||||
| -rw-r--r-- | addons/hr_expense/static/src/js/upload_mixin.js | 74 | ||||
| -rw-r--r-- | addons/hr_expense/static/src/xml/documents_upload_views.xml | 28 | ||||
| -rw-r--r-- | addons/hr_expense/views/assets.xml | 6 | ||||
| -rw-r--r-- | addons/hr_expense/views/hr_expense_views.xml | 11 | ||||
| -rw-r--r-- | addons/sale_expense/models/hr_expense.py | 5 |
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): |
