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
}
}
}
}