package handler import ( "bufio" "encoding/csv" "fmt" "io" "net/http" "strings" "dealroom/internal/rbac" "dealroom/templates" ) func (h *Handler) handleRequestList(w http.ResponseWriter, r *http.Request) { profile := getProfile(r.Context()) deals := h.getDeals(profile) // Get all requests grouped by deal dealRequests := make(map[string][]*templates.RequestsByGroup) for _, deal := range deals { reqs := h.getRequests(deal.ID, profile) // Group by buyer_group groups := make(map[string][]*templates.RequestItem) for _, req := range reqs { group := req.BuyerGroup if group == "" { group = "Unassigned" } groups[group] = append(groups[group], &templates.RequestItem{ ID: req.ID, ItemNumber: req.ItemNumber, Section: req.Section, Description: req.Description, Priority: req.Priority, AtlasStatus: req.AtlasStatus, AtlasNote: req.AtlasNote, Confidence: req.Confidence, BuyerComment: req.BuyerComment, SellerComment: req.SellerComment, BuyerGroup: req.BuyerGroup, }) } var groupList []*templates.RequestsByGroup for name, items := range groups { groupList = append(groupList, &templates.RequestsByGroup{Name: name, Requests: items}) } dealRequests[deal.ID] = groupList } templates.RequestListPage(profile, deals, dealRequests).Render(r.Context(), w) } func (h *Handler) handleRequestListUpload(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } profile := getProfile(r.Context()) err := r.ParseMultipartForm(10 << 20) // 10MB if err != nil { http.Error(w, "Error parsing form", 400) return } dealID := r.FormValue("deal_id") targetGroup := r.FormValue("target_group") // "all" or specific group name uploadMode := r.FormValue("upload_mode") // "replace", "add", "group_specific" convertFolders := r.FormValue("convert_folders") // "yes" or "no" if dealID == "" { http.Error(w, "Deal ID required", 400) return } file, _, err := r.FormFile("request_list") if err != nil { http.Error(w, "File is required", 400) return } defer file.Close() // Parse CSV (supports basic CSV format: section, item_number, description, priority) reader := csv.NewReader(bufio.NewReader(file)) reader.FieldsPerRecord = -1 // variable fields reader.TrimLeadingSpace = true var items []struct { section, itemNumber, description, priority string } lineNum := 0 for { record, err := reader.Read() if err == io.EOF { break } if err != nil { continue } lineNum++ if lineNum == 1 { // skip header row continue } if len(record) < 3 { continue } item := struct { section, itemNumber, description, priority string }{ section: strings.TrimSpace(record[0]), itemNumber: strings.TrimSpace(record[1]), description: strings.TrimSpace(record[2]), priority: "medium", } if len(record) >= 4 { p := strings.ToLower(strings.TrimSpace(record[3])) if p == "high" || p == "medium" || p == "low" { item.priority = p } } items = append(items, item) } if len(items) == 0 { http.Error(w, "No valid items found in CSV", 400) return } // Handle upload mode if uploadMode == "replace" { if targetGroup == "all" || targetGroup == "" { h.db.Exec("DELETE FROM diligence_requests WHERE deal_id = ?", dealID) } else { h.db.Exec("DELETE FROM diligence_requests WHERE deal_id = ? AND buyer_group = ?", dealID, targetGroup) } } buyerGroup := "" if targetGroup != "all" && targetGroup != "" { buyerGroup = targetGroup } isBuyerSpecific := 0 if uploadMode == "group_specific" && buyerGroup != "" { isBuyerSpecific = 1 } // Insert request items for _, item := range items { id := generateID("req") h.db.Exec(`INSERT INTO diligence_requests (id, deal_id, item_number, section, description, priority, buyer_group, is_buyer_specific, visible_to_buyer_group, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, id, dealID, item.itemNumber, item.section, item.description, item.priority, buyerGroup, isBuyerSpecific, buyerGroup, profile.ID) } // Optionally convert folder structure if convertFolders == "yes" { // Create folders from unique sections sections := make(map[string]bool) for _, item := range items { sections[item.section] = true } for section := range sections { var existing int h.db.QueryRow("SELECT COUNT(*) FROM folders WHERE deal_id = ? AND name = ?", dealID, section).Scan(&existing) if existing == 0 { folderID := generateID("folder") h.db.Exec("INSERT INTO folders (id, deal_id, parent_id, name, created_by) VALUES (?, ?, '', ?, ?)", folderID, dealID, section, profile.ID) } } } // Auto-assign existing files to matching requests h.autoAssignFilesToRequests(dealID) h.logActivity(dealID, profile.ID, profile.OrganizationID, "upload", "request_list", fmt.Sprintf("%d items", len(items)), "") http.Redirect(w, r, "/deals/"+dealID, http.StatusSeeOther) } func (h *Handler) autoAssignFilesToRequests(dealID string) { // Get all unlinked requests rows, err := h.db.Query("SELECT id, description, section FROM diligence_requests WHERE deal_id = ? AND (linked_file_ids = '' OR linked_file_ids IS NULL)", dealID) if err != nil { return } defer rows.Close() type reqInfo struct { id, description, section string } var reqs []reqInfo for rows.Next() { var r reqInfo rows.Scan(&r.id, &r.description, &r.section) reqs = append(reqs, r) } // Get all files files, err := h.db.Query("SELECT id, name FROM files WHERE deal_id = ?", dealID) if err != nil { return } defer files.Close() type fileInfo struct { id, name string } var fileList []fileInfo for files.Next() { var f fileInfo files.Scan(&f.id, &f.name) fileList = append(fileList, f) } // Simple keyword matching for _, req := range reqs { words := strings.Fields(strings.ToLower(req.description)) for _, f := range fileList { fname := strings.ToLower(f.name) matchCount := 0 for _, w := range words { if len(w) > 3 && strings.Contains(fname, w) { matchCount++ } } if matchCount >= 2 { h.db.Exec("UPDATE diligence_requests SET linked_file_ids = ? WHERE id = ?", f.id, req.id) break } } } } func (h *Handler) handleUpdateComment(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } profile := getProfile(r.Context()) reqID := r.FormValue("request_id") value := r.FormValue("value") field := "seller_comment" if rbac.EffectiveIsBuyer(profile) { field = "buyer_comment" } h.db.Exec("UPDATE diligence_requests SET "+field+" = ?, updated_at = datetime('now') WHERE id = ?", value, reqID) w.Header().Set("Content-Type", "text/html") w.Write([]byte(`✓ Saved`)) }