package handler import ( "encoding/json" "fmt" "net/http" "strings" "time" ) func (h *Handler) handleCreateStatement(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } profile := getProfile(r.Context()) dealID := r.FormValue("deal_id") title := strings.TrimSpace(r.FormValue("title")) body := strings.TrimSpace(r.FormValue("body")) if dealID == "" || title == "" || body == "" { http.Error(w, "deal_id, title, and body are required", 400) return } respID := generateID("resp") _, err := h.db.Exec( `INSERT INTO responses (id, deal_id, type, title, body, extraction_status, created_by) VALUES (?, ?, 'statement', ?, ?, 'pending', ?)`, respID, dealID, title, body, profile.ID) if err != nil { http.Error(w, fmt.Sprintf("Error creating statement: %v", err), 500) return } // Enqueue for chunking + embedding + matching if h.extractor != nil { h.enqueueExtraction(respID, "", dealID) } http.Redirect(w, r, "/deals/"+dealID+"?tab=requests", http.StatusSeeOther) } func (h *Handler) handleConfirmLink(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } profile := getProfile(r.Context()) requestID := r.FormValue("request_id") responseID := r.FormValue("response_id") chunkID := r.FormValue("chunk_id") if requestID == "" || responseID == "" || chunkID == "" { http.Error(w, "Missing fields", 400) return } _, err := h.db.Exec( "UPDATE request_links SET confirmed = 1, confirmed_by = ?, confirmed_at = ? WHERE request_id = ? AND response_id = ? AND chunk_id = ?", profile.ID, time.Now().UTC().Format("2006-01-02 15:04:05"), requestID, responseID, chunkID) if err != nil { http.Error(w, "Error confirming link", 500) return } // Update request status to answered if not already h.db.Exec("UPDATE diligence_requests SET status = 'answered' WHERE id = ? AND status != 'answered'", requestID) w.Header().Set("Content-Type", "text/html") w.Write([]byte(`Confirmed`)) } func (h *Handler) handleRejectLink(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } requestID := r.FormValue("request_id") responseID := r.FormValue("response_id") chunkID := r.FormValue("chunk_id") if requestID == "" || responseID == "" || chunkID == "" { http.Error(w, "Missing fields", 400) return } h.db.Exec("DELETE FROM request_links WHERE request_id = ? AND response_id = ? AND chunk_id = ?", requestID, responseID, chunkID) w.Header().Set("Content-Type", "text/html") w.Write([]byte(`Rejected`)) } func (h *Handler) handlePendingLinks(w http.ResponseWriter, r *http.Request) { dealID := strings.TrimPrefix(r.URL.Path, "/deals/responses/pending/") if dealID == "" { http.Error(w, "Missing deal ID", 400) return } rows, err := h.db.Query(` SELECT rl.request_id, rl.response_id, rl.chunk_id, rl.confidence, dr.description, r.title, r.type FROM request_links rl JOIN diligence_requests dr ON rl.request_id = dr.id JOIN responses r ON rl.response_id = r.id WHERE dr.deal_id = ? AND rl.confirmed = 0 AND rl.auto_linked = 1 ORDER BY rl.confidence DESC `, dealID) if err != nil { http.Error(w, "Error loading pending links", 500) return } defer rows.Close() type pendingLink struct { RequestID string `json:"request_id"` ResponseID string `json:"response_id"` ChunkID string `json:"chunk_id"` Confidence float64 `json:"confidence"` RequestDesc string `json:"request_desc"` ResponseTitle string `json:"response_title"` ResponseType string `json:"response_type"` } var links []pendingLink for rows.Next() { var l pendingLink rows.Scan(&l.RequestID, &l.ResponseID, &l.ChunkID, &l.Confidence, &l.RequestDesc, &l.ResponseTitle, &l.ResponseType) links = append(links, l) } if links == nil { links = []pendingLink{} } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(links) } func (h *Handler) handleSaveAssignmentRules(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } dealID := r.FormValue("deal_id") rulesJSON := r.FormValue("rules") if dealID == "" { http.Error(w, "Missing deal_id", 400) return } type ruleInput struct { Keyword string `json:"keyword"` AssigneeID string `json:"assignee_id"` } var rules []ruleInput if err := json.Unmarshal([]byte(rulesJSON), &rules); err != nil { http.Error(w, "Invalid rules JSON", 400) return } // Delete existing rules and insert new set h.db.Exec("DELETE FROM assignment_rules WHERE deal_id = ?", dealID) for _, rule := range rules { if rule.Keyword == "" || rule.AssigneeID == "" { continue } id := generateID("rule") h.db.Exec("INSERT INTO assignment_rules (id, deal_id, keyword, assignee_id) VALUES (?, ?, ?, ?)", id, dealID, rule.Keyword, rule.AssigneeID) } // Re-run auto-assignment h.autoAssignByRules(dealID) http.Redirect(w, r, "/deals/"+dealID+"?tab=requests", http.StatusSeeOther) } func (h *Handler) handleGetAssignmentRules(w http.ResponseWriter, r *http.Request) { dealID := strings.TrimPrefix(r.URL.Path, "/deals/assignment-rules/") if dealID == "" { http.Error(w, "Missing deal ID", 400) return } rows, err := h.db.Query(` SELECT ar.id, ar.keyword, ar.assignee_id, COALESCE(p.full_name, ar.assignee_id) FROM assignment_rules ar LEFT JOIN profiles p ON ar.assignee_id = p.id WHERE ar.deal_id = ? ORDER BY ar.keyword `, dealID) if err != nil { http.Error(w, "Error loading rules", 500) return } defer rows.Close() type ruleOut struct { ID string `json:"id"` Keyword string `json:"keyword"` AssigneeID string `json:"assignee_id"` AssigneeName string `json:"assignee_name"` } var rules []ruleOut for rows.Next() { var r ruleOut rows.Scan(&r.ID, &r.Keyword, &r.AssigneeID, &r.AssigneeName) rules = append(rules, r) } if rules == nil { rules = []ruleOut{} } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(rules) } // autoAssignByRules assigns unassigned requests based on keyword→assignee rules. func (h *Handler) autoAssignByRules(dealID string) { // Load rules ruleRows, err := h.db.Query("SELECT keyword, assignee_id FROM assignment_rules WHERE deal_id = ?", dealID) if err != nil { return } defer ruleRows.Close() type rule struct { keyword, assigneeID string } var rules []rule for ruleRows.Next() { var r rule ruleRows.Scan(&r.keyword, &r.assigneeID) rules = append(rules, r) } if len(rules) == 0 { return } // Load unassigned requests reqRows, err := h.db.Query("SELECT id, section, description FROM diligence_requests WHERE deal_id = ? AND (assignee_id = '' OR assignee_id IS NULL)", dealID) if err != nil { return } defer reqRows.Close() type reqInfo struct { id, section, desc string } var reqs []reqInfo for reqRows.Next() { var r reqInfo reqRows.Scan(&r.id, &r.section, &r.desc) reqs = append(reqs, r) } for _, req := range reqs { text := strings.ToLower(req.section + " " + req.desc) for _, rule := range rules { if strings.Contains(text, strings.ToLower(rule.keyword)) { h.db.Exec("UPDATE diligence_requests SET assignee_id = ? WHERE id = ?", rule.assigneeID, req.id) break } } } }