package handlers import ( "database/sql" "encoding/json" "net/http" "time" ) type ReceiptHandler struct { DB *sql.DB } func NewReceiptHandler(db *sql.DB) *ReceiptHandler { return &ReceiptHandler{DB: db} } type Receipt struct { ID string `json:"id"` EmployeeID string `json:"employee_id"` ExpenseID *string `json:"expense_id"` ImageURL string `json:"image_url"` OCRText string `json:"ocr_text"` OCRData map[string]interface{} `json:"ocr_data"` OCRConfidence float64 `json:"ocr_confidence"` Status string `json:"status"` ProcessedAt *time.Time `json:"processed_at"` CreatedAt time.Time `json:"created_at"` } func (h *ReceiptHandler) ListReceipts(w http.ResponseWriter, r *http.Request) { status := r.URL.Query().Get("status") if status == "" { status = "all" } var query string var args []interface{} if status == "all" { query = `SELECT id, employee_id, expense_id, image_url, ocr_text, ocr_data, ocr_confidence, status, processed_at, created_at FROM boc_receipts ORDER BY created_at DESC LIMIT 100` } else { query = `SELECT id, employee_id, expense_id, image_url, ocr_text, ocr_data, ocr_confidence, status, processed_at, created_at FROM boc_receipts WHERE status = $1 ORDER BY created_at DESC LIMIT 100` args = append(args, status) } rows, err := h.DB.Query(query, args...) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() receipts := []Receipt{} for rows.Next() { var rc Receipt var ocrData []byte if err := rows.Scan(&rc.ID, &rc.EmployeeID, &rc.ExpenseID, &rc.ImageURL, &rc.OCRText, &ocrData, &rc.OCRConfidence, &rc.Status, &rc.ProcessedAt, &rc.CreatedAt); err != nil { continue } json.Unmarshal(ocrData, &rc.OCRData) receipts = append(receipts, rc) } writeJSON(w, http.StatusOK, map[string]interface{}{ "receipts": receipts, "total": len(receipts), }) } func (h *ReceiptHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) { var req struct { EmployeeID string `json:"employee_id"` ImageURL string `json:"image_url"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } var id string err := h.DB.QueryRow(` INSERT INTO boc_receipts (employee_id, image_url, status) VALUES ($1, $2, 'pending') RETURNING id `, req.EmployeeID, req.ImageURL).Scan(&id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to upload receipt") return } // TODO: Trigger async OCR processing go h.processOCR(id, req.ImageURL) writeJSON(w, http.StatusCreated, map[string]interface{}{ "id": id, "message": "Receipt uploaded, OCR processing started", }) } func (h *ReceiptHandler) processOCR(receiptID, imageURL string) { // Placeholder for OCR processing // In production, this would call an OCR service (AWS Textract, Google Vision, etc.) // Simulate OCR processing time.Sleep(2 * time.Second) ocrData := map[string]interface{}{ "amount": 125.50, "date": time.Now().Format("2006-01-02"), "vendor": "Example Store", "category": "Mat", } ocrJSON, _ := json.Marshal(ocrData) h.DB.Exec(` UPDATE boc_receipts SET ocr_text = $1, ocr_data = $2, ocr_confidence = $3, status = 'processed', processed_at = NOW() WHERE id = $4 `, "Example Store\nDate: 2026-07-12\nTotal: $125.50", ocrJSON, 0.95, receiptID) } func (h *ReceiptHandler) ApproveReceipt(w http.ResponseWriter, r *http.Request) { var req struct { ReceiptID string `json:"receipt_id"` Amount float64 `json:"amount"` Category string `json:"category"` Description string `json:"description"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } tx, err := h.DB.Begin() if err != nil { writeError(w, http.StatusInternalServerError, "transaction error") return } defer tx.Rollback() // Create expense from receipt var expenseID string err = tx.QueryRow(` INSERT INTO boc_expenses (category, description, amount, currency, status, receipt_url) VALUES ($1, $2, $3, 'USD', 'pending', (SELECT image_url FROM boc_receipts WHERE id = $4)) RETURNING id `, req.Category, req.Description, req.Amount, req.ReceiptID).Scan(&expenseID) if err != nil { writeError(w, http.StatusInternalServerError, "failed to create expense") return } // Link receipt to expense _, err = tx.Exec(`UPDATE boc_receipts SET expense_id = $1, status = 'approved' WHERE id = $2`, expenseID, req.ReceiptID) if err != nil { writeError(w, http.StatusInternalServerError, "failed to update receipt") return } if err := tx.Commit(); err != nil { writeError(w, http.StatusInternalServerError, "commit failed") return } writeJSON(w, http.StatusOK, map[string]interface{}{ "expense_id": expenseID, "message": "Receipt approved and expense created", }) }