Replaces Enterprise's documents_account with a Fusion-native bridge. When a PDF/image lands in the Documents app, users can convert it into a draft vendor bill via a wizard that copies the document's binary onto the new account.move and posts a chatter note linking back to the source document. Adds: - documents.document.move_id (Many2one to the linked invoice) - documents.document.is_invoice_candidate (computed; True for unlinked PDF/image binaries) - documents.document.action_create_invoice() opens the wizard - account.move.source_document_ids reverse linkage + statinfo button - fusion.create.invoice.from.document.wizard (TransientModel + form) - ir.actions.server bound to documents.document so the workflow appears in the kanban/list Actions menu (the Documents app has no regular form view to inherit from in v19) The wizard: - defaults to the company's first purchase journal - supports vendor bill or vendor credit note - copies the source attachment onto the new move - posts a chatter note linking back - marks the document linked so it stops appearing as a candidate Auto-installs when documents + fusion_accounting_core are both present. 8 unit tests cover the candidate flag, wizard happy path, attachment copy, reverse linkage, already-linked guard, non-PDF guard, and credit-note creation. Made-with: Cursor
141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
"""Tests for the documents.document <-> account.move bridge."""
|
|
|
|
import base64
|
|
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests import tagged
|
|
from odoo.tests.common import TransactionCase
|
|
|
|
|
|
@tagged('post_install', '-at_install', 'fusion_accounting_documents')
|
|
class TestDocumentToInvoice(TransactionCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.vendor = cls.env['res.partner'].create({
|
|
'name': 'Test Doc Vendor',
|
|
'supplier_rank': 1,
|
|
})
|
|
cls.purchase_journal = cls.env['account.journal'].search(
|
|
[('type', '=', 'purchase'),
|
|
('company_id', '=', cls.env.company.id)],
|
|
limit=1,
|
|
)
|
|
|
|
def _make_document(self, name='Test Bill PDF',
|
|
mimetype='application/pdf',
|
|
payload=b'%PDF-fake-bill-content'):
|
|
attachment = self.env['ir.attachment'].create({
|
|
'name': name,
|
|
'datas': base64.b64encode(payload),
|
|
'mimetype': mimetype,
|
|
})
|
|
Document = self.env['documents.document']
|
|
doc_vals = {
|
|
'name': name,
|
|
'attachment_id': attachment.id,
|
|
'mimetype': mimetype,
|
|
'type': 'binary',
|
|
}
|
|
if 'folder_id' in Document._fields:
|
|
folder = Document.search(
|
|
[('type', '=', 'folder')], limit=1,
|
|
)
|
|
if folder:
|
|
doc_vals['folder_id'] = folder.id
|
|
return Document.create(doc_vals)
|
|
|
|
def test_invoice_candidate_flag_pdf(self):
|
|
doc = self._make_document()
|
|
self.assertTrue(doc.is_invoice_candidate)
|
|
|
|
def test_invoice_candidate_flag_image(self):
|
|
doc = self._make_document(
|
|
name='scan.png',
|
|
mimetype='image/png',
|
|
payload=b'\x89PNG\r\n\x1a\nfake',
|
|
)
|
|
self.assertTrue(doc.is_invoice_candidate)
|
|
|
|
def test_invoice_candidate_flag_text_excluded(self):
|
|
doc = self._make_document(
|
|
name='note.txt',
|
|
mimetype='text/plain',
|
|
payload=b'just a note',
|
|
)
|
|
self.assertFalse(doc.is_invoice_candidate)
|
|
|
|
def test_action_create_invoice_opens_wizard(self):
|
|
doc = self._make_document()
|
|
action = doc.action_create_invoice()
|
|
self.assertEqual(action['type'], 'ir.actions.act_window')
|
|
self.assertEqual(
|
|
action['res_model'],
|
|
'fusion.create.invoice.from.document.wizard',
|
|
)
|
|
self.assertEqual(action['target'], 'new')
|
|
self.assertEqual(action['context']['default_document_id'], doc.id)
|
|
|
|
def test_wizard_creates_invoice_and_links(self):
|
|
doc = self._make_document()
|
|
wizard = self.env['fusion.create.invoice.from.document.wizard'].create({
|
|
'document_id': doc.id,
|
|
'partner_id': self.vendor.id,
|
|
'move_type': 'in_invoice',
|
|
})
|
|
self.assertTrue(wizard.journal_id, "Default journal should resolve")
|
|
action = wizard.action_create_invoice()
|
|
|
|
self.assertEqual(action['res_model'], 'account.move')
|
|
move = self.env['account.move'].browse(action['res_id'])
|
|
self.assertEqual(move.move_type, 'in_invoice')
|
|
self.assertEqual(move.partner_id, self.vendor)
|
|
|
|
self.assertEqual(doc.move_id, move)
|
|
self.assertFalse(doc.is_invoice_candidate,
|
|
"Linked docs should no longer be candidates")
|
|
|
|
self.assertEqual(move.source_document_count, 1)
|
|
self.assertIn(doc, move.source_document_ids)
|
|
|
|
attachments = self.env['ir.attachment'].search([
|
|
('res_model', '=', 'account.move'),
|
|
('res_id', '=', move.id),
|
|
])
|
|
self.assertTrue(
|
|
attachments,
|
|
"An attachment copy should land on the new move",
|
|
)
|
|
|
|
def test_action_create_invoice_already_linked_raises(self):
|
|
doc = self._make_document()
|
|
existing_move = self.env['account.move'].create({
|
|
'move_type': 'in_invoice',
|
|
'partner_id': self.vendor.id,
|
|
})
|
|
doc.move_id = existing_move.id
|
|
with self.assertRaises(UserError):
|
|
doc.action_create_invoice()
|
|
|
|
def test_action_create_invoice_non_candidate_raises(self):
|
|
doc = self._make_document(
|
|
name='note.txt',
|
|
mimetype='text/plain',
|
|
payload=b'hello',
|
|
)
|
|
with self.assertRaises(UserError):
|
|
doc.action_create_invoice()
|
|
|
|
def test_wizard_creates_credit_note(self):
|
|
doc = self._make_document(name='credit-note.pdf')
|
|
wizard = self.env['fusion.create.invoice.from.document.wizard'].create({
|
|
'document_id': doc.id,
|
|
'partner_id': self.vendor.id,
|
|
'move_type': 'in_refund',
|
|
})
|
|
action = wizard.action_create_invoice()
|
|
move = self.env['account.move'].browse(action['res_id'])
|
|
self.assertEqual(move.move_type, 'in_refund')
|
|
self.assertEqual(doc.move_id, move)
|