Files
Odoo-Modules/fusion_accounting_documents/models/documents_document.py
gsinghpal 71f39c8d33 feat(fusion_accounting_documents): Documents app <-> invoice bridge
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
2026-04-20 00:34:50 -04:00

72 lines
2.3 KiB
Python

"""Bridge documents.document to accounting moves.
Adds a Many2one link to the created invoice/move, a computed
``is_invoice_candidate`` flag for PDFs/images that have not yet been
turned into a vendor bill, and the ``action_create_invoice`` entry
point used by both the form button and the server action.
"""
from odoo import _, api, fields, models
from odoo.exceptions import UserError
INVOICE_CANDIDATE_MIMETYPES = (
'application/pdf',
'image/png',
'image/jpeg',
'image/jpg',
)
class DocumentsDocument(models.Model):
_inherit = 'documents.document'
move_id = fields.Many2one(
'account.move',
string='Linked Invoice/Move',
copy=False,
ondelete='set null',
help="The accounting move this document was used to create.",
)
is_invoice_candidate = fields.Boolean(
string='Is Invoice Candidate',
compute='_compute_is_invoice_candidate',
store=True,
help="True when this document looks like a vendor bill "
"(PDF/image binary) and has not yet been linked to a move.",
)
@api.depends('mimetype', 'type', 'move_id')
def _compute_is_invoice_candidate(self):
for d in self:
d.is_invoice_candidate = (
d.type == 'binary'
and (d.mimetype or '') in INVOICE_CANDIDATE_MIMETYPES
and not d.move_id
)
def action_create_invoice(self):
"""Open the wizard to create a vendor invoice from this document."""
self.ensure_one()
if self.move_id:
raise UserError(_(
"This document is already linked to invoice %s.",
self.move_id.display_name,
))
if self.type == 'folder':
raise UserError(_(
"Folders cannot be turned into invoices."
))
if (self.mimetype or '') not in INVOICE_CANDIDATE_MIMETYPES:
raise UserError(_(
"Only PDF or image documents can be turned into invoices."
))
return {
'type': 'ir.actions.act_window',
'name': _('Create Invoice from Document'),
'res_model': 'fusion.create.invoice.from.document.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_document_id': self.id},
}