[IMP] hr_expense: bring back the sheets + other new features

NB:
- The option no_delete is not working, a fix will come from chm
- The many2many widget on a sheet creation is not working correctly as
  the called command is a (1, id, values) without a (4, id, _) before.
  A fix will also come from chm, but a disgusting hack has been
  provided for this particular case (to remove after chm fix)
This commit is contained in:
Yannick Tivisse
2016-06-17 12:14:57 +02:00
parent 5aa9bde34a
commit 717f9217bb
21 changed files with 1101 additions and 340 deletions
-1
View File
@@ -2,5 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
import report
import wizard
+2 -1
View File
@@ -32,9 +32,10 @@ This module also uses analytic accounting and is compatible with the invoice on
'data/hr_expense_data.xml',
'data/hr_expense_sequence.xml',
'wizard/hr_expense_refuse_reason.xml',
'wizard/hr_expense_register_payment.xml',
'views/hr_expense_views.xml',
'security/ir_rule.xml',
'views/report_expense.xml',
'report/report_expense_sheet.xml',
'views/hr_dashboard.xml',
'views/hr_expense.xml',
'views/tour_views.xml',
+6 -7
View File
@@ -21,31 +21,30 @@
<!-- Expense-related subtypes for messaging / Chatter -->
<record id="mt_expense_approved" model="mail.message.subtype">
<field name="name">Approved</field>
<field name="res_model">hr.expense</field>
<field name="default" eval="False"/>
<field name="res_model">hr.expense.sheet</field>
<field name="default" eval="True"/>
<field name="description">Expense approved</field>
</record>
<record id="mt_expense_refused" model="mail.message.subtype">
<field name="name">Refused</field>
<field name="res_model">hr.expense</field>
<field name="res_model">hr.expense.sheet</field>
<field name="default" eval="True"/>
<field name="description">Expense refused</field>
</record>
<record id="mt_expense_confirmed" model="mail.message.subtype">
<field name="name">To Approve</field>
<field name="res_model">hr.expense</field>
<field name="res_model">hr.expense.sheet</field>
<field name="description">Expense confirmed, waiting confirmation</field>
<field name="default" eval="True"/>
<field name="default" eval="False"/>
</record>
<!-- Department (Parent) related subtypes for messaging / Chatter -->
<record id="mt_department_expense_confirmed" model="mail.message.subtype">
<field name="name">Expenses To Approve</field>
<field name="res_model">hr.department</field>
<field name="default" eval="True"/>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_expense_confirmed')"/>
<field name="relation_field">department_id</field>
<field name="sequence" eval="10"/>
</record>
</data>
</odoo>
+15 -1
View File
@@ -3,7 +3,21 @@
<data noupdate="1">
<record id="hr.employee_mit" model="hr.employee">
<field name="address_home_id" ref="base.res_partner_address_5"/>
<field name="address_home_id" ref="base.res_partner_address_3"/>
</record>
<record id="base.res_partner_address_fp" model="res.partner">
<field name="name">Pieter Parter's Farm</field>
<field name="parent_id" eval="ref('base.partner_root')"/>
<field name="supplier">1</field>
<field name="street">Chaussée de Namur, 40</field>
<field name="zip">1367</field>
<field name="city">Grand-Rosière-Hottomont</field>
<field name="country_id" ref="base.be"/>
</record>
<record id="hr.employee_fp" model="hr.employee">
<field name="address_home_id" ref="base.res_partner_address_fp"/>
</record>
<record id="car_travel" model="product.product">
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="expense_tip_1" model="web.tip">
<field name="title"></field>
<field name="description">Select the lines you want to submit to be reimbursed.</field>
<field name="action_id" ref="hr_expense.hr_expense_actions_all"/>
<field name="model">hr.expense</field>
<field name="mode">list</field>
<field name="trigger_selector">input.oe_list_record_selector:visible</field>
<field name="highlight_selector">input.oe_list_record_selector:visible</field>
<field name="placement">bottom</field>
</record>
<record id="expense_tip_2" model="web.tip">
<field name="title"></field>
<field name="description">Click on Action to submit multiple expenses to your manager.</field>
<field name="action_id" ref="hr_expense.hr_expense_actions_all"/>
<field name="model">hr.expense</field>
<field name="mode">list</field>
<field name="trigger_selector">div.oe_sidebar.btn-group:visible</field>
<field name="highlight_selector">div.oe_sidebar.btn-group:visible</field>
<field name="placement">bottom</field>
</record>
<record id="expense_tip_3" model="web.tip">
<field name="title"></field>
<field name="description">Use the messaging tool to log a note and attach a document to the expense. (e.g. scan of the bill)</field>
<field name="action_id" ref="hr_expense.hr_expense_actions_all"/>
<field name="model">hr.expense</field>
<field name="mode">form</field>
<field name="trigger_selector">div.btn-group.btn-group-sm:visible > button</field>
<field name="highlight_selector">.o_timeline_compose_post.btn.btn-primary:visible</field>
<field name="placement">bottom</field>
</record>
</odoo>
@@ -12,6 +12,6 @@ class AccountMoveLine(models.Model):
res = super(AccountMoveLine, self).reconcile(writeoff_acc_id=writeoff_acc_id, writeoff_journal_id=writeoff_journal_id)
account_move_ids = [l.move_id.id for l in self if float_compare(l.move_id.matched_percentage, 1, precision_digits=5) == 0]
if account_move_ids:
expenses = self.env['hr.expense'].search([('account_move_id', 'in', account_move_ids)])
expenses.paid_expenses()
expense_sheets = self.env['hr.expense.sheet'].search([('account_move_id', 'in', account_move_ids)])
expense_sheets.set_to_paid()
return res
+5 -5
View File
@@ -7,10 +7,10 @@ from openerp import fields, models
class HrDepartment(models.Model):
_inherit = 'hr.department'
def _compute_expense_to_approve(self):
expense_data = self.env['hr.expense'].read_group([('department_id', 'in', self.ids), ('state', '=', 'submit')], ['department_id'], ['department_id'])
result = dict((data['department_id'][0], data['department_id_count']) for data in expense_data)
def _compute_expense_sheets_to_approve(self):
expense_sheet_data = self.env['hr.expense.sheet'].read_group([('department_id', 'in', self.ids), ('state', '=', 'submit')], ['department_id'], ['department_id'])
result = dict((data['department_id'][0], data['department_id_count']) for data in expense_sheet_data)
for department in self:
department.expense_to_approve_count = result.get(department.id, 0)
department.expense_sheets_to_approve_count = result.get(department.id, 0)
expense_to_approve_count = fields.Integer(compute='_compute_expense_to_approve', string='Expenses to Approve')
expense_sheets_to_approve_count = fields.Integer(compute='_compute_expense_sheets_to_approve', string='Expenses Reports to Approve')
+239 -110
View File
@@ -12,11 +12,10 @@ class HrExpense(models.Model):
_name = "hr.expense"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Expense"
_order = "date desc"
_order = "date desc, id desc"
name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)]})
name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today)
date = fields.Date(readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, string="Date")
accounting_date = fields.Date(readonly=True, states={'draft': [('readonly', False)]}, string='Accounting Date')
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1))
product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True)
product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id'))
@@ -29,22 +28,29 @@ class HrExpense(models.Model):
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id)
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account')
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category'))
department_id = fields.Many2one('hr.department', string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]})
description = fields.Text()
payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], default='own_account', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, string="Payment By")
journal_id = fields.Many2one('account.journal', string='Expense Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', '=', 'purchase')], limit=1), help="The journal used when the expense is done.")
bank_journal_id = fields.Many2one('account.journal', string='Bank Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', 'in', ['case', 'bank'])], limit=1), help="The payment method used when the expense is paid by the company.")
account_move_id = fields.Many2one('account.move', string='Journal Entry', copy=False, track_visibility="onchange")
attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments')
state = fields.Selection([('draft', 'To Submit'),
('submit', 'Submitted'),
('approve', 'Approved'),
('post', 'Waiting Payment'),
('done', 'Paid'),
('cancel', 'Refused')
], string='Status', index=True, readonly=True, track_visibility='onchange', copy=False, default='draft', required=True,
help='When the expense request is created the status is \'To Submit\'.\n It is submitted by the employee and request is sent to manager, the status is \'Submitted\'.\
\nIf the manager approve it, the status is \'Approved\'.\n If the accountant genrate the accounting entries for the expense request, the status is \'Waiting Payment\'.')
state = fields.Selection([('draft', 'To Report'),
('reported', 'Reported'),
('done', 'Posted'),
('refused', 'Refused')
], compute='_compute_state', default="draft", string='Status', index=True, readonly=True, copy=False, required=True, store=True,
help="Status of the expense.")
sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", readonly=True)
reference = fields.Char(string="Bill Reference")
@api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state')
def _compute_state(self):
for expense in self:
if not expense.sheet_id:
expense.state = "draft"
elif expense.sheet_id.state == "cancel":
expense.state = "refused"
elif not expense.sheet_id.account_move_id:
expense.state = "reported"
else:
expense.state = "done"
@api.depends('quantity', 'unit_amount', 'tax_ids', 'currency_id')
def _compute_amount(self):
@@ -60,6 +66,13 @@ class HrExpense(models.Model):
for expense in self:
expense.attachment_number = attachment.get(expense.id, 0)
@api.multi
def action_get_attachment_view(self):
res = self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment')
res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)]
res['context'] = {'default_res_model': 'hr.expense', 'default_res_id': self.id}
return res
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
@@ -77,76 +90,31 @@ 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'))
@api.onchange('employee_id')
def _onchange_employee_id(self):
self.department_id = self.employee_id.department_id
def _add_followers(self):
user_ids = []
employee = self.employee_id
if employee.user_id:
user_ids.append(employee.user_id.id)
if employee.parent_id:
user_ids.append(employee.parent_id.user_id.id)
if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id:
user_ids.append(employee.department_id.manager_id.user_id.id)
self.message_subscribe_users(user_ids=user_ids)
@api.model
def create(self, vals):
hr_expense = super(HrExpense, self).create(vals)
if vals.get('employee_id'):
hr_expense._add_followers()
return hr_expense
@api.multi
def write(self, vals):
res = super(HrExpense, self).write(vals)
if vals.get('employee_id'):
self._add_followers()
return res
@api.multi
def unlink(self):
if any(expense.state not in ['draft', 'cancel'] for expense in self):
raise UserError(_('You can only delete draft or refused expenses!'))
return super(HrExpense, self).unlink()
def view_sheet(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'hr.expense.sheet',
'target': 'current',
'res_id': self.sheet_id.id
}
@api.multi
def submit_expenses(self):
if any(expense.state != 'draft' for expense in self):
raise UserError(_("You can only submit draft expenses!"))
self.write({'state': 'submit'})
@api.multi
def approve_expenses(self):
self.write({'state': 'approve'})
@api.multi
def refuse_expenses(self, reason):
self.write({'state': 'cancel'})
if self.employee_id.user_id:
body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (self.name, reason))
self.message_post(body=body, partner_ids=[self.employee_id.user_id.partner_id.id])
@api.multi
def paid_expenses(self):
self.write({'state': 'done'})
@api.multi
def reset_expenses(self):
return self.write({'state': 'draft'})
@api.multi
def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'approve':
return 'hr_expense.mt_expense_approved'
elif 'state' in init_values and self.state == 'submit':
return 'hr_expense.mt_expense_confirmed'
elif 'state' in init_values and self.state == 'cancel':
return 'hr_expense.mt_expense_refused'
return super(HrExpense, self)._track_subtype(init_values)
raise UserError(_("You cannot report twice the same line!"))
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'hr.expense.sheet',
'target': 'current',
'context': {
'default_expense_line_ids': [line.id for line in self],
'default_employee_id': self[0].employee_id.id,
}
}
def _prepare_move_line(self, line):
'''
@@ -157,15 +125,16 @@ class HrExpense(models.Model):
'date_maturity': line.get('date_maturity'),
'partner_id': partner_id,
'name': line['name'][:64],
'date': self.sheet_id.accounting_date,
'debit': line['price'] > 0 and line['price'],
'credit': line['price'] < 0 and -line['price'],
'credit': line['price'] < 0 and - line['price'],
'account_id': line['account_id'],
'analytic_line_ids': line.get('analytic_line_ids'),
'amount_currency': line['price'] > 0 and abs(line.get('amount_currency')) or -abs(line.get('amount_currency')),
'amount_currency': line['price'] > 0 and abs(line.get('amount_currency')) or - abs(line.get('amount_currency')),
'currency_id': line.get('currency_id'),
'tax_line_id': line.get('tax_line_id'),
'ref': line.get('ref'),
'quantity': line.get('quantity',1.00),
'quantity': line.get('quantity', 1.00),
'product_id': line.get('product_id'),
'product_uom_id': line.get('uom_id'),
'analytic_account_id': line.get('analytic_account_id'),
@@ -203,22 +172,10 @@ class HrExpense(models.Model):
'''
main function that is called when trying to create the accounting entries related to an expense
'''
if any(expense.state != 'approve' for expense in self):
raise UserError(_("You can only generate accounting entry for approved expense(s)."))
if any(expense.employee_id != self[0].employee_id for expense in self):
raise UserError(_("Expenses must belong to the same Employee."))
if any(not expense.journal_id for expense in self):
raise UserError(_("Expenses must have an expense journal specified to generate accounting entries."))
journal_dict = {}
maxdate = False
for expense in self:
acc_date = expense.accounting_date or expense.date
if acc_date > maxdate:
maxdate = acc_date
jrn = expense.bank_journal_id if expense.payment_mode == 'company_account' else expense.journal_id
acc_date = expense.sheet_id.accounting_date or fields.Date.context_today(self)
jrn = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
journal_dict.setdefault(jrn, [])
journal_dict[jrn].append(expense)
@@ -227,7 +184,7 @@ class HrExpense(models.Model):
move = self.env['account.move'].create({
'journal_id': journal.id,
'company_id': self.env.user.company_id.id,
'date': maxdate,
'date': acc_date,
})
for expense in expense_list:
company_currency = expense.company_id.currency_id
@@ -236,11 +193,11 @@ class HrExpense(models.Model):
move_lines = expense._move_line_get()
#create one more move line, a counterline for the total on payable account
total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, maxdate)
total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date)
if expense.payment_mode == 'company_account':
if not expense.bank_journal_id.default_credit_account_id:
raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.bank_journal_id.name))
emp_account = expense.bank_journal_id.default_credit_account_id.id
if not expense.sheet_id.bank_journal_id.default_credit_account_id:
raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name))
emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id
else:
if not expense.employee_id.address_home_id:
raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name))
@@ -251,18 +208,18 @@ class HrExpense(models.Model):
'name': expense.employee_id.name,
'price': total,
'account_id': emp_account,
'date_maturity': expense.accounting_date or expense.date,
'date_maturity': acc_date,
'amount_currency': diff_currency_p and total_currency or False,
'currency_id': diff_currency_p and expense.currency_id.id or False,
'ref': expense.employee_id.address_home_id.ref or False
})
#convert eml into an osv-valid format
lines = map(lambda x:(0, 0, expense._prepare_move_line(x)), move_lines)
lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
move.write({'line_ids': lines})
expense.write({'account_move_id': move.id, 'state': 'post'})
expense.sheet_id.write({'account_move_id': move.id})
if expense.payment_mode == 'company_account':
expense.paid_expenses()
expense.sheet_id.paid_expense_sheets()
move.post()
return True
@@ -270,8 +227,8 @@ class HrExpense(models.Model):
def _move_line_get(self):
account_move = []
for expense in self:
if expense.account_id:
account = expense.account_id
if expense.sheet_id and expense.sheet_id.account_id:
account = expense.sheet_id.account_id
elif expense.product_id:
account = expense.product_id.product_tmpl_id._get_product_accounts()['expense']
if not account:
@@ -291,7 +248,7 @@ class HrExpense(models.Model):
'product_id': expense.product_id.id,
'uom_id': expense.product_uom_id.id,
'analytic_account_id': expense.analytic_account_id.id,
}
}
account_move.append(move_line)
# Calculate tax lines and adjust base line
@@ -317,3 +274,175 @@ class HrExpense(models.Model):
res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)]
res['context'] = {'default_res_model': 'hr.expense', 'default_res_id': self.id}
return res
class HrExpenseSheet(models.Model):
_name = "hr.expense.sheet"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Expense Report"
_order = "accounting_date desc"
name = fields.Char(string='Expense Report Summary', required=True)
expense_line_ids = fields.One2many('hr.expense', 'sheet_id', string='Expense Lines', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, copy=False)
state = fields.Selection([('submit', 'Submitted'),
('approve', 'Approved'),
('post', 'Posted'),
('done', 'Paid'),
('cancel', 'Refused')
], string='Status', index=True, readonly=True, track_visibility='onchange', copy=False, default='submit', required=True,
help='Expense Report State')
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'submit': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1))
address_id = fields.Many2one('res.partner', string="Employee Home Address")
payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], related='expense_line_ids.payment_mode', default='own_account', readonly=True, string="Payment By")
responsible_id = fields.Many2one('res.users', 'Validation By', readonly=True, copy=False, states={'submit': [('readonly', False)], 'submit': [('readonly', False)]})
total_amount = fields.Float(string='Total Amount', store=True, compute='_compute_amount', digits=dp.get_precision('Account'))
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'submit': [('readonly', False)]}, default=lambda self: self.env.user.company_id)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'submit': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id)
attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments')
journal_id = fields.Many2one('account.journal', string='Expense Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]},
default=lambda self: self.env['ir.model.data'].xmlid_to_object('hr_expense.hr_expense_account_journal') or self.env['account.journal'].search([('type', '=', 'purchase')], limit=1),
help="The journal used when the expense is done.")
bank_journal_id = fields.Many2one('account.journal', string='Bank Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', 'in', ['case', 'bank'])], limit=1), help="The payment method used when the expense is paid by the company.")
accounting_date = fields.Date(string="Accounting Date")
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_payable_id', 'account.chart.template'))
account_move_id = fields.Many2one('account.move', string='Journal Entry', copy=False, track_visibility="onchange")
department_id = fields.Many2one('hr.department', string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]})
@api.multi
def check_consistency(self):
if any(sheet.employee_id != self[0].employee_id for sheet in self):
raise UserError(_("Expenses must belong to the same Employee."))
expense_lines = self.mapped('expense_line_ids')
if expense_lines and any(expense.payment_mode != expense_lines[0].payment_mode for expense in expense_lines):
raise UserError(_("Expenses must have been paid by the same entity (Company or employee)"))
@api.model
def create(self, vals):
sheet = super(HrExpenseSheet, self).create(vals)
self.check_consistency()
if vals.get('employee_id'):
sheet._add_followers()
return sheet
@api.multi
def write(self, vals):
res = super(HrExpenseSheet, self).write(vals)
self.check_consistency()
if vals.get('employee_id'):
self._add_followers()
return res
@api.multi
def set_to_paid(self):
self.write({'state': 'done'})
@api.multi
def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'approve':
return 'hr_expense.mt_expense_approved'
elif 'state' in init_values and self.state == 'submit':
return 'hr_expense.mt_expense_confirmed'
elif 'state' in init_values and self.state == 'cancel':
return 'hr_expense.mt_expense_refused'
return super(HrExpenseSheet, self)._track_subtype(init_values)
def _add_followers(self):
user_ids = []
employee = self.employee_id
if employee.user_id:
user_ids.append(employee.user_id.id)
if employee.parent_id:
user_ids.append(employee.parent_id.user_id.id)
if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id:
user_ids.append(employee.department_id.manager_id.user_id.id)
self.message_subscribe_users(user_ids=user_ids)
@api.onchange('employee_id')
def _onchange_employee_id(self):
self.address_id = self.employee_id.address_home_id
self.department_id = self.employee_id.department_id
self.account_id = self.employee_id.address_home_id.id and self.employee_id.address_home_id.property_account_payable_id.id
@api.onchange('payment_mode')
def _onchange_payment_mode(self):
if self.payment_mode == 'own_account':
self.account_id = self.employee_id.address_home_id.property_account_payable_id.id
else:
self.account_id = self.bank_journal_id.default_credit_account_id.id
@api.one
@api.depends('expense_line_ids')
def _compute_amount(self):
self.total_amount = sum(self.expense_line_ids.mapped('total_amount'))
# FIXME: A 4 command is missing to explicitly declare the one2many relation
# between the sheet and the lines when using 'default_expense_line_ids':[ids]
# in the context. A fix from chm-odoo should come since
# several saas versions but sadly I had to add this hack to avoid this
# issue
@api.model
def _add_missing_default_values(self, values):
values = super(HrExpenseSheet, self)._add_missing_default_values(values)
if self.env.context.get('default_expense_line_ids', False):
lines_to_add = []
for line in values.get('expense_line_ids', []):
if line[0] == 1:
lines_to_add.append([4, line[1], False])
values['expense_line_ids'] = lines_to_add + values['expense_line_ids']
return values
@api.one
def _compute_attachment_number(self):
self.attachment_number = sum(self.expense_line_ids.mapped('attachment_number'))
@api.multi
def refuse_expenses(self, reason):
self.write({'state': 'cancel'})
for sheet in self:
body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (sheet.name, reason))
sheet.message_post(body=body)
@api.multi
def approve_expense_sheets(self):
self.message_post(body=_("The expense has been validated by %s") % (self.env.user.name))
self.write({'state': 'approve', 'responsible_id': self.env.user.id})
@api.multi
def refuse_expense_sheets(self, reason):
self.write({'state': 'cancel'})
for sheet in self:
body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (sheet.name, reason))
sheet.message_post(body=body)
@api.multi
def paid_expense_sheets(self):
self.write({'state': 'done'})
@api.multi
def reset_expense_sheets(self):
return self.write({'state': 'submit'})
@api.multi
def action_sheet_move_create(self):
if any(sheet.state != 'approve' for sheet in self):
raise UserError(_("You can only generate accounting entry for approved expense(s)."))
if any(not sheet.journal_id for sheet in self):
raise UserError(_("Expenses must have an expense journal specified to generate accounting entries."))
res = self.mapped('expense_line_ids').action_move_create()
if not self.accounting_date:
self.accounting_date = self.account_move_id.date
self.write({'state': 'post'})
return res
@api.multi
def action_get_attachment_view(self):
res = self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment')
res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.expense_line_ids.ids)]
res['context'] = {'default_res_model': 'hr.expense.sheet', 'default_res_id': self.id}
return res
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import hr_expense_report
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import osv
from openerp.tools.misc import formatLang
from openerp.tools.translate import _
from openerp.report import report_sxw
from openerp.exceptions import UserError
class report_expense(report_sxw.rml_parse):
def set_context(self, objects, data, ids, report_type=None):
res = super(report_expense, self).set_context(objects, data, ids, report_type=report_type)
hr_expense_obj = self.pool.get('hr.expense')
context = {'lang': self.pool.get('res.users').browse(self.cr, self.uid, self.uid).lang}
state_field = hr_expense_obj.fields_get(self.cr, self.uid, 'state', context=context)['state']['selection']
state_dict = {}
for state_tuple in state_field:
state_dict[state_tuple[0]] = state_tuple[1]
expenses_info = {}
expenses = hr_expense_obj.search(self.cr, self.uid, [('id', 'in', ids)], order="employee_id, currency_id, state, date")
for expense in hr_expense_obj.browse(self.cr, self.uid, expenses):
key = expense.employee_id.name + '-' + expense.currency_id.name + '-' + expense.state
if expenses_info.get(key):
expenses_info[key]['lines'] += expense
expenses_info[key]['total_amount'] += expense.total_amount
else:
expenses_info[key] = {
'employee_name': expense.employee_id.name,
'total_amount': expense.total_amount,
'lines': expense,
'currency': expense.currency_id,
'validator_name': expense.employee_id.parent_id.name,
'notes': [],
'notes_index': {},
'state': state_dict[expense.state],
}
if expense.description:
index = len(expenses_info[key]['notes']) + 1
expenses_info[key]['notes'].append({'description': expense.description, 'index':index})
expenses_info[key]['notes_index'][expense.id] = index
# Qweb for-each do not work on dict, so we send a list and we sort it by the name of the employee
# that way if we have two sheet for the same employee they will follow in the report
self.localcontext.update({
'get_expenses': lambda : [v for k,v in sorted(expenses_info.items())],
})
return res
class report_hr_expense(osv.AbstractModel):
_name = 'report.hr_expense.report_expense'
_inherit = 'report.abstract_report'
_template = 'hr_expense.report_expense'
_wrapped_report_class = report_expense
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_expense_sheet">
<t t-call="report.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report.external_layout">
<div class="page">
<h2>Expenses Report</h2>
<div class="row mt32 mb32">
<div class="col-xs-2">
<strong>Employee:</strong>
<p t-field="o.employee_id.name"/>
</div>
<div class="col-xs-2">
<strong>Date:</strong>
<p t-field="o.accounting_date"/>
</div>
<div class="col-xs-3">
<strong>Description:</strong>
<p t-field="o.name"/>
</div>
<div class="col-xs-2">
<strong>Validated By:</strong>
<p t-field="o.responsible_id"/>
</div>
<div class="col-xs-3">
<strong>Payment By:</strong>
<p t-field="o.payment_mode"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th class="text-center">Ref.</th>
<th>Unit Price</th>
<th>Taxes</th>
<th class="text-center">Qty</th>
<th class="text-right">Price</th>
</tr>
</thead>
<tbody>
<tr t-foreach="o.expense_line_ids" t-as="line">
<td><span t-field="line.date"/></td>
<td>
<span t-field="line.name"/>
<span t-field="line.description"/><br/>
<span t-field="line.analytic_account_id.name"/>
</td>
<td style="text-center">
<span t-field="line.reference"/>
</td>
<td>
<span t-field="line.unit_amount"/>
</td>
<td>
<span t-field="line.tax_ids"/>
</td>
<td class="text-center">
<span t-field="line.quantity"/>
</td>
<td class="text-right">
<span t-field="line.total_amount"
t-field-options='{"widget": "monetary", "display_currency":"o.currency_id"}'/>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td><strong>Total</strong></td>
<td class="text-right">
<span t-field="o.total_amount"
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
</td>
</tr>
</table>
</div>
</div>
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
</div>
</t>
</t>
</t>
</template>
<report
id="action_report_hr_expense_sheet"
string="Expenses Report"
model="hr.expense.sheet"
report_type="qweb-pdf"
name="hr_expense.report_expense_sheet"
file="hr_expense.report_expense_sheet"
/>
</odoo>
@@ -2,6 +2,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_expense_user,hr.expense.user,model_hr_expense,base.group_hr_user,1,1,1,1
access_hr_expense_employee,hr.expense.employee,model_hr_expense,base.group_user,1,1,1,1
access_hr_expense_sheet_user,hr.expense.sheet.user,model_hr_expense_sheet,base.group_hr_user,1,1,1,1
access_hr_expense_sheet_employee,hr.expense.sheet.employee,model_hr_expense_sheet,base.group_user,1,1,1,1
access_product_product_hr_expense_user,product.product.hr.expense.user,product.model_product_product,base.group_hr_user,1,1,1,1
access_product_template_hr_expense_user,product.template.hr.expense.user,product.model_product_template,base.group_hr_user,1,1,1,1
access_product_uom_hr_expense_user,product.uom.hr.expense.user,product.model_product_uom,base.group_hr_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_expense_user hr.expense.user model_hr_expense base.group_hr_user 1 1 1 1
3 access_hr_expense_employee hr.expense.employee model_hr_expense base.group_user 1 1 1 1
4 access_product_product_hr_expense_user access_hr_expense_sheet_user product.product.hr.expense.user hr.expense.sheet.user product.model_product_product model_hr_expense_sheet base.group_hr_user 1 1 1 1
5 access_hr_expense_sheet_employee hr.expense.sheet.employee model_hr_expense_sheet base.group_user 1 1 1 1
6 access_product_product_hr_expense_user product.product.hr.expense.user product.model_product_product base.group_hr_user 1 1 1 1
7 access_product_template_hr_expense_user product.template.hr.expense.user product.model_product_template base.group_hr_user 1 1 1 1
8 access_product_uom_hr_expense_user product.uom.hr.expense.user product.model_product_uom base.group_hr_user 1 1 1 1
9 access_account_journal_user account.journal.user account.model_account_journal base.group_hr_user 1 1 1 1
+1
View File
@@ -0,0 +1 @@
from . import test_expenses
+74
View File
@@ -0,0 +1,74 @@
from openerp.tests.common import TransactionCase
from openerp import workflow
class TestCheckJournalEntry(TransactionCase):
"""
Check journal entries when the expense product is having tax which is tax included.
"""
def setUp(self):
super(TestCheckJournalEntry, self).setUp()
cr, uid = self.cr, self.uid
self.expense_obj = self.registry('hr.expense.sheet')
self.exp_line_obj = self.registry('hr.expense')
self.product_obj = self.registry('product.product')
self.tax_obj = self.registry('account.tax')
_, self.product_id = self.registry("ir.model.data").get_object_reference(cr, uid, "hr_expense", "air_ticket")
self.employee = self.registry("ir.model.data").xmlid_to_object(cr, uid, "hr.employee_mit")
self.tax_id = self.tax_obj.create(cr, uid, {
'name': 'Expense 10%',
'amount': 10,
'amount_type': 'percent',
'type_tax_use': 'purchase',
'price_include': True,
})
self.product_obj.write(cr, uid, self.product_id, {'supplier_taxes_id': [(6, 0, [self.tax_id])]})
# Create payable account for the expense
user_type_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'data_account_type_payable')[1]
account_payable_id = self.registry('account.account').create(cr, uid, {'code': 'X1111', 'name': 'HR Expense - Test Payable Account', 'user_type_id': user_type_id, 'reconcile': True})
self.employee.address_home_id.property_account_payable_id = account_payable_id
# Create expenses account for the expense
user_type_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'data_account_type_expenses')[1]
account_expense_id = self.registry('account.account').create(cr, uid, {'code': 'X2120', 'name': 'HR Expense - Test Purchase Account', 'user_type_id': user_type_id})
# Assign it to the air ticket product
self.registry('product.product').write(cr, uid, self.product_id, {'property_account_expense_id': account_expense_id})
# Create Sale Journal
company_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company')[1]
self.registry('account.journal').create(cr, uid, {'name': 'Purchase Journal - Test', 'code': 'HRTPJ', 'type': 'purchase', 'company_id': company_id})
self.expense_id = self.expense_obj.create(cr, uid, {
'name': 'Expense for John Smith',
'employee_id': self.employee.id,
})
self.expense_line = self.exp_line_obj.create(cr, uid, {
'name': 'Car Travel Expenses',
'employee_id': self.employee.id,
'product_id': self.product_id,
'unit_amount': 700.00,
'tax_ids': [(6, 0, [self.tax_id])],
'sheet_id': self.expense_id,
})
def test_journal_entry(self):
cr, uid = self.cr, self.uid
self.expense = self.expense_obj.browse(cr, uid, self.expense_id)
#Submitted to Manager
self.assertEquals(self.expense.state, 'submit', 'Expense is not in Reported state')
#Approve
self.expense.approve_expense_sheets()
self.assertEquals(self.expense.state, 'approve', 'Expense is not in Approved state')
#Create Expense Entries
self.expense.action_sheet_move_create()
self.assertEquals(self.expense.state, 'post', 'Expense is not in Waiting Payment state')
self.assertTrue(self.expense.account_move_id.id, 'Expense Journal Entry is not created')
# [(line.debit, line.credit, line.tax_line_id.id) for line in self.expense.expense_line_ids.account_move_id.line_ids]
# should git this result [(0.0, 700.0, False), (63.64, 0.0, 179), (636.36, 0.0, False)]
for line in self.expense.account_move_id.line_ids:
if line.credit:
self.assertAlmostEquals(line.credit, 700.00)
else:
if not line.tax_line_id.id == self.tax_id:
self.assertAlmostEquals(line.debit, 636.36)
else:
self.assertAlmostEquals(line.debit, 63.64)
+9 -9
View File
@@ -7,18 +7,18 @@
<field name="arch" type="xml">
<data>
<xpath expr="//templates" position="before">
<field name="expense_to_approve_count"/>
<field name="expense_sheets_to_approve_count"/>
</xpath>
<xpath expr="//div[contains(@class, 'o_kanban_primary_right')]" position="inside">
<div t-if="record.expense_to_approve_count.raw_value > 0" class="row">
<div t-if="record.expense_sheets_to_approve_count.raw_value > 0" class="row">
<div class="col-xs-9">
<a name="%(hr_expense_action_from_department)d" type="action">
Expenses
<a name="%(action_hr_expense_sheet_department_to_approve)d" type="action">
Expense Reports
</a>
</div>
<div class="col-xs-3">
<t t-esc="record.expense_to_approve_count.raw_value"/>
<t t-esc="record.expense_sheets_to_approve_count.raw_value"/>
</div>
</div>
</xpath>
@@ -26,8 +26,8 @@
<xpath expr="//div[contains(@class, 'o_kanban_manage_to_approve')]" position="inside">
<div class="row">
<div class="col-xs-12 text-left">
<a name="%(hr_expense_action_from_department)d" type="action">
<t t-esc="record.expense_to_approve_count.raw_value or 0"/> Expenses
<a name="%(action_hr_expense_sheet_department_to_approve)d" type="action">
<t t-esc="record.expense_sheets_to_approve_count.raw_value or 0"/> Expense Reports
</a>
</div>
</div>
@@ -36,8 +36,8 @@
<xpath expr="//div[contains(@class, 'o_kanban_manage_reports')]" position="inside">
<div class="row">
<div class="col-xs-12 text-left">
<a name="%(action_hr_expense_report_filtered)d" type="action">
Expenses
<a name="%(action_hr_expense_sheet_department_filtered)d" type="action">
Expense Reports
</a>
</div>
</div>
+451 -127
View File
@@ -1,41 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Expenses -->
<record id="view_expenses_tree" model="ir.ui.view">
<field name="name">hr.expense.tree</field>
<field name="model">hr.expense</field>
<field name="arch" type="xml">
<tree string="Expenses" decoration-warning="state=='draft'" decoration-bf="message_unread == True">
<tree string="Expenses" decoration-bf="message_unread == True">
<field name="currency_id" invisible="True"/>
<field name="attachment_number" invisible="True"/>
<field name="date"/>
<field name="name"/>
<field name="employee_id"/>
<field name="sheet_id" invisible="not context.get('show_report', False)"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="total_amount" sum="Total Amount" widget="monetary"/>
<field name="total_amount" sum="Total Amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="state"/>
<field name="message_unread" invisible="1"/>
<button name="submit_expenses" states="draft" string="Submit to Manager" type="object" icon="fa-check"/>
<button name="approve_expenses" states="submit" string="Click here to approve" type="object" icon="fa-check" groups="base.group_hr_manager"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="submit" string="Click here to refuse" type="action" icon="fa-times" groups="base.group_hr_manager"/>
</tree>
</field>
</record>
<record id="hr_expense_my_tree" model="ir.ui.view">
<field name="name">my.hr.expense.tree</field>
<field name="model">hr.expense</field>
<field name="arch" type="xml">
<tree string="Expenses" decoration-warning="state=='draft'" decoration-bf="message_unread == True">
<field name="date"/>
<field name="name"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="total_amount" sum="Total Amount" widget="monetary"/>
<field name="state"/>
<field name="message_unread" invisible="1"/>
<button name="submit_expenses" states="draft" string="Submit to Manager" type="object" icon="fa-check"/>
<button name="approve_expenses" states="submit" string="Click here to approve" type="object" icon="fa-check" groups="base.group_hr_manager"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="submit" string="Click here to refuse" type="action" icon="fa-times" groups="base.group_hr_manager"/>
<button name="action_get_attachment_view" string="View Attached Documents" type="object" icon="fa-paperclip" attrs="{'invisible': [('attachment_number', '=', 0)]}"/>
</tree>
</field>
</record>
@@ -48,12 +28,8 @@
<form string="Expenses">
<header>
<button name="submit_expenses" states="draft" string="Submit to Manager" type="object" class="oe_highlight"/>
<button name="approve_expenses" states="submit" string="Approve" type="object" groups="base.group_hr_user" class="oe_highlight"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="submit" string="Refuse" type="action" groups="base.group_hr_user" />
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="approve" string="Refuse" type="action" groups="account.group_account_user" />
<button name="reset_expenses" states="cancel" string="Set to Draft" type="object"/>
<button name="action_move_create" states="approve" string="Post Journal Entries" type="object" groups="account.group_account_user" class="oe_highlight"/>
<field name="state" widget="statusbar" statusbar_visible="draft,submit,approve,post,done" statusbar_colors='{"submit":"blue","cancel":"red"}'/>
<field name="state" widget="statusbar" statusbar_visible="draft,reported,done" statusbar_colors='{"reported":"blue"}'/>
<button name="view_sheet" type="object" string="View Report" class="oe_highlight" attrs="{'invisible': [('state', '=','draft')]}"/>
</header>
<sheet>
<div class="oe_button_box">
@@ -67,7 +43,7 @@
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" placeholder="e.g. Business lunch with X"/>
<field name="name" placeholder="e.g. Business Trip NY"/>
</h1>
</div>
<group>
@@ -79,27 +55,21 @@
<field name="quantity" class="oe_inline"/>
<field name="product_uom_id" widget="selection" class="oe_inline" groups="product.group_uom"/>
</div>
<field name="tax_ids" widget="many2many_tags" groups="account.group_account_user"/>
<field name="tax_ids" domain="[('type_tax_use', '=', 'purchase')]" widget="many2many_tags" groups="account.group_no_one"/>
</group><group>
<field name="reference"/>
<field name="date"/>
<field name="accounting_date"/>
<field name="account_id"/>
<field name="employee_id"/>
<field name="sheet_id" invisible="1"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="department_id" invisible="1"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="journal_id" domain="[('type', '=', 'purchase')]" groups="account.group_account_user"
options="{'no_open': True, 'no_create': True}"/>
<field name="account_id" groups="account.group_account_user"/>
<field name="account_move_id" domain="[('type', '=', 'purchase'])]" readonly="1"/>
</group><group>
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
<label for="payment_mode"/>
<div>
<field name="payment_mode" widget="radio"/>
<field name="bank_journal_id"
attrs="{'invisible': [('payment_mode', '=', 'own_account')], 'required': [('payment_mode', '=', 'company_account')]}" domain="[('type', 'in', ['cash', 'bank'])]"
widget="selection"/>
</div>
</group>
@@ -187,21 +157,21 @@
<field name="name" string="Expenses"/>
<field name="date"/>
<field name="employee_id"/>
<field name="department_id" string="Department" context="{'invisible_department': False}"/>
<field name="product_id"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<separator />
<filter domain="[('state', '=', 'draft')]" string="New" help="New Expense"/>
<filter domain="[('state', '=', 'submit')]" string="To Approve" name="submitted" help="Confirmed Expenses"/>
<filter domain="['|', ('state', '=', 'approve'), ('state', '=', 'post')]" string="To Pay" name="approved" help="Expenses to Invoice"/>
<filter domain="[('state', '=', 'draft')]" string="To Report" name="to_report" help="New Expense"/>
<filter domain="[('state', '=', 'report')]" string="Reported" name="submitted" help="Confirmed Expenses"/>
<filter domain="[('state', '=', 'refused')]" string="Refused" name="refused" help="Refused Expenses"/>
<filter domain="[('state', '!=', 'cancel')]" string="Not Refused" name="uncancelled" help="Actual expense sheets, not the refused ones"/>
<separator />
<filter string="My Team Expenses" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="base.group_hr_manager" help="Expenses of Your Team Member"/>
<filter string="My Expenses" domain="[('employee_id.user_id', '=', uid)]"/>
<separator />
<filter string="New Mail" name="message_unread" domain="[('message_unread', '=', True)]"/>
<group expand="0" string="Group By">
<filter string="Report" domain="[]" context="{'group_by': 'sheet_id'}"/>
<filter string="Employee" domain="[]" context="{'group_by': 'employee_id'}"/>
<filter string="Department" domain="[]" context="{'group_by': 'department_id'}" groups="hr.group_multi_departments"/>
<filter string="Company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
<filter string="Product" domain="[]" context="{'group_by': 'product_id'}"/>
<filter string="Analytic Account" domain="[]" context="{'group_by': 'analytic_account_id'}" groups="analytic.group_analytic_accounting"/>
@@ -214,12 +184,43 @@
</field>
</record>
<record id="expense_all" model="ir.actions.act_window">
<record id="hr_expense_actions_all" model="ir.actions.act_window">
<field name="name">All Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="search_view_id" ref="view_hr_expense_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create new expenses.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="hr_expense_actions_my_all" model="ir.actions.act_window">
<field name="name">My Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="search_view_id" ref="view_hr_expense_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="context">{'show_report': True}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create new expenses.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="hr_expense_actions_my_unsubmitted" model="ir.actions.act_window">
<field name="name">My Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="search_view_id" ref="view_hr_expense_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="context">{'search_default_to_report': 1}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create new expenses.
@@ -232,22 +233,22 @@
<record id="hr_expense_tree_action" model="ir.actions.act_window.view">
<field name="sequence" eval="0"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="hr_expense_my_tree"/>
<field name="act_window_id" ref="expense_all"/>
<field name="view_id" ref="view_expenses_tree"/>
<field name="act_window_id" ref="hr_expense_actions_all"/>
</record>
<record id="hr_expense_kanban_action" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="hr_expense_kanban_view"/>
<field name="act_window_id" ref="expense_all"/>
<field name="act_window_id" ref="hr_expense_actions_all"/>
</record>
<record id="hr_expense_form_action" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="hr_expense_form_view"/>
<field name="act_window_id" ref="expense_all"/>
<field name="act_window_id" ref="hr_expense_actions_all"/>
</record>
<record id="view_product_hr_expense_form" model="ir.ui.view">
@@ -293,14 +294,318 @@
</field>
</record>
<record id="action_request_approve_expense" model="ir.actions.act_window">
<field name="name">Expenses to Approve</field>
<record id="action_approved_expense" model="ir.actions.act_window">
<field name="name">Unsubmitted Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="context">{'search_default_submitted': 1, 'needaction_menu_ref': 'hr_expense.menu_expense_all'}</field>
<field name="search_view_id" ref="view_hr_expense_filter"/>
<field name="view_id" ref="view_expenses_tree"/>
<field name="domain">[]</field>
<field name="context">{"search_default_unsubmitted": 1}</field>
<field name="help" type="html">
<p>
From here the accountant will be able to approve as well as refuse the expenses which are verified by the HR Manager.
</p>
</field>
</record>
<record id="hr_expense_action" model="ir.actions.act_window">
<field name="name">Expenses Analysis</field>
<field name="res_model">hr.expense</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="context">{
'search_default_uncancelled': 1,
}
</field>
</record>
<record id="view_hr_expense_sheet_tree" model="ir.ui.view">
<field name="name">hr.expense.sheet.tree</field>
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<tree string="Expense Reports" decoration-warning="state=='draft'" decoration-bf="message_unread == True">
<field name="accounting_date"/>
<field name="name" string="Expense Sheet"/>
<field name="employee_id"/>
<field name="total_amount" sum="Total Amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="state"/>
<field name="message_unread" invisible="1"/>
</tree>
</field>
</record>
<record id="view_hr_expense_sheet_form" model="ir.ui.view">
<field name="name">hr.expense.sheet.form</field>
<field name="model">hr.expense.sheet</field>
<field eval="25" name="priority"/>
<field name="arch" type="xml">
<form string="Expense Reports">
<header>
<button name="approve_expense_sheets" states="submit" string="Approve" type="object" groups="base.group_hr_user" class="oe_highlight"/>
<button name="action_sheet_move_create" states="approve" string="Post Journal Entries" type="object" groups="account.group_account_user" class="oe_highlight"/>
<button name="%(hr_expense.hr_expense_register_payment_wizard_action)d" type="action" string="Register Payment" class="oe_highlight" attrs="{'invisible': [('state', '!=', 'post')]}" context="{'default_amount': total_amount, 'partner_id': address_id}" groups="account.group_account_user"/>
<button name="reset_expense_sheets" states="cancel" string="Resubmit" type="object"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="submit" string="Refuse" type="action" groups="base.group_hr_user" />
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="approve" string="Refuse" type="action" groups="account.group_account_user"/>
<field name="state" widget="statusbar" statusbar_visible="draft,submit,approve,post,done" statusbar_colors='{"submit":"blue","cancel":"red"}'/>
</header>
<sheet>
<div class="oe_button_box">
<button name="action_get_attachment_view"
class="oe_stat_button"
icon="fa-book"
type="object">
<field name="attachment_number" widget="statinfo" string="Documents"/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Business lunch with X"/>
</h1>
</div>
<group>
<group>
<field name="employee_id"/>
<field name="payment_mode"/>
<field name="responsible_id" invisible="1"/>
<field name="total_amount" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="address_id" invisible="1"/>
<field name="department_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<field name="journal_id" domain="[('type', '=', 'purchase')]" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'own_account')]}"/>
<field name="bank_journal_id" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'company_account')]}"/>
<field name="account_id" groups="account.group_account_user"/>
<field name="accounting_date" groups="account.group_account_user" attrs="{'invisible': [('state', 'not in', ['approve', 'post', 'done'])]}"/>
<field name="account_move_id" attrs="{'invisible': [('state', 'not in', ['post', 'done'])]}" groups="account.group_account_user"/>
</group>
</group>
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft')]" options="{'no_delete': True}">
<tree>
<field name="date"/>
<field name="name"/>
<field name="state" invisible="1"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="total_amount" sum="Total Amount" widget="monetary"/>
<field name="message_unread" invisible="1"/>
<field name="attachment_number" invisible="1"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="attachment_number" string=" "/>
<button name="action_get_attachment_view" string="View Attachments" type="object" icon="fa-paperclip"/>
</tree>
</field>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="view_hr_expense_sheet_kanban" model="ir.ui.view">
<field name="name">hr.expense.sheet.kanban</field>
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="employee_id"/>
<field name="total_amount"/>
<field name="accounting_date"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="row">
<div class="col-xs-6">
<strong><span><t t-esc="record.name.value"/></span></strong>
</div>
<div class="col-xs-6">
<strong><span class="pull-right text-right"><t t-esc="record.total_amount.value"/></span></strong>
</div>
</div>
<div class="row">
<div class="col-xs-6 text-muted">
<span><t t-esc="record.employee_id.value"/> <t t-esc="record.date.value"/></span>
</div>
<div class="col-xs-6">
<span class="pull-right text-right">
<field name="state" widget="kanban_label_selection" options="{'classes': {'draft': 'default', 'submit': 'default', 'cancel': 'danger', 'post': 'warning',
'done': 'success'}}"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_hr_expense_sheet_pivot" model="ir.ui.view">
<field name="name">hr.expense.sheet.pivot</field>
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<pivot string="Expenses Analysis" disable_linking="True">
<field name="employee_id" type="row"/>
<field name="accounting_date" interval="month" type="col"/>
<field name="total_amount" type="measure"/>
</pivot>
</field>
</record>
<record id="view_hr_expense_sheet_graph" model="ir.ui.view">
<field name="name">hr.expense.sheet.graph</field>
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<graph string="Expenses Analysis">
<field name="employee_id" type="col"/>
<field name="accounting_date" interval="month" type="row"/>
<field name="total_amount" type="measure"/>
</graph>
</field>
</record>
<record id="view_hr_expense_sheet_filter" model="ir.ui.view">
<field name="name">hr.expense.sheet.filter</field>
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<search string="Expense">
<field name="name" string="Expenses"/>
<field name="accounting_date"/>
<field name="state"/>
<field name="employee_id"/>
<field name="department_id" string="Department"/>
<separator />
<filter domain="[('state', '=', 'submit')]" string="To Approve" name="submitted" help="Confirmed Expenses"/>
<filter domain="[('state', '=', 'approve')]" string="To Post" name="to_post" help="Approved Expenses"/>
<filter domain="['|', ('state', '=', 'approve'), ('state', '=', 'post')]" string="To Pay" name="approved" help="Expenses to Invoice"/>
<separator/>
<filter string="My Team Reports" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="base.group_hr_manager" help="Expenses of Your Team Member"/>
<filter string="My Reports" domain="[('employee_id.user_id', '=', uid)]"/>
<separator />
<filter string="New Mail" name="message_unread" domain="[('message_unread', '=', True)]"/>
<group expand="0" string="Group By">
<filter string="Employee" domain="[]" context="{'group_by': 'employee_id'}"/>
<filter string="Department" domain="[]" context="{'group_by': 'department_id'}" groups="hr.group_multi_departments"/>
<filter string="Company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
<filter string="Expenses Month" domain="[]" context="{'group_by': 'accounting_date'}" help="Expenses by Month"/>
</group>
<separator />
<filter domain="[('employee_id.active', '=', False)]" string="Former Employees" name="inactive" groups="base.group_hr_user,base.group_hr_manager"/>
</search>
</field>
</record>
<record id="action_hr_expense_sheet_my_to_approve" model="ir.actions.act_window">
<field name="name">My 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="view_hr_expense_sheet_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="context">{'search_default_submitted': 1}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new expense report.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_my_all" model="ir.actions.act_window">
<field name="name">My 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="view_hr_expense_sheet_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new expense report.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_all_to_approve" model="ir.actions.act_window">
<field name="name">My 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="view_hr_expense_sheet_filter"/>
<field name="domain">[]</field>
<field name="context">{'search_default_submitted': 1}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new expense report.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_all_to_post" model="ir.actions.act_window">
<field name="name">My 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="view_hr_expense_sheet_filter"/>
<field name="domain">[]</field>
<field name="context">{'search_default_to_post': 1}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new expense report.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_all_to_pay" model="ir.actions.act_window">
<field name="name">My 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="view_hr_expense_sheet_filter"/>
<field name="domain">[]</field>
<field name="context">{'search_default_approved': 1}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new expense report.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_all_all" model="ir.actions.act_window">
<field name="name">My 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="view_hr_expense_sheet_filter"/>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new expense report.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_request_approve_expense_sheet" model="ir.actions.act_window">
<field name="name">Reports to Approve</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="context">{'search_default_submitted': 1, 'needaction_menu_ref': 'hr_expense.menu_hr_expense_all'}</field>
<field name="search_view_id" ref="view_hr_expense_sheet_filter"/>
<field name="view_id" ref="view_hr_expense_sheet_tree"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create new expenses.
@@ -310,88 +615,107 @@
</field>
</record>
<record id="hr_expense_submit_action_server" model="ir.actions.server">
<field name="name">Submit Expenses</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="state">code</field>
<field name="code">
self.submit_expenses(cr, uid, context.get('active_ids', []), context=context)
</field>
</record>
<record id="action_hr_expense_submit" model="ir.values">
<field name="name">action_hr_expense_submit</field>
<field name="action_id" ref="hr_expense_submit_action_server" />
<field name="value" eval="'ir.actions.server,' + str(ref('hr_expense_submit_action_server'))" />
<field name="key">action</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="model">hr.expense</field>
<field name="key2">client_action_multi</field>
</record>
<record id="hr_expense_entry_action_server" model="ir.actions.server">
<field name="name">Generate Accounting Entries</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="state">code</field>
<field name="code">
self.action_move_create(cr, uid, context.get('active_ids', []), context=context)
</field>
</record>
<record id="action_hr_expense_account_entry" model="ir.values">
<field name="name">action_hr_expense_account_entry</field>
<field name="action_id" ref="hr_expense_entry_action_server" />
<field name="value" eval="'ir.actions.server,' + str(ref('hr_expense_entry_action_server'))" />
<field name="key">action</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="model">hr.expense</field>
<field name="key2">client_action_multi</field>
</record>
<menuitem id="menu_expense_approved" parent="account.menu_finance_payables"
action="action_approved_expense" sequence="15"/>
<menuitem id="menu_hr_expense_root" name="Expenses" sequence="100" web_icon="hr_expense,static/description/icon.png"/>
<menuitem id="menu_hr_expense_configuration" name="Configuration" parent="menu_hr_expense_root"
sequence="100"/>
<menuitem id="menu_hr_product" name="Expense Products" parent="menu_hr_expense_configuration"
action="hr_expense_product" groups="base.group_hr_manager"/>
<menuitem id="menu_expense_all" action="expense_all" name="My Expenses" parent="menu_hr_expense_root"/>
<menuitem id="menu_expense_to_approve" action="action_request_approve_expense" name="To Approve" parent="menu_hr_expense_root" groups="base.group_hr_user"/>
<record id="hr_expense_action_from_department" model="ir.actions.act_window">
<field name="name">Expenses to Approve</field>
<field name="res_model">hr.expense</field>
<record id="action_request_to_post_expense_sheet" model="ir.actions.act_window">
<field name="name">Reports to Post</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="search_view_id" ref="view_hr_expense_filter"/>
<field name="context">{'search_default_to_post': 1, 'needaction_menu_ref': 'hr_expense.menu_hr_expense_all'}</field>
<field name="search_view_id" ref="view_hr_expense_sheet_filter"/>
<field name="view_id" ref="view_hr_expense_sheet_tree"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create new expenses.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_request_to_pay_expense_sheet" model="ir.actions.act_window">
<field name="name">Reports to Pay</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="context">{'search_default_approved': 1, 'needaction_menu_ref': 'hr_expense.menu_hr_expense_all'}</field>
<field name="search_view_id" ref="view_hr_expense_sheet_filter"/>
<field name="view_id" ref="view_hr_expense_sheet_tree"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create new expenses.
</p><p>
Once you have created your expense, submit it to your manager who will validate it.
</p>
</field>
</record>
<record id="action_hr_expense_sheet_department_to_approve" model="ir.actions.act_window">
<field name="name">Expense Reports to Approve</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="search_view_id" ref="view_hr_expense_sheet_filter"/>
<field name="context">{
'search_default_submitted': 1,
'search_default_department_id': [active_id],
'default_department_id': active_id
}
</field>
</record>
<record id="action_hr_expense_sheet_department_filtered" model="ir.actions.act_window">
<field name="name">Expense Reports Analysis</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="context">{
'search_default_department_id': [active_id],
'default_department_id': active_id}
</field>
</record>
<record id="hr_expense_action" model="ir.actions.act_window">
<field name="name">Expenses Analysis</field>
<field name="res_model">hr.expense</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph,tree,form,kanban</field>
<record id="hr_expense_submit_action_server" model="ir.actions.server">
<field name="name">Submit To Manager</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="state">code</field>
<field name="code">
if context.get('active_model') == 'hr.expense' and context.get('active_ids'):
action = env['hr.expense'].browse(context['active_ids']).submit_expenses()
</field>
</record>
<menuitem id="menu_hr_expense" name="Reports" sequence="99" parent="menu_hr_expense_root" action="hr_expense_action"/>
<record id="action_hr_expense_submit" model="ir.values">
<field name="name">Submit To Manager</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="model">hr.expense</field>
<field name="key2">client_action_multi</field>
<field eval="'ir.actions.server,%d'%hr_expense_submit_action_server" name="value"/>
</record>
<menuitem id="menu_hr_expense_root" name="Expenses" sequence="100" web_icon="hr_expense,static/description/icon.png"/>
<record id="action_hr_expense_report_filtered" model="ir.actions.act_window">
<field name="name">Expenses Analysis</field>
<field name="res_model">hr.expense</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="context">{
'search_default_department_id': [active_id],
'default_department_id': active_id}
</field>
</record>
<menuitem id="menu_hr_expense_my_expenses" name="My Expenses" sequence="10" parent="menu_hr_expense_root"/>
<menuitem id="menu_hr_expense_my_unsubmitted" name="Unsubmitted Expenses" sequence="1" parent="menu_hr_expense_my_expenses" action="hr_expense_actions_my_unsubmitted"/>
<menuitem id="menu_hr_expense_my_all" name="All Expenses" sequence="2" parent="menu_hr_expense_my_expenses"
action="hr_expense_actions_my_all"/>
<menuitem id="menu_hr_expense_sheet_my_reports" name="My Reports" sequence="15" parent="menu_hr_expense_root"/>
<menuitem id="menu_hr_expense_sheet_my_to_approve" name="To Approve" sequence="1" parent="menu_hr_expense_sheet_my_reports" action="action_hr_expense_sheet_my_to_approve"/>
<menuitem id="menu_hr_expense_sheet_my_all" name="All Reports" sequence="2" parent="menu_hr_expense_sheet_my_reports" action="action_hr_expense_sheet_my_all"/>
<menuitem id="menu_hr_expense_sheet_all" name="All Reports" sequence="20" parent="menu_hr_expense_root"/>
<menuitem id="menu_hr_expense_sheet_all_to_approve" name="To Approve" sequence="1" parent="menu_hr_expense_sheet_all"
action="action_hr_expense_sheet_all_to_approve" groups="base.group_hr_user"/>
<menuitem id="menu_hr_expense_sheet_all_to_post" name="To Post" sequence="2" parent="menu_hr_expense_sheet_all"
action="action_hr_expense_sheet_all_to_post" groups="base.group_hr_manager"/>
<menuitem id="menu_hr_expense_sheet_all_to_pay" name="To Pay" sequence="3" parent="menu_hr_expense_sheet_all"
action="action_hr_expense_sheet_all_to_pay" groups="base.group_hr_manager"/>
<menuitem id="menu_hr_expense_sheet_all_all" name="All" sequence="4" parent="menu_hr_expense_sheet_all"
action="action_hr_expense_sheet_all_all" groups="base.group_hr_manager"/>
<menuitem id="menu_hr_expense_configuration" name="Configuration" parent="menu_hr_expense_root"
sequence="100"/>
<menuitem id="menu_hr_product" name="Expense Products" parent="menu_hr_expense_configuration"
action="hr_expense_product" groups="base.group_hr_manager" sequence="10"/>
</odoo>
+1
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import hr_expense_refuse_reason
import hr_expense_register_payment
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import api, fields, models
from odoo import api, fields, models
class HrExpenseRefuseWizard(models.TransientModel):
@@ -17,6 +17,6 @@ class HrExpenseRefuseWizard(models.TransientModel):
context = dict(self._context or {})
active_ids = context.get('active_ids', [])
expense = self.env['hr.expense'].browse(active_ids)
expense.refuse_expenses(self.description)
expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
expense_sheet.refuse_expenses(self.description)
return {'type': 'ir.actions.act_window_close'}
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from openerp.exceptions import ValidationError
class HrExpenseRegisterPaymentWizard(models.TransientModel):
_name = "hr.expense.register.payment.wizard"
_description = "Hr Expense Register Payment wizard"
@api.model
def _default_partner_id(self):
context = dict(self._context or {})
active_ids = context.get('active_ids', [])
expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
return expense_sheet.address_id.id or expense_sheet.employee_id.id and expense_sheet.employee_id.address_home_id.id
partner_id = fields.Many2one('res.partner', string='Partner', required=True, default=_default_partner_id)
journal_id = fields.Many2one('account.journal', string='Payment Method', required=True, domain=[('type', 'in', ('bank', 'cash'))])
company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', readonly=True, required=True)
payment_method_id = fields.Many2one('account.payment.method', string='Payment Type', required=True)
amount = fields.Monetary(string='Payment Amount', required=True)
currency_id = fields.Many2one('res.currency', string='Currency', required=True, default=lambda self: self.env.user.company_id.currency_id)
payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True)
communication = fields.Char(string='Memo')
hide_payment_method = fields.Boolean(compute='_compute_hide_payment_method',
help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'")
@api.one
@api.constrains('amount')
def _check_amount(self):
if not self.amount > 0.0:
raise ValidationError('The payment amount must be strictly positive.')
@api.one
@api.depends('journal_id')
def _compute_hide_payment_method(self):
if not self.journal_id:
self.hide_payment_method = True
return
journal_payment_methods = self.journal_id.outbound_payment_method_ids
self.hide_payment_method = len(journal_payment_methods) == 1 and journal_payment_methods[0].code == 'manual'
@api.onchange('journal_id')
def _onchange_journal(self):
if self.journal_id:
# Set default payment method (we consider the first to be the default one)
payment_methods = self.journal_id.outbound_payment_method_ids
self.payment_method_id = payment_methods and payment_methods[0] or False
# Set payment method domain (restrict to methods enabled for the journal and to selected payment type)
return {'domain': {'payment_method_id': [('payment_type', '=', 'outbound'), ('id', 'in', payment_methods.ids)]}}
return {}
@api.multi
def expense_post_payment(self):
self.ensure_one()
context = dict(self._context or {})
active_ids = context.get('active_ids', [])
expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
# Create payment and post it
payment = self.env['account.payment'].create({
'partner_type': 'supplier',
'payment_type': 'outbound',
'partner_id': self.partner_id.id,
'journal_id': self.journal_id.id,
'company_id': self.company_id.id,
'payment_method_id': self.payment_method_id.id,
'amount': self.amount,
'currency_id': self.currency_id.id,
'payment_date': self.payment_date,
'communication': self.communication
})
payment.post()
# Log the payment in the chatter
body = (_("A payment of %s %s with the reference %s related to your expense %s has been made.") % (payment.amount, payment.currency_id.symbol, payment.name, expense_sheet.name))
expense_sheet.message_post(body=body)
return {'type': 'ir.actions.act_window_close'}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_expense_register_payment_view_form" model="ir.ui.view">
<field name="name">hr.expense.register.payment.wizard.form</field>
<field name="model">hr.expense.register.payment.wizard</field>
<field name="arch" type="xml">
<form string="Register Payment">
<sheet>
<field name="id" invisible="1"/>
<div class="oe_title">
<h1>Draft Payment</h1>
</div>
<group>
<group>
<field name="partner_id" required="1" context="{'default_is_company': True, 'default_supplier': True}"/>
<field name="journal_id" widget="selection"/>
<field name="hide_payment_method" invisible="1"/>
<field name="payment_method_id" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/>
<label for="amount"/>
<div name="amount_div" class="o_row">
<field name="amount"/>
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency"/>
</div>
</group>
<group>
<field name="payment_date"/>
<field name="communication"/>
</group>
</group>
</sheet>
<footer>
<button string='Validate' name="expense_post_payment" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Expenses -->
<record id="hr_expense_register_payment_wizard_action" model="ir.actions.act_window">
<field name="name">Register Payment</field>
<field name="res_model">hr.expense.register.payment.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="hr_expense_register_payment_view_form"/>
<field name="target">new</field>
<field name="context">{'default_payment_type': 'inbound'}</field>
<field name="domain">[('partner_type', '=', 'customer')]</field>
</record>
</odoo>
+18 -10
View File
@@ -27,20 +27,24 @@ class TestSaleExpense(TestSale):
account_payable = self.env['account.account'].create({'code': 'X1111', 'name': 'HR Expense - Test Payable Account', 'user_type_id': self.env.ref('account.data_account_type_payable').id, 'reconcile': True})
employee = self.env['hr.employee'].create({'name': 'Test employee', 'user_id': self.user.id, 'address_home_id': self.user.partner_id.id})
self.user.partner_id.property_account_payable_id = account_payable.id
# Submit to Manager
sheet = self.env['hr.expense.sheet'].create({
'name': 'Expense for John Smith',
'employee_id': employee.id,
'journal_id': journal.id,
})
exp = self.env['hr.expense'].create({
'name': 'Air Travel',
'product_id': prod_exp_1.id,
'analytic_account_id': so.project_id.id,
'unit_amount': 621.54,
'journal_id': journal.id,
'employee_id': employee.id,
'sheet_id': sheet.id
})
# Submit to Manager
exp.submit_expenses()
# Approve
exp.approve_expenses()
sheet.approve_expense_sheets()
# Create Expense Entries
exp.action_move_create()
sheet.action_sheet_move_create()
# expense should now be in sales order
self.assertTrue(prod_exp_1 in map(lambda so: so.product_id, so.order_line), 'Sale Expense: expense product should be in so')
sol = so.order_line.filtered(lambda sol: sol.product_id.id == prod_exp_1.id)
@@ -50,6 +54,12 @@ class TestSaleExpense(TestSale):
# create some expense and validate it (expense at sales price)
init_price = so.amount_total
prod_exp_2 = self.env.ref('hr_expense.car_travel')
# Submit to Manager
sheet = self.env['hr.expense.sheet'].create({
'name': 'Expense for John Smith',
'employee_id': employee.id,
'journal_id': journal.id,
})
exp = self.env['hr.expense'].create({
'name': 'Car Travel',
'product_id': prod_exp_2.id,
@@ -57,15 +67,13 @@ class TestSaleExpense(TestSale):
'product_uom_id': self.env.ref('product.product_uom_km').id,
'unit_amount': 0.15,
'quantity': 100,
'journal_id': journal.id,
'employee_id': employee.id,
'sheet_id': sheet.id
})
# Submit to Manager
exp.submit_expenses()
# Approve
exp.approve_expenses()
sheet.approve_expense_sheets()
# Create Expense Entries
exp.action_move_create()
sheet.action_sheet_move_create()
# expense should now be in sales order
self.assertTrue(prod_exp_2 in map(lambda so: so.product_id, so.order_line), 'Sale Expense: expense product should be in so')
sol = so.order_line.filtered(lambda sol: sol.product_id.id == prod_exp_2.id)