From c65f5e8a3df7d90d0fce025a6af8c36e8a05539d Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Mon, 7 Sep 2026 16:44:55 +0800 Subject: [PATCH] fix(service): populate Excel pricing cells and verify attachment readback parity Co-authored-by: Cursor --- internal/bizdev/service_test.go | 71 +++++++++++++++++++++++++++++++++ internal/quote/quote.go | 31 ++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/internal/bizdev/service_test.go b/internal/bizdev/service_test.go index 9ca50733..c7a073a3 100644 --- a/internal/bizdev/service_test.go +++ b/internal/bizdev/service_test.go @@ -1,12 +1,17 @@ package bizdev_test import ( + "bytes" "context" + "os" "path/filepath" + "strconv" "strings" "testing" "time" + "github.com/xuri/excelize/v2" + "chat-agent-system/internal/bizdev" "chat-agent-system/internal/config" "chat-agent-system/internal/quote" @@ -78,4 +83,70 @@ func TestExecuteTaskIndependent(t *testing.T) { if !strings.Contains(quoteRes.MarkdownBody, "微创吻合器研发") { t.Errorf("markdown missing product_name") } + + xlsxPrice, err := readQuoteSheetTotal(quoteRes.Attachments[0].Data) + if err != nil { + t.Fatalf("read Excel J18 from attachment: %v", err) + } + if diff := xlsxPrice - summary.TotalPrice; diff < -0.01 || diff > 0.01 { + t.Errorf("Excel J18 total %v != summary total %v", xlsxPrice, summary.TotalPrice) + } +} + +func TestExecuteTaskWritesMaterials(t *testing.T) { + tmpDir := t.TempDir() + workDir := filepath.Join(tmpDir, "workspace") + cfg := &config.Config{ + DataDir: tmpDir, + AgentBackend: "stub", + AgentTimeout: 5 * time.Second, + } + + svc := bizdev.NewService(cfg) + materialContent := []byte("客户提供的补充需求说明") + res, err := svc.ExecuteTask(context.Background(), bizdev.TaskRunRequest{ + TaskKey: "intake", + ProjectName: "测试项目", + CustomerName: "测试客户", + WorkDir: workDir, + Materials: []bizdev.MaterialInput{ + {Filename: "requirements.txt", Content: materialContent}, + }, + }) + if err != nil { + t.Fatalf("execute intake with materials failed: %v", err) + } + + materialPath := filepath.Join(res.WorkDir, "materials_text", "requirements.txt") + data, err := os.ReadFile(materialPath) + if err != nil { + t.Fatalf("expected material file at %s: %v", materialPath, err) + } + if !bytes.Equal(data, materialContent) { + t.Errorf("material content mismatch: got %q, want %q", data, materialContent) + } +} + +func readQuoteSheetTotal(xlsxData []byte) (float64, error) { + f, err := excelize.OpenReader(bytes.NewReader(xlsxData)) + if err != nil { + return 0, err + } + defer f.Close() + + const sheet = "报价模型" + raw, err := f.GetCellValue(sheet, "J18") + if err != nil { + return 0, err + } + return parseExcelNumber(raw) +} + +func parseExcelNumber(raw string) (float64, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return 0, nil + } + raw = strings.ReplaceAll(raw, ",", "") + return strconv.ParseFloat(raw, 64) } diff --git a/internal/quote/quote.go b/internal/quote/quote.go index 464e8953..2f8e91c5 100644 --- a/internal/quote/quote.go +++ b/internal/quote/quote.go @@ -78,6 +78,28 @@ func CalculateAndFill(in Input, templateXlsx string, outPath string) (*Summary, if idx, _ := f.GetSheetIndex(sheet); idx != -1 { _ = f.SetCellValue(sheet, "B3", in.CustomerName) _ = f.SetCellValue(sheet, "B4", in.ProductName) + _ = f.SetCellValue(sheet, "B5", totalPrice) + + writeSummaryLine(f, sheet, 8, summary.Lines[0]) + writeSummaryLine(f, sheet, 9, summary.Lines[1]) + writeSummaryLine(f, sheet, 10, summary.Lines[2]) + + var totalPersonYear, totalCost, totalBuffer, totalMargin, totalTax float64 + for _, line := range summary.Lines { + totalPersonYear += line.PersonYear + totalCost += line.Cost + totalBuffer += line.Buffer + totalMargin += line.Margin + totalTax += line.Tax + } + writeSummaryLine(f, sheet, 18, SummaryLine{ + PersonYear: totalPersonYear, + Cost: totalCost, + Buffer: totalBuffer, + Margin: totalMargin, + Tax: totalTax, + Price: totalPrice, + }) } if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil { @@ -89,3 +111,12 @@ func CalculateAndFill(in Input, templateXlsx string, outPath string) (*Summary, return summary, nil } + +func writeSummaryLine(f *excelize.File, sheet string, row int, line SummaryLine) { + _ = f.SetCellValue(sheet, fmt.Sprintf("D%d", row), line.PersonYear) + _ = f.SetCellValue(sheet, fmt.Sprintf("E%d", row), line.Cost) + _ = f.SetCellValue(sheet, fmt.Sprintf("F%d", row), line.Buffer) + _ = f.SetCellValue(sheet, fmt.Sprintf("G%d", row), line.Margin) + _ = f.SetCellValue(sheet, fmt.Sprintf("H%d", row), line.Tax) + _ = f.SetCellValue(sheet, fmt.Sprintf("J%d", row), line.Price) +}