[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:
@@ -2,5 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import models
|
||||
import report
|
||||
import wizard
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -0,0 +1 @@
|
||||
from . import test_expenses
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user