From e28939211619a5da8d102a43f46f2bcd01efa436 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Thu, 2 Apr 2026 15:35:22 +0700 Subject: [PATCH] feat: implement split expense sequences and add wait_post state for company-paid expenses with pending realizations --- data/ir_sequence_data.xml | 15 +++++++++++---- models/hr_expense.py | 4 +++- models/hr_expense_sheet.py | 20 +++++++++++++++++++- views/hr_expense_views.xml | 26 ++++++++++++++++++++------ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/data/ir_sequence_data.xml b/data/ir_sequence_data.xml index fe9bdbb..2ed9fe0 100644 --- a/data/ir_sequence_data.xml +++ b/data/ir_sequence_data.xml @@ -8,10 +8,17 @@ 5 - - Expense - hr.expense.sequence - EXP/%(year)s/%(month)s/ + + Expense Reimbursement + hr.expense.sequence.reimbursement + RMBS/%(year)s/%(month)s/ + 5 + + + + Expense Kasbon + hr.expense.sequence.kasbon + KSBN/%(year)s/%(month)s/ 5 diff --git a/models/hr_expense.py b/models/hr_expense.py index 6c168cf..d436529 100644 --- a/models/hr_expense.py +++ b/models/hr_expense.py @@ -65,7 +65,9 @@ class HrExpense(models.Model): def create(self, vals_list): for vals in vals_list: if vals.get('sequence_name', _('New')) == _('New'): - vals['sequence_name'] = self.env['ir.sequence'].next_by_code('hr.expense.sequence') or _('New') + payment_mode = vals.get('payment_mode', 'own_account') + seq_code = 'hr.expense.sequence.reimbursement' if payment_mode == 'own_account' else 'hr.expense.sequence.kasbon' + vals['sequence_name'] = self.env['ir.sequence'].next_by_code(seq_code) or _('New') return super().create(vals_list) def action_create_realization(self): diff --git a/models/hr_expense_sheet.py b/models/hr_expense_sheet.py index b2f60ad..104aa3b 100644 --- a/models/hr_expense_sheet.py +++ b/models/hr_expense_sheet.py @@ -4,7 +4,11 @@ from datetime import timedelta class HrExpenseSheet(models.Model): _inherit = 'hr.expense.sheet' - @api.depends('account_move_ids.payment_state', 'account_move_ids.amount_residual') + state = fields.Selection(selection_add=[ + ('wait_post', 'Wait Post') + ], ondelete={'wait_post': 'set default'}) + + @api.depends('account_move_ids.payment_state', 'account_move_ids.amount_residual', 'expense_line_ids.receipt_received', 'expense_line_ids.realization_ids.state') def _compute_state(self): # Store original states to detect transition to 'done' original_states = {sheet.id: sheet.state for sheet in self} @@ -12,6 +16,20 @@ class HrExpenseSheet(models.Model): super()._compute_state() for sheet in self: + # Check for Company Account expenses + company_paid = sheet.expense_line_ids.filtered(lambda e: e.payment_mode == 'company_account') + + if company_paid: + # If Odoo thought it was 'done' (paid), we might need to hold it at 'wait_post' + if sheet.state == 'done': + # All receipts must be marked received (checked earlier by _compute_receipt_status) + # We also check if at least one realization exists and all are posted + realizations = company_paid.mapped('realization_ids') + has_posted_realization = realizations and all(r.state == 'posted' for r in realizations) + + if sheet.receipt_status != 'received' or not has_posted_realization: + sheet.state = 'wait_post' + if original_states.get(sheet.id) != 'done' and sheet.state == 'done': # Transitioned to 'Paid' today = fields.Date.today() diff --git a/views/hr_expense_views.xml b/views/hr_expense_views.xml index 8f8e512..a74ff89 100644 --- a/views/hr_expense_views.xml +++ b/views/hr_expense_views.xml @@ -47,7 +47,7 @@ type="object" class="oe_stat_button" icon="fa-plus-square-o" - invisible="payment_mode != 'company_account' or state != 'done' or realization_count != 0"/> + invisible="payment_mode != 'company_account' or state not in ['done', 'reported'] or realization_count != 0"/> @@ -137,12 +137,12 @@ - - account.group_account_invoice - account.group_account_invoice + + draft,submit,approve,post,wait_post,done + @@ -160,7 +160,7 @@ icon="fa-plus" class="text-primary" title="Add receipts for this expense" - invisible="payment_mode != 'company_account' or parent.state not in ['post', 'done'] or realization_count != 0"/> + invisible="payment_mode != 'company_account' or parent.state not in ['post', 'wait_post', 'done'] or realization_count != 0"/>