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:
@@ -84,3 +84,64 @@ class ExpensesTools:
|
||||
async def post_chatter_note(self, model: str, record_id: int, note: str) -> bool:
|
||||
await self._o.call(model, 'message_post', [[record_id]], {'body': note, 'message_type': 'comment'})
|
||||
return True
|
||||
|
||||
async def get_employee_id_for_user(self, user_id) -> int | None:
|
||||
if not user_id:
|
||||
return None
|
||||
try:
|
||||
records = await self._o.search_read(
|
||||
'hr.employee', [('user_id', '=', int(user_id))], ['id', 'name'], limit=1)
|
||||
return records[0]['id'] if records else None
|
||||
except Exception as exc:
|
||||
logger.warning('get_employee_id_for_user failed user_id=%s: %s', user_id, exc)
|
||||
return None
|
||||
|
||||
async def get_default_expense_product(self) -> int | None:
|
||||
try:
|
||||
records = await self._o.search_read(
|
||||
'product.product',
|
||||
[('can_be_expensed', '=', True), ('type', '=', 'service')],
|
||||
['id', 'name'], limit=1)
|
||||
return records[0]['id'] if records else None
|
||||
except Exception as exc:
|
||||
logger.warning('get_default_expense_product failed: %s', exc)
|
||||
return None
|
||||
|
||||
async def create_expense_sheet(self, name: str, employee_id: int):
|
||||
return await self._o.create('hr.expense.sheet', {
|
||||
'name': name,
|
||||
'employee_id': employee_id,
|
||||
})
|
||||
|
||||
async def create_expense(self, sheet_id: int, employee_id: int, name: str,
|
||||
total_amount: float, date: str, product_id: int = None,
|
||||
description: str = ''):
|
||||
vals: dict = {
|
||||
'name': name,
|
||||
'employee_id': employee_id,
|
||||
'sheet_id': sheet_id,
|
||||
'total_amount': total_amount,
|
||||
'quantity': 1.0,
|
||||
}
|
||||
if date:
|
||||
vals['date'] = date
|
||||
if product_id:
|
||||
vals['product_id'] = product_id
|
||||
if description:
|
||||
vals['description'] = description
|
||||
return await self._o.create('hr.expense', vals)
|
||||
|
||||
async def attach_receipt(self, model: str, record_id: int, filename: str,
|
||||
file_b64: str, mimetype: str) -> bool:
|
||||
try:
|
||||
await self._o.create('ir.attachment', {
|
||||
'name': filename,
|
||||
'datas': file_b64,
|
||||
'res_model': model,
|
||||
'res_id': record_id,
|
||||
'mimetype': mimetype,
|
||||
})
|
||||
return True
|
||||
except Exception as exc:
|
||||
logger.warning('attach_receipt failed %s/%s: %s', model, record_id, exc)
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user