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:
@@ -3,7 +3,7 @@ import logging
|
||||
import requests
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.http import request, Response
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -58,3 +58,41 @@ class AiApprovalController(http.Controller):
|
||||
session_id=session_id,
|
||||
)
|
||||
return result
|
||||
|
||||
@http.route('/ai/upload', type='http', auth='user', methods=['POST'], csrf=False)
|
||||
def upload(self, **kwargs):
|
||||
bot = request.env['ab.ai.bot'].sudo().get_active_bot()
|
||||
if not bot:
|
||||
return Response(
|
||||
json.dumps({'error': 'No bot configured'}),
|
||||
content_type='application/json', status=503)
|
||||
|
||||
url = bot._get_service_url() + '/upload'
|
||||
message = request.httprequest.form.get(
|
||||
'message', 'Create an employee expense report from these receipts.')
|
||||
session_id = request.httprequest.form.get('session_id', '')
|
||||
|
||||
files_data = [
|
||||
('files', (f.filename, f.read(), f.content_type or 'application/octet-stream'))
|
||||
for f in request.httprequest.files.getlist('files')
|
||||
]
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
url,
|
||||
data={
|
||||
'user_id': str(request.env.user.id),
|
||||
'message': message,
|
||||
'session_id': session_id,
|
||||
},
|
||||
files=files_data or [('files', ('empty', b'', 'application/octet-stream'))],
|
||||
headers=bot._build_headers(),
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return Response(resp.text, content_type='application/json')
|
||||
except Exception as exc:
|
||||
_logger.error('upload proxy failed: %s', exc)
|
||||
return Response(
|
||||
json.dumps({'error': str(exc), 'reply': 'Upload failed. Please try again.'}),
|
||||
content_type='application/json', status=500)
|
||||
|
||||
Reference in New Issue
Block a user