feat: prevent accidental journal reversals for expense payments by hiding the reverse button
This commit is contained in:
parent
819bce0bd0
commit
15e9c93dad
31
README.md
31
README.md
@ -1,4 +1,4 @@
|
|||||||
# HR Expense Account Split & Kiosk
|
# HR Expense Account Split & Kiosk (Odoo 19)
|
||||||
|
|
||||||
This module enhances Odoo's standard Expense workflow by providing account-splitting logic, an **Anonymous Expense Kiosk** for employees, and automated realization accounting.
|
This module enhances Odoo's standard Expense workflow by providing account-splitting logic, an **Anonymous Expense Kiosk** for employees, and automated realization accounting.
|
||||||
|
|
||||||
@ -6,25 +6,30 @@ This module enhances Odoo's standard Expense workflow by providing account-split
|
|||||||
|
|
||||||
### 1. Dynamic Sequences
|
### 1. Dynamic Sequences
|
||||||
- **Employee Reimbursement (RMBS)**: Expenses paid by the employee follow the prefix `RMBS/YYYY/MM/XXXXX`.
|
- **Employee Reimbursement (RMBS)**: Expenses paid by the employee follow the prefix `RMBS/YYYY/MM/XXXXX`.
|
||||||
* **Company Advance (KSBN)**: Expenses paid by the company (Kasbon) follow the prefix `KSBN/YYYY/MM/XXXXX`.
|
- **Company Advance (KSBN)**: Expenses paid by the company (Kasbon) follow the prefix `KSBN/YYYY/MM/XXXXX`.
|
||||||
* This ensures clear separation between standard reimbursements and company-issued advances at a glance.
|
- This ensures clear separation between standard reimbursements and company-issued advances at a glance.
|
||||||
|
|
||||||
### 2. Enhanced Status Workflow
|
### 2. Enhanced Status Workflow (Odoo 19 Refined)
|
||||||
- **Wait Post Status (Yellow)**: A new intermediate status for company-paid expense reports.
|
- **Wait Post Status (Yellow)**: A new intermediate status for company-paid expense reports.
|
||||||
- **Workflow**: `Approved` -> `Posted` -> **`Wait Post`** -> `Done`.
|
- **Workflow**: `Approved` -> `Posted` -> **`Wait Post`** -> `Done`.
|
||||||
- **Logic**: The report moves to **Wait Post** after the advance is paid. It stays here until the employee submits all receipts and the accountant posts the final realization journal.
|
- **Logic**: The report moves to **Wait Post** after the advance is paid. It stays here until the employee submits all receipts and the accountant posts the final realization journal.
|
||||||
- **Done (Green)**: Only reached when all realization accounting is completed, ensuring the physical and financial cycles are fully synchronized.
|
- **Done (Green)**: Only reached when all realization accounting is completed, ensuring the physical and financial cycles are fully synchronized.
|
||||||
|
- **Architectural Note**: In Odoo 19, the legacy `hr.expense.sheet` logic has been merged directly into the core `hr.expense` model for better performance and consistency.
|
||||||
|
|
||||||
### 3. Account Splitting
|
### 3. Account Splitting & Kasbon Logic
|
||||||
- **Dynamic Selection**: Automatically routes expenses to different GL accounts based on the `Paid By` field.
|
- **Dynamic Selection**: Automatically routes expenses to different GL accounts based on the `Paid By` field.
|
||||||
- **Configuration**: Set distinct `Expense Account (Employee)` and `Expense Account (Company)` directly on the Expense Category form.
|
- **Configuration**: Set distinct `Expense Account (Employee)` and `Expense Account (Company)` directly on the Expense Category form.
|
||||||
|
- **Advance Account**: Company-paid advances (Kasbon) use account **115101** for the debit side of the initial payment journal entry.
|
||||||
|
|
||||||
### 4. Anonymous Expense Kiosk
|
### 4. Journal Entry Safeguards
|
||||||
|
- **Reversal Protection**: The **Reverse Entry** button is automatically hidden on journal entries linked to expenses. This prevents accidental reversals that would cause the accounting ledger to fall out of sync with the expense status.
|
||||||
|
|
||||||
|
### 5. Anonymous Expense Kiosk
|
||||||
- **PIN-Protected Access**: Secure employee login via a 4-digit PIN on a tablet interface.
|
- **PIN-Protected Access**: Secure employee login via a 4-digit PIN on a tablet interface.
|
||||||
- **Real-Time Totaling**: Automatically summarizes multiple physical receipts into a single realization.
|
- **Real-Time Totaling**: Automatically summarizes multiple physical receipts into a single realization.
|
||||||
- **Image Optimization**: Client-side JPEG compression (1024px, 70% quality) reduces server storage usage by ~90%.
|
- **Image Optimization**: Client-side JPEG compression (1024px, 70% quality) reduces server storage usage by ~90%.
|
||||||
|
|
||||||
### 5. Automated Realization Accounting
|
### 6. Automated Realization Accounting
|
||||||
- **Balanced Journal Entries**: Automatically calculates discrepancies between the advance paid and actual spending.
|
- **Balanced Journal Entries**: Automatically calculates discrepancies between the advance paid and actual spending.
|
||||||
- **Discrepancy Accounts**:
|
- **Discrepancy Accounts**:
|
||||||
- **Over-spent (Spent > Paid)**: Balance is moved to `216109 Biaya Lain yang masih harus dibayar` (Liability/Payable).
|
- **Over-spent (Spent > Paid)**: Balance is moved to `216109 Biaya Lain yang masih harus dibayar` (Liability/Payable).
|
||||||
@ -33,21 +38,19 @@ This module enhances Odoo's standard Expense workflow by providing account-split
|
|||||||
- **Create Vendor Payment**: One-click button on the realization form to pay the employee the difference.
|
- **Create Vendor Payment**: One-click button on the realization form to pay the employee the difference.
|
||||||
- **Create Customer Payment**: One-click button on the realization form to record the employee returning the excess funds.
|
- **Create Customer Payment**: One-click button on the realization form to record the employee returning the excess funds.
|
||||||
|
|
||||||
### 6. Validation & Security
|
|
||||||
- **Mandatory Receipts**: Prevents submission of employee-paid expenses without attachments.
|
|
||||||
- **Overdue Tracking**: Highlights missing realization receipts in red/alerts for company-paid expenses.
|
|
||||||
|
|
||||||
## 🛠 Configuration
|
## 🛠 Configuration
|
||||||
|
|
||||||
1. **GL Accounts**:
|
1. **GL Accounts**:
|
||||||
- **Expenses > Configuration > Expense Categories**.
|
- **Expenses > Configuration > Expense Categories**.
|
||||||
- Define the two accounts under the **Accounting** tab.
|
- Define the two accounts under the **Accounting** tab.
|
||||||
2. **Kiosk Token**:
|
2. **Kiosk Activation**:
|
||||||
- URL structure: `/hr_expense/kiosk/d56db48c463444c88b86f14980d7a185`.
|
- Go to **Expenses > Configuration > Settings**.
|
||||||
|
- The **Kiosk URL** is displayed in the "Expense Kiosk" block. You can regenerate the token here if needed.
|
||||||
3. **Employee PINs**:
|
3. **Employee PINs**:
|
||||||
- Set 4-digit PINs on employee records for Kiosk access.
|
- Set 4-digit PINs on employee records for Kiosk access.
|
||||||
|
|
||||||
## 📋 Technical Notes
|
## 📋 Technical Notes
|
||||||
|
- **Odoo Version**: 19.0
|
||||||
- **Controller**: `/hr_expense/kiosk/<token>`
|
- **Controller**: `/hr_expense/kiosk/<token>`
|
||||||
- **Models**: `hr.expense`, `hr.expense.sheet`, `hr.expense.realization`, `account.payment`
|
- **Models**: `hr.expense`, `hr.expense.realization`, `account.move`, `account.payment`
|
||||||
- **JS Framework**: Odoo 19 OWL
|
- **JS Framework**: Odoo 19 OWL
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
'views/hr_expense_realization_views.xml',
|
'views/hr_expense_realization_views.xml',
|
||||||
'views/hr_expense_kiosk_templates.xml',
|
'views/hr_expense_kiosk_templates.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
|
'views/account_move_views.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
'hr_expense_account_split.assets_public_kiosk': [
|
'hr_expense_account_split.assets_public_kiosk': [
|
||||||
|
|||||||
@ -1,8 +1,19 @@
|
|||||||
from odoo import models, api
|
from odoo import models, fields, api
|
||||||
|
|
||||||
class AccountMove(models.Model):
|
class AccountMove(models.Model):
|
||||||
_inherit = 'account.move'
|
_inherit = 'account.move'
|
||||||
|
|
||||||
|
is_expense_payment = fields.Boolean(
|
||||||
|
string="Is Expense Payment",
|
||||||
|
compute="_compute_is_expense_payment",
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('expense_ids')
|
||||||
|
def _compute_is_expense_payment(self):
|
||||||
|
for move in self:
|
||||||
|
move.is_expense_payment = bool(move.sudo().expense_ids)
|
||||||
|
|
||||||
def _get_hr_expense_base_class(self):
|
def _get_hr_expense_base_class(self):
|
||||||
""" Returns the hr_expense class in the MRO to jump over it. """
|
""" Returns the hr_expense class in the MRO to jump over it. """
|
||||||
mro = type(self).mro()
|
mro = type(self).mro()
|
||||||
|
|||||||
17
views/account_move_views.xml
Normal file
17
views/account_move_views.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Override account move form to hide reverse button for expense payments -->
|
||||||
|
<record id="view_move_form_inherit_expense_payment" model="ir.ui.view">
|
||||||
|
<field name="name">account.move.form.inherit.expense.payment</field>
|
||||||
|
<field name="model">account.move</field>
|
||||||
|
<field name="inherit_id" ref="account.view_move_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//button[@name='%(account.action_view_account_move_reversal)d']" position="attributes">
|
||||||
|
<attribute name="invisible">move_type != 'entry' or state != 'posted' or payment_state == 'reversed' or is_expense_payment</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//sheet" position="inside">
|
||||||
|
<field name="is_expense_payment" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user