hr_expense_account_split/controllers/hr_expense_kiosk_controller.py

192 lines
8.5 KiB
Python

# -*- coding: utf-8 -*-
from odoo import http, fields, _
from odoo.http import request
from odoo.tools.image import image_data_uri
import base64
import json
class HrExpenseKioskController(http.Controller):
def _check_token(self, token):
""" Verify the kiosk token and return the company. """
company = request.env['res.company'].sudo().search([('expense_kiosk_key', '=', token)], limit=1)
if not company:
return False
return company
@http.route('/hr_expense/kiosk/<string:token>', type='http', auth='public', website=True)
def hr_expense_kiosk_mode(self, token, **kwargs):
""" Render the main Kiosk UI using the token. """
company = self._check_token(token)
if not company:
return request.not_found()
return request.render('hr_expense_account_split.hr_expense_kiosk_mode_template', {
'session_info': request.env['ir.http'].get_frontend_session_info(),
'kiosk_token': token,
'company_name': company.name,
'json': json,
})
@http.route('/hr_expense/kiosk_data/<string:token>', type='json', auth='public')
def get_kiosk_data(self, token):
""" Get all employees for selection. """
company = self._check_token(token)
if not company:
return []
employees = request.env['hr.employee'].sudo().search_read(
domain=[('company_id', '=', company.id)],
fields=['id', 'name', 'job_id', 'avatar_128']
)
# Convert avatar to data URI
for emp in employees:
emp['avatar_url'] = image_data_uri(emp['avatar_128']) if emp.get('avatar_128') else '/web/static/img/placeholder.png'
emp['job_name'] = emp.get('job_id')[1] if emp.get('job_id') else ''
# Fetch expense categories (products)
products = request.env['product.product'].sudo().search_read(
domain=[('can_be_expensed', '=', True), ('company_id', 'in', [company.id, False])],
fields=['id', 'display_name', 'image_128']
)
for prod in products:
prod['image_url'] = image_data_uri(prod['image_128']) if prod.get('image_128') else '/web/static/img/placeholder.png'
return {
'employees': employees,
'categories': products,
}
@http.route('/hr_expense/kiosk_validate_pin/<string:token>', type='json', auth='public')
def validate_pin(self, token, employee_id, pin):
""" Validates the 4-digit PIN of the employee. """
if not self._check_token(token):
return {'status': 'error', 'message': _("Invalid access token.")}
employee = request.env['hr.employee'].sudo().browse(employee_id)
if not employee.exists():
return {'status': 'error', 'message': _("Employee not found.")}
if employee.pin == pin:
return {'status': 'ok'}
else:
return {'status': 'error', 'message': _("Incorrect PIN.")}
@http.route('/hr_expense/kiosk_get_pending/<string:token>', type='json', auth='public')
def get_pending(self, token, employee_id):
""" Returns pending realizations for the employee. """
if not self._check_token(token):
return []
return request.env['hr.expense.realization'].sudo().get_pending_realizations(employee_id)
@http.route('/hr_expense/kiosk_get_submitted/<string:token>', type='json', auth='public')
def get_submitted(self, token, employee_id):
""" Returns submitted expense reports for the employee. """
if not self._check_token(token):
return []
sheets = request.env['hr.expense.sheet'].sudo().search([
('employee_id', '=', employee_id),
('state', 'not in', ['draft', 'cancel'])
], order='create_date desc')
result = []
state_selection = dict(request.env['hr.expense.sheet']._fields['state']._description_selection(request.env))
payment_selection = dict(request.env['hr.expense.sheet']._fields['payment_state']._description_selection(request.env))
for sheet in sheets:
result.append({
'id': sheet.id,
'name': sheet.name,
'sequences': sheet.expense_sequences or '',
'date': sheet.create_date.strftime('%Y-%m-%d'),
'total_amount': sheet.currency_id.symbol + " " + "{:,.2f}".format(sheet.total_amount),
'state': state_selection.get(sheet.state),
'state_raw': sheet.state,
'payment_status': payment_selection.get(sheet.payment_state),
'payment_state_raw': sheet.payment_state,
})
return result
@http.route('/hr_expense/kiosk_submit_realization/<string:token>', type='json', auth='public')
def submit_realization(self, token, employee_id, expense_id, lines=None):
""" Creates a realization report from the kiosk. """
if not self._check_token(token):
return {'status': 'error', 'message': _("Invalid access token.")}
try:
expense = request.env['hr.expense'].sudo().browse(expense_id)
if not expense or expense.employee_id.id != employee_id:
return {'status': 'error', 'message': _("Invalid expense selection.")}
if not lines:
return {'status': 'error', 'message': _("No receipt lines provided.")}
realization_vals = {
'expense_id': expense_id,
'description': _("Submitted via Kiosk"),
'line_ids': []
}
for line in lines:
image_base64 = line.get('image')
line_vals = {
'description': line.get('description') or _("Receipt for ") + expense.name,
'amount': float(line.get('amount') or 0.0),
'attachment_id': image_base64.split(',')[-1] if image_base64 else False,
'attachment_name': 'receipt.jpg' if image_base64 else False,
}
realization_vals['line_ids'].append((0, 0, line_vals))
realization = request.env['hr.expense.realization'].sudo().create(realization_vals)
realization.action_confirm()
return {'status': 'ok', 'res_id': realization.id}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/hr_expense/kiosk_submit_new_expense/<string:token>', type='json', auth='public')
def submit_new_expense(self, token, employee_id, product_id, amount, description, payment_mode=None, image_base64=None):
""" Creates a new expense (e.g. out of pocket) from the kiosk. """
if not self._check_token(token):
return {'status': 'error', 'message': _("Invalid access token.")}
try:
employee = request.env['hr.employee'].sudo().browse(employee_id)
# Since product_id could be generic, let's try to find a default expense product
if not product_id:
product = request.env['product.product'].sudo().search([('can_be_expensed', '=', True)], limit=1)
product_id = product.id if product else False
if not product_id:
return {'status': 'error', 'message': _("No expense product found in the system.")}
vals = {
'name': description or _("New Expense"),
'employee_id': employee_id,
'product_id': product_id,
'total_amount': float(amount) if amount else 0.0,
'date': fields.Date.today(),
'payment_mode': payment_mode or 'own_account',
'company_id': employee.company_id.id,
'currency_id': employee.company_id.currency_id.id,
}
expense = request.env['hr.expense'].sudo().create(vals)
if image_base64:
request.env['ir.attachment'].sudo().create({
'name': 'receipt.jpg',
'res_model': 'hr.expense',
'res_id': expense.id,
'datas': image_base64.split(',')[-1],
})
# Use sudo to allow the public user to trigger the workflow
expense.sudo().action_submit_expenses()
if expense.sheet_id:
expense.sheet_id.sudo().action_submit_sheet()
return {'status': 'ok', 'res_id': expense.id}
except Exception as e:
return {'status': 'error', 'message': str(e)}