642 lines
22 KiB
Go
642 lines
22 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
"server/pkg/jwtutil"
|
|
"server/services"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
// BackendCrmProjectController CRM 项目管理
|
|
type BackendCrmProjectController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type projectPayload struct {
|
|
ProjectName string `json:"project_name"`
|
|
ProjectNo string `json:"project_no"`
|
|
CustomerID uint64 `json:"customer_id"`
|
|
CustomerName string `json:"customer_name"`
|
|
OwnerUserID string `json:"owner_user_id"`
|
|
OwnerUserName string `json:"owner_user_name"`
|
|
Industry string `json:"industry"`
|
|
Amount float64 `json:"amount"`
|
|
Status int8 `json:"status"`
|
|
StartDate string `json:"start_date"`
|
|
EndDate string `json:"end_date"`
|
|
ContactPerson string `json:"contact_person"`
|
|
ContactPosition string `json:"contact_position"`
|
|
ContactPhone string `json:"contact_phone"`
|
|
Address string `json:"address"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// List GET /backend/crm/project/list
|
|
func (c *BackendCrmProjectController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
status := strings.TrimSpace(c.GetString("status"))
|
|
ownerID := strings.TrimSpace(c.GetString("owner_user_id"))
|
|
|
|
tenantID := pipelineTenantID(claims)
|
|
cond := orm.NewCondition().And("tenant_id", tenantID).And("delete_time__isnull", true)
|
|
if keyword != "" {
|
|
kw := orm.NewCondition().
|
|
Or("project_name__contains", keyword).
|
|
Or("project_no__contains", keyword).
|
|
Or("customer_name__contains", keyword)
|
|
cond = cond.AndCond(kw)
|
|
}
|
|
if status != "" {
|
|
cond = cond.And("status", status)
|
|
}
|
|
if ownerID != "" {
|
|
cond = cond.And("owner_user_id", ownerID)
|
|
}
|
|
qs := models.Orm.QueryTable(new(models.TenantCrmProject)).SetCond(cond)
|
|
|
|
total, _ := qs.Count()
|
|
var list []models.TenantCrmProject
|
|
if total > 0 {
|
|
_, _ = qs.OrderBy("-id").Offset((page - 1) * pageSize).Limit(pageSize).All(&list)
|
|
}
|
|
attachBusinessName(tenantID, list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"list": list, "total": total, "page": page, "pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// Detail GET /backend/crm/project/:id
|
|
func (c *BackendCrmProjectController) Detail() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
var proj models.TenantCrmProject
|
|
err = models.Orm.QueryTable(new(models.TenantCrmProject)).
|
|
Filter("id", id).Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).One(&proj)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "项目未找到")
|
|
return
|
|
}
|
|
tmp := []models.TenantCrmProject{proj}
|
|
attachBusinessName(pipelineTenantID(claims), tmp)
|
|
proj.BusinessName = tmp[0].BusinessName
|
|
// 存量项目兜底:确保文档库「共享文档 / 项目文档」下已建立同名文件夹(失败静默,不影响详情)
|
|
if proj.DocCategoryID == 0 {
|
|
if cid, err := services.EnsureCrmProjectDocCategory(claims.TenantId, proj.ID, proj.ProjectName, 0); err == nil && cid > 0 {
|
|
proj.DocCategoryID = cid
|
|
}
|
|
}
|
|
pipelineOk(&c.Controller, proj)
|
|
}
|
|
|
|
// Create POST /backend/crm/project
|
|
func (c *BackendCrmProjectController) Create() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p projectPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.ProjectName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "项目名称不能为空")
|
|
return
|
|
}
|
|
|
|
tenantID := pipelineTenantID(claims)
|
|
var custIDVal *uint64
|
|
customerName := strings.TrimSpace(p.CustomerName)
|
|
if p.CustomerID > 0 {
|
|
var cust models.TenantCrmCustomer
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
|
|
Filter("id", p.CustomerID).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&cust); err == nil {
|
|
cid := cust.ID
|
|
custIDVal = &cid
|
|
customerName = cust.CustomerName
|
|
}
|
|
}
|
|
status := p.Status
|
|
if status == 0 {
|
|
status = 1
|
|
}
|
|
now := time.Now()
|
|
proj := models.TenantCrmProject{
|
|
TenantID: tenantID,
|
|
ProjectName: strings.TrimSpace(p.ProjectName),
|
|
ProjectNo: strings.TrimSpace(p.ProjectNo),
|
|
CustomerID: custIDVal,
|
|
CustomerName: customerName,
|
|
OwnerUserID: firstNonEmpty(p.OwnerUserID, pipelineUID(claims)),
|
|
OwnerUserName: firstNonEmpty(p.OwnerUserName, resolveUserName(claims)),
|
|
Industry: strings.TrimSpace(p.Industry),
|
|
Amount: p.Amount,
|
|
Status: status,
|
|
StartDate: parsePipelineDate(p.StartDate),
|
|
EndDate: parsePipelineDate(p.EndDate),
|
|
ContactPerson: strings.TrimSpace(p.ContactPerson),
|
|
ContactPosition: strings.TrimSpace(p.ContactPosition),
|
|
ContactPhone: strings.TrimSpace(p.ContactPhone),
|
|
Address: strings.TrimSpace(p.Address),
|
|
Remark: p.Remark,
|
|
CreateUserID: pipelineUID(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&proj)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
// 项目建立后,同步在文档库「共享文档 / 项目文档」下建立同名文件夹(失败不影响项目创建)
|
|
if cid, err := services.EnsureCrmProjectDocCategory(claims.TenantId, uint64(id), proj.ProjectName, 0); err == nil {
|
|
proj.DocCategoryID = cid
|
|
}
|
|
crmWriteLog(tenantID, 3, uint64(id), "create", "创建项目:"+proj.ProjectName, claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id, "doc_category_id": proj.DocCategoryID})
|
|
}
|
|
|
|
// Update PUT /backend/crm/project/:id
|
|
func (c *BackendCrmProjectController) Update() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p projectPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
var proj models.TenantCrmProject
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmProject)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&proj); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "项目未找到")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.ProjectName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "项目名称不能为空")
|
|
return
|
|
}
|
|
|
|
proj.ProjectName = strings.TrimSpace(p.ProjectName)
|
|
proj.ProjectNo = strings.TrimSpace(p.ProjectNo)
|
|
if p.CustomerID > 0 {
|
|
var cust models.TenantCrmCustomer
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
|
|
Filter("id", p.CustomerID).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&cust); err == nil {
|
|
cid := cust.ID
|
|
proj.CustomerID = &cid
|
|
proj.CustomerName = cust.CustomerName
|
|
}
|
|
} else if strings.TrimSpace(p.CustomerName) != "" {
|
|
proj.CustomerName = strings.TrimSpace(p.CustomerName)
|
|
}
|
|
if strings.TrimSpace(p.OwnerUserID) != "" {
|
|
proj.OwnerUserID = strings.TrimSpace(p.OwnerUserID)
|
|
}
|
|
if strings.TrimSpace(p.OwnerUserName) != "" {
|
|
proj.OwnerUserName = strings.TrimSpace(p.OwnerUserName)
|
|
}
|
|
proj.Industry = strings.TrimSpace(p.Industry)
|
|
proj.Amount = p.Amount
|
|
if p.Status != 0 {
|
|
proj.Status = p.Status
|
|
}
|
|
proj.StartDate = parsePipelineDate(p.StartDate)
|
|
proj.EndDate = parsePipelineDate(p.EndDate)
|
|
proj.ContactPerson = strings.TrimSpace(p.ContactPerson)
|
|
proj.ContactPosition = strings.TrimSpace(p.ContactPosition)
|
|
proj.ContactPhone = strings.TrimSpace(p.ContactPhone)
|
|
proj.Address = strings.TrimSpace(p.Address)
|
|
proj.Remark = p.Remark
|
|
proj.UpdateTime = time.Now()
|
|
|
|
if _, err := models.Orm.Update(&proj); err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
|
return
|
|
}
|
|
// 项目改名时同步重命名文档库中的项目文件夹(失败静默,不影响项目更新)
|
|
if proj.DocCategoryID > 0 {
|
|
_ = services.RenameCrmProjectDocCategory(claims.TenantId, proj.DocCategoryID, proj.ProjectName)
|
|
}
|
|
crmWriteLog(tenantID, 3, proj.ID, "update", "更新项目:"+proj.ProjectName, claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": proj.ID})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/project/:id
|
|
func (c *BackendCrmProjectController) Delete() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
var proj models.TenantCrmProject
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmProject)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&proj); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "项目未找到")
|
|
return
|
|
}
|
|
if !canDeleteCrmRecord(claims, proj.CreateUserID) {
|
|
pipelineErr(&c.Controller, 403, 403, "只有创建人、租户管理员或平台管理员可以删除该项目")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmProject)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ========================== 项目文档(OA 文档库绑定) ==========================
|
|
|
|
// projectDocItem 项目文档条目:文档库文档 + 所在文件夹名称。
|
|
type projectDocItem struct {
|
|
models.OaDoc
|
|
FolderName string `json:"folder_name"`
|
|
}
|
|
|
|
// projectDocContext 解析项目并确保其在文档库「共享文档 / 项目文档」下的同名文件夹存在。
|
|
// 存量项目(doc_category_id=0)在首次访问项目文档时自动补建,保证历史项目也能在文档库中看到。
|
|
// 失败时已写出错误响应,返回 ok=false。
|
|
func (c *BackendCrmProjectController) projectDocContext(claims *jwtutil.Claims) (*models.TenantCrmProject, uint64, bool) {
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return nil, 0, false
|
|
}
|
|
var proj models.TenantCrmProject
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmProject)).
|
|
Filter("id", id).Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).One(&proj); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "项目未找到")
|
|
return nil, 0, false
|
|
}
|
|
cid, err := services.EnsureCrmProjectDocCategory(claims.TenantId, proj.ID, proj.ProjectName, proj.DocCategoryID)
|
|
if err != nil || cid == 0 {
|
|
pipelineErr(&c.Controller, 500, 500, "项目文档文件夹准备失败")
|
|
return nil, 0, false
|
|
}
|
|
proj.DocCategoryID = cid
|
|
return &proj, cid, true
|
|
}
|
|
|
|
// DocList GET /backend/crm/project/:id/docs
|
|
// 项目文档列表:读取文档库中该项目文件夹(含子文件夹)下的全部文档,与文档库内容实时一致。
|
|
func (c *BackendCrmProjectController) DocList() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
proj, cid, ok := c.projectDocContext(claims)
|
|
if !ok {
|
|
return
|
|
}
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 200 {
|
|
pageSize = 20
|
|
}
|
|
actor := services.NewOaDocActor(claims.TenantId, uint64(claims.UserID))
|
|
// 可选按文件夹筛选(含其子文件夹);不属于项目文件夹的参数回落到项目根,避免越权查询
|
|
filterCategory := cid
|
|
if v, _ := c.GetUint64("category_id"); v > 0 && services.CrmProjectDocCategoryInScope(actor, cid, v) {
|
|
filterCategory = v
|
|
}
|
|
list, total, err := services.OaDocList(actor, services.OaDocListParams{
|
|
Actor: actor,
|
|
Scope: models.DocScopeShared,
|
|
Keyword: strings.TrimSpace(c.GetString("keyword")),
|
|
CategoryID: filterCategory,
|
|
Status: -1,
|
|
DocType: -1,
|
|
Star: -1,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "查询失败: "+err.Error())
|
|
return
|
|
}
|
|
// 附带所在文件夹名称(项目文件夹本身或其中子分类)
|
|
nameMap := services.CrmProjectDocCategoryNameMap(claims.TenantId)
|
|
items := make([]projectDocItem, 0, len(list))
|
|
for _, d := range list {
|
|
folder := nameMap[d.CategoryID]
|
|
if folder == "" {
|
|
folder = proj.ProjectName
|
|
}
|
|
items = append(items, projectDocItem{OaDoc: d, FolderName: folder})
|
|
}
|
|
// 项目文件夹树(根为项目文件夹),供前端选择/新建子文件夹
|
|
folders, err := services.CrmProjectDocFolderTree(actor, cid)
|
|
if err != nil {
|
|
folders = []*services.ProjectDocFolder{}
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"category_id": cid,
|
|
"category_name": proj.ProjectName,
|
|
"folders": folders,
|
|
"list": items,
|
|
"total": total,
|
|
"page": page,
|
|
"pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// DocUpload POST /backend/crm/project/:id/doc-upload
|
|
// 上传项目文档:文件先经 POST /backend/uploadfile 上传,再调用本接口把元数据写入文档库项目文件夹,
|
|
// 同时同步登记一条 CRM 附件,保证项目「附件」链路同样可见。
|
|
func (c *BackendCrmProjectController) DocUpload() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
proj, cid, ok := c.projectDocContext(claims)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
FileID uint64 `json:"file_id"`
|
|
FileName string `json:"file_name"`
|
|
FileURL string `json:"file_url"`
|
|
FileSize int64 `json:"file_size"`
|
|
Ext string `json:"ext"`
|
|
Title string `json:"title"`
|
|
Summary string `json:"summary"`
|
|
CategoryID uint64 `json:"category_id"` // 目标文件夹,0=项目根文件夹
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
fileName := strings.TrimSpace(p.FileName)
|
|
fileURL := strings.TrimSpace(p.FileURL)
|
|
if fileName == "" || fileURL == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "文件信息不完整")
|
|
return
|
|
}
|
|
ext := strings.ToLower(strings.TrimPrefix(strings.TrimSpace(p.Ext), "."))
|
|
if ext == "" {
|
|
ext = strings.ToLower(strings.TrimPrefix(filepath.Ext(fileName), "."))
|
|
}
|
|
var size uint64
|
|
if p.FileSize > 0 {
|
|
size = uint64(p.FileSize)
|
|
}
|
|
operatorName := resolveUserName(claims)
|
|
actor := services.NewOaDocActor(claims.TenantId, uint64(claims.UserID))
|
|
// 目标文件夹:默认项目根文件夹;指定时必须在项目文件夹内
|
|
targetCategory := cid
|
|
if p.CategoryID > 0 {
|
|
if !services.CrmProjectDocCategoryInScope(actor, cid, p.CategoryID) {
|
|
pipelineErr(&c.Controller, 403, 403, "只能上传到项目自己的文件夹内")
|
|
return
|
|
}
|
|
targetCategory = p.CategoryID
|
|
}
|
|
doc, err := services.OaDocCreate(actor, operatorName, services.OaDocSaveParams{
|
|
SetCategory: true,
|
|
CategoryID: targetCategory,
|
|
SetVisibility: true,
|
|
Visibility: models.DocVisibilityPublic,
|
|
Title: strings.TrimSpace(p.Title),
|
|
FileID: p.FileID,
|
|
FileURL: fileURL,
|
|
FileName: fileName,
|
|
Ext: ext,
|
|
Size: size,
|
|
Summary: strings.TrimSpace(p.Summary),
|
|
Status: models.DocStatusPublish,
|
|
OwnerID: uint64(claims.UserID),
|
|
OwnerName: operatorName,
|
|
})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "同步文档库失败: "+err.Error())
|
|
return
|
|
}
|
|
// 同步登记 CRM 附件(失败不影响文档库结果)
|
|
now := time.Now()
|
|
var fileIDPtr *uint64
|
|
if doc.FileID > 0 {
|
|
fid := doc.FileID
|
|
fileIDPtr = &fid
|
|
}
|
|
_, _ = models.Orm.Insert(&models.TenantCrmAttach{
|
|
TenantID: pipelineTenantID(claims),
|
|
RelatedType: 3,
|
|
RelatedID: proj.ID,
|
|
FileID: fileIDPtr,
|
|
FileName: doc.FileName,
|
|
FileURL: doc.FileURL,
|
|
FileSize: int64(doc.Size),
|
|
UploaderID: pipelineUID(claims),
|
|
UploaderName: operatorName,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
})
|
|
crmWriteLog(pipelineTenantID(claims), 3, proj.ID, "doc", "上传项目文档:"+doc.Title, claims)
|
|
pipelineOk(&c.Controller, doc)
|
|
}
|
|
|
|
// DocDelete POST /backend/crm/project/:id/doc-delete
|
|
// 删除项目文档:同步软删文档库文档与对应的 CRM 附件记录。
|
|
func (c *BackendCrmProjectController) DocDelete() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
proj, cid, ok := c.projectDocContext(claims)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
DocID uint64 `json:"doc_id"`
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
if err := json.Unmarshal(raw, &p); err != nil || p.DocID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
actor := services.NewOaDocActor(claims.TenantId, uint64(claims.UserID))
|
|
doc, err := services.OaDocGet(actor, p.DocID)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "文档不存在")
|
|
return
|
|
}
|
|
// 仅允许删除本项目文件夹(含子文件夹)内的文档
|
|
if !services.CrmProjectDocCategoryInScope(actor, cid, doc.CategoryID) {
|
|
pipelineErr(&c.Controller, 403, 403, "该文档不属于当前项目")
|
|
return
|
|
}
|
|
if _, err := services.OaDocDelete(actor, []uint64{doc.ID}); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, err.Error())
|
|
return
|
|
}
|
|
// 同步软删 CRM 附件记录(按文件ID匹配,缺失时按文件名+地址匹配)
|
|
now := time.Now()
|
|
q := models.Orm.QueryTable(new(models.TenantCrmAttach)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("related_type", 3).
|
|
Filter("related_id", proj.ID).
|
|
Filter("delete_time__isnull", true)
|
|
if doc.FileID > 0 {
|
|
q = q.Filter("file_id", doc.FileID)
|
|
} else {
|
|
q = q.Filter("file_name", doc.FileName).Filter("file_url", doc.FileURL)
|
|
}
|
|
_, _ = q.Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
crmWriteLog(pipelineTenantID(claims), 3, proj.ID, "doc", "删除项目文档:"+doc.Title, claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// DocFolderCreate POST /backend/crm/project/:id/doc-folder
|
|
// 在项目文件夹下新建子文件夹(对应文档库中的文档分类),用于分类存放项目文档。
|
|
// parent_id 缺省时建在项目根文件夹下;只允许在项目自己的文件夹树内创建。
|
|
func (c *BackendCrmProjectController) DocFolderCreate() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
proj, cid, ok := c.projectDocContext(claims)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
Name string `json:"name"`
|
|
ParentID uint64 `json:"parent_id"`
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
name := strings.TrimSpace(p.Name)
|
|
if name == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "请输入文件夹名称")
|
|
return
|
|
}
|
|
parentID := p.ParentID
|
|
if parentID == 0 {
|
|
parentID = cid
|
|
}
|
|
actor := services.NewOaDocActor(claims.TenantId, uint64(claims.UserID))
|
|
if !services.CrmProjectDocCategoryInScope(actor, cid, parentID) {
|
|
pipelineErr(&c.Controller, 403, 403, "只能在项目文件夹内创建子文件夹")
|
|
return
|
|
}
|
|
item, err := services.OaDocCategoryCreate(actor, models.DocScopeShared, parentID, name, "项目文档:"+proj.ProjectName, 0)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 3, proj.ID, "doc-folder", "新建项目文档文件夹:"+item.Name, claims)
|
|
pipelineOk(&c.Controller, item)
|
|
}
|
|
|
|
// DocFolderDelete POST /backend/crm/project/:id/doc-folder-delete
|
|
// 删除项目内的子文件夹(需为空:无子文件夹、无文档);项目根文件夹不可删除。
|
|
func (c *BackendCrmProjectController) DocFolderDelete() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
proj, cid, ok := c.projectDocContext(claims)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
FolderID uint64 `json:"folder_id"`
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
if err := json.Unmarshal(raw, &p); err != nil || p.FolderID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if p.FolderID == cid {
|
|
pipelineErr(&c.Controller, 400, 400, "项目根文件夹不可删除")
|
|
return
|
|
}
|
|
actor := services.NewOaDocActor(claims.TenantId, uint64(claims.UserID))
|
|
if !services.CrmProjectDocCategoryInScope(actor, cid, p.FolderID) {
|
|
pipelineErr(&c.Controller, 403, 403, "该文件夹不属于当前项目")
|
|
return
|
|
}
|
|
var folder models.OaDocCategory
|
|
if err := models.Orm.QueryTable(new(models.OaDocCategory)).
|
|
Filter("tid", claims.TenantId).Filter("id", p.FolderID).
|
|
Filter("is_deleted", 0).One(&folder); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "文件夹不存在")
|
|
return
|
|
}
|
|
if err := services.OaDocCategoryDelete(actor, p.FolderID); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 3, proj.ID, "doc-folder", "删除项目文档文件夹:"+folder.Name, claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|