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
This commit is contained in:
1
fusion_accounting_documents/wizards/__init__.py
Normal file
1
fusion_accounting_documents/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import create_invoice_from_document
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Wizard to create a vendor invoice from a Documents document.
|
||||
|
||||
The wizard creates an empty draft ``account.move`` of the chosen
|
||||
move type, copies the document's binary attachment onto the new
|
||||
move, posts a chatter note linking back to the source document,
|
||||
and finally stores the move on ``documents.document.move_id`` so
|
||||
the source no longer appears as an invoice candidate.
|
||||
"""
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
MOVE_TYPE_LABELS = {
|
||||
'in_invoice': _('Vendor Bill'),
|
||||
'in_refund': _('Vendor Credit Note'),
|
||||
}
|
||||
|
||||
|
||||
class CreateInvoiceFromDocumentWizard(models.TransientModel):
|
||||
_name = 'fusion.create.invoice.from.document.wizard'
|
||||
_description = 'Create Vendor Invoice from Document'
|
||||
|
||||
document_id = fields.Many2one(
|
||||
'documents.document',
|
||||
string='Source Document',
|
||||
required=True,
|
||||
readonly=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
document_name = fields.Char(related='document_id.name', readonly=True)
|
||||
document_mimetype = fields.Char(related='document_id.mimetype', readonly=True)
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Vendor',
|
||||
domain="[('supplier_rank', '>', 0)]",
|
||||
)
|
||||
move_type = fields.Selection(
|
||||
[
|
||||
('in_invoice', 'Vendor Bill'),
|
||||
('in_refund', 'Vendor Credit Note'),
|
||||
],
|
||||
string='Type',
|
||||
default='in_invoice',
|
||||
required=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
required=True,
|
||||
)
|
||||
journal_id = fields.Many2one(
|
||||
'account.journal',
|
||||
string='Journal',
|
||||
domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]",
|
||||
default=lambda self: self._default_journal(),
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _default_journal(self):
|
||||
return self.env['account.journal'].search(
|
||||
[('type', '=', 'purchase'),
|
||||
('company_id', '=', self.env.company.id)],
|
||||
limit=1,
|
||||
)
|
||||
|
||||
@api.onchange('company_id')
|
||||
def _onchange_company_id(self):
|
||||
if self.journal_id and self.journal_id.company_id != self.company_id:
|
||||
self.journal_id = self.env['account.journal'].search(
|
||||
[('type', '=', 'purchase'),
|
||||
('company_id', '=', self.company_id.id)],
|
||||
limit=1,
|
||||
)
|
||||
|
||||
def action_create_invoice(self):
|
||||
self.ensure_one()
|
||||
if not self.document_id:
|
||||
raise UserError(_("No document selected."))
|
||||
if self.document_id.move_id:
|
||||
raise UserError(_(
|
||||
"Document %(doc)s is already linked to invoice %(inv)s.",
|
||||
doc=self.document_id.display_name,
|
||||
inv=self.document_id.move_id.display_name,
|
||||
))
|
||||
if not self.journal_id:
|
||||
raise UserError(_(
|
||||
"No purchase journal configured for company %s.",
|
||||
self.company_id.display_name,
|
||||
))
|
||||
|
||||
move_vals = {
|
||||
'move_type': self.move_type,
|
||||
'journal_id': self.journal_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
}
|
||||
if self.partner_id:
|
||||
move_vals['partner_id'] = self.partner_id.id
|
||||
|
||||
move = self.env['account.move'].create(move_vals)
|
||||
|
||||
attachment = self.document_id.attachment_id
|
||||
if attachment:
|
||||
attachment_copy = attachment.copy({
|
||||
'res_model': 'account.move',
|
||||
'res_id': move.id,
|
||||
})
|
||||
move.message_post(
|
||||
body=_(
|
||||
"Created from Documents source: <strong>%s</strong>",
|
||||
self.document_id.name,
|
||||
),
|
||||
attachment_ids=[attachment_copy.id],
|
||||
)
|
||||
else:
|
||||
move.message_post(body=_(
|
||||
"Created from Documents source: <strong>%s</strong> "
|
||||
"(no attachment to copy).",
|
||||
self.document_id.name,
|
||||
))
|
||||
|
||||
self.document_id.move_id = move.id
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': MOVE_TYPE_LABELS.get(self.move_type, _('Invoice')),
|
||||
'res_model': 'account.move',
|
||||
'view_mode': 'form',
|
||||
'res_id': move.id,
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_create_invoice_from_document_wizard_form" model="ir.ui.view">
|
||||
<field name="name">fusion.create.invoice.from.document.wizard.form</field>
|
||||
<field name="model">fusion.create.invoice.from.document.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create Invoice from Document">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="document_id" invisible="1"/>
|
||||
<field name="document_name" readonly="1"/>
|
||||
<field name="document_mimetype" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="move_type"/>
|
||||
<field name="partner_id" options="{'no_create': True}"/>
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="journal_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_create_invoice"
|
||||
string="Create Invoice"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
data-hotkey="q"/>
|
||||
<button string="Cancel"
|
||||
class="btn-secondary"
|
||||
special="cancel"
|
||||
data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user