from odoo import api, fields, models, _, Command from odoo.exceptions import UserError, ValidationError class HrExpenseRealization(models.Model): _name = 'hr.expense.realization' _description = 'Expense Realization' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'date desc, id desc' name = fields.Char(string='Reference', required=True, copy=False, default=lambda self: _('New')) expense_id = fields.Many2one( 'hr.expense', string='Source Expense', required=True, domain="[('payment_mode', '=', 'company_account')]", ondelete='cascade' ) employee_id = fields.Many2one('hr.employee', string='Employee', related='expense_id.employee_id', store=True) company_id = fields.Many2one('res.company', string='Company', related='expense_id.company_id', store=True) currency_id = fields.Many2one('res.currency', string='Currency', related='expense_id.currency_id', store=True) date = fields.Date(string='Date', default=fields.Date.context_today, required=True) description = fields.Text(string='Description') line_ids = fields.One2many('hr.expense.realization.line', 'realization_id', string='Receipt Lines') total_amount = fields.Monetary(string='Total Amount', compute='_compute_total_amount', store=True, currency_field='currency_id') state = fields.Selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('posted', 'Posted') ], string='Status', default='draft', tracking=True) default_counterpart_account_id = fields.Many2one('account.account', string='Default Counterpart Account', tracking=True, groups="account.group_account_invoice") journal_id = fields.Many2one('account.journal', string='Journal', tracking=True, groups="account.group_account_invoice", default=lambda self: self.env['account.journal'].search([('name', '=', 'Realisasi')], limit=1)) move_id = fields.Many2one('account.move', string='Journal Entry', readonly=True, groups="account.group_account_invoice", help="Reference to the first journal entry created.") @api.depends('line_ids.amount') def _compute_total_amount(self): for rec in self: rec.total_amount = sum(rec.line_ids.mapped('amount')) @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code('hr.expense.realization') or _('New') return super().create(vals_list) def action_confirm(self): self.ensure_one() if not self.line_ids: raise UserError(_("Please add at least one receipt line.")) self.state = 'confirmed' if self.expense_id: self.expense_id.write({'receipt_received': True}) # Explicitly trigger recompute of the sheet status if self.expense_id.sheet_id: self.expense_id.sheet_id._compute_receipt_status() def action_apply_default_account(self): self.ensure_one() if not self.default_counterpart_account_id: raise UserError(_("Please set a Default Counterpart Account first.")) for line in self.line_ids: if not line.counterpart_account_id: line.counterpart_account_id = self.default_counterpart_account_id def action_post(self): self.ensure_one() if self.state != 'confirmed': raise UserError(_("Only confirmed realizations can be posted.")) if not self.journal_id: raise UserError(_("Please specify the Journal before posting.")) # Determine the Expense Account product = self.expense_id.product_id.with_company(self.company_id) expense_account = product.property_account_expense_company_id or product.property_account_expense_id if not expense_account: expense_account = self.env['ir.property']._get('property_account_expense_categ_id', 'product.category') if not expense_account: raise UserError(_("No expense account found for the product or its category.")) moves = self.env['account.move'] for line in self.line_ids: if not line.counterpart_account_id: raise UserError(_("Please specify a Counterpart Account for the receipt: %s") % line.description) move_vals = { 'journal_id': self.journal_id.id, 'date': self.date, 'ref': f"Realization: {self.expense_id.name} - {line.description}", 'move_type': 'entry', 'line_ids': [ Command.create({ 'name': f"Realization: {self.expense_id.name} ({line.description})", 'account_id': expense_account.id, 'debit': 0.0, 'credit': line.amount, 'partner_id': self.employee_id.sudo().work_contact_id.id, 'expense_id': self.expense_id.id, }), Command.create({ 'name': f"Realization Counterpart: {line.description}", 'account_id': line.counterpart_account_id.id, 'debit': line.amount, 'credit': 0.0, 'partner_id': self.employee_id.sudo().work_contact_id.id, }), ], } move = self.env['account.move'].create(move_vals) move.action_post() line.move_id = move.id moves |= move self.write({ 'state': 'posted', 'move_id': moves[0].id if moves else False }) class HrExpenseRealizationLine(models.Model): _name = 'hr.expense.realization.line' _description = 'Expense Realization Line' realization_id = fields.Many2one('hr.expense.realization', string='Realization', ondelete='cascade', required=True) currency_id = fields.Many2one('res.currency', related='realization_id.currency_id') description = fields.Char(string='Description', required=True) amount = fields.Monetary(string='Amount', required=True, currency_field='currency_id') attachment_id = fields.Binary(string='Receipt Attachment') attachment_name = fields.Char(string='Attachment Name') counterpart_account_id = fields.Many2one('account.account', string='Counterpart Account', groups="account.group_account_invoice") move_id = fields.Many2one('account.move', string='Journal Entry', readonly=True, groups="account.group_account_invoice")