feat: file upload + expense report creation from Discuss attachments
- Discuss bot now reads ir.attachment from incoming messages; file-only messages no longer silently dropped - ZIP files are described (contents listed) and bot asks clarifying question before acting; user's follow-up reply looks back for pending attachments so files don't need to be re-uploaded - receipt_parser: extracts text from ZIP (recursive), JPG/PNG/etc (OCR), PDF (pdfplumber), HTML, TXT - expenses_agent: full rewrite fixing broken method signatures; adds create_expense_sheet / create_expense / attach_receipt flow driven by LLM receipt parsing (Ollama, HIPAA-locked) - master_agent: extra_context threads receipts + user_id into directives - FastAPI /upload multipart endpoint; registered in main.py - Odoo /ai/upload controller proxies files to agent service - ab_ai_bot: dispatch_message_with_files() for multipart uploads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,44 @@ class AbAiBot(models.Model):
|
||||
_logger.error('dispatch_message failed: %s', exc)
|
||||
raise UserError(_('Could not reach AI service: %s') % exc)
|
||||
|
||||
def dispatch_message_with_files(self, user_id, message, attachments, context=None, session_id=None):
|
||||
"""Send a message with file attachments to the /upload endpoint as multipart."""
|
||||
self.ensure_one()
|
||||
import base64
|
||||
url = self._get_service_url() + '/upload'
|
||||
|
||||
files = []
|
||||
for att in attachments:
|
||||
try:
|
||||
data = base64.b64decode(att.datas) if att.datas else b''
|
||||
files.append(('files', (att.name or 'attachment',
|
||||
data,
|
||||
att.mimetype or 'application/octet-stream')))
|
||||
except Exception as exc:
|
||||
_logger.warning('Could not encode attachment %s: %s', att.id, exc)
|
||||
|
||||
# Omit Content-Type so requests sets the multipart boundary automatically
|
||||
headers = {}
|
||||
if self.webhook_secret:
|
||||
headers['X-ActiveBlue-Signature'] = self.webhook_secret
|
||||
|
||||
form_data = {
|
||||
'user_id': str(user_id),
|
||||
'message': message or 'Create an employee expense report from these receipts.',
|
||||
'session_id': session_id or '',
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.post(url, data=form_data, files=files or [('files', ('empty', b'', 'text/plain'))],
|
||||
headers=headers, timeout=120)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except requests.exceptions.Timeout:
|
||||
raise UserError(_('AI service timed out. Please try again.'))
|
||||
except requests.exceptions.RequestException as exc:
|
||||
_logger.error('dispatch_message_with_files failed: %s', exc)
|
||||
raise UserError(_('Could not reach AI service: %s') % exc)
|
||||
|
||||
@api.model
|
||||
def cron_ping_all(self):
|
||||
any_online = False
|
||||
|
||||
Reference in New Issue
Block a user