1267 lines
42 KiB
Go
1267 lines
42 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
"github.com/beego/beego/v2/client/orm"
|
|
)
|
|
|
|
// =============================================================
|
|
// CRM 招投标管理 - 补充接口
|
|
// =============================================================
|
|
//
|
|
// 覆盖开发计划中尚未实现的接口:
|
|
// - 招标公告 / 投标方 / 开标评标 / 定标结果(招标侧子表)
|
|
// - 投标立项审批 / 标书版本(投标侧子表)
|
|
// - 标书模板库 / 资质文件库(公共库)
|
|
// - 招标统计 / 投标统计
|
|
//
|
|
// 约定沿用既有实现:软删除(delete_time)、多租户(tenant_id)、
|
|
// 统一 pipelineClaims / pipelineOk / pipelineErr 鉴权与响应。
|
|
// =============================================================
|
|
|
|
// ------------------------------ 招标公告 ------------------------------
|
|
|
|
// BackendCrmBiddingTenderAnnounceController 招标公告
|
|
type BackendCrmBiddingTenderAnnounceController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type tenderAnnouncePayload struct {
|
|
TenderID uint64 `json:"tender_id"`
|
|
AnnounceType string `json:"announce_type"`
|
|
Title string `json:"title"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/tender-announce/list?tender_id=1
|
|
func (c *BackendCrmBiddingTenderAnnounceController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tenderID, _ := c.GetInt64("tender_id")
|
|
if tenderID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "tender_id 必填")
|
|
return
|
|
}
|
|
var list []models.TenantCrmBiddingTenderAnnounce
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderAnnounce)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("tender_id", tenderID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-id").
|
|
All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list, "total": len(list)})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/tender-announce
|
|
func (c *BackendCrmBiddingTenderAnnounceController) 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 tenderAnnouncePayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if p.TenderID == 0 || strings.TrimSpace(p.Title) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "公告信息不完整")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingTenderAnnounce{
|
|
TenantID: pipelineTenantID(claims),
|
|
TenderID: p.TenderID,
|
|
AnnounceType: firstNonEmpty(p.AnnounceType, "1"),
|
|
Title: strings.TrimSpace(p.Title),
|
|
Content: p.Content,
|
|
PublishStatus: 0,
|
|
CreateUserID: pipelineUID(claims),
|
|
CreateUserName: resolveUserName(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "创建招标公告", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Publish POST /backend/crm/bidding/tender-announce/:id/publish
|
|
// 发布公告:置为已发布并记录发布时间,同时把招标主表推进到「公告中」。
|
|
func (c *BackendCrmBiddingTenderAnnounceController) Publish() {
|
|
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 row models.TenantCrmBiddingTenderAnnounce
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTenderAnnounce)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "公告不存在")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row.PublishStatus = 1
|
|
row.PublishTime = &now
|
|
row.UpdateTime = now
|
|
_, _ = models.Orm.Update(&row, "publish_status", "publish_time", "update_time")
|
|
|
|
// 主表状态推进:草稿(0) -> 公告中(1)
|
|
if row.TenderID > 0 {
|
|
var t models.TenantCrmBiddingTender
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
|
Filter("id", row.TenderID).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&t); err == nil && t.Status == 0 {
|
|
t.Status = 1
|
|
t.PublishDate = &now
|
|
t.UpdateTime = now
|
|
_, _ = models.Orm.Update(&t, "status", "publish_date", "update_time")
|
|
}
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "publish", "发布招标公告", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/bidding/tender-announce/:id
|
|
func (c *BackendCrmBiddingTenderAnnounceController) 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)
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderAnnounce)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "delete", "删除招标公告", claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ------------------------------ 投标方/供应商 ------------------------------
|
|
|
|
// BackendCrmBiddingTenderSupplierController 投标方
|
|
type BackendCrmBiddingTenderSupplierController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type tenderSupplierPayload struct {
|
|
TenderID uint64 `json:"tender_id"`
|
|
SupplierID *uint64 `json:"supplier_id"`
|
|
SupplierName string `json:"supplier_name"`
|
|
RegisterStatus int8 `json:"register_status"`
|
|
BidAmount float64 `json:"bid_amount"`
|
|
BidFiles string `json:"bid_files"`
|
|
ContactPerson string `json:"contact_person"`
|
|
ContactPhone string `json:"contact_phone"`
|
|
IsWinner int8 `json:"is_winner"`
|
|
Ranking uint32 `json:"ranking"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/tender-supplier/list?tender_id=1
|
|
func (c *BackendCrmBiddingTenderSupplierController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tenderID, _ := c.GetInt64("tender_id")
|
|
if tenderID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "tender_id 必填")
|
|
return
|
|
}
|
|
var list []models.TenantCrmBiddingTenderSupplier
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderSupplier)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("tender_id", tenderID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-id").
|
|
All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list, "total": len(list)})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/tender-supplier
|
|
func (c *BackendCrmBiddingTenderSupplierController) 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 tenderSupplierPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if p.TenderID == 0 || strings.TrimSpace(p.SupplierName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "投标方信息不完整")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingTenderSupplier{
|
|
TenantID: pipelineTenantID(claims),
|
|
TenderID: p.TenderID,
|
|
SupplierID: p.SupplierID,
|
|
SupplierName: strings.TrimSpace(p.SupplierName),
|
|
RegisterStatus: p.RegisterStatus,
|
|
RegisterTime: &now,
|
|
BidAmount: p.BidAmount,
|
|
BidFiles: p.BidFiles,
|
|
ContactPerson: p.ContactPerson,
|
|
ContactPhone: p.ContactPhone,
|
|
Remark: p.Remark,
|
|
CreateUserID: pipelineUID(claims),
|
|
CreateUserName: resolveUserName(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "登记投标方", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update PUT /backend/crm/bidding/tender-supplier/:id
|
|
func (c *BackendCrmBiddingTenderSupplierController) 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 tenderSupplierPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
var row models.TenantCrmBiddingTenderSupplier
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTenderSupplier)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "记录不存在")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.SupplierName) != "" {
|
|
row.SupplierName = strings.TrimSpace(p.SupplierName)
|
|
}
|
|
row.SupplierID = p.SupplierID
|
|
row.RegisterStatus = p.RegisterStatus
|
|
row.BidAmount = p.BidAmount
|
|
row.BidFiles = p.BidFiles
|
|
row.ContactPerson = p.ContactPerson
|
|
row.ContactPhone = p.ContactPhone
|
|
row.Ranking = p.Ranking
|
|
row.IsWinner = p.IsWinner
|
|
row.Remark = p.Remark
|
|
row.UpdateTime = time.Now()
|
|
_, _ = models.Orm.Update(&row)
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "update", "更新投标方信息", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/bidding/tender-supplier/:id
|
|
func (c *BackendCrmBiddingTenderSupplierController) 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)
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderSupplier)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "delete", "删除投标方", claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ------------------------------ 开标评标 ------------------------------
|
|
|
|
// BackendCrmBiddingTenderEvaluationController 开标评标
|
|
type BackendCrmBiddingTenderEvaluationController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type tenderEvaluationPayload struct {
|
|
TenderID uint64 `json:"tender_id"`
|
|
SupplierID *uint64 `json:"supplier_id"`
|
|
SupplierName string `json:"supplier_name"`
|
|
TechnicalScore float64 `json:"technical_score"`
|
|
BusinessScore float64 `json:"business_score"`
|
|
PriceScore float64 `json:"price_score"`
|
|
ServiceScore float64 `json:"service_score"`
|
|
WeightConfig string `json:"weight_config"`
|
|
Ranking uint32 `json:"ranking"`
|
|
Comment string `json:"comment"`
|
|
Result int8 `json:"result"`
|
|
}
|
|
|
|
// evaluationWeights 评标权重配置,缺省时四项等权(各项满分按 100 计)。
|
|
type evaluationWeights struct {
|
|
Technical float64 `json:"technical"`
|
|
Business float64 `json:"business"`
|
|
Price float64 `json:"price"`
|
|
Service float64 `json:"service"`
|
|
}
|
|
|
|
// calcTotalScore 按权重折算总分;权重解析失败或全为 0 时退化为四项直接求和。
|
|
func calcTotalScore(p tenderEvaluationPayload) float64 {
|
|
var w evaluationWeights
|
|
_ = json.Unmarshal([]byte(p.WeightConfig), &w)
|
|
sum := w.Technical + w.Business + w.Price + w.Service
|
|
if sum <= 0 {
|
|
return p.TechnicalScore + p.BusinessScore + p.PriceScore + p.ServiceScore
|
|
}
|
|
return p.TechnicalScore*w.Technical +
|
|
p.BusinessScore*w.Business +
|
|
p.PriceScore*w.Price +
|
|
p.ServiceScore*w.Service
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/tender-evaluation/list?tender_id=1
|
|
func (c *BackendCrmBiddingTenderEvaluationController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tenderID, _ := c.GetInt64("tender_id")
|
|
if tenderID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "tender_id 必填")
|
|
return
|
|
}
|
|
var list []models.TenantCrmBiddingTenderEvaluation
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderEvaluation)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("tender_id", tenderID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-total_score").
|
|
All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list, "total": len(list)})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/tender-evaluation
|
|
func (c *BackendCrmBiddingTenderEvaluationController) 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 tenderEvaluationPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if p.TenderID == 0 || strings.TrimSpace(p.SupplierName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "评标信息不完整")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingTenderEvaluation{
|
|
TenantID: pipelineTenantID(claims),
|
|
TenderID: p.TenderID,
|
|
SupplierID: p.SupplierID,
|
|
SupplierName: strings.TrimSpace(p.SupplierName),
|
|
TechnicalScore: p.TechnicalScore,
|
|
BusinessScore: p.BusinessScore,
|
|
PriceScore: p.PriceScore,
|
|
ServiceScore: p.ServiceScore,
|
|
TotalScore: calcTotalScore(p),
|
|
WeightConfig: p.WeightConfig,
|
|
Ranking: p.Ranking,
|
|
Comment: p.Comment,
|
|
Result: p.Result,
|
|
EvaluationUserID: pipelineUID(claims),
|
|
EvaluationUserName: resolveUserName(claims),
|
|
EvaluationTime: &now,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
// 录入评标后重排该招标下所有供应商名次(按总分降序)
|
|
resortEvaluationRanking(p.TenderID, pipelineTenantID(claims))
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "新增评标打分", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// resortEvaluationRanking 按总分降序重写 ranking 字段。
|
|
func resortEvaluationRanking(tenderID uint64, tenantID string) {
|
|
var list []models.TenantCrmBiddingTenderEvaluation
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderEvaluation)).
|
|
Filter("tenant_id", tenantID).
|
|
Filter("tender_id", tenderID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-total_score").
|
|
All(&list)
|
|
for i := range list {
|
|
list[i].Ranking = uint32(i + 1)
|
|
_, _ = models.Orm.Update(&list[i], "ranking")
|
|
}
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/bidding/tender-evaluation/:id
|
|
func (c *BackendCrmBiddingTenderEvaluationController) 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)
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderEvaluation)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "delete", "删除评标记录", claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ------------------------------ 定标结果 ------------------------------
|
|
|
|
// BackendCrmBiddingTenderResultController 定标结果(只读,写入由 tender/:id/award 完成)
|
|
type BackendCrmBiddingTenderResultController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/tender-result/list?tender_id=1
|
|
func (c *BackendCrmBiddingTenderResultController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tenderID, _ := c.GetInt64("tender_id")
|
|
if tenderID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "tender_id 必填")
|
|
return
|
|
}
|
|
var list []models.TenantCrmBiddingTenderResult
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderResult)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("tender_id", tenderID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-id").
|
|
All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list, "total": len(list)})
|
|
}
|
|
|
|
// Detail GET /backend/crm/bidding/tender-result/detail/:id
|
|
func (c *BackendCrmBiddingTenderResultController) 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)
|
|
var row models.TenantCrmBiddingTenderResult
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTenderResult)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "定标结果不存在")
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, row)
|
|
}
|
|
|
|
// ------------------------------ 投标立项审批 ------------------------------
|
|
|
|
// BackendCrmBiddingBidApprovalController 投标立项审批
|
|
type BackendCrmBiddingBidApprovalController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type bidApprovalPayload struct {
|
|
BidID uint64 `json:"bid_id"`
|
|
ApprovalType string `json:"approval_type"`
|
|
ApprovalStatus int8 `json:"approval_status"`
|
|
ApprovalUsers string `json:"approval_users"`
|
|
Comment string `json:"comment"`
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/bid-approval/list?bid_id=1
|
|
func (c *BackendCrmBiddingBidApprovalController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
bidID, _ := c.GetInt64("bid_id")
|
|
if bidID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "bid_id 必填")
|
|
return
|
|
}
|
|
var list []models.TenantCrmBiddingBidApproval
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidApproval)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("bid_id", bidID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-id").
|
|
All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list, "total": len(list)})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/bid-approval 提交立项审批
|
|
func (c *BackendCrmBiddingBidApprovalController) 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 bidApprovalPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if p.BidID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "bid_id 必填")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingBidApproval{
|
|
TenantID: pipelineTenantID(claims),
|
|
BidID: p.BidID,
|
|
ApprovalType: firstNonEmpty(p.ApprovalType, "1"),
|
|
ApprovalStatus: 1, // 1审批中
|
|
ApprovalUsers: p.ApprovalUsers,
|
|
SubmitUserID: pipelineUID(claims),
|
|
SubmitUserName: resolveUserName(claims),
|
|
SubmitTime: &now,
|
|
Comment: p.Comment,
|
|
CurrentUserID: pipelineUID(claims),
|
|
CurrentUserName: resolveUserName(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "提交失败: "+err.Error())
|
|
return
|
|
}
|
|
// 主表推进到「立项审批中」
|
|
var bid models.TenantCrmBiddingBid
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
|
Filter("id", p.BidID).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&bid); err == nil && bid.Status == 0 {
|
|
bid.Status = 1
|
|
bid.UpdateTime = now
|
|
_, _ = models.Orm.Update(&bid, "status", "update_time")
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "提交立项审批", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Approve POST /backend/crm/bidding/bid-approval/:id/approve 审批通过/驳回
|
|
// body: { "pass": true, "comment": "..." }
|
|
func (c *BackendCrmBiddingBidApprovalController) Approve() {
|
|
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)
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var body struct {
|
|
Pass bool `json:"pass"`
|
|
Comment string `json:"comment"`
|
|
}
|
|
_ = json.Unmarshal(raw, &body)
|
|
|
|
var row models.TenantCrmBiddingBidApproval
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBidApproval)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "审批记录不存在")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
if body.Pass {
|
|
row.ApprovalStatus = 2 // 2已通过
|
|
} else {
|
|
row.ApprovalStatus = 3 // 3已驳回
|
|
}
|
|
row.Comment = body.Comment
|
|
row.CompleteTime = &now
|
|
row.UpdateTime = now
|
|
_, _ = models.Orm.Update(&row, "approval_status", "comment", "complete_time", "update_time")
|
|
|
|
// 审批通过 -> 投标转为「投标中」;驳回 -> 退回草稿
|
|
var bid models.TenantCrmBiddingBid
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
|
Filter("id", row.BidID).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&bid); err == nil {
|
|
if body.Pass {
|
|
bid.Status = 2
|
|
} else {
|
|
bid.Status = 0
|
|
}
|
|
bid.UpdateTime = now
|
|
_, _ = models.Orm.Update(&bid, "status", "update_time")
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "approve", "立项审批处理", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// ------------------------------ 标书版本 ------------------------------
|
|
|
|
// BackendCrmBiddingBidDocumentController 标书版本
|
|
type BackendCrmBiddingBidDocumentController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type bidDocumentPayload struct {
|
|
BidID uint64 `json:"bid_id"`
|
|
TemplateID *uint64 `json:"template_id"`
|
|
Version string `json:"version"`
|
|
Title string `json:"title"`
|
|
Content string `json:"content"`
|
|
FileURL string `json:"file_url"`
|
|
Status int8 `json:"status"`
|
|
ReviewComment string `json:"review_comment"`
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/bid-document/list?bid_id=1
|
|
func (c *BackendCrmBiddingBidDocumentController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
bidID, _ := c.GetInt64("bid_id")
|
|
if bidID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "bid_id 必填")
|
|
return
|
|
}
|
|
var list []models.TenantCrmBiddingBidDocument
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidDocument)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("bid_id", bidID).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-id").
|
|
All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list, "total": len(list)})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/bid-document
|
|
func (c *BackendCrmBiddingBidDocumentController) 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 bidDocumentPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if p.BidID == 0 || strings.TrimSpace(p.Title) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "标书信息不完整")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingBidDocument{
|
|
TenantID: pipelineTenantID(claims),
|
|
BidID: p.BidID,
|
|
TemplateID: p.TemplateID,
|
|
Version: firstNonEmpty(p.Version, "v1.0"),
|
|
Title: strings.TrimSpace(p.Title),
|
|
Content: p.Content,
|
|
FileURL: p.FileURL,
|
|
Status: p.Status,
|
|
EditorUserID: pipelineUID(claims),
|
|
EditorUserName: resolveUserName(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "新建标书版本", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update PUT /backend/crm/bidding/bid-document/:id
|
|
func (c *BackendCrmBiddingBidDocumentController) 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)
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p bidDocumentPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
var row models.TenantCrmBiddingBidDocument
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBidDocument)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "标书不存在")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.Title) != "" {
|
|
row.Title = strings.TrimSpace(p.Title)
|
|
}
|
|
row.Version = firstNonEmpty(p.Version, row.Version)
|
|
row.Content = p.Content
|
|
row.FileURL = p.FileURL
|
|
row.Status = p.Status
|
|
row.ReviewComment = p.ReviewComment
|
|
if p.Status == 3 || p.Status == 4 {
|
|
row.ReviewUserID = pipelineUID(claims)
|
|
row.ReviewUserName = resolveUserName(claims)
|
|
now := time.Now()
|
|
row.ReviewTime = &now
|
|
if p.Status == 4 {
|
|
row.SubmitTime = &now
|
|
}
|
|
}
|
|
row.UpdateTime = time.Now()
|
|
_, _ = models.Orm.Update(&row)
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "update", "更新标书版本", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/bidding/bid-document/:id
|
|
func (c *BackendCrmBiddingBidDocumentController) 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)
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidDocument)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "delete", "删除标书版本", claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ------------------------------ 标书模板库 ------------------------------
|
|
|
|
// BackendCrmBiddingBidTemplateController 标书模板库(公共库,租户内共享)
|
|
type BackendCrmBiddingBidTemplateController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type bidTemplatePayload struct {
|
|
TemplateName string `json:"template_name"`
|
|
TemplateType string `json:"template_type"`
|
|
Industry string `json:"industry"`
|
|
Description string `json:"description"`
|
|
Content string `json:"content"`
|
|
FileURL string `json:"file_url"`
|
|
Status int8 `json:"status"`
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/template/list?keyword=&template_type=
|
|
func (c *BackendCrmBiddingBidTemplateController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
templateType := strings.TrimSpace(c.GetString("template_type"))
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 200 {
|
|
pageSize = 20
|
|
}
|
|
qs := models.Orm.QueryTable(new(models.TenantCrmBiddingBidTemplate)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true)
|
|
if keyword != "" {
|
|
qs = qs.Filter("template_name__icontains", keyword)
|
|
}
|
|
if templateType != "" {
|
|
qs = qs.Filter("template_type", templateType)
|
|
}
|
|
total, _ := qs.Count()
|
|
var list []models.TenantCrmBiddingBidTemplate
|
|
_, _ = qs.OrderBy("-id").Limit(pageSize).Offset((page - 1) * pageSize).All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"list": list, "total": total, "page": page, "pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/template
|
|
func (c *BackendCrmBiddingBidTemplateController) 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 bidTemplatePayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.TemplateName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "模板名称必填")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingBidTemplate{
|
|
TenantID: pipelineTenantID(claims),
|
|
TemplateName: strings.TrimSpace(p.TemplateName),
|
|
TemplateType: firstNonEmpty(p.TemplateType, "1"),
|
|
Industry: p.Industry,
|
|
Description: p.Description,
|
|
Content: p.Content,
|
|
FileURL: p.FileURL,
|
|
Status: 1,
|
|
OwnerUserID: pipelineUID(claims),
|
|
OwnerUserName: resolveUserName(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "新建标书模板", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update PUT /backend/crm/bidding/template/:id
|
|
func (c *BackendCrmBiddingBidTemplateController) 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)
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p bidTemplatePayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
var row models.TenantCrmBiddingBidTemplate
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBidTemplate)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "模板不存在")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.TemplateName) != "" {
|
|
row.TemplateName = strings.TrimSpace(p.TemplateName)
|
|
}
|
|
row.TemplateType = firstNonEmpty(p.TemplateType, row.TemplateType)
|
|
row.Industry = p.Industry
|
|
row.Description = p.Description
|
|
row.Content = p.Content
|
|
row.FileURL = p.FileURL
|
|
if p.Status == 0 || p.Status == 1 {
|
|
row.Status = p.Status
|
|
}
|
|
row.UpdateTime = time.Now()
|
|
_, _ = models.Orm.Update(&row)
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "update", "更新标书模板", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/bidding/template/:id
|
|
func (c *BackendCrmBiddingBidTemplateController) 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)
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidTemplate)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "delete", "删除标书模板", claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ------------------------------ 资质文件库 ------------------------------
|
|
|
|
// BackendCrmBiddingBidQualificationController 资质文件库
|
|
type BackendCrmBiddingBidQualificationController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type bidQualificationPayload struct {
|
|
QualificationName string `json:"qualification_name"`
|
|
QualificationType string `json:"qualification_type"`
|
|
QualificationNo string `json:"qualification_no"`
|
|
IssuingAuthority string `json:"issuing_authority"`
|
|
IssueDate string `json:"issue_date"`
|
|
ExpireDate string `json:"expire_date"`
|
|
AlertDays uint32 `json:"alert_days"`
|
|
FileURL string `json:"file_url"`
|
|
FileName string `json:"file_name"`
|
|
FileSize uint64 `json:"file_size"`
|
|
Description string `json:"description"`
|
|
Status int8 `json:"status"`
|
|
}
|
|
|
|
// List GET /backend/crm/bidding/qualification/list?keyword=&status=
|
|
// 返回时按到期日自动标注即将过期(不落库,避免定时任务未跑时状态陈旧)。
|
|
func (c *BackendCrmBiddingBidQualificationController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
status := strings.TrimSpace(c.GetString("status"))
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 200 {
|
|
pageSize = 20
|
|
}
|
|
qs := models.Orm.QueryTable(new(models.TenantCrmBiddingBidQualification)).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true)
|
|
if keyword != "" {
|
|
qs = qs.Filter("qualification_name__icontains", keyword)
|
|
}
|
|
if status != "" {
|
|
qs = qs.Filter("status", status)
|
|
}
|
|
total, _ := qs.Count()
|
|
var list []models.TenantCrmBiddingBidQualification
|
|
_, _ = qs.OrderBy("expire_date").Limit(pageSize).Offset((page - 1) * pageSize).All(&list)
|
|
|
|
// 临期提醒:距到期不足 alert_days 天标记为「即将过期」,已过期标记为「失效」
|
|
type qualItem struct {
|
|
models.TenantCrmBiddingBidQualification
|
|
ExpiringDays int `json:"expiring_days"`
|
|
}
|
|
out := make([]qualItem, 0, len(list))
|
|
now := time.Now()
|
|
for _, q := range list {
|
|
days := -1
|
|
if q.ExpireDate != nil {
|
|
d := int(q.ExpireDate.Sub(now).Hours() / 24)
|
|
days = d
|
|
if d < 0 {
|
|
q.Status = 0
|
|
} else if q.AlertDays > 0 && uint32(d) <= q.AlertDays {
|
|
q.Status = 2
|
|
}
|
|
}
|
|
out = append(out, qualItem{TenantCrmBiddingBidQualification: q, ExpiringDays: days})
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"list": out, "total": total, "page": page, "pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// Create POST /backend/crm/bidding/qualification
|
|
func (c *BackendCrmBiddingBidQualificationController) 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 bidQualificationPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.QualificationName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "资质名称必填")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmBiddingBidQualification{
|
|
TenantID: pipelineTenantID(claims),
|
|
QualificationName: strings.TrimSpace(p.QualificationName),
|
|
QualificationType: firstNonEmpty(p.QualificationType, "1"),
|
|
QualificationNo: p.QualificationNo,
|
|
IssuingAuthority: p.IssuingAuthority,
|
|
IssueDate: parsePipelineDate(p.IssueDate),
|
|
ExpireDate: parsePipelineDate(p.ExpireDate),
|
|
AlertDays: p.AlertDays,
|
|
FileURL: p.FileURL,
|
|
FileName: p.FileName,
|
|
FileSize: p.FileSize,
|
|
Description: p.Description,
|
|
Status: 1,
|
|
OwnerUserID: pipelineUID(claims),
|
|
OwnerUserName: resolveUserName(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, uint64(id), "create", "新增企业资质", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update PUT /backend/crm/bidding/qualification/:id
|
|
func (c *BackendCrmBiddingBidQualificationController) 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)
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p bidQualificationPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
var row models.TenantCrmBiddingBidQualification
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBidQualification)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "资质不存在")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.QualificationName) != "" {
|
|
row.QualificationName = strings.TrimSpace(p.QualificationName)
|
|
}
|
|
row.QualificationType = firstNonEmpty(p.QualificationType, row.QualificationType)
|
|
row.QualificationNo = p.QualificationNo
|
|
row.IssuingAuthority = p.IssuingAuthority
|
|
if p.IssueDate != "" {
|
|
row.IssueDate = parsePipelineDate(p.IssueDate)
|
|
}
|
|
if p.ExpireDate != "" {
|
|
row.ExpireDate = parsePipelineDate(p.ExpireDate)
|
|
}
|
|
if p.AlertDays > 0 {
|
|
row.AlertDays = p.AlertDays
|
|
}
|
|
row.FileURL = p.FileURL
|
|
row.FileName = p.FileName
|
|
row.FileSize = p.FileSize
|
|
row.Description = p.Description
|
|
if p.Status == 0 || p.Status == 1 || p.Status == 2 {
|
|
row.Status = p.Status
|
|
}
|
|
row.UpdateTime = time.Now()
|
|
_, _ = models.Orm.Update(&row)
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "update", "更新企业资质", claims)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/bidding/qualification/:id
|
|
func (c *BackendCrmBiddingBidQualificationController) 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)
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidQualification)).
|
|
Filter("id", id).
|
|
Filter("tenant_id", pipelineTenantID(claims)).
|
|
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
crmWriteLog(pipelineTenantID(claims), 5, id, "delete", "删除企业资质", claims)
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// ------------------------------ 统计 ------------------------------
|
|
|
|
// BackendCrmBiddingStatisticsController 招投标统计
|
|
type BackendCrmBiddingStatisticsController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
// groupRows 执行分组聚合,返回 [{name, count}]。
|
|
func groupRows(table, field, tenantID string) []map[string]interface{} {
|
|
var maps []orm.Params
|
|
_, _ = models.Orm.Raw(
|
|
"SELECT "+field+" AS name, COUNT(*) AS count FROM "+table+
|
|
" WHERE tenant_id=? AND delete_time IS NULL GROUP BY "+field,
|
|
tenantID,
|
|
).Values(&maps)
|
|
out := make([]map[string]interface{}, 0, len(maps))
|
|
for _, m := range maps {
|
|
out = append(out, map[string]interface{}{"name": m["name"], "count": m["count"]})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// sumField 对某数值字段求和。
|
|
func sumField(table, field, tenantID string) float64 {
|
|
var v float64
|
|
_ = models.Orm.Raw(
|
|
"SELECT IFNULL(SUM("+field+"),0) FROM "+table+
|
|
" WHERE tenant_id=? AND delete_time IS NULL",
|
|
tenantID,
|
|
).QueryRow(&v)
|
|
return v
|
|
}
|
|
|
|
// Tender GET /backend/crm/bidding/statistics/tender
|
|
func (c *BackendCrmBiddingStatisticsController) Tender() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tid := pipelineTenantID(claims)
|
|
var total int64
|
|
_ = models.Orm.Raw(
|
|
"SELECT COUNT(*) FROM yz_backend_crm_bidding_tender WHERE tenant_id=? AND delete_time IS NULL",
|
|
tid,
|
|
).QueryRow(&total)
|
|
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"total": total,
|
|
"by_status": groupRows("yz_backend_crm_bidding_tender", "status", tid),
|
|
"by_type": groupRows("yz_backend_crm_bidding_tender", "tender_type", tid),
|
|
"budget_total": sumField("yz_backend_crm_bidding_tender", "budget_amount", tid),
|
|
"award_total": sumField("yz_backend_crm_bidding_tender", "winner_amount", tid),
|
|
})
|
|
}
|
|
|
|
// Bid GET /backend/crm/bidding/statistics/bid
|
|
func (c *BackendCrmBiddingStatisticsController) Bid() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tid := pipelineTenantID(claims)
|
|
var total int64
|
|
_ = models.Orm.Raw(
|
|
"SELECT COUNT(*) FROM yz_backend_crm_bidding_bid WHERE tenant_id=? AND delete_time IS NULL",
|
|
tid,
|
|
).QueryRow(&total)
|
|
|
|
var won int64
|
|
_ = models.Orm.Raw(
|
|
"SELECT COUNT(*) FROM yz_backend_crm_bidding_bid WHERE tenant_id=? AND delete_time IS NULL AND status IN (4,6)",
|
|
tid,
|
|
).QueryRow(&won)
|
|
|
|
// 按月统计近 12 个月立项数
|
|
var monthly []orm.Params
|
|
_, _ = models.Orm.Raw(
|
|
"SELECT DATE_FORMAT(create_time,'%Y-%m') AS month, COUNT(*) AS count "+
|
|
"FROM yz_backend_crm_bidding_bid "+
|
|
"WHERE tenant_id=? AND delete_time IS NULL AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) "+
|
|
"GROUP BY month ORDER BY month",
|
|
tid,
|
|
).Values(&monthly)
|
|
|
|
winRate := 0.0
|
|
if total > 0 {
|
|
winRate = float64(won) / float64(total) * 100
|
|
}
|
|
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"total": total,
|
|
"won": won,
|
|
"win_rate": winRate,
|
|
"by_status": groupRows("yz_backend_crm_bidding_bid", "status", tid),
|
|
"amount_won": sumField("yz_backend_crm_bidding_bid", "bid_amount", tid),
|
|
"expense": sumField("yz_backend_crm_bidding_bid_expense", "amount", tid),
|
|
"monthly": monthly,
|
|
})
|
|
}
|