# -*- coding: utf-8 -*- from odoo import fields from odoo.tests import TransactionCase from odoo.exceptions import ValidationError class TestBatchPaymentIntegration(TransactionCase): """Test cases for batch payment integration with deduction functionality""" def setUp(self): super(TestBatchPaymentIntegration, self).setUp() # Create test partners (suppliers) self.partner1 = self.env['res.partner'].create({ 'name': 'Test Vendor 1', 'supplier_rank': 1, }) self.partner2 = self.env['res.partner'].create({ 'name': 'Test Vendor 2', 'supplier_rank': 1, }) # Get or create bank journal self.journal = self.env['account.journal'].search([ ('type', '=', 'bank'), ('company_id', '=', self.env.company.id) ], limit=1) if not self.journal: self.journal = self.env['account.journal'].create({ 'name': 'Test Bank', 'type': 'bank', 'code': 'TBNK', 'company_id': self.env.company.id, }) # Create substract account (expense account) self.substract_account = self.env['account.account'].create({ 'name': 'Withholding Tax Account', 'code': 'WHT001', 'account_type': 'expense', 'company_id': self.env.company.id, }) # Create expense account for batch payment lines self.expense_account = self.env['account.account'].create({ 'name': 'General Expense Account', 'code': 'EXP001', 'account_type': 'expense', 'company_id': self.env.company.id, }) def test_property_batch_payment_line_field_transfer(self): """ **Feature: vendor-payment-diff-amount, Property 13: Batch payment line field transfer** **Validates: Requirements 7.4, 7.5** Property: For any batch payment line with amount_substract and substract_account_id values, when payments are generated, the created payment should have the same amount_substract and substract_account_id values. """ # Create batch payment batch_payment = self.env['account.batch.payment'].create({ 'journal_id': self.journal.id, 'batch_type': 'outbound', 'date': fields.Date.today(), }) # Create direct payment lines with deduction fields line1 = self.env['account.batch.payment.line'].create({ 'batch_payment_id': batch_payment.id, 'partner_id': self.partner1.id, 'amount': 1000.0, 'amount_substract': 100.0, 'substract_account_id': self.substract_account.id, 'expense_account_id': self.expense_account.id, 'memo': 'Payment 1 with deduction', 'date': fields.Date.today(), }) line2 = self.env['account.batch.payment.line'].create({ 'batch_payment_id': batch_payment.id, 'partner_id': self.partner2.id, 'amount': 2000.0, 'amount_substract': 200.0, 'substract_account_id': self.substract_account.id, 'expense_account_id': self.expense_account.id, 'memo': 'Payment 2 with deduction', 'date': fields.Date.today(), }) # Verify lines were created with correct values self.assertEqual(line1.amount_substract, 100.0, "Line 1 should have amount_substract of 100") self.assertEqual(line1.substract_account_id, self.substract_account, "Line 1 should have correct substract_account_id") self.assertEqual(line2.amount_substract, 200.0, "Line 2 should have amount_substract of 200") self.assertEqual(line2.substract_account_id, self.substract_account, "Line 2 should have correct substract_account_id") # Generate payments from lines batch_payment.generate_payments_from_lines() # Verify payments were generated self.assertTrue(line1.payment_id, "Payment should be generated for line 1") self.assertTrue(line2.payment_id, "Payment should be generated for line 2") # Get the generated payments payment1 = line1.payment_id payment2 = line2.payment_id # Verify payment 1 has correct deduction fields transferred self.assertEqual(payment1.amount, 1000.0, "Payment 1 should have amount of 1000") self.assertEqual(payment1.amount_substract, 100.0, "Payment 1 should have amount_substract of 100 (transferred from line)") self.assertEqual(payment1.substract_account_id, self.substract_account, "Payment 1 should have correct substract_account_id (transferred from line)") self.assertEqual(payment1.final_payment_amount, 900.0, "Payment 1 final_payment_amount should be 900 (1000 - 100)") # Verify payment 2 has correct deduction fields transferred self.assertEqual(payment2.amount, 2000.0, "Payment 2 should have amount of 2000") self.assertEqual(payment2.amount_substract, 200.0, "Payment 2 should have amount_substract of 200 (transferred from line)") self.assertEqual(payment2.substract_account_id, self.substract_account, "Payment 2 should have correct substract_account_id (transferred from line)") self.assertEqual(payment2.final_payment_amount, 1800.0, "Payment 2 final_payment_amount should be 1800 (2000 - 200)") # Verify payments are posted self.assertEqual(payment1.state, 'posted', "Payment 1 should be posted") self.assertEqual(payment2.state, 'posted', "Payment 2 should be posted") # Verify journal entries have correct structure with deduction lines move1 = payment1.move_id move2 = payment2.move_id self.assertTrue(move1, "Payment 1 should have journal entry") self.assertTrue(move2, "Payment 2 should have journal entry") # Verify payment 1 journal entry has substract line substract_lines1 = move1.line_ids.filtered( lambda l: l.account_id == self.substract_account ) self.assertGreater(len(substract_lines1), 0, "Payment 1 journal entry should have substract account line") # Verify payment 2 journal entry has substract line substract_lines2 = move2.line_ids.filtered( lambda l: l.account_id == self.substract_account ) self.assertGreater(len(substract_lines2), 0, "Payment 2 journal entry should have substract account line") # Verify bank lines have correct amounts (original amount) bank_account = payment1.outstanding_account_id bank_line1 = move1.line_ids.filtered(lambda l: l.account_id == bank_account) bank_line2 = move2.line_ids.filtered(lambda l: l.account_id == bank_account) self.assertAlmostEqual(sum(bank_line1.mapped('credit')), 1000.0, places=2, msg="Payment 1 bank credit should be 1000 (original amount)") self.assertAlmostEqual(sum(bank_line2.mapped('credit')), 2000.0, places=2, msg="Payment 2 bank credit should be 2000 (original amount)") def test_unit_batch_payment_line_fields_exist(self): """ Unit test: Verify batch payment line model has deduction fields. Tests Requirements 7.1, 7.2, 7.3 """ # Create batch payment batch_payment = self.env['account.batch.payment'].create({ 'journal_id': self.journal.id, 'batch_type': 'outbound', 'date': fields.Date.today(), }) # Create batch payment line line = self.env['account.batch.payment.line'].create({ 'batch_payment_id': batch_payment.id, 'partner_id': self.partner1.id, 'amount': 1000.0, 'date': fields.Date.today(), }) # Verify fields exist self.assertTrue(hasattr(line, 'amount_substract'), "Batch payment line should have amount_substract field") self.assertTrue(hasattr(line, 'substract_account_id'), "Batch payment line should have substract_account_id field") # Verify fields can be set line.write({ 'amount_substract': 100.0, 'substract_account_id': self.substract_account.id, }) self.assertEqual(line.amount_substract, 100.0, "amount_substract should be settable") self.assertEqual(line.substract_account_id, self.substract_account, "substract_account_id should be settable") def test_unit_batch_payment_without_deduction(self): """ Unit test: Verify batch payment works without deduction fields. Tests that the integration doesn't break existing functionality. """ # Create batch payment batch_payment = self.env['account.batch.payment'].create({ 'journal_id': self.journal.id, 'batch_type': 'outbound', 'date': fields.Date.today(), }) # Create direct payment line WITHOUT deduction fields line = self.env['account.batch.payment.line'].create({ 'batch_payment_id': batch_payment.id, 'partner_id': self.partner1.id, 'amount': 1000.0, 'expense_account_id': self.expense_account.id, 'memo': 'Payment without deduction', 'date': fields.Date.today(), }) # Verify line was created without deduction fields self.assertEqual(line.amount_substract, 0.0, "amount_substract should default to 0") self.assertFalse(line.substract_account_id, "substract_account_id should be False by default") # Generate payments from lines batch_payment.generate_payments_from_lines() # Verify payment was generated self.assertTrue(line.payment_id, "Payment should be generated") # Get the generated payment payment = line.payment_id # Verify payment has no deduction self.assertEqual(payment.amount, 1000.0, "Payment should have amount of 1000") self.assertEqual(payment.amount_substract, 0.0, "Payment should have amount_substract of 0") self.assertFalse(payment.substract_account_id, "Payment should not have substract_account_id") self.assertEqual(payment.final_payment_amount, 1000.0, "Payment final_payment_amount should equal amount") # Verify payment is posted self.assertEqual(payment.state, 'posted', "Payment should be posted") # Verify journal entry has standard structure (no substract line) move = payment.move_id self.assertTrue(move, "Payment should have journal entry") # Verify no substract line substract_lines = move.line_ids.filtered( lambda l: l.account_id == self.substract_account ) self.assertEqual(len(substract_lines), 0, "Journal entry should not have substract account line") def test_unit_batch_payment_mixed_lines(self): """ Unit test: Verify batch payment with mixed lines (some with deduction, some without). Tests Requirements 7.4, 7.5 """ # Create batch payment batch_payment = self.env['account.batch.payment'].create({ 'journal_id': self.journal.id, 'batch_type': 'outbound', 'date': fields.Date.today(), }) # Create line WITH deduction line_with_deduction = self.env['account.batch.payment.line'].create({ 'batch_payment_id': batch_payment.id, 'partner_id': self.partner1.id, 'amount': 1000.0, 'amount_substract': 100.0, 'substract_account_id': self.substract_account.id, 'expense_account_id': self.expense_account.id, 'memo': 'With deduction', 'date': fields.Date.today(), }) # Create line WITHOUT deduction line_without_deduction = self.env['account.batch.payment.line'].create({ 'batch_payment_id': batch_payment.id, 'partner_id': self.partner2.id, 'amount': 2000.0, 'expense_account_id': self.expense_account.id, 'memo': 'Without deduction', 'date': fields.Date.today(), }) # Generate payments from lines batch_payment.generate_payments_from_lines() # Verify both payments were generated self.assertTrue(line_with_deduction.payment_id, "Payment should be generated for line with deduction") self.assertTrue(line_without_deduction.payment_id, "Payment should be generated for line without deduction") # Get the generated payments payment_with = line_with_deduction.payment_id payment_without = line_without_deduction.payment_id # Verify payment with deduction self.assertEqual(payment_with.amount_substract, 100.0, "Payment with deduction should have amount_substract") self.assertEqual(payment_with.substract_account_id, self.substract_account, "Payment with deduction should have substract_account_id") self.assertEqual(payment_with.final_payment_amount, 900.0, "Payment with deduction final amount should be 900") # Verify payment without deduction self.assertEqual(payment_without.amount_substract, 0.0, "Payment without deduction should have amount_substract of 0") self.assertFalse(payment_without.substract_account_id, "Payment without deduction should not have substract_account_id") self.assertEqual(payment_without.final_payment_amount, 2000.0, "Payment without deduction final amount should equal amount") # Verify both payments are posted self.assertEqual(payment_with.state, 'posted', "Payment with deduction should be posted") self.assertEqual(payment_without.state, 'posted', "Payment without deduction should be posted")