feat: implement split expense sequences and add wait_post state for company-paid expenses with pending realizations

This commit is contained in:
Suherdy Yacob 2026-04-02 15:35:22 +07:00
parent 83ea721aa8
commit e289392116
4 changed files with 53 additions and 12 deletions

View File

@ -8,10 +8,17 @@
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_hr_expense" model="ir.sequence">
<field name="name">Expense</field>
<field name="code">hr.expense.sequence</field>
<field name="prefix">EXP/%(year)s/%(month)s/</field>
<record id="seq_hr_expense_reimbursement" model="ir.sequence">
<field name="name">Expense Reimbursement</field>
<field name="code">hr.expense.sequence.reimbursement</field>
<field name="prefix">RMBS/%(year)s/%(month)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_hr_expense_kasbon" model="ir.sequence">
<field name="name">Expense Kasbon</field>
<field name="code">hr.expense.sequence.kasbon</field>
<field name="prefix">KSBN/%(year)s/%(month)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>

View File

@ -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):

View File

@ -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()

View File

@ -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"/>
</div>
</xpath>
<xpath expr="//sheet" position="before">
@ -137,12 +137,12 @@
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_form"/>
<field name="arch" type="xml">
<!-- Restrict Posting to Accountants -->
<xpath expr="//button[@name='action_sheet_move_create']" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute>
</xpath>
<xpath expr="//button[@name='action_register_payment']" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute>
</xpath>
<xpath expr="//field[@name='state']" position="attributes">
<attribute name="statusbar_visible">draft,submit,approve,post,wait_post,done</attribute>
</xpath>
<xpath expr="//field[@name='expense_line_ids']/tree/field[@name='name']" position="before">
<field name="sequence_name" column_invisible="not parent.id"/>
@ -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"/>
<!-- Button to View Receipts if already exist -->
<button name="action_view_realizations"
string="View Receipts"
@ -168,7 +168,7 @@
icon="fa-external-link"
class="text-success"
title="View linked receipts"
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"/>
</xpath>
</field>
</record>
@ -179,6 +179,9 @@
<field name="model">hr.expense.sheet</field>
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='state']" position="attributes">
<attribute name="decoration-warning">state == 'wait_post'</attribute>
</xpath>
<xpath expr="//field[@name='state']" position="before">
<field name="expense_sequences" optional="show"/>
<field name="amount_paid" optional="show" sum="Total Payment" string="Payment Total"/>
@ -200,6 +203,7 @@
<field name="arch" type="xml">
<xpath expr="//filter[@name='my_reports']" position="after">
<separator/>
<filter string="Wait Post" name="filter_wait_post" domain="[('state', '=', 'wait_post')]"/>
<filter string="Pending Receipts" name="filter_receipt_pending" domain="[('receipt_status', '=', 'pending')]"/>
<filter string="Receipts Received" name="filter_receipt_received" domain="[('receipt_status', '=', 'received')]"/>
</xpath>
@ -208,4 +212,14 @@
</xpath>
</field>
</record>
<!-- Update All Reports Action to include Wait Post in SearchPanel -->
<record id="hr_expense.action_hr_expense_sheet_all" model="ir.actions.act_window">
<field name="context">{ 'searchpanel_default_state': ["draft", "submit", "approve", "post", "wait_post", "done"] }</field>
</record>
<!-- Update My Reports Action to include Wait Post in SearchPanel if applicable -->
<record id="hr_expense.action_hr_expense_sheet_my_all" model="ir.actions.act_window">
<field name="context">{ 'searchpanel_default_state': ["draft", "submit", "approve", "post", "wait_post", "done"], 'search_default_my_reports': 1, 'search_default_not_refused_reports': 1 }</field>
</record>
</odoo>