commit 56d4c42956cf0874e08c88fabf54a6e1e89af8bb Author: Suherdy Yacob Date: Tue Mar 31 11:55:30 2026 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..752df4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.py[cod] +__pycache__/ +*.swp +*~ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..2605809 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# HR Expense Payment Status Override + +## Overview +This custom Odoo 17 module overrides the default behavior for Expense Sheets (`hr.expense.sheet`) that are paid by the "Company" (`company_account`). + +Natively, Odoo automatically marks an expense sheet as `Paid` as soon as the journal entries are posted, regardless of whether the generated `account.payment` is actually reconciled with a bank statement. + +This module changes that behavior: +- It tracks the generated `account.payment`. +- If the payment is NOT matched to a bank statement (`is_matched == False`), the Expense Sheet status stops at **In Payment**. +- Once the payment is fully reconciled with a bank statement, the Expense Sheet correctly transitions to **Paid**. + +## Dependencies +- `hr_expense` +- `account` + +## Usage +1. Install the module. +2. Submit and approve an expense. +3. Post the journal entries (paid by Company). +4. Verify the status updates to "In Payment". +5. Reconcile the payment with a bank statement and verify the status updates to "Paid". diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..e61eb55 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': 'HR Expense Payment Status Override', + 'version': '17.0.1.0.0', + 'summary': 'Override the hardcoded "Paid" status for expenses paid by the company, keeping them "In Payment" until reconciled.', + 'category': 'Human Resources/Expenses', + 'author': 'Suherdy Yacob', + 'depends': ['hr_expense', 'account'], + 'data': [], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..02264fd --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +from . import hr_expense_sheet diff --git a/models/hr_expense_sheet.py b/models/hr_expense_sheet.py new file mode 100644 index 0000000..d716c91 --- /dev/null +++ b/models/hr_expense_sheet.py @@ -0,0 +1,27 @@ +from odoo import api, models + +class HrExpenseSheet(models.Model): + _inherit = 'hr.expense.sheet' + + @api.depends('account_move_ids.payment_state', 'account_move_ids.payment_id.is_matched') + def _compute_from_account_move_ids(self): + # Call the original method to compute default values first + super()._compute_from_account_move_ids() + + # Override specific logic to prevent company-paid expenses from immediately jumping to 'Paid' + for sheet in self: + if sheet.payment_mode == 'company_account': + if sheet.account_move_ids: + moves = sheet.account_move_ids - sheet.account_move_ids.filtered('reversal_move_id') + if moves: + payments = moves.mapped('payment_id') + + # Find if any linked payment is not yet matched to a bank statement + unmatched_payments = payments.filtered(lambda p: not p.is_matched) + + if unmatched_payments: + # If there are unmatched payments, it should stay 'In Payment' + sheet.payment_state = 'in_payment' + else: + # If all payments are matched (or we have manual entries), it is Paid + sheet.payment_state = 'paid'