211 lines
6.7 KiB
Go
211 lines
6.7 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"cnc-sales-backend/internal/auth"
|
|
"cnc-sales-backend/internal/models"
|
|
)
|
|
|
|
// exportBackup 全量导出(与前端数据中心 JSON 备份格式兼容)
|
|
func (s *Server) exportBackup(w http.ResponseWriter, r *http.Request) {
|
|
uid := auth.UserID(r)
|
|
|
|
// 客户
|
|
customers := []models.Customer{}
|
|
rows, err := s.db.Query("SELECT "+customerCols+" FROM customers WHERE owner_id=? AND deleted_at IS NULL ORDER BY created_at", uid)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导出失败")
|
|
return
|
|
}
|
|
for rows.Next() {
|
|
c := models.Customer{}
|
|
if err := rows.Scan(&c.ID, &c.Company, &c.Industry, &c.Address, &c.Level, &c.Stage,
|
|
&c.MachineModel, &c.Budget, &c.WorkpieceNeeds, &c.PainPoints,
|
|
&c.NextFollowDate, &c.Source, &c.Remark, &c.CreatedAt, &c.UpdatedAt); err != nil {
|
|
continue
|
|
}
|
|
c.Contacts, _ = s.loadContacts(c.ID)
|
|
c.FollowUps, _ = s.loadFollowUps(c.ID)
|
|
customers = append(customers, c)
|
|
}
|
|
rows.Close()
|
|
|
|
// 任务
|
|
tasks := []models.Task{}
|
|
rows, err = s.db.Query("SELECT "+taskCols+" FROM tasks t LEFT JOIN customers c ON c.id=t.customer_id WHERE t.owner_id=? AND t.deleted_at IS NULL", uid)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导出失败")
|
|
return
|
|
}
|
|
for rows.Next() {
|
|
if t, ok := scanTask(rows); ok {
|
|
tasks = append(tasks, *t)
|
|
}
|
|
}
|
|
rows.Close()
|
|
|
|
// 资料
|
|
notes := []models.Note{}
|
|
rows, err = s.db.Query("SELECT id, cat, title, content, updated_at FROM notes WHERE owner_id=? AND deleted_at IS NULL", uid)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导出失败")
|
|
return
|
|
}
|
|
for rows.Next() {
|
|
var n models.Note
|
|
if err := rows.Scan(&n.ID, &n.Cat, &n.Title, &n.Content, &n.UpdatedAt); err == nil {
|
|
notes = append(notes, n)
|
|
}
|
|
}
|
|
rows.Close()
|
|
|
|
writeJSON(w, http.StatusOK, models.Backup{
|
|
SchemaVersion: 1,
|
|
ExportedAt: nowStr(),
|
|
Customers: customers,
|
|
Tasks: tasks,
|
|
Notes: notes,
|
|
})
|
|
}
|
|
|
|
// importBackup 全量导入(按 id upsert,数据归属当前用户)
|
|
func (s *Server) importBackup(w http.ResponseWriter, r *http.Request) {
|
|
uid := auth.UserID(r)
|
|
var b models.Backup
|
|
if err := decodeJSON(r, &b); err != nil {
|
|
writeErr(w, http.StatusBadRequest, "请求格式错误")
|
|
return
|
|
}
|
|
|
|
tx, err := s.db.Begin()
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入失败")
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
imported := map[string]int{"customers": 0, "tasks": 0, "notes": 0}
|
|
|
|
for _, c := range b.Customers {
|
|
c.Level = models.NormalizeLevel(c.Level)
|
|
c.Stage = models.NormalizeStage(c.Stage)
|
|
if c.ID == "" {
|
|
c.ID = genID()
|
|
}
|
|
if c.CreatedAt == "" {
|
|
c.CreatedAt = nowStr()
|
|
}
|
|
if c.UpdatedAt == "" {
|
|
c.UpdatedAt = nowStr()
|
|
}
|
|
res, err := tx.Exec(`UPDATE customers SET company=?, industry=?, address=?, level=?, stage=?,
|
|
machine_model=?, budget=?, workpiece_needs=?, pain_points=?, next_follow_date=?,
|
|
source=?, remark=?, updated_at=? WHERE id=? AND owner_id=?`,
|
|
c.Company, c.Industry, c.Address, c.Level, c.Stage, c.MachineModel, c.Budget,
|
|
c.WorkpieceNeeds, c.PainPoints, c.NextFollowDate, c.Source, c.Remark, c.UpdatedAt, c.ID, uid)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入客户失败")
|
|
return
|
|
}
|
|
affected, _ := res.RowsAffected()
|
|
if affected == 0 {
|
|
_, err = tx.Exec(`INSERT INTO customers (id, owner_id, company, industry, address, level, stage,
|
|
machine_model, budget, workpiece_needs, pain_points, next_follow_date, source, remark,
|
|
created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
c.ID, uid, c.Company, c.Industry, c.Address, c.Level, c.Stage, c.MachineModel,
|
|
c.Budget, c.WorkpieceNeeds, c.PainPoints, c.NextFollowDate, c.Source, c.Remark,
|
|
c.CreatedAt, c.UpdatedAt)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入客户失败")
|
|
return
|
|
}
|
|
}
|
|
if _, err := tx.Exec("DELETE FROM contacts WHERE customer_id=?", c.ID); err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入失败")
|
|
return
|
|
}
|
|
for _, ct := range c.Contacts {
|
|
if _, err := tx.Exec("INSERT INTO contacts (customer_id, name, role, phone) VALUES (?,?,?,?)",
|
|
c.ID, ct.Name, ct.Role, ct.Phone); err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入失败")
|
|
return
|
|
}
|
|
}
|
|
if _, err := tx.Exec("DELETE FROM follow_ups WHERE customer_id=?", c.ID); err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入失败")
|
|
return
|
|
}
|
|
for _, f := range c.FollowUps {
|
|
if _, err := tx.Exec("INSERT INTO follow_ups (customer_id, time, content) VALUES (?,?,?)",
|
|
c.ID, f.Time, f.Content); err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入失败")
|
|
return
|
|
}
|
|
}
|
|
imported["customers"]++
|
|
}
|
|
|
|
for _, t := range b.Tasks {
|
|
t.Priority = models.NormalizePriority(t.Priority)
|
|
t.Status = models.NormalizeStatus(t.Status)
|
|
t.Repeat = models.NormalizeRepeat(deref(t.Repeat))
|
|
if t.ID == "" {
|
|
t.ID = genID()
|
|
}
|
|
if t.CreatedAt == "" {
|
|
t.CreatedAt = nowStr()
|
|
}
|
|
res, err := tx.Exec(`UPDATE tasks SET title=?, customer_id=?, due_date=?, priority=?, status=?,
|
|
repeat=? WHERE id=? AND owner_id=?`,
|
|
t.Title, nullOrEmpty(t.CustomerID), t.DueDate, t.Priority, t.Status,
|
|
nullOrEmpty(t.Repeat), t.ID, uid)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入任务失败")
|
|
return
|
|
}
|
|
if affected, _ := res.RowsAffected(); affected == 0 {
|
|
_, err = tx.Exec(`INSERT INTO tasks (id, owner_id, title, customer_id, due_date, priority,
|
|
status, repeat, created_at) VALUES (?,?,?,?,?,?,?,?,?)`,
|
|
t.ID, uid, t.Title, nullOrEmpty(t.CustomerID), t.DueDate, t.Priority, t.Status,
|
|
nullOrEmpty(t.Repeat), t.CreatedAt)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入任务失败")
|
|
return
|
|
}
|
|
}
|
|
imported["tasks"]++
|
|
}
|
|
|
|
for _, n := range b.Notes {
|
|
n.Cat = models.NormalizeNoteCat(n.Cat)
|
|
if n.ID == "" {
|
|
n.ID = genID()
|
|
}
|
|
if n.UpdatedAt == "" {
|
|
n.UpdatedAt = nowStr()
|
|
}
|
|
res, err := tx.Exec("UPDATE notes SET cat=?, title=?, content=?, updated_at=? WHERE id=? AND owner_id=?",
|
|
n.Cat, n.Title, n.Content, n.UpdatedAt, n.ID, uid)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入资料失败")
|
|
return
|
|
}
|
|
if affected, _ := res.RowsAffected(); affected == 0 {
|
|
_, err = tx.Exec("INSERT INTO notes (id, owner_id, cat, title, content, updated_at) VALUES (?,?,?,?,?,?)",
|
|
n.ID, uid, n.Cat, n.Title, n.Content, n.UpdatedAt)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入资料失败")
|
|
return
|
|
}
|
|
}
|
|
imported["notes"]++
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "导入提交失败")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"imported": imported})
|
|
}
|