# -*- coding: utf-8 -*- """ Tests for l10n_ru_contract_sale — overdue debt check on sale order confirmation and invoice preparation with contract fields. Validates: Requirements 16.1, 16.2, 16.3 """ from datetime import date, timedelta from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _get_or_create_accounts(env): """Return a receivable and income account for the current company.""" receivable = env['account.account'].search([ ('account_type', '=', 'asset_receivable'), ('company_ids', 'in', env.company.id), ], limit=1) payable = env['account.account'].search([ ('account_type', '=', 'liability_payable'), ('company_ids', 'in', env.company.id), ], limit=1) income = env['account.account'].search([ ('account_type', '=', 'income'), ('company_ids', 'in', env.company.id), ], limit=1) return receivable, payable, income def _get_or_create_journal(env): """Return or create a sales journal.""" journal = env['account.journal'].search([ ('type', '=', 'sale'), ('company_id', '=', env.company.id), ], limit=1) if not journal: journal = env['account.journal'].create({ 'name': 'Test Sales Journal CS', 'type': 'sale', 'code': 'TSCS', 'company_id': env.company.id, }) return journal def _get_or_create_payment_term(env): """Return or create a payment term.""" term = env['account.payment.term'].search([], limit=1) if not term: term = env['account.payment.term'].create({'name': 'Immediate CS'}) return term def _make_profile(env, journal, payment_term, receivable, payable, max_receivable=10000.0): """Create a contract.profile.""" return env['contract.profile'].create({ 'name': 'Test Profile CS', 'journal_id': journal.id, 'payment_term_id': payment_term.id, 'receivable_account_id': receivable.id, 'payable_account_id': payable.id, 'max_receivable_id': max_receivable, }) def _make_partner(env): """Create a company partner.""" return env['res.partner'].create({ 'name': 'Test Partner CS', 'is_company': True, }) def _make_contract(env, partner, profile): """Create a minimal partner.contract.customer.""" return env['partner.contract.customer'].create({ 'date_start': '2024-01-01', 'date_end': '2024-12-31', 'type': 'customer', 'profile_id': profile.id, 'partner_id': partner.id, 'partner_type': 'company', 'company_id': env.company.id, }) def _make_product(env): """Return or create a storable product.""" product = env['product.product'].search([ ('type', '=', 'service'), ], limit=1) if not product: product = env['product.product'].create({ 'name': 'Test Product CS', 'type': 'service', }) return product def _make_sale_order(env, partner, contract, product): """Create a confirmed-ready sale order linked to a contract.""" order = env['sale.order'].create({ 'partner_id': partner.id, 'mt_contract_id': contract.id, 'order_line': [(0, 0, { 'product_id': product.id, 'product_uom_qty': 1.0, 'price_unit': 500.0, })], }) return order def _make_overdue_invoice(env, partner, journal, income_account, amount, days_overdue=10): """Create a posted out_invoice that is overdue and unpaid.""" due_date = date.today() - timedelta(days=days_overdue) invoice = env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': partner.id, 'journal_id': journal.id, 'invoice_date': due_date, 'invoice_date_due': due_date, 'invoice_line_ids': [(0, 0, { 'name': 'Overdue line', 'quantity': 1.0, 'price_unit': amount, 'account_id': income_account.id, })], }) invoice.action_post() return invoice # --------------------------------------------------------------------------- # TestContractSaleOrder # --------------------------------------------------------------------------- class TestContractSaleOrder(TransactionCase): """ Tests for sale order confirmation with overdue debt check and invoice preparation with contract fields. Validates: Requirements 16.1, 16.2, 16.3 """ def setUp(self): super().setUp() self.receivable, self.payable, self.income = _get_or_create_accounts(self.env) self.journal = _get_or_create_journal(self.env) self.payment_term = _get_or_create_payment_term(self.env) self.partner = _make_partner(self.env) self.product = _make_product(self.env) # ------------------------------------------------------------------ # Requirement 16.1 — overdue debt > max_receivable raises ValidationError # ------------------------------------------------------------------ def test_confirm_with_overdue_debt_raises(self): """ Req 16.1 — confirming a sale order when the partner has overdue debt exceeding max_receivable raises ValidationError. """ profile = _make_profile( self.env, self.journal, self.payment_term, self.receivable, self.payable, max_receivable=100.0, ) contract = _make_contract(self.env, self.partner, profile) # Create an overdue posted invoice with amount > max_receivable _make_overdue_invoice( self.env, self.partner, self.journal, self.income, amount=500.0, # 500 > 100 (max_receivable) ) order = _make_sale_order(self.env, self.partner, contract, self.product) with self.assertRaises(ValidationError): order.action_confirm() # ------------------------------------------------------------------ # Requirement 16.2 — debt within limit confirms without errors # ------------------------------------------------------------------ def test_confirm_within_debt_limit_ok(self): """ Req 16.2 — confirming a sale order when overdue debt is within max_receivable succeeds without errors. """ profile = _make_profile( self.env, self.journal, self.payment_term, self.receivable, self.payable, max_receivable=10000.0, ) contract = _make_contract(self.env, self.partner, profile) # Create an overdue invoice with amount well below max_receivable _make_overdue_invoice( self.env, self.partner, self.journal, self.income, amount=50.0, # 50 < 10000 (max_receivable) ) order = _make_sale_order(self.env, self.partner, contract, self.product) # Should not raise order.action_confirm() self.assertEqual(order.state, 'sale', "Order should be confirmed (state='sale') when debt is within limit") # ------------------------------------------------------------------ # Requirement 16.3 — _prepare_invoice copies mt_contract_id and osnovanie # ------------------------------------------------------------------ def test_prepare_invoice_copies_contract(self): """ Req 16.3 — _prepare_invoice transfers mt_contract_id and osnovanie from the sale order's contract to the invoice values dict. """ profile = _make_profile( self.env, self.journal, self.payment_term, self.receivable, self.payable, max_receivable=10000.0, ) contract = _make_contract(self.env, self.partner, profile) order = _make_sale_order(self.env, self.partner, contract, self.product) order.action_confirm() invoice_vals = order._prepare_invoice() self.assertEqual( invoice_vals.get('mt_contract_id'), contract.id, "_prepare_invoice should copy mt_contract_id from the contract", ) self.assertIn( 'osnovanie', invoice_vals, "_prepare_invoice should set 'osnovanie' in invoice values", ) self.assertTrue( invoice_vals['osnovanie'], "'osnovanie' should be a non-empty string", ) self.assertIn( contract.name, invoice_vals['osnovanie'], "'osnovanie' should contain the contract name", )