Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions hr_expense_petty_cash/models/account_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ def action_post(self):
self._check_petty_cash_amount()
return super().action_post()

def _prepare_product_base_line_for_taxes_computation(self, product_line):
results = super()._prepare_product_base_line_for_taxes_computation(product_line)
if product_line.expense_id.payment_mode == "petty_cash":
results["special_mode"] = "total_included"
return results

@api.constrains("invoice_line_ids", "line_ids")
def _check_petty_cash_amount(self):
petty_cash_env = self.env["petty.cash"].sudo()
Expand Down
54 changes: 31 additions & 23 deletions hr_expense_petty_cash/models/hr_expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,30 +97,38 @@ def _get_default_expense_sheet_values(self):
return [values]
return super(HrExpense, todo)._get_default_expense_sheet_values()

def _get_petty_cash_move_line(
self,
move_line_name,
partner_id,
total_amount,
total_amount_currency,
tax_ids,
account=False,
):
account_date = (
self.date
or self.sheet_id.accounting_date
or fields.Date.context_today(self)
)
ml_dict = {
def _get_petty_cash_move_line_source_vals(self, move_line_name, partner):
self.ensure_one()
return {
"name": move_line_name,
"debit": total_amount if total_amount > 0.0 else 0.0,
"credit": -total_amount if total_amount < 0.0 else 0.0,
"account_id": account and account.id or self.account_id.id,
"date_maturity": account_date,
"amount_currency": total_amount_currency,
"currency_id": self.currency_id.id,
"expense_id": self.id,
"partner_id": partner_id,
"tax_ids": [Command.set(tax_ids.ids)],
"partner_id": partner.id,
"account_id": self._get_base_account().id,
"analytic_distribution": self.analytic_distribution,
"tax_ids": [Command.set(self.tax_ids.ids)],
"amount_currency": self.total_amount_currency,
"debit": self.total_amount if self.total_amount > 0.0 else 0.0,
"credit": 0.0,
}
return ml_dict

def _get_petty_cash_move_line_dest_vals(self, move_line_name, partner):
self.ensure_one()
return {
"name": move_line_name,
"currency_id": self.currency_id.id,
"expense_id": self.id,
"partner_id": self.petty_cash_id.partner_id.id,
"account_id": self.petty_cash_id.account_id.id,
"amount_currency": -self.total_amount_currency,
"debit": 0.0,
"credit": self.total_amount if self.total_amount > 0.0 else 0.0,
}

def _get_petty_cash_move_line_vals(self, move_line_name, partner):
self.ensure_one()
move_line_vals = [
self._get_petty_cash_move_line_source_vals(move_line_name, partner),
self._get_petty_cash_move_line_dest_vals(move_line_name, partner),
]
return move_line_vals
24 changes: 3 additions & 21 deletions hr_expense_petty_cash/models/hr_expense_sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,28 +118,10 @@ def _get_petty_cash_move_line_vals(self):
move_line_name = (
expense.employee_id.name + ": " + expense.name.split("\n")[0][:64]
)
partner_id = expense.employee_id.sudo().work_contact_id.id
# source move line
move_line_src = expense._get_petty_cash_move_line(
move_line_name,
partner_id,
expense.total_amount,
expense.total_amount_currency,
expense.tax_ids,
partner = expense.employee_id.sudo().work_contact_id
move_line_vals.extend(
expense._get_petty_cash_move_line_vals(move_line_name, partner)
)
move_line_values = [move_line_src]

# destination move line
move_line_dst = expense._get_petty_cash_move_line(
move_line_name,
expense.petty_cash_id.partner_id.id,
-expense.total_amount,
-expense.total_amount_currency,
expense.tax_ids,
expense.petty_cash_id.account_id,
)
move_line_values.append(move_line_dst)
move_line_vals.extend(move_line_values)
return move_line_vals

def _prepare_bills_vals(self):
Expand Down
93 changes: 92 additions & 1 deletion hr_expense_petty_cash/tests/test_hr_expense_petty_cash.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ def setUpClass(cls):
cls.petty_cash_holder = cls._create_petty_cash_holder(cls, cls.partner_1)
cls.petty_cash_holder_2 = cls._create_petty_cash_holder(cls, cls.partner_3)

# Analytic setup, for testing analytic distribution on petty cash
cls.analytic_plan = cls.env["account.analytic.plan"].create(
{"name": "Petty Cash Plan - Test"}
)
cls.analytic_account = cls.env["account.analytic.account"].create(
{
"name": "Petty Cash Analytic - Test",
"plan_id": cls.analytic_plan.id,
}
)
# Tax setup (price-included) to test petty cash tax split.
cls.tax_included = cls.env["account.tax"].create(
{
"name": "Petty Cash VAT 7% (included) - Test",
"amount_type": "percent",
"amount": 7.0,
"price_include": True,
"type_tax_use": "purchase",
"company_id": cls.env.company.id,
}
)

def _create_petty_cash_holder(self, partner):
petty_cash_holder = self.petty_cash_obj.create(
{
Expand All @@ -85,6 +107,8 @@ def _create_expense(
employee,
payment_mode="own_account",
petty_cash_holder=False,
analytic_distribution=False,
tax_ids=False,
):
with Form(self.exp_obj) as expense:
expense.name = "Expense - Test"
Expand All @@ -94,7 +118,12 @@ def _create_expense(
expense = expense.save()

expense.total_amount = amount
expense.tax_ids = False # no VAT
if tax_ids:
expense.tax_ids = tax_ids
else:
expense.tax_ids = False # no VAT
if analytic_distribution:
expense.analytic_distribution = analytic_distribution

if payment_mode == "petty_cash":
expense.payment_mode = "petty_cash"
Expand Down Expand Up @@ -314,3 +343,65 @@ def test_04_change_partner_bill_not_petty_cash(self):
set(invoice.invoice_line_ids.mapped("name")),
{"Test line 1", "Test line 2"},
)

def test_05_petty_cash_with_analytic_and_tax(self):
"""Petty cash entry must carry analytic on the expense side and split
the included tax into proper base + tax lines while the clearing side
keeps the total amount paid."""
# Fund the petty cash holder first.
self.petty_cash_holder.petty_cash_limit = 2000.0
invoice = self._create_invoice(self.partner_1.id)
invoice.is_petty_cash = True
invoice._onchange_is_petty_cash()
invoice.invoice_line_ids.price_unit = 2000.0
invoice.action_post()
self.petty_cash_holder._compute_petty_cash_balance()
self.assertEqual(self.petty_cash_holder.petty_cash_balance, 2000.0)

# Expense 1070 (tax-included) -> base 1000 + 7% VAT 70.
expense = self._create_expense(
1070.0,
self.employee_1,
"petty_cash",
self.petty_cash_holder,
analytic_distribution={str(self.analytic_account.id): 100},
tax_ids=self.tax_included,
)
expense.action_submit_expenses()
sheet = self._create_expense_sheet(expense)
sheet.action_submit_sheet()
sheet.action_approve_expense_sheets()
if sheet.state == "approve":
sheet.action_sheet_move_post()
self.assertEqual(sheet.state, "post")

move = sheet.account_move_ids
self.assertEqual(move.move_type, "entry")

# Expense base line carries the analytic distribution.
base_line = move.line_ids.filtered(
lambda line: not line.tax_line_id
and line.account_id != self.petty_cash_account_id
)
self.assertEqual(len(base_line), 1)
self.assertEqual(
base_line.analytic_distribution,
{str(self.analytic_account.id): 100},
)
# Clearing side carries no analytic distribution.
petty_cash_line = move.line_ids.filtered(
lambda line: line.account_id == self.petty_cash_account_id
)
self.assertEqual(len(petty_cash_line), 1)
self.assertFalse(petty_cash_line.analytic_distribution)
# Clearing side reflects the full tax-included amount paid.
self.assertEqual(sum(petty_cash_line.mapped("credit")), 1070.0)
# The move must be balanced.
self.assertEqual(sum(move.line_ids.mapped("debit")), 1070.0)
self.assertEqual(sum(move.line_ids.mapped("credit")), 1070.0)
# A tax line must have been generated.
tax_lines = move.line_ids.filtered(lambda line: line.tax_line_id)
self.assertTrue(
tax_lines,
"Petty cash entry should generate a tax line.",
)
Loading