import base64 import io from unittest.mock import patch from PIL import Image, ImageDraw from odoo.exceptions import UserError from odoo.tests import tagged from odoo.tests.common import TransactionCase @tagged('post_install', '-at_install') class TestInvoiceOcrFlow(TransactionCase): def setUp(self): super().setUp() self.partner = self.env['res.partner'].create({ 'name': 'Test Vendor', 'supplier_rank': 1, }) self.move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner.id, }) def test_ocr_state_default(self): self.assertEqual(self.move.ocr_state, 'not_requested') def test_action_request_ocr_no_attachment_raises(self): with self.assertRaises(UserError): self.move.action_request_ocr() def test_action_request_ocr_with_image(self): img = Image.new('RGB', (800, 120), color='white') draw = ImageDraw.Draw(img) try: from PIL import ImageFont font = ImageFont.truetype( '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 36, ) except Exception: font = None draw.text((20, 30), "TOTAL $50.00 INV-9999", fill='black', font=font) buf = io.BytesIO() img.save(buf, format='PNG') self.env['ir.attachment'].create({ 'name': 'test_invoice.png', 'datas': base64.b64encode(buf.getvalue()), 'res_model': 'account.move', 'res_id': self.move.id, 'mimetype': 'image/png', }) # Mock the LLM call to avoid a real API roundtrip. with patch( 'odoo.addons.fusion_accounting_ocr.models.account_move.parse_invoice_fields', return_value={ 'vendor_name': None, 'invoice_number': 'INV-9999', 'invoice_date': None, 'due_date': None, 'currency': None, 'subtotal': None, 'tax_total': None, 'total': 50.0, 'line_items': [], }, ): self.move.action_request_ocr() self.assertEqual(self.move.ocr_state, 'done') self.assertEqual(self.move.ocr_backend, 'tesseract') self.assertGreater(self.move.ocr_confidence, 0) self.assertIsNotNone(self.move.ocr_extracted_data) # Parsed invoice_number should land on the invoice's ref field. self.assertEqual(self.move.ref, 'INV-9999') # OCR log row was created. self.assertEqual(len(self.move.ocr_log_ids), 1) log = self.move.ocr_log_ids self.assertEqual(log.backend, 'tesseract') self.assertGreater(log.raw_text_length, 0) def test_apply_does_not_overwrite_user_entered_ref(self): self.move.ref = 'USER-SET-REF' img = Image.new('RGB', (400, 80), color='white') ImageDraw.Draw(img).text((10, 30), "INV-7777", fill='black') buf = io.BytesIO() img.save(buf, format='PNG') self.env['ir.attachment'].create({ 'name': 't.png', 'datas': base64.b64encode(buf.getvalue()), 'res_model': 'account.move', 'res_id': self.move.id, 'mimetype': 'image/png', }) with patch( 'odoo.addons.fusion_accounting_ocr.models.account_move.parse_invoice_fields', return_value={ 'vendor_name': None, 'invoice_number': 'INV-7777', 'invoice_date': None, 'due_date': None, 'currency': None, 'subtotal': None, 'tax_total': None, 'total': None, 'line_items': [], }, ): self.move.action_request_ocr() # User-entered ref must not be overwritten. self.assertEqual(self.move.ref, 'USER-SET-REF') def test_only_vendor_bills_supported(self): customer_invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner.id, }) with self.assertRaises(UserError): customer_invoice.action_request_ocr()