KTD1 fourth step: - squad.Options.TaskPreparer + TaskPreparation (Attachment + Backfills + MirrorTemplateToOutput); squad stays sole writer of its workspace files - CDO quote calc/xlsx/backfill implementation moves to bizdev (quote_preparer.go); squad/quote_backfill.go relocated to bizdev - arch path rule enforced the boundary: bizdev may not name/write output.md; mirroring decided squad-side via flag - squad -> quote import eliminated; engine.go delegations now local - verified: build/vet/arch green, squad+bizdev short tests pass
84 lines
2.6 KiB
Go
84 lines
2.6 KiB
Go
package bizdev
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"chat-agent-system/internal/quote"
|
|
)
|
|
|
|
// FormatMoney 格式化金额用于 Markdown 表格展示
|
|
func FormatMoney(v float64) string {
|
|
if v == 0 {
|
|
return "0"
|
|
}
|
|
if v == float64(int64(v)) {
|
|
return strconv.FormatInt(int64(v), 10)
|
|
}
|
|
return strconv.FormatFloat(v, 'f', 2, 64)
|
|
}
|
|
|
|
// RenderCDOQuoteTable 将 quote.Summary 转换为标准 Markdown 表格
|
|
func RenderCDOQuoteTable(summary *quote.Summary) string {
|
|
var b strings.Builder
|
|
b.WriteString("| 报价代码 | 服务内容 | 人数 | 人年 | 成本 | 加Buffer20% | 加毛利40% | 加税7% | 设备设施等费用 | 税后价格 |\n")
|
|
b.WriteString("|---|---|---:|---:|---:|---:|---:|---:|---:|---:|\n")
|
|
|
|
var totalPersonYear, totalCost, totalBuffer, totalMargin, totalTax float64
|
|
for _, line := range summary.Lines {
|
|
fmt.Fprintf(&b, "| %s | %s | — | %s | %s | %s | %s | %s | — | %s |\n",
|
|
line.Code, line.Name,
|
|
FormatMoney(line.PersonYear),
|
|
FormatMoney(line.Cost),
|
|
FormatMoney(line.Buffer),
|
|
FormatMoney(line.Margin),
|
|
FormatMoney(line.Tax),
|
|
FormatMoney(line.Price),
|
|
)
|
|
totalPersonYear += line.PersonYear
|
|
totalCost += line.Cost
|
|
totalBuffer += line.Buffer
|
|
totalMargin += line.Margin
|
|
totalTax += line.Tax
|
|
}
|
|
|
|
fmt.Fprintf(&b, "| | **总计** | — | **%s** | **%s** | **%s** | **%s** | **%s** | — | **%s** |\n",
|
|
FormatMoney(totalPersonYear),
|
|
FormatMoney(totalCost),
|
|
FormatMoney(totalBuffer),
|
|
FormatMoney(totalMargin),
|
|
FormatMoney(totalTax),
|
|
FormatMoney(summary.TotalPrice),
|
|
)
|
|
return b.String()
|
|
}
|
|
|
|
// BackfillCDOQuoteMarkdown 将计算结果回填到 cdo_quote 模板中
|
|
func BackfillCDOQuoteMarkdown(body string, summary *quote.Summary) string {
|
|
conclusion := fmt.Sprintf(
|
|
"按公司 CDO 定价核算模板计算,税后合计 **%s 元**(含 Buffer 20%%、毛利 40%%、税 7%%)。数字与配套 Excel「报价模型」一致。",
|
|
FormatMoney(summary.TotalPrice),
|
|
)
|
|
body = replaceSection(body, "## 结论", conclusion)
|
|
|
|
tableIntro := "表中「人年」列与 Excel 模板同一列,对应模型投入人月合计(工时/160)。\n\n"
|
|
body = replaceSection(body, "## 报价模型", tableIntro+RenderCDOQuoteTable(summary))
|
|
|
|
return body
|
|
}
|
|
|
|
func replaceSection(body, heading, content string) string {
|
|
start := strings.Index(body, heading)
|
|
if start == -1 {
|
|
return body
|
|
}
|
|
rest := body[start+len(heading):]
|
|
nextHeading := strings.Index(rest, "\n## ")
|
|
if nextHeading == -1 {
|
|
return body[:start] + heading + "\n\n" + content + "\n"
|
|
}
|
|
end := start + len(heading) + nextHeading
|
|
return body[:start] + heading + "\n\n" + content + "\n" + body[end:]
|
|
}
|