| import { updateTransaction } from "@midday/db/queries"; |
| import { DocumentClient } from "@midday/documents"; |
| import { triggerJob } from "@midday/job-client"; |
| import { createClient } from "@midday/supabase/job"; |
| import type { Job } from "bullmq"; |
| import type { ProcessTransactionAttachmentPayload } from "../../schemas/transactions"; |
| import { getDb } from "../../utils/db"; |
| import { convertHeicToJpeg } from "../../utils/image-processing"; |
| import { BaseProcessor } from "../base"; |
|
|
| |
| |
| |
| |
| export class ProcessTransactionAttachmentProcessor extends BaseProcessor<ProcessTransactionAttachmentPayload> { |
| async process(job: Job<ProcessTransactionAttachmentPayload>): Promise<void> { |
| const { transactionId, mimetype, filePath, teamId } = job.data; |
| const supabase = createClient(); |
|
|
| this.logger.info("Processing transaction attachment", { |
| transactionId, |
| filePath: filePath.join("/"), |
| mimetype, |
| teamId, |
| }); |
|
|
| |
| if (mimetype === "image/heic") { |
| this.logger.info("Converting HEIC to JPG", { |
| filePath: filePath.join("/"), |
| }); |
|
|
| const { data } = await supabase.storage |
| .from("vault") |
| .download(filePath.join("/")); |
|
|
| if (!data) { |
| throw new Error("File not found"); |
| } |
|
|
| const buffer = await data.arrayBuffer(); |
|
|
| |
| const { buffer: image } = await convertHeicToJpeg(buffer, this.logger); |
|
|
| |
| const { data: uploadedData } = await supabase.storage |
| .from("vault") |
| .upload(filePath.join("/"), image, { |
| contentType: "image/jpeg", |
| upsert: true, |
| }); |
|
|
| if (!uploadedData) { |
| throw new Error("Failed to upload converted image"); |
| } |
| } |
|
|
| const filename = filePath.at(-1); |
|
|
| |
| |
| const { data: signedUrlData } = await supabase.storage |
| .from("vault") |
| .createSignedUrl(filePath.join("/"), 600); |
|
|
| if (!signedUrlData) { |
| throw new Error("File not found"); |
| } |
|
|
| const document = new DocumentClient(); |
|
|
| this.logger.info("Extracting tax information from document", { |
| transactionId, |
| filename, |
| mimetype, |
| }); |
|
|
| const result = await document.getInvoiceOrReceipt({ |
| documentUrl: signedUrlData.signedUrl, |
| mimetype, |
| }); |
|
|
| |
| if (result.tax_rate && result.tax_type) { |
| this.logger.info("Updating transaction with tax information", { |
| transactionId, |
| taxRate: result.tax_rate, |
| taxType: result.tax_type, |
| }); |
|
|
| const db = getDb(); |
| await updateTransaction(db, { |
| id: transactionId, |
| teamId, |
| taxRate: result.tax_rate ?? undefined, |
| taxType: result.tax_type ?? undefined, |
| }); |
|
|
| this.logger.info("Transaction updated with tax information", { |
| transactionId, |
| taxRate: result.tax_rate, |
| taxType: result.tax_type, |
| }); |
| } else { |
| this.logger.info("No tax information found in document", { |
| transactionId, |
| }); |
| } |
|
|
| |
| |
| try { |
| await triggerJob( |
| "process-document", |
| { |
| mimetype, |
| filePath, |
| teamId, |
| }, |
| "documents", |
| ); |
|
|
| this.logger.info("Triggered document processing for classification", { |
| transactionId, |
| filePath: filePath.join("/"), |
| }); |
| } catch (error) { |
| this.logger.warn("Failed to trigger document processing (non-critical)", { |
| transactionId, |
| error: error instanceof Error ? error.message : "Unknown error", |
| }); |
| |
| } |
| } |
| } |
|
|