diff --git a/models/account_payment.py b/models/account_payment.py
index c9327b4..8008a9a 100644
--- a/models/account_payment.py
+++ b/models/account_payment.py
@@ -34,3 +34,22 @@ class AccountPayment(models.Model):
return res
return super()._synchronize_to_moves(changed_fields)
+
+ def action_cancel(self):
+ res = super().action_cancel()
+ for payment in self:
+ if payment.expense_sheet_id:
+ payment.expense_sheet_id._compute_state()
+ # Also check if it's linked via realization
+ if payment.realization_id and payment.realization_id.expense_sheet_id:
+ payment.realization_id.expense_sheet_id._compute_state()
+ return res
+
+ def action_draft(self):
+ res = super().action_draft()
+ for payment in self:
+ if payment.expense_sheet_id:
+ payment.expense_sheet_id._compute_state()
+ if payment.realization_id and payment.realization_id.expense_sheet_id:
+ payment.realization_id.expense_sheet_id._compute_state()
+ return res
diff --git a/models/hr_expense_sheet.py b/models/hr_expense_sheet.py
index 104aa3b..1b0ae73 100644
--- a/models/hr_expense_sheet.py
+++ b/models/hr_expense_sheet.py
@@ -20,13 +20,15 @@ class HrExpenseSheet(models.Model):
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 Odoo thought it was 'done' (fully or partially paid/in_payment),
+ # we may need to hold it at 'wait_post' until realization is complete.
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)
+ # Also consider payment state: if it's NOT paid or in_payment, it should definitely stay in the state super() set (e.g. 'posted')
+ # Standard Odoo sets state='done' when payment_state is 'paid' or 'in_payment'.
+
if sheet.receipt_status != 'received' or not has_posted_realization:
sheet.state = 'wait_post'
@@ -134,3 +136,30 @@ class HrExpenseSheet(models.Model):
sheet.receipt_status = 'received'
else:
sheet.receipt_status = 'pending'
+
+ def action_reset_expense_sheets(self):
+ """
+ Overriding reset to handle realizations.
+ If a realization is posted, we should probably warn or at least prevent
+ resetting if we want strict audit. For now, we'll allow it but
+ cancel any draft/confirmed realizations.
+ """
+ for sheet in self:
+ realizations = sheet.expense_line_ids.mapped('realization_ids')
+ posted_realizations = realizations.filtered(lambda r: r.state == 'posted')
+ if posted_realizations:
+ raise UserError(_("You cannot reset this report because it has one or more Posted Realizations (%s). Please reverse or cancel the realization journal entries first.") % ", ".join(posted_realizations.mapped('name')))
+
+ # Reset draft/confirmed ones back to draft if resetting the sheet
+ realizations.filtered(lambda r: r.state != 'posted').write({'state': 'draft'})
+
+ return super().action_reset_expense_sheets()
+
+ def action_refuse_expense_sheets(self):
+ """ Handle realizations on refusal as well. """
+ for sheet in self:
+ realizations = sheet.expense_line_ids.mapped('realization_ids')
+ if realizations.filtered(lambda r: r.state == 'posted'):
+ raise UserError(_("You cannot refuse this report because it has Posted Realizations. Revert them first."))
+ realizations.write({'state': 'draft'})
+ return super().action_refuse_expense_sheets()
diff --git a/views/hr_expense_views.xml b/views/hr_expense_views.xml
index a74ff89..bc6691d 100644
--- a/views/hr_expense_views.xml
+++ b/views/hr_expense_views.xml
@@ -136,13 +136,25 @@
hr.expense.sheet
-
-
- account.group_account_invoice
+
+
+ state not in ['approve', 'post', 'done', 'cancel', 'wait_post']
+
+ state not in ['submit', 'approve', 'post', 'wait_post']
+
+
draft,submit,approve,post,wait_post,done
+
+
+