增加招投标模块
This commit is contained in:
@@ -0,0 +1,846 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// =============================================================
|
||||
// CRM 招投标管理 - 投标方(Bid)
|
||||
// =============================================================
|
||||
//
|
||||
// 状态机:0草稿 -> 1立项审批中 -> 2投标中 -> 3已开标 -> (4中标 | 5落标) -> (6已转合同 | 7已归档)
|
||||
// 关联:商机(1)/项目(2)/独立(0) -> 中标 -> 合同/项目
|
||||
// =============================================================
|
||||
|
||||
// BackendCrmBiddingBidController 投标方
|
||||
type BackendCrmBiddingBidController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// bidPayload 创建/更新请求体
|
||||
type bidPayload struct {
|
||||
BidNo string `json:"bid_no"`
|
||||
Title string `json:"title"`
|
||||
BidType string `json:"bid_type"`
|
||||
SourceType int8 `json:"source_type"`
|
||||
SourceID uint64 `json:"source_id"`
|
||||
BusinessID uint64 `json:"business_id"`
|
||||
BusinessName string `json:"business_name"`
|
||||
ProjectID uint64 `json:"project_id"`
|
||||
ProjectName string `json:"project_name"`
|
||||
TenderID uint64 `json:"tender_id"`
|
||||
TenderNo string `json:"tender_no"`
|
||||
CustomerID uint64 `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
PublishOrg string `json:"publish_org"`
|
||||
BidAmount float64 `json:"bid_amount"`
|
||||
BidDeposit float64 `json:"bid_deposit"`
|
||||
PublishDate string `json:"publish_date"`
|
||||
RegisterEndDate string `json:"register_end_date"`
|
||||
BidEndDate string `json:"bid_end_date"`
|
||||
OpenBidDate string `json:"open_bid_date"`
|
||||
ContactPerson string `json:"contact_person"`
|
||||
ContactPhone string `json:"contact_phone"`
|
||||
BidAddress string `json:"bid_address"`
|
||||
OpenAddress string `json:"open_address"`
|
||||
OwnerUserID string `json:"owner_user_id"`
|
||||
OwnerUserName string `json:"owner_user_name"`
|
||||
Description string `json:"description"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List GET /backend/crm/bidding/bid/list
|
||||
func (c *BackendCrmBiddingBidController) 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"))
|
||||
winStatus := strings.TrimSpace(c.GetString("win_status"))
|
||||
sourceType := strings.TrimSpace(c.GetString("source_type"))
|
||||
ownerID := strings.TrimSpace(c.GetString("owner_user_id"))
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
cond := orm.NewCondition().And("tenant_id", tenantID).And("delete_time__isnull", true)
|
||||
cond = crmApplyOwnerScope(cond, claims)
|
||||
|
||||
if keyword != "" {
|
||||
kw := orm.NewCondition().
|
||||
Or("title__contains", keyword).
|
||||
Or("bid_no__contains", keyword).
|
||||
Or("customer_name__contains", keyword).
|
||||
Or("business_name__contains", keyword).
|
||||
Or("contact_person__contains", keyword)
|
||||
cond = cond.AndCond(kw)
|
||||
}
|
||||
if status != "" {
|
||||
cond = cond.And("status", status)
|
||||
}
|
||||
if winStatus != "" {
|
||||
cond = cond.And("win_status", winStatus)
|
||||
}
|
||||
if sourceType != "" {
|
||||
if v, errParse := strconv.Atoi(sourceType); errParse == nil {
|
||||
cond = cond.And("source_type", int8(v))
|
||||
}
|
||||
}
|
||||
if ownerID != "" {
|
||||
cond = cond.And("owner_user_id", ownerID)
|
||||
}
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).SetCond(cond)
|
||||
total, _ := qs.Count()
|
||||
var list []models.TenantCrmBiddingBid
|
||||
if total > 0 {
|
||||
_, _ = qs.OrderBy("-id").Offset((page - 1) * pageSize).Limit(pageSize).All(&list)
|
||||
}
|
||||
pipelineOk(&c.Controller, map[string]interface{}{
|
||||
"list": list, "total": total, "page": page, "pageSize": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// Detail GET /backend/crm/bidding/bid/detail/:id
|
||||
func (c *BackendCrmBiddingBidController) 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 row models.TenantCrmBiddingBid
|
||||
err = models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("id", id).Filter("tenant_id", pipelineTenantID(claims)).
|
||||
Filter("delete_time__isnull", true).One(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, row.OwnerUserID, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 403, 403, "无权访问该投标数据")
|
||||
return
|
||||
}
|
||||
pipelineOk(&c.Controller, row)
|
||||
}
|
||||
|
||||
// Create POST /backend/crm/bidding/bid
|
||||
func (c *BackendCrmBiddingBidController) 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 bidPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(p.Title) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "投标标题不能为空")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(p.CustomerName) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "请填写招标方名称")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingBid{
|
||||
TenantID: tenantID,
|
||||
BidNo: strings.TrimSpace(p.BidNo),
|
||||
Title: strings.TrimSpace(p.Title),
|
||||
BidType: firstNonEmpty(p.BidType, "1"),
|
||||
Status: 0,
|
||||
SourceType: p.SourceType,
|
||||
BusinessID: nilOrID(p.BusinessID),
|
||||
BusinessName: strings.TrimSpace(p.BusinessName),
|
||||
ProjectID: nilOrID(p.ProjectID),
|
||||
ProjectName: strings.TrimSpace(p.ProjectName),
|
||||
TenderID: nilOrID(p.TenderID),
|
||||
TenderNo: strings.TrimSpace(p.TenderNo),
|
||||
CustomerID: nilOrID(p.CustomerID),
|
||||
CustomerName: strings.TrimSpace(p.CustomerName),
|
||||
PublishOrg: strings.TrimSpace(p.PublishOrg),
|
||||
BidAmount: p.BidAmount,
|
||||
BidDeposit: p.BidDeposit,
|
||||
PublishDate: parsePipelineDate(p.PublishDate),
|
||||
RegisterEndDate: parsePipelineDateTime(p.RegisterEndDate),
|
||||
BidEndDate: parsePipelineDateTime(p.BidEndDate),
|
||||
OpenBidDate: parsePipelineDateTime(p.OpenBidDate),
|
||||
ContactPerson: strings.TrimSpace(p.ContactPerson),
|
||||
ContactPhone: strings.TrimSpace(p.ContactPhone),
|
||||
BidAddress: strings.TrimSpace(p.BidAddress),
|
||||
OpenAddress: strings.TrimSpace(p.OpenAddress),
|
||||
OwnerUserID: firstNonEmpty(p.OwnerUserID, pipelineUID(claims)),
|
||||
OwnerUserName: firstNonEmpty(p.OwnerUserName, resolveUserName(claims)),
|
||||
Description: p.Description,
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateUserName: resolveUserName(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
|
||||
// 唯一性校验:同租户内不允许重复创建同名投标
|
||||
if crmNameDuplicated(new(models.TenantCrmBiddingBid), tenantID, "title", row.Title) {
|
||||
pipelineErr(&c.Controller, 409, 409, "该投标标题已存在")
|
||||
return
|
||||
}
|
||||
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(tenantID, 4, uint64(id), "create", "创建投标:"+row.Title, claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Update PUT /backend/crm/bidding/bid/:id
|
||||
func (c *BackendCrmBiddingBidController) 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 bidPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var row models.TenantCrmBiddingBid
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, row.OwnerUserID, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该投标数据")
|
||||
return
|
||||
}
|
||||
if row.Locked == 1 || row.Status == 6 {
|
||||
pipelineErr(&c.Controller, 400, 400, "投标记录已锁定或已转合同,不能编辑")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(p.Title) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "投标标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
row.BidNo = strings.TrimSpace(p.BidNo)
|
||||
row.Title = strings.TrimSpace(p.Title)
|
||||
if strings.TrimSpace(p.BidType) != "" {
|
||||
row.BidType = p.BidType
|
||||
}
|
||||
if p.SourceType > 0 {
|
||||
row.SourceType = p.SourceType
|
||||
}
|
||||
row.BusinessID = nilOrID(p.BusinessID)
|
||||
row.BusinessName = strings.TrimSpace(p.BusinessName)
|
||||
row.ProjectID = nilOrID(p.ProjectID)
|
||||
row.ProjectName = strings.TrimSpace(p.ProjectName)
|
||||
row.TenderID = nilOrID(p.TenderID)
|
||||
row.TenderNo = strings.TrimSpace(p.TenderNo)
|
||||
row.CustomerID = nilOrID(p.CustomerID)
|
||||
row.CustomerName = strings.TrimSpace(p.CustomerName)
|
||||
row.PublishOrg = strings.TrimSpace(p.PublishOrg)
|
||||
row.BidAmount = p.BidAmount
|
||||
row.BidDeposit = p.BidDeposit
|
||||
row.PublishDate = parsePipelineDate(p.PublishDate)
|
||||
row.RegisterEndDate = parsePipelineDateTime(p.RegisterEndDate)
|
||||
row.BidEndDate = parsePipelineDateTime(p.BidEndDate)
|
||||
row.OpenBidDate = parsePipelineDateTime(p.OpenBidDate)
|
||||
row.ContactPerson = strings.TrimSpace(p.ContactPerson)
|
||||
row.ContactPhone = strings.TrimSpace(p.ContactPhone)
|
||||
row.BidAddress = strings.TrimSpace(p.BidAddress)
|
||||
row.OpenAddress = strings.TrimSpace(p.OpenAddress)
|
||||
if strings.TrimSpace(p.OwnerUserID) != "" {
|
||||
row.OwnerUserID = strings.TrimSpace(p.OwnerUserID)
|
||||
}
|
||||
if strings.TrimSpace(p.OwnerUserName) != "" {
|
||||
row.OwnerUserName = strings.TrimSpace(p.OwnerUserName)
|
||||
}
|
||||
row.Description = p.Description
|
||||
row.Remark = p.Remark
|
||||
row.UpdateTime = time.Now()
|
||||
|
||||
if _, err := models.Orm.Update(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(tenantID, 4, row.ID, "update", "更新投标:"+row.Title, claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": row.ID})
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/crm/bidding/bid/:id
|
||||
func (c *BackendCrmBiddingBidController) 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 row models.TenantCrmBiddingBid
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, row.OwnerUserID, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该投标数据")
|
||||
return
|
||||
}
|
||||
if row.Locked == 1 || row.Status == 6 {
|
||||
pipelineErr(&c.Controller, 400, 400, "投标记录已锁定或已转合同,不能删除")
|
||||
return
|
||||
}
|
||||
if !canDeleteCrmRecord(claims, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "只有创建人、租户管理员或平台管理员可以删除该投标")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
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)
|
||||
}
|
||||
|
||||
// ConvertContract POST /backend/crm/bidding/bid/:id/contract 中标转合同
|
||||
func (c *BackendCrmBiddingBidController) ConvertContract() {
|
||||
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 p struct {
|
||||
ContractName string `json:"contract_name"`
|
||||
ContractNo string `json:"contract_no"`
|
||||
ContractCategory string `json:"contract_category"`
|
||||
SignDate string `json:"sign_date"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var bid models.TenantCrmBiddingBid
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&bid); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, bid.OwnerUserID, bid.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该投标数据")
|
||||
return
|
||||
}
|
||||
if bid.WinStatus != 1 {
|
||||
pipelineErr(&c.Controller, 400, 400, "只有中标的投标才能转合同")
|
||||
return
|
||||
}
|
||||
if bid.ContractID != nil && *bid.ContractID > 0 {
|
||||
pipelineErr(&c.Controller, 400, 400, "该投标已转合同,不能重复转")
|
||||
return
|
||||
}
|
||||
|
||||
contractName := strings.TrimSpace(p.ContractName)
|
||||
if contractName == "" {
|
||||
contractName = bid.Title
|
||||
}
|
||||
category := strings.TrimSpace(p.ContractCategory)
|
||||
if category == "" {
|
||||
category = "2" // 默认:服务类
|
||||
}
|
||||
now := time.Now()
|
||||
bidIDVal := bid.ID
|
||||
|
||||
contract := models.TenantCrmContract{
|
||||
TenantID: tenantID,
|
||||
ContractNo: strings.TrimSpace(p.ContractNo),
|
||||
ContractName: contractName,
|
||||
ContractCategory: category,
|
||||
OurRole: 2, // 我方为乙方
|
||||
PartyCount: 2,
|
||||
ProjectID: bid.ProjectID, // 中标项目关联
|
||||
ProjectName: bid.ProjectName,
|
||||
OwnerUserID: bid.OwnerUserID,
|
||||
OwnerUserName: bid.OwnerUserName,
|
||||
SignDate: parsePipelineDate(p.SignDate),
|
||||
Step: 1,
|
||||
Status: 1,
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
contractID, err := models.Orm.Insert(&contract)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建合同失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 更新投标状态:状态置6已转合同,存 contract_id,加锁
|
||||
cid := uint64(contractID)
|
||||
bid.ContractID = &cid
|
||||
bid.Status = 6
|
||||
bid.Locked = 1
|
||||
bidIDForLog := bidIDVal
|
||||
bid.UpdateTime = now
|
||||
if _, err := models.Orm.Update(&bid, "contract_id", "status", "locked", "update_time"); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新投标状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
crmWriteLog(tenantID, 4, bidIDForLog, "convert-contract", "中标转合同:"+contractName, claims)
|
||||
crmWriteLog(tenantID, 1, cid, "create", "由投标「"+bid.Title+"」中标转化生成", claims)
|
||||
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"contract_id": contractID})
|
||||
}
|
||||
|
||||
// ConvertProject POST /backend/crm/bidding/bid/:id/project 中标转项目
|
||||
func (c *BackendCrmBiddingBidController) ConvertProject() {
|
||||
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 p struct {
|
||||
ProjectName string `json:"project_name"`
|
||||
ProjectNo string `json:"project_no"`
|
||||
Amount float64 `json:"amount"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var bid models.TenantCrmBiddingBid
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&bid); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, bid.OwnerUserID, bid.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该投标数据")
|
||||
return
|
||||
}
|
||||
if bid.WinStatus != 1 {
|
||||
pipelineErr(&c.Controller, 400, 400, "只有中标的投标才能转项目")
|
||||
return
|
||||
}
|
||||
if bid.NewProjectID != nil && *bid.NewProjectID > 0 {
|
||||
pipelineErr(&c.Controller, 400, 400, "该投标已转项目,不能重复转")
|
||||
return
|
||||
}
|
||||
|
||||
projectName := strings.TrimSpace(p.ProjectName)
|
||||
if projectName == "" {
|
||||
projectName = bid.Title
|
||||
}
|
||||
amount := p.Amount
|
||||
if amount == 0 {
|
||||
amount = bid.BidAmount
|
||||
}
|
||||
now := time.Now()
|
||||
bidIDVal := bid.ID
|
||||
|
||||
proj := models.TenantCrmProject{
|
||||
TenantID: tenantID,
|
||||
ProjectName: projectName,
|
||||
ProjectNo: strings.TrimSpace(p.ProjectNo),
|
||||
ProjectType: 1, // 单一项目
|
||||
BusinessID: bid.BusinessID,
|
||||
CustomerID: bid.CustomerID,
|
||||
CustomerName: bid.CustomerName,
|
||||
OwnerUserID: bid.OwnerUserID,
|
||||
OwnerUserName: bid.OwnerUserName,
|
||||
UpstreamAmount: amount,
|
||||
Amount: amount,
|
||||
Status: 1,
|
||||
StartDate: parsePipelineDate(p.StartDate),
|
||||
EndDate: parsePipelineDate(p.EndDate),
|
||||
ContactPerson: bid.ContactPerson,
|
||||
ContactPhone: bid.ContactPhone,
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
projID, err := models.Orm.Insert(&proj)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建项目失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pid := uint64(projID)
|
||||
bid.NewProjectID = &pid
|
||||
bid.ProjectID = &pid
|
||||
bid.ProjectName = projectName
|
||||
bid.UpdateTime = now
|
||||
if _, err := models.Orm.Update(&bid, "new_project_id", "project_id", "project_name", "update_time"); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新投标状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
crmWriteLog(tenantID, 4, bidIDVal, "convert-project", "中标转项目:"+projectName, claims)
|
||||
crmWriteLog(tenantID, 3, pid, "create", "由投标「"+bid.Title+"」中标转化生成", claims)
|
||||
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"project_id": projID})
|
||||
}
|
||||
|
||||
// SubmitResult POST /backend/crm/bidding/bid/:id/result 提交开标结果
|
||||
func (c *BackendCrmBiddingBidController) SubmitResult() {
|
||||
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 p struct {
|
||||
ResultType int8 `json:"result_type"`
|
||||
AnnounceDate string `json:"announce_date"`
|
||||
AnnounceURL string `json:"announce_url"`
|
||||
TotalScore float64 `json:"total_score"`
|
||||
OurRanking uint32 `json:"our_ranking"`
|
||||
CompetitorCount uint32 `json:"competitor_count"`
|
||||
Reason string `json:"reason"`
|
||||
Lessons string `json:"lessons"`
|
||||
NextStep string `json:"next_step"`
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var bid models.TenantCrmBiddingBid
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&bid); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, bid.OwnerUserID, bid.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该投标数据")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
result := models.TenantCrmBiddingBidResult{
|
||||
TenantID: tenantID,
|
||||
BidID: bid.ID,
|
||||
ResultType: p.ResultType,
|
||||
AnnounceDate: parsePipelineDateTime(p.AnnounceDate),
|
||||
AnnounceURL: strings.TrimSpace(p.AnnounceURL),
|
||||
TotalScore: p.TotalScore,
|
||||
OurRanking: p.OurRanking,
|
||||
CompetitorCount: p.CompetitorCount,
|
||||
Reason: p.Reason,
|
||||
Lessons: p.Lessons,
|
||||
NextStep: strings.TrimSpace(p.NextStep),
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateUserName: resolveUserName(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
resultID, err := models.Orm.Insert(&result)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "保存投标结果失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 同步投标主表状态/得分
|
||||
bid.WinStatus = p.ResultType
|
||||
bid.WinDate = parsePipelineDateTime(p.AnnounceDate)
|
||||
bid.Status = 3 // 已开标
|
||||
bid.TotalScore = p.TotalScore
|
||||
bid.OurRanking = p.OurRanking
|
||||
bid.CompetitorCount = p.CompetitorCount
|
||||
if p.ResultType == 1 {
|
||||
bid.Status = 4 // 中标
|
||||
} else if p.ResultType == 2 {
|
||||
bid.Status = 5 // 落标
|
||||
}
|
||||
bid.UpdateTime = now
|
||||
if _, err := models.Orm.Update(&bid, "win_status", "win_date", "status", "total_score", "our_ranking", "competitor_count", "update_time"); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新投标状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
crmWriteLog(tenantID, 4, bid.ID, "submit-result",
|
||||
fmt.Sprintf("提交开标结果:%s(%s)", resultTypeText(p.ResultType), bid.Title), claims)
|
||||
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": resultID})
|
||||
}
|
||||
|
||||
// FromBusiness POST /backend/crm/bidding/bid/from-business 从商机创建投标
|
||||
func (c *BackendCrmBiddingBidController) FromBusiness() {
|
||||
claims, err := pipelineClaims(&c.Controller)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
BusinessID uint64 `json:"business_id"`
|
||||
Title string `json:"title"`
|
||||
CustomerID uint64 `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
PublishOrg string `json:"publish_org"`
|
||||
BidType string `json:"bid_type"`
|
||||
BidAmount float64 `json:"bid_amount"`
|
||||
BidEndDate string `json:"bid_end_date"`
|
||||
OpenBidDate string `json:"open_bid_date"`
|
||||
OwnerUserID string `json:"owner_user_id"`
|
||||
OwnerUserName string `json:"owner_user_name"`
|
||||
Description string `json:"description"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.BusinessID == 0 {
|
||||
pipelineErr(&c.Controller, 400, 400, "请选择商机")
|
||||
return
|
||||
}
|
||||
tenantID := pipelineTenantID(claims)
|
||||
|
||||
var biz models.TenantCrmBusiness
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBusiness)).
|
||||
Filter("id", p.BusinessID).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&biz); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "商机未找到")
|
||||
return
|
||||
}
|
||||
if biz.Locked == 1 {
|
||||
pipelineErr(&c.Controller, 400, 400, "商机已转化并锁定,不能再发起投标")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
title = biz.BusinessName
|
||||
}
|
||||
bizID := biz.ID
|
||||
row := models.TenantCrmBiddingBid{
|
||||
TenantID: tenantID,
|
||||
Title: title,
|
||||
BidType: firstNonEmpty(p.BidType, "1"),
|
||||
Status: 0,
|
||||
SourceType: 1, // 1=商机
|
||||
SourceID: &bizID,
|
||||
BusinessID: &bizID,
|
||||
BusinessName: biz.BusinessName,
|
||||
CustomerID: biz.CustomerID,
|
||||
CustomerName: firstNonEmpty(p.CustomerName, biz.CustomerName),
|
||||
PublishOrg: firstNonEmpty(p.PublishOrg, biz.CustomerName),
|
||||
BidAmount: p.BidAmount,
|
||||
BidEndDate: parsePipelineDateTime(p.BidEndDate),
|
||||
OpenBidDate: parsePipelineDateTime(p.OpenBidDate),
|
||||
ContactPerson: biz.ContactPerson,
|
||||
ContactPhone: biz.ContactPhone,
|
||||
OwnerUserID: firstNonEmpty(p.OwnerUserID, biz.OwnerUserID, pipelineUID(claims)),
|
||||
OwnerUserName: firstNonEmpty(p.OwnerUserName, biz.OwnerUserName, resolveUserName(claims)),
|
||||
Description: p.Description,
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateUserName: resolveUserName(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
|
||||
if crmNameDuplicated(new(models.TenantCrmBiddingBid), tenantID, "title", row.Title) {
|
||||
pipelineErr(&c.Controller, 409, 409, "该投标标题已存在")
|
||||
return
|
||||
}
|
||||
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(tenantID, 4, uint64(id), "from-business", "从商机「"+biz.BusinessName+"」创建投标", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id, "business": biz})
|
||||
}
|
||||
|
||||
// FromProject POST /backend/crm/bidding/bid/from-project 从项目创建投标
|
||||
func (c *BackendCrmBiddingBidController) FromProject() {
|
||||
claims, err := pipelineClaims(&c.Controller)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ProjectID uint64 `json:"project_id"`
|
||||
Title string `json:"title"`
|
||||
BidType string `json:"bid_type"`
|
||||
BidAmount float64 `json:"bid_amount"`
|
||||
BidEndDate string `json:"bid_end_date"`
|
||||
OpenBidDate string `json:"open_bid_date"`
|
||||
OwnerUserID string `json:"owner_user_id"`
|
||||
OwnerUserName string `json:"owner_user_name"`
|
||||
Description string `json:"description"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.ProjectID == 0 {
|
||||
pipelineErr(&c.Controller, 400, 400, "请选择项目")
|
||||
return
|
||||
}
|
||||
tenantID := pipelineTenantID(claims)
|
||||
|
||||
var proj models.TenantCrmProject
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmProject)).
|
||||
Filter("id", p.ProjectID).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&proj); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "项目未找到")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
title = proj.ProjectName
|
||||
}
|
||||
projID := proj.ID
|
||||
row := models.TenantCrmBiddingBid{
|
||||
TenantID: tenantID,
|
||||
Title: title,
|
||||
BidType: firstNonEmpty(p.BidType, "1"),
|
||||
Status: 0,
|
||||
SourceType: 2, // 2=项目
|
||||
SourceID: &projID,
|
||||
ProjectID: &projID,
|
||||
ProjectName: proj.ProjectName,
|
||||
BusinessID: proj.BusinessID,
|
||||
BusinessName: "", // 需要回查商机名(不阻塞,简化处理)
|
||||
CustomerID: proj.CustomerID,
|
||||
CustomerName: proj.CustomerName,
|
||||
BidAmount: p.BidAmount,
|
||||
BidEndDate: parsePipelineDateTime(p.BidEndDate),
|
||||
OpenBidDate: parsePipelineDateTime(p.OpenBidDate),
|
||||
OwnerUserID: firstNonEmpty(p.OwnerUserID, proj.OwnerUserID, pipelineUID(claims)),
|
||||
OwnerUserName: firstNonEmpty(p.OwnerUserName, proj.OwnerUserName, resolveUserName(claims)),
|
||||
Description: p.Description,
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateUserName: resolveUserName(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
|
||||
if crmNameDuplicated(new(models.TenantCrmBiddingBid), tenantID, "title", row.Title) {
|
||||
pipelineErr(&c.Controller, 409, 409, "该投标标题已存在")
|
||||
return
|
||||
}
|
||||
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(tenantID, 4, uint64(id), "from-project", "从项目「"+proj.ProjectName+"」创建投标", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id, "project": proj})
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 辅助函数
|
||||
// =====================================================================
|
||||
|
||||
// nilOrID 当 id>0 时返回指针,否则返回 nil
|
||||
func nilOrID(id uint64) *uint64 {
|
||||
if id == 0 {
|
||||
return nil
|
||||
}
|
||||
v := id
|
||||
return &v
|
||||
}
|
||||
|
||||
// resultTypeText 投标结果文本
|
||||
func resultTypeText(t int8) string {
|
||||
switch t {
|
||||
case 1:
|
||||
return "中标"
|
||||
case 2:
|
||||
return "未中标"
|
||||
case 3:
|
||||
return "废标"
|
||||
case 4:
|
||||
return "流标"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,208 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// =============================================================
|
||||
// CRM 招投标管理 - 到期提醒(开发计划 4.1)
|
||||
// =============================================================
|
||||
//
|
||||
// 不新增表、不引入定时任务:所有提醒都是「按当前时间实时计算」的,
|
||||
// 避免定时任务没跑导致状态陈旧。前端在招投标首页拉取近 N 天的待办。
|
||||
//
|
||||
// 覆盖四类到期:
|
||||
// 1. 投标:报名截止 / 投标截止 / 开标时间
|
||||
// 2. 招标:报名截止 / 投标截止 / 开标时间
|
||||
// 3. 资质:证书到期
|
||||
//
|
||||
// GET /backend/crm/bidding/reminders?days=7
|
||||
// =============================================================
|
||||
|
||||
// BackendCrmBiddingReminderController 招投标到期提醒
|
||||
type BackendCrmBiddingReminderController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// biddingReminderItem 一条到期提醒
|
||||
type biddingReminderItem struct {
|
||||
BizType string `json:"biz_type"` // bid | tender | qualification
|
||||
BizID uint64 `json:"biz_id"` // 业务记录ID
|
||||
Kind string `json:"kind"` // register_end | bid_end | open_bid | expire
|
||||
KindName string `json:"kind_name"` // 提醒类型名称
|
||||
Title string `json:"title"` // 记录标题
|
||||
DueDate string `json:"due_date"` // 到期时间 yyyy-MM-dd HH:mm:ss
|
||||
RemainDays int `json:"remain_days"` // 剩余天数,负值表示已逾期
|
||||
Level string `json:"level"` // danger(<=1天/已逾期) | warning(<=3天) | info
|
||||
OwnerName string `json:"owner_user_name"`
|
||||
}
|
||||
|
||||
// List GET /backend/crm/bidding/reminders?days=7
|
||||
func (c *BackendCrmBiddingReminderController) List() {
|
||||
claims, err := pipelineClaims(&c.Controller)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid := pipelineTenantID(claims)
|
||||
|
||||
days, _ := c.GetInt("days", 7)
|
||||
if days <= 0 || days > 365 {
|
||||
days = 7
|
||||
}
|
||||
now := time.Now()
|
||||
deadline := now.AddDate(0, 0, days)
|
||||
|
||||
items := make([]biddingReminderItem, 0, 32)
|
||||
|
||||
// ---------- 投标侧 ----------
|
||||
var bids []models.TenantCrmBiddingBid
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBid)).
|
||||
Filter("tenant_id", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status__in", 0, 1, 2, 3).
|
||||
All(&bids)
|
||||
for _, b := range bids {
|
||||
if !canAccessCrmRecord(claims, b.OwnerUserID, b.CreateUserID) {
|
||||
continue
|
||||
}
|
||||
items = append(items, pickBidReminders(b, now, deadline)...)
|
||||
}
|
||||
|
||||
// ---------- 招标侧 ----------
|
||||
var tenders []models.TenantCrmBiddingTender
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
Filter("tenant_id", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status__in", 0, 1, 2, 3, 4, 5).
|
||||
All(&tenders)
|
||||
for _, t := range tenders {
|
||||
if !canAccessCrmRecord(claims, t.OwnerUserID, t.CreateUserID) {
|
||||
continue
|
||||
}
|
||||
items = append(items, pickTenderReminders(t, now, deadline)...)
|
||||
}
|
||||
|
||||
// ---------- 资质到期 ----------
|
||||
var quals []models.TenantCrmBiddingBidQualification
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidQualification)).
|
||||
Filter("tenant_id", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&quals)
|
||||
for _, q := range quals {
|
||||
if q.ExpireDate == nil {
|
||||
continue
|
||||
}
|
||||
due := *q.ExpireDate
|
||||
// 资质到期包含「已过期」,便于用户发现失效证书
|
||||
if due.After(deadline) {
|
||||
continue
|
||||
}
|
||||
items = append(items, biddingReminderItem{
|
||||
BizType: "qualification",
|
||||
BizID: q.ID,
|
||||
Kind: "expire",
|
||||
KindName: "资质到期",
|
||||
Title: q.QualificationName,
|
||||
DueDate: due.Format("2006-01-02"),
|
||||
RemainDays: daysBetween(now, due),
|
||||
Level: levelOf(daysBetween(now, due)),
|
||||
OwnerName: q.OwnerUserName,
|
||||
})
|
||||
}
|
||||
|
||||
// 按到期时间升序:最紧急的排在最前
|
||||
sort.SliceStable(items, func(i, j int) bool {
|
||||
if items[i].RemainDays != items[j].RemainDays {
|
||||
return items[i].RemainDays < items[j].RemainDays
|
||||
}
|
||||
return items[i].DueDate < items[j].DueDate
|
||||
})
|
||||
|
||||
pipelineOk(&c.Controller, map[string]interface{}{
|
||||
"list": items,
|
||||
"total": len(items),
|
||||
"days": days,
|
||||
})
|
||||
}
|
||||
|
||||
// pickBidReminders 从一条投标记录中挑出落在窗口内的到期项
|
||||
func pickBidReminders(b models.TenantCrmBiddingBid, now, deadline time.Time) []biddingReminderItem {
|
||||
out := make([]biddingReminderItem, 0, 3)
|
||||
add := func(kind, kindName string, due *time.Time) {
|
||||
if due == nil {
|
||||
return
|
||||
}
|
||||
d := *due
|
||||
if d.Before(now) || d.After(deadline) {
|
||||
return
|
||||
}
|
||||
out = append(out, biddingReminderItem{
|
||||
BizType: "bid",
|
||||
BizID: b.ID,
|
||||
Kind: kind,
|
||||
KindName: kindName,
|
||||
Title: b.Title,
|
||||
DueDate: d.Format("2006-01-02 15:04:05"),
|
||||
RemainDays: daysBetween(now, d),
|
||||
Level: levelOf(daysBetween(now, d)),
|
||||
OwnerName: b.OwnerUserName,
|
||||
})
|
||||
}
|
||||
add("register_end", "报名截止", b.RegisterEndDate)
|
||||
add("bid_end", "投标截止", b.BidEndDate)
|
||||
add("open_bid", "开标时间", b.OpenBidDate)
|
||||
return out
|
||||
}
|
||||
|
||||
// pickTenderReminders 从一条招标记录中挑出落在窗口内的到期项
|
||||
func pickTenderReminders(t models.TenantCrmBiddingTender, now, deadline time.Time) []biddingReminderItem {
|
||||
out := make([]biddingReminderItem, 0, 3)
|
||||
add := func(kind, kindName string, due *time.Time) {
|
||||
if due == nil {
|
||||
return
|
||||
}
|
||||
d := *due
|
||||
if d.Before(now) || d.After(deadline) {
|
||||
return
|
||||
}
|
||||
out = append(out, biddingReminderItem{
|
||||
BizType: "tender",
|
||||
BizID: t.ID,
|
||||
Kind: kind,
|
||||
KindName: kindName,
|
||||
Title: t.Title,
|
||||
DueDate: d.Format("2006-01-02 15:04:05"),
|
||||
RemainDays: daysBetween(now, d),
|
||||
Level: levelOf(daysBetween(now, d)),
|
||||
OwnerName: t.OwnerUserName,
|
||||
})
|
||||
}
|
||||
add("register_end", "报名截止", t.RegisterEndDate)
|
||||
add("bid_end", "投标截止", t.BidEndDate)
|
||||
add("open_bid", "开标时间", t.OpenBidDate)
|
||||
return out
|
||||
}
|
||||
|
||||
// daysBetween 计算今天(0点)到目标时间的剩余天数
|
||||
func daysBetween(now, target time.Time) int {
|
||||
n := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
t := time.Date(target.Year(), target.Month(), target.Day(), 0, 0, 0, 0, target.Location())
|
||||
return int(t.Sub(n).Hours() / 24)
|
||||
}
|
||||
|
||||
// levelOf 剩余天数 -> 提醒等级
|
||||
func levelOf(days int) string {
|
||||
if days <= 1 {
|
||||
return "danger"
|
||||
}
|
||||
if days <= 3 {
|
||||
return "warning"
|
||||
}
|
||||
return "info"
|
||||
}
|
||||
@@ -0,0 +1,812 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// =============================================================
|
||||
// CRM 招投标管理 - 子表 CRUD(统一接口)
|
||||
// =============================================================
|
||||
//
|
||||
// 每个子表控制器提供统一的接口:
|
||||
// - List 按外键 bid_id / tender_id 列出
|
||||
// - Create 新增(关联外键由请求体传入)
|
||||
// - Delete 软删除
|
||||
// - Update 更新(用于文件改名、任务进度等)
|
||||
// =============================================================
|
||||
|
||||
// ------------------------------ 投标文件 ------------------------------
|
||||
|
||||
// BackendCrmBiddingBidFileController 投标文件
|
||||
type BackendCrmBiddingBidFileController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// bidFilePayload
|
||||
type bidFilePayload struct {
|
||||
BidID uint64 `json:"bid_id"`
|
||||
FileType string `json:"file_type"`
|
||||
FileName string `json:"file_name"`
|
||||
FileURL string `json:"file_url"`
|
||||
FileSize uint64 `json:"file_size"`
|
||||
FileExt string `json:"file_ext"`
|
||||
Version string `json:"version"`
|
||||
UploadUserID string `json:"upload_user_id"`
|
||||
UploadUserName string `json:"upload_user_name"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List GET /backend/crm/bidding/bid-file/list?bid_id=1
|
||||
func (c *BackendCrmBiddingBidFileController) 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.TenantCrmBiddingBidFile
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidFile)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Create POST /backend/crm/bidding/bid-file
|
||||
func (c *BackendCrmBiddingBidFileController) 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 bidFilePayload
|
||||
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
|
||||
}
|
||||
if strings.TrimSpace(p.FileName) == "" || strings.TrimSpace(p.FileURL) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "文件信息不完整")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingBidFile{
|
||||
TenantID: pipelineTenantID(claims),
|
||||
BidID: p.BidID,
|
||||
FileType: firstNonEmpty(p.FileType, "1"),
|
||||
FileName: strings.TrimSpace(p.FileName),
|
||||
FileURL: strings.TrimSpace(p.FileURL),
|
||||
FileSize: p.FileSize,
|
||||
FileExt: strings.TrimSpace(p.FileExt),
|
||||
Version: firstNonEmpty(p.Version, "1.0"),
|
||||
UploadUserID: firstNonEmpty(p.UploadUserID, pipelineUID(claims)),
|
||||
UploadUserName: firstNonEmpty(p.UploadUserName, resolveUserName(claims)),
|
||||
Remark: p.Remark,
|
||||
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, p.BidID, "create", "上传投标文件", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/crm/bidding/bid-file/:id
|
||||
func (c *BackendCrmBiddingBidFileController) 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
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidFile)).
|
||||
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
|
||||
}
|
||||
var delRow models.TenantCrmBiddingBidFile
|
||||
_ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidFile)).Filter("id", id).One(&delRow)
|
||||
crmWriteLog(pipelineTenantID(claims), 5, delRow.BidID, "delete", "删除投标文件", claims)
|
||||
pipelineOk(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// ------------------------------ 投标任务 ------------------------------
|
||||
|
||||
// BackendCrmBiddingBidTaskController 投标任务
|
||||
type BackendCrmBiddingBidTaskController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type bidTaskPayload struct {
|
||||
BidID uint64 `json:"bid_id"`
|
||||
TaskName string `json:"task_name"`
|
||||
TaskType string `json:"task_type"`
|
||||
Content string `json:"content"`
|
||||
OwnerUserID string `json:"owner_user_id"`
|
||||
OwnerUserName string `json:"owner_user_name"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
Status int8 `json:"status"`
|
||||
Progress int8 `json:"progress"`
|
||||
Files string `json:"files"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List
|
||||
func (c *BackendCrmBiddingBidTaskController) 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.TenantCrmBiddingBidTask
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidTask)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Create
|
||||
func (c *BackendCrmBiddingBidTaskController) 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 bidTaskPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.BidID == 0 || strings.TrimSpace(p.TaskName) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "任务信息不完整")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingBidTask{
|
||||
TenantID: pipelineTenantID(claims),
|
||||
BidID: p.BidID,
|
||||
TaskName: strings.TrimSpace(p.TaskName),
|
||||
TaskType: firstNonEmpty(p.TaskType, "1"),
|
||||
Content: p.Content,
|
||||
OwnerUserID: firstNonEmpty(p.OwnerUserID, pipelineUID(claims)),
|
||||
OwnerUserName: firstNonEmpty(p.OwnerUserName, resolveUserName(claims)),
|
||||
StartDate: parsePipelineDate(p.StartDate),
|
||||
EndDate: parsePipelineDate(p.EndDate),
|
||||
Status: p.Status,
|
||||
Progress: p.Progress,
|
||||
Files: p.Files,
|
||||
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, p.BidID, "create", "新建投标任务", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Update
|
||||
func (c *BackendCrmBiddingBidTaskController) 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 bidTaskPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.TenantCrmBiddingBidTask
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingBidTask)).
|
||||
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.TaskName) != "" {
|
||||
row.TaskName = strings.TrimSpace(p.TaskName)
|
||||
}
|
||||
if strings.TrimSpace(p.TaskType) != "" {
|
||||
row.TaskType = p.TaskType
|
||||
}
|
||||
row.Content = p.Content
|
||||
if strings.TrimSpace(p.OwnerUserID) != "" {
|
||||
row.OwnerUserID = strings.TrimSpace(p.OwnerUserID)
|
||||
row.OwnerUserName = strings.TrimSpace(p.OwnerUserName)
|
||||
}
|
||||
row.StartDate = parsePipelineDate(p.StartDate)
|
||||
row.EndDate = parsePipelineDate(p.EndDate)
|
||||
row.Status = p.Status
|
||||
row.Progress = p.Progress
|
||||
row.Files = p.Files
|
||||
row.Remark = p.Remark
|
||||
row.UpdateTime = time.Now()
|
||||
if _, err := models.Orm.Update(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(pipelineTenantID(claims), 5, row.BidID, "update", "更新投标任务", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": row.ID})
|
||||
}
|
||||
|
||||
// Delete
|
||||
func (c *BackendCrmBiddingBidTaskController) 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
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidTask)).
|
||||
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
|
||||
}
|
||||
var delRow models.TenantCrmBiddingBidTask
|
||||
_ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidTask)).Filter("id", id).One(&delRow)
|
||||
crmWriteLog(pipelineTenantID(claims), 5, delRow.BidID, "delete", "删除投标任务", claims)
|
||||
pipelineOk(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// ------------------------------ 投标结果 ------------------------------
|
||||
|
||||
// BackendCrmBiddingBidResultController 投标结果
|
||||
type BackendCrmBiddingBidResultController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// List
|
||||
func (c *BackendCrmBiddingBidResultController) 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.TenantCrmBiddingBidResult
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidResult)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Detail
|
||||
func (c *BackendCrmBiddingBidResultController) 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 row models.TenantCrmBiddingBidResult
|
||||
err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidResult)).
|
||||
Filter("id", id).
|
||||
Filter("tenant_id", pipelineTenantID(claims)).
|
||||
Filter("delete_time__isnull", true).One(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "投标结果未找到")
|
||||
return
|
||||
}
|
||||
pipelineOk(&c.Controller, row)
|
||||
}
|
||||
|
||||
// ------------------------------ 投标报价 ------------------------------
|
||||
|
||||
// BackendCrmBiddingBidQuoteController 投标报价
|
||||
type BackendCrmBiddingBidQuoteController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type bidQuotePayload struct {
|
||||
BidID uint64 `json:"bid_id"`
|
||||
QuoteName string `json:"quote_name"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
Currency string `json:"currency"`
|
||||
TaxRate float64 `json:"tax_rate"`
|
||||
ValidDate string `json:"valid_date"`
|
||||
Products string `json:"products"`
|
||||
CostAnalysis string `json:"cost_analysis"`
|
||||
ProfitRate float64 `json:"profit_rate"`
|
||||
Status int8 `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List
|
||||
func (c *BackendCrmBiddingBidQuoteController) 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.TenantCrmBiddingBidQuote
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidQuote)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Create
|
||||
func (c *BackendCrmBiddingBidQuoteController) 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 bidQuotePayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.BidID == 0 || strings.TrimSpace(p.QuoteName) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "报价信息不完整")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingBidQuote{
|
||||
TenantID: pipelineTenantID(claims),
|
||||
BidID: p.BidID,
|
||||
QuoteName: strings.TrimSpace(p.QuoteName),
|
||||
TotalAmount: p.TotalAmount,
|
||||
Currency: firstNonEmpty(p.Currency, "CNY"),
|
||||
TaxRate: p.TaxRate,
|
||||
ValidDate: parsePipelineDate(p.ValidDate),
|
||||
Products: p.Products,
|
||||
CostAnalysis: p.CostAnalysis,
|
||||
ProfitRate: p.ProfitRate,
|
||||
Status: 1,
|
||||
SubmitUserID: pipelineUID(claims),
|
||||
SubmitUserName: resolveUserName(claims),
|
||||
SubmitTime: &now,
|
||||
Remark: p.Remark,
|
||||
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, p.BidID, "create", "新建投标报价", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Delete
|
||||
func (c *BackendCrmBiddingBidQuoteController) 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
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidQuote)).
|
||||
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
|
||||
}
|
||||
var delRow models.TenantCrmBiddingBidQuote
|
||||
_ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidQuote)).Filter("id", id).One(&delRow)
|
||||
crmWriteLog(pipelineTenantID(claims), 5, delRow.BidID, "delete", "删除投标报价", claims)
|
||||
pipelineOk(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// ------------------------------ 投标竞争对手 ------------------------------
|
||||
|
||||
// BackendCrmBiddingBidCompetitorController
|
||||
type BackendCrmBiddingBidCompetitorController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type bidCompetitorPayload struct {
|
||||
BidID uint64 `json:"bid_id"`
|
||||
CompetitorName string `json:"competitor_name"`
|
||||
CompetitorType int8 `json:"competitor_type"`
|
||||
QuoteAmount float64 `json:"quote_amount"`
|
||||
Advantages string `json:"advantages"`
|
||||
Weaknesses string `json:"weaknesses"`
|
||||
Source string `json:"source"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List
|
||||
func (c *BackendCrmBiddingBidCompetitorController) 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.TenantCrmBiddingBidCompetitor
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidCompetitor)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Create
|
||||
func (c *BackendCrmBiddingBidCompetitorController) 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 bidCompetitorPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.BidID == 0 || strings.TrimSpace(p.CompetitorName) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "竞争对手信息不完整")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingBidCompetitor{
|
||||
TenantID: pipelineTenantID(claims),
|
||||
BidID: p.BidID,
|
||||
CompetitorName: strings.TrimSpace(p.CompetitorName),
|
||||
CompetitorType: firstNonEmptyInt8(p.CompetitorType, 1),
|
||||
QuoteAmount: p.QuoteAmount,
|
||||
Advantages: p.Advantages,
|
||||
Weaknesses: p.Weaknesses,
|
||||
Source: strings.TrimSpace(p.Source),
|
||||
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, p.BidID, "create", "新增竞争对手", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Delete
|
||||
func (c *BackendCrmBiddingBidCompetitorController) 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
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidCompetitor)).
|
||||
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
|
||||
}
|
||||
var delRow models.TenantCrmBiddingBidCompetitor
|
||||
_ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidCompetitor)).Filter("id", id).One(&delRow)
|
||||
crmWriteLog(pipelineTenantID(claims), 5, delRow.BidID, "delete", "删除竞争对手", claims)
|
||||
pipelineOk(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// ------------------------------ 投标费用 ------------------------------
|
||||
|
||||
// BackendCrmBiddingBidExpenseController
|
||||
type BackendCrmBiddingBidExpenseController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type bidExpensePayload struct {
|
||||
BidID uint64 `json:"bid_id"`
|
||||
ExpenseType int8 `json:"expense_type"`
|
||||
ExpenseName string `json:"expense_name"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
PaymentStatus int8 `json:"payment_status"`
|
||||
PaymentDate string `json:"payment_date"`
|
||||
Description string `json:"description"`
|
||||
VoucherURL string `json:"voucher_url"`
|
||||
}
|
||||
|
||||
// List
|
||||
func (c *BackendCrmBiddingBidExpenseController) 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.TenantCrmBiddingBidExpense
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidExpense)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Create
|
||||
func (c *BackendCrmBiddingBidExpenseController) 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 bidExpensePayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.BidID == 0 || strings.TrimSpace(p.ExpenseName) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "费用信息不完整")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingBidExpense{
|
||||
TenantID: pipelineTenantID(claims),
|
||||
BidID: p.BidID,
|
||||
ExpenseType: firstNonEmptyInt8(p.ExpenseType, 1),
|
||||
ExpenseName: strings.TrimSpace(p.ExpenseName),
|
||||
Amount: p.Amount,
|
||||
Currency: firstNonEmpty(p.Currency, "CNY"),
|
||||
PaymentStatus: p.PaymentStatus,
|
||||
PaymentDate: parsePipelineDateTime(p.PaymentDate),
|
||||
Description: p.Description,
|
||||
VoucherURL: strings.TrimSpace(p.VoucherURL),
|
||||
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, p.BidID, "create", "登记投标费用", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Delete
|
||||
func (c *BackendCrmBiddingBidExpenseController) 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
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingBidExpense)).
|
||||
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
|
||||
}
|
||||
var delRow models.TenantCrmBiddingBidExpense
|
||||
_ = models.Orm.QueryTable(new(models.TenantCrmBiddingBidExpense)).Filter("id", id).One(&delRow)
|
||||
crmWriteLog(pipelineTenantID(claims), 5, delRow.BidID, "delete", "删除投标费用", claims)
|
||||
pipelineOk(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// ------------------------------ 招标文件 ------------------------------
|
||||
|
||||
// BackendCrmBiddingTenderFileController 招标文件
|
||||
type BackendCrmBiddingTenderFileController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type tenderFilePayload struct {
|
||||
TenderID uint64 `json:"tender_id"`
|
||||
FileType string `json:"file_type"`
|
||||
FileName string `json:"file_name"`
|
||||
FileURL string `json:"file_url"`
|
||||
FileSize uint64 `json:"file_size"`
|
||||
FileExt string `json:"file_ext"`
|
||||
UploadUserID string `json:"upload_user_id"`
|
||||
UploadUserName string `json:"upload_user_name"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List
|
||||
func (c *BackendCrmBiddingTenderFileController) 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.TenantCrmBiddingTenderFile
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderFile)).
|
||||
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})
|
||||
}
|
||||
|
||||
// Create
|
||||
func (c *BackendCrmBiddingTenderFileController) 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 tenderFilePayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.TenderID == 0 || strings.TrimSpace(p.FileName) == "" || strings.TrimSpace(p.FileURL) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "文件信息不完整")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingTenderFile{
|
||||
TenantID: pipelineTenantID(claims),
|
||||
TenderID: p.TenderID,
|
||||
FileType: firstNonEmpty(p.FileType, "1"),
|
||||
FileName: strings.TrimSpace(p.FileName),
|
||||
FileURL: strings.TrimSpace(p.FileURL),
|
||||
FileSize: p.FileSize,
|
||||
FileExt: strings.TrimSpace(p.FileExt),
|
||||
UploadUserID: firstNonEmpty(p.UploadUserID, pipelineUID(claims)),
|
||||
UploadUserName: firstNonEmpty(p.UploadUserName, resolveUserName(claims)),
|
||||
Remark: p.Remark,
|
||||
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, p.TenderID, "create", "上传招标文件", claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Delete
|
||||
func (c *BackendCrmBiddingTenderFileController) 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
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderFile)).
|
||||
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
|
||||
}
|
||||
var delRow models.TenantCrmBiddingTenderFile
|
||||
_ = models.Orm.QueryTable(new(models.TenantCrmBiddingTenderFile)).Filter("id", id).One(&delRow)
|
||||
crmWriteLog(pipelineTenantID(claims), 5, delRow.TenderID, "delete", "删除招标文件", claims)
|
||||
pipelineOk(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 工具函数
|
||||
// =====================================================================
|
||||
|
||||
// firstNonEmptyInt8 返回第一个非零的 int8
|
||||
func firstNonEmptyInt8(v, def int8) int8 {
|
||||
if v == 0 {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// =============================================================
|
||||
// CRM 招投标管理 - 招标方(Tender)
|
||||
// =============================================================
|
||||
//
|
||||
// 状态机:0草稿 -> 1公告中 -> 2报名中 -> 3投标中 -> 4开标中 -> 5评标中 -> 6已定标 -> 7已归档
|
||||
// 关联:定标 -> 中标供应商 -> 转合同
|
||||
// =============================================================
|
||||
|
||||
// BackendCrmBiddingTenderController 招标方
|
||||
type BackendCrmBiddingTenderController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// tenderPayload 创建/更新请求体
|
||||
type tenderPayload struct {
|
||||
TenderNo string `json:"tender_no"`
|
||||
Title string `json:"title"`
|
||||
TenderType string `json:"tender_type"`
|
||||
CustomerID uint64 `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
PublishOrg string `json:"publish_org"`
|
||||
BudgetAmount float64 `json:"budget_amount"`
|
||||
BidDeposit float64 `json:"bid_deposit"`
|
||||
PublishDate string `json:"publish_date"`
|
||||
RegisterStartDate string `json:"register_start_date"`
|
||||
RegisterEndDate string `json:"register_end_date"`
|
||||
BidStartDate string `json:"bid_start_date"`
|
||||
BidEndDate string `json:"bid_end_date"`
|
||||
OpenBidDate string `json:"open_bid_date"`
|
||||
ContactPerson string `json:"contact_person"`
|
||||
ContactPhone string `json:"contact_phone"`
|
||||
ContactEmail string `json:"contact_email"`
|
||||
BidAddress string `json:"bid_address"`
|
||||
OpenAddress string `json:"open_address"`
|
||||
Description string `json:"description"`
|
||||
Requirement string `json:"requirement"`
|
||||
EvaluationMethod string `json:"evaluation_method"`
|
||||
OwnerUserID string `json:"owner_user_id"`
|
||||
OwnerUserName string `json:"owner_user_name"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// List GET /backend/crm/bidding/tender/list
|
||||
func (c *BackendCrmBiddingTenderController) 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)
|
||||
cond = crmApplyOwnerScope(cond, claims)
|
||||
|
||||
if keyword != "" {
|
||||
kw := orm.NewCondition().
|
||||
Or("title__contains", keyword).
|
||||
Or("tender_no__contains", keyword).
|
||||
Or("customer_name__contains", keyword).
|
||||
Or("publish_org__contains", keyword)
|
||||
cond = cond.AndCond(kw)
|
||||
}
|
||||
if status != "" {
|
||||
if v, errParse := strconv.Atoi(status); errParse == nil {
|
||||
cond = cond.And("status", int8(v))
|
||||
}
|
||||
}
|
||||
if ownerID != "" {
|
||||
cond = cond.And("owner_user_id", ownerID)
|
||||
}
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).SetCond(cond)
|
||||
total, _ := qs.Count()
|
||||
var list []models.TenantCrmBiddingTender
|
||||
if total > 0 {
|
||||
_, _ = qs.OrderBy("-id").Offset((page - 1) * pageSize).Limit(pageSize).All(&list)
|
||||
}
|
||||
pipelineOk(&c.Controller, map[string]interface{}{
|
||||
"list": list, "total": total, "page": page, "pageSize": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// Detail GET /backend/crm/bidding/tender/detail/:id
|
||||
func (c *BackendCrmBiddingTenderController) 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 row models.TenantCrmBiddingTender
|
||||
err = models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
Filter("id", id).Filter("tenant_id", pipelineTenantID(claims)).
|
||||
Filter("delete_time__isnull", true).One(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "招标记录未找到")
|
||||
return
|
||||
}
|
||||
pipelineOk(&c.Controller, row)
|
||||
}
|
||||
|
||||
// Create POST /backend/crm/bidding/tender
|
||||
func (c *BackendCrmBiddingTenderController) 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 tenderPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(p.Title) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "招标标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
now := time.Now()
|
||||
row := models.TenantCrmBiddingTender{
|
||||
TenantID: tenantID,
|
||||
TenderNo: strings.TrimSpace(p.TenderNo),
|
||||
Title: strings.TrimSpace(p.Title),
|
||||
TenderType: firstNonEmpty(p.TenderType, "1"),
|
||||
Status: 0,
|
||||
CustomerID: nilOrID(p.CustomerID),
|
||||
CustomerName: strings.TrimSpace(p.CustomerName),
|
||||
PublishOrg: firstNonEmpty(p.PublishOrg, p.CustomerName),
|
||||
BudgetAmount: p.BudgetAmount,
|
||||
BidDeposit: p.BidDeposit,
|
||||
PublishDate: parsePipelineDate(p.PublishDate),
|
||||
RegisterStartDate: parsePipelineDateTime(p.RegisterStartDate),
|
||||
RegisterEndDate: parsePipelineDateTime(p.RegisterEndDate),
|
||||
BidStartDate: parsePipelineDateTime(p.BidStartDate),
|
||||
BidEndDate: parsePipelineDateTime(p.BidEndDate),
|
||||
OpenBidDate: parsePipelineDateTime(p.OpenBidDate),
|
||||
ContactPerson: strings.TrimSpace(p.ContactPerson),
|
||||
ContactPhone: strings.TrimSpace(p.ContactPhone),
|
||||
ContactEmail: strings.TrimSpace(p.ContactEmail),
|
||||
BidAddress: strings.TrimSpace(p.BidAddress),
|
||||
OpenAddress: strings.TrimSpace(p.OpenAddress),
|
||||
Description: p.Description,
|
||||
Requirement: p.Requirement,
|
||||
EvaluationMethod: firstNonEmpty(p.EvaluationMethod, "1"),
|
||||
OwnerUserID: firstNonEmpty(p.OwnerUserID, pipelineUID(claims)),
|
||||
OwnerUserName: firstNonEmpty(p.OwnerUserName, resolveUserName(claims)),
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateUserName: resolveUserName(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
|
||||
if crmNameDuplicated(new(models.TenantCrmBiddingTender), tenantID, "title", row.Title) {
|
||||
pipelineErr(&c.Controller, 409, 409, "该招标标题已存在")
|
||||
return
|
||||
}
|
||||
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(tenantID, 5, uint64(id), "create", "创建招标:"+row.Title, claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Update PUT /backend/crm/bidding/tender/:id
|
||||
func (c *BackendCrmBiddingTenderController) 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 tenderPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var row models.TenantCrmBiddingTender
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "招标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, row.OwnerUserID, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该招标数据")
|
||||
return
|
||||
}
|
||||
if row.Locked == 1 || row.Status == 6 {
|
||||
pipelineErr(&c.Controller, 400, 400, "招标记录已定标锁定,不能编辑")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(p.Title) == "" {
|
||||
pipelineErr(&c.Controller, 400, 400, "招标标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
row.TenderNo = strings.TrimSpace(p.TenderNo)
|
||||
row.Title = strings.TrimSpace(p.Title)
|
||||
if strings.TrimSpace(p.TenderType) != "" {
|
||||
row.TenderType = p.TenderType
|
||||
}
|
||||
row.CustomerID = nilOrID(p.CustomerID)
|
||||
row.CustomerName = strings.TrimSpace(p.CustomerName)
|
||||
row.PublishOrg = strings.TrimSpace(p.PublishOrg)
|
||||
row.BudgetAmount = p.BudgetAmount
|
||||
row.BidDeposit = p.BidDeposit
|
||||
row.PublishDate = parsePipelineDate(p.PublishDate)
|
||||
row.RegisterStartDate = parsePipelineDateTime(p.RegisterStartDate)
|
||||
row.RegisterEndDate = parsePipelineDateTime(p.RegisterEndDate)
|
||||
row.BidStartDate = parsePipelineDateTime(p.BidStartDate)
|
||||
row.BidEndDate = parsePipelineDateTime(p.BidEndDate)
|
||||
row.OpenBidDate = parsePipelineDateTime(p.OpenBidDate)
|
||||
row.ContactPerson = strings.TrimSpace(p.ContactPerson)
|
||||
row.ContactPhone = strings.TrimSpace(p.ContactPhone)
|
||||
row.ContactEmail = strings.TrimSpace(p.ContactEmail)
|
||||
row.BidAddress = strings.TrimSpace(p.BidAddress)
|
||||
row.OpenAddress = strings.TrimSpace(p.OpenAddress)
|
||||
row.Description = p.Description
|
||||
row.Requirement = p.Requirement
|
||||
if strings.TrimSpace(p.EvaluationMethod) != "" {
|
||||
row.EvaluationMethod = p.EvaluationMethod
|
||||
}
|
||||
if strings.TrimSpace(p.OwnerUserID) != "" {
|
||||
row.OwnerUserID = strings.TrimSpace(p.OwnerUserID)
|
||||
}
|
||||
if strings.TrimSpace(p.OwnerUserName) != "" {
|
||||
row.OwnerUserName = strings.TrimSpace(p.OwnerUserName)
|
||||
}
|
||||
row.Remark = p.Remark
|
||||
row.UpdateTime = time.Now()
|
||||
|
||||
if _, err := models.Orm.Update(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
crmWriteLog(tenantID, 5, row.ID, "update", "更新招标:"+row.Title, claims)
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"id": row.ID})
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/crm/bidding/tender/:id
|
||||
func (c *BackendCrmBiddingTenderController) 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 row models.TenantCrmBiddingTender
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "招标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, row.OwnerUserID, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该招标数据")
|
||||
return
|
||||
}
|
||||
if row.Locked == 1 || row.Status == 6 {
|
||||
pipelineErr(&c.Controller, 400, 400, "招标记录已定标锁定,不能删除")
|
||||
return
|
||||
}
|
||||
if !canDeleteCrmRecord(claims, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "只有创建人、租户管理员或平台管理员可以删除该招标")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
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)
|
||||
}
|
||||
|
||||
// Award POST /backend/crm/bidding/tender/:id/award 定标
|
||||
func (c *BackendCrmBiddingTenderController) Award() {
|
||||
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 p struct {
|
||||
WinnerSupplierID uint64 `json:"winner_supplier_id"`
|
||||
WinnerSupplierName string `json:"winner_supplier_name"`
|
||||
WinnerAmount float64 `json:"winner_amount"`
|
||||
Announcement string `json:"announcement"`
|
||||
ResultType int8 `json:"result_type"` // 1定标 2流标 3废标
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var row models.TenantCrmBiddingTender
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "招标记录未找到")
|
||||
return
|
||||
}
|
||||
if !canAccessCrmRecord(claims, row.OwnerUserID, row.CreateUserID) {
|
||||
pipelineErr(&c.Controller, 401, 401, "无权操作该招标数据")
|
||||
return
|
||||
}
|
||||
if row.Locked == 1 {
|
||||
pipelineErr(&c.Controller, 400, 400, "招标记录已定标,不能重复定标")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
resultType := p.ResultType
|
||||
if resultType == 0 {
|
||||
resultType = 1
|
||||
}
|
||||
|
||||
// 写定标结果
|
||||
result := models.TenantCrmBiddingTenderResult{
|
||||
TenantID: tenantID,
|
||||
TenderID: row.ID,
|
||||
ResultType: resultType,
|
||||
WinnerSupplierID: nilOrID(p.WinnerSupplierID),
|
||||
WinnerSupplierName: strings.TrimSpace(p.WinnerSupplierName),
|
||||
WinnerAmount: p.WinnerAmount,
|
||||
ResultDate: &now,
|
||||
Announcement: p.Announcement,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateUserName: resolveUserName(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
if _, err := models.Orm.Insert(&result); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "写入定标结果失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 更新招标主表:状态 6已定标 + 锁定
|
||||
row.Status = 6
|
||||
row.Locked = 1
|
||||
row.WinnerSupplierID = nilOrID(p.WinnerSupplierID)
|
||||
row.WinnerSupplierName = strings.TrimSpace(p.WinnerSupplierName)
|
||||
row.WinnerAmount = p.WinnerAmount
|
||||
row.WinnerDate = &now
|
||||
row.UpdateTime = now
|
||||
if _, err := models.Orm.Update(&row, "status", "locked", "winner_supplier_id", "winner_supplier_name", "winner_amount", "winner_date", "update_time"); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新招标状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
crmWriteLog(tenantID, 5, row.ID, "award",
|
||||
fmt.Sprintf("定标:%s 中标金额 %.2f", p.WinnerSupplierName, p.WinnerAmount), claims)
|
||||
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"result_id": result.ID})
|
||||
}
|
||||
|
||||
// AwardToContract POST /backend/crm/bidding/tender/:id/contract 定标转合同
|
||||
func (c *BackendCrmBiddingTenderController) AwardToContract() {
|
||||
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 p struct {
|
||||
ContractName string `json:"contract_name"`
|
||||
ContractNo string `json:"contract_no"`
|
||||
ContractCategory string `json:"contract_category"`
|
||||
SignDate string `json:"sign_date"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := pipelineTenantID(claims)
|
||||
var row models.TenantCrmBiddingTender
|
||||
if err := models.Orm.QueryTable(new(models.TenantCrmBiddingTender)).
|
||||
Filter("id", id).Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).One(&row); err != nil {
|
||||
pipelineErr(&c.Controller, 404, 404, "招标记录未找到")
|
||||
return
|
||||
}
|
||||
if row.Status != 6 || row.Locked != 1 {
|
||||
pipelineErr(&c.Controller, 400, 400, "招标未定标,无法转合同")
|
||||
return
|
||||
}
|
||||
if row.ContractID != nil && *row.ContractID > 0 {
|
||||
pipelineErr(&c.Controller, 400, 400, "该招标已转合同,不能重复转")
|
||||
return
|
||||
}
|
||||
|
||||
contractName := strings.TrimSpace(p.ContractName)
|
||||
if contractName == "" {
|
||||
contractName = row.Title
|
||||
}
|
||||
category := strings.TrimSpace(p.ContractCategory)
|
||||
if category == "" {
|
||||
category = "2" // 服务类
|
||||
}
|
||||
now := time.Now()
|
||||
tenderID := row.ID
|
||||
|
||||
contract := models.TenantCrmContract{
|
||||
TenantID: tenantID,
|
||||
ContractNo: strings.TrimSpace(p.ContractNo),
|
||||
ContractName: contractName,
|
||||
ContractCategory: category,
|
||||
OurRole: 1, // 我方为甲方
|
||||
PartyCount: 2,
|
||||
OwnerUserID: row.OwnerUserID,
|
||||
OwnerUserName: row.OwnerUserName,
|
||||
SignDate: parsePipelineDate(p.SignDate),
|
||||
Step: 1,
|
||||
Status: 1,
|
||||
Remark: p.Remark,
|
||||
CreateUserID: pipelineUID(claims),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
contractID, err := models.Orm.Insert(&contract)
|
||||
if err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "创建合同失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cid := uint64(contractID)
|
||||
row.ContractID = &cid
|
||||
row.UpdateTime = now
|
||||
if _, err := models.Orm.Update(&row, "contract_id", "update_time"); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "更新招标合同ID失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
crmWriteLog(tenantID, 5, tenderID, "award-contract", "定标转合同:"+contractName, claims)
|
||||
crmWriteLog(tenantID, 1, cid, "create", "由招标「"+row.Title+"」定标转化生成", claims)
|
||||
|
||||
pipelineOk(&c.Controller, map[string]interface{}{"contract_id": contractID})
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
-- ============================================================
|
||||
-- CRM 招投标管理 —— 菜单配置(yz_system_menu)
|
||||
-- 幂等脚本:按 path 判重,重复执行不会产生重复菜单
|
||||
-- 执行:mysql -h <host> -P <port> -u <user> -p <db> < bidding_menus.sql
|
||||
-- ============================================================
|
||||
|
||||
-- 菜单表字段说明(yz_system_menu)
|
||||
-- type: 1=目录菜单,2=页面菜单
|
||||
-- pid : 父菜单ID
|
||||
-- views: 可见端标识,沿用父级 '[2]'(后台)
|
||||
-- is_visible / status: 1 表示显示并启用
|
||||
|
||||
-- 顶层入口「招投标」已存在:path = '/apps/crm/bidding'
|
||||
SET @bidding_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/crm/bidding' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- ---------- 一级子页面 ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @bidding_id, '我要投标', '/apps/crm/bidding/bid', 'apps/crm/bidding/bid/index.vue',
|
||||
'Trophy', 1, 1, 1, '[2]', 2, 'system', '投标方视角:从商机/项目发起投标,中标可转合同/项目'
|
||||
FROM DUAL
|
||||
WHERE @bidding_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/bid' AND delete_time IS NULL) x);
|
||||
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @bidding_id, '我要招标', '/apps/crm/bidding/tender', 'apps/crm/bidding/tender/index.vue',
|
||||
'Document', 2, 1, 1, '[2]', 2, 'system', '招标方视角:发布公告、报名、评标、定标'
|
||||
FROM DUAL
|
||||
WHERE @bidding_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/tender' AND delete_time IS NULL) x);
|
||||
|
||||
-- ---------- 公共资料库(目录 + 两个页面) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @bidding_id, '公共资料库', '/apps/crm/bidding/library', NULL,
|
||||
'Folder', 3, 1, 1, '[2]', 1, 'system', '标书模板 + 企业资质'
|
||||
FROM DUAL
|
||||
WHERE @bidding_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/library' AND delete_time IS NULL) x);
|
||||
|
||||
SET @library_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/crm/bidding/library' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @library_id, '标书模板', '/apps/crm/bidding/library/template', 'apps/crm/bidding/library/template.vue',
|
||||
'Files', 1, 1, 1, '[2]', 2, 'system', '技术标/商务标/综合标模板库'
|
||||
FROM DUAL
|
||||
WHERE @library_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/library/template' AND delete_time IS NULL) x);
|
||||
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @library_id, '企业资质', '/apps/crm/bidding/library/qualification', 'apps/crm/bidding/library/qualification.vue',
|
||||
'Stamp', 2, 1, 1, '[2]', 2, 'system', '证照资质库,临期自动预警'
|
||||
FROM DUAL
|
||||
WHERE @library_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/library/qualification' AND delete_time IS NULL) x);
|
||||
|
||||
-- ---------- 统计报表(目录 + 两个页面) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @bidding_id, '统计报表', '/apps/crm/bidding/statistics', NULL,
|
||||
'DataAnalysis', 4, 1, 1, '[2]', 1, 'system', '招标/投标数据统计'
|
||||
FROM DUAL
|
||||
WHERE @bidding_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/statistics' AND delete_time IS NULL) x);
|
||||
|
||||
SET @stats_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/crm/bidding/statistics' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @stats_id, '招标统计', '/apps/crm/bidding/statistics/tender', 'apps/crm/bidding/statistics/tender.vue',
|
||||
'PieChart', 1, 1, 1, '[2]', 2, 'system', '招标状态/类型分布、预算与中标金额'
|
||||
FROM DUAL
|
||||
WHERE @stats_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/statistics/tender' AND delete_time IS NULL) x);
|
||||
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, type, creater, remark)
|
||||
SELECT @stats_id, '投标统计', '/apps/crm/bidding/statistics/bid', 'apps/crm/bidding/statistics/bid.vue',
|
||||
'Histogram', 2, 1, 1, '[2]', 2, 'system', '投标数量、中标率、费用投入与月度趋势'
|
||||
FROM DUAL
|
||||
WHERE @stats_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/crm/bidding/statistics/bid' AND delete_time IS NULL) x);
|
||||
|
||||
-- ---------- 结果校验 ----------
|
||||
SELECT id, pid, title, path, component_path, type, sort
|
||||
FROM yz_system_menu
|
||||
WHERE delete_time IS NULL AND path LIKE '/apps/crm/bidding%'
|
||||
ORDER BY pid, sort;
|
||||
@@ -0,0 +1,526 @@
|
||||
-- =============================================================
|
||||
-- CRM 招投标管理模块表结构(17 张)
|
||||
--
|
||||
-- 模块说明:CRM 系统下的「招投标管理」子模块
|
||||
-- - 我要招标(甲方):6 张表
|
||||
-- - 我要投标(乙方):11 张表
|
||||
-- 设计原则:
|
||||
-- 1. 命名统一以 yz_backend_crm_bidding_ 为前缀
|
||||
-- 2. 软删除:delete_time
|
||||
-- 3. 多租户:tenant_id
|
||||
-- 4. 与现有 CRM 模块(business/contract/project)字段风格一致
|
||||
-- 5. 中标/定标后只存 ID 引用(contract_id、project_id),不冗余数据
|
||||
--
|
||||
-- 状态机:
|
||||
-- 招标:0草稿 -> 1公告中 -> 2报名中 -> 3投标中 -> 4开标中 -> 5评标中 -> 6已定标 -> 7已归档
|
||||
-- 投标:0草稿 -> 1立项审批中 -> 2投标中 -> 3已开标 -> (4中标 | 5落标) -> (6已转合同 | 7已归档)
|
||||
-- =============================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- 第一部分:我要招标(6 张)
|
||||
-- ============================================================
|
||||
|
||||
-- ---------------- 1. 招标项目主表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_tender` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户ID',
|
||||
`tender_no` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '招标编号',
|
||||
`title` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '招标标题/项目名称',
|
||||
`tender_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '招标类型: 1公开招标 2邀请招标 3竞争性谈判 4询比 5单一来源',
|
||||
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1公告中 2报名中 3投标中 4开标中 5评标中 6已定标 7已归档',
|
||||
`customer_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '招标方客户ID(我方客户库)',
|
||||
`customer_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '招标方名称',
|
||||
`publish_org` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '发布单位',
|
||||
`budget_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '预算金额',
|
||||
`bid_deposit` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '保证金',
|
||||
`publish_date` DATE DEFAULT NULL COMMENT '发布日期',
|
||||
`register_start_date` DATETIME DEFAULT NULL COMMENT '报名开始',
|
||||
`register_end_date` DATETIME DEFAULT NULL COMMENT '报名截止',
|
||||
`bid_start_date` DATETIME DEFAULT NULL COMMENT '投标开始',
|
||||
`bid_end_date` DATETIME DEFAULT NULL COMMENT '投标截止',
|
||||
`open_bid_date` DATETIME DEFAULT NULL COMMENT '开标时间',
|
||||
`contact_person` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '联系人',
|
||||
`contact_phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '联系电话',
|
||||
`contact_email` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '联系邮箱',
|
||||
`bid_address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '投标地址',
|
||||
`open_address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '开标地址',
|
||||
`description` TEXT COMMENT '项目描述',
|
||||
`requirement` TEXT COMMENT '投标要求',
|
||||
`evaluation_method` VARCHAR(50) NOT NULL DEFAULT '1' COMMENT '评标方法: 1综合评估 2最低价 3性价比',
|
||||
`winner_supplier_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '中标供应商ID',
|
||||
`winner_supplier_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '中标供应商名称',
|
||||
`winner_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '中标金额',
|
||||
`winner_date` DATETIME DEFAULT NULL COMMENT '定标时间',
|
||||
`contract_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '转合同ID',
|
||||
`project_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '关联项目ID',
|
||||
`owner_user_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '负责人ID',
|
||||
`owner_user_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '负责人姓名',
|
||||
`department_id` BIGINT UNSIGNED DEFAULT 0 COMMENT '部门ID',
|
||||
`locked` TINYINT NOT NULL DEFAULT 0 COMMENT '0否 1是(定标后锁定)',
|
||||
`remark` TEXT COMMENT '备注',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_status` (`tenant_id`, `status`),
|
||||
KEY `idx_tender_no` (`tenant_id`, `tender_no`),
|
||||
KEY `idx_owner` (`tenant_id`, `owner_user_id`),
|
||||
KEY `idx_customer` (`tenant_id`, `customer_id`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-招标项目主表';
|
||||
|
||||
-- ---------------- 2. 招标文件表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_tender_file` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`tender_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '招标项目ID',
|
||||
`file_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1招标文件 2补充文件 3答疑文件 4其他',
|
||||
`file_name` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '文件名称',
|
||||
`file_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '文件URL',
|
||||
`file_size` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
`file_ext` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '扩展名',
|
||||
`upload_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`upload_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`remark` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_tender` (`tenant_id`, `tender_id`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-招标文件';
|
||||
|
||||
-- ---------------- 3. 招标公告表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_tender_announce` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`tender_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '招标项目ID',
|
||||
`announce_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1招标公告 2变更公告 3中标公告 4终止公告',
|
||||
`title` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '公告标题',
|
||||
`content` LONGTEXT COMMENT '公告内容(富文本)',
|
||||
`publish_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1已发布',
|
||||
`publish_time` DATETIME DEFAULT NULL COMMENT '发布时间',
|
||||
`view_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '浏览次数',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_tender` (`tenant_id`, `tender_id`),
|
||||
KEY `idx_announce_type` (`tenant_id`, `announce_type`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-招标公告';
|
||||
|
||||
-- ---------------- 4. 投标方/供应商表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_tender_supplier` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`tender_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '招标项目ID',
|
||||
`supplier_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '供应商ID(ERP/CRM共享)',
|
||||
`supplier_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '投标方/供应商名称',
|
||||
`register_status` TINYINT NOT NULL DEFAULT 1 COMMENT '1待审核 2已通过 3已拒绝 4已退标',
|
||||
`register_time` DATETIME DEFAULT NULL COMMENT '报名时间',
|
||||
`bid_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '报价金额',
|
||||
`bid_files` TEXT COMMENT '投标文件JSON(多文件)',
|
||||
`contact_person` VARCHAR(50) NOT NULL DEFAULT '',
|
||||
`contact_phone` VARCHAR(20) NOT NULL DEFAULT '',
|
||||
`is_winner` TINYINT NOT NULL DEFAULT 0 COMMENT '0否 1是中标方',
|
||||
`ranking` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '排名',
|
||||
`remark` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_tender` (`tenant_id`, `tender_id`),
|
||||
KEY `idx_supplier` (`tenant_id`, `supplier_id`),
|
||||
KEY `idx_register_status` (`tenant_id`, `register_status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标方/供应商';
|
||||
|
||||
-- ---------------- 5. 开标评标表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_tender_evaluation` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`tender_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '招标项目ID',
|
||||
`supplier_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '被评标供应商ID',
|
||||
`supplier_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '供应商名称',
|
||||
`technical_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '技术分',
|
||||
`business_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '商务分',
|
||||
`price_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '价格分',
|
||||
`service_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '服务分',
|
||||
`total_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '总分',
|
||||
`weight_config` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '权重配置JSON',
|
||||
`ranking` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '排名',
|
||||
`evaluation_user_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '评标人ID',
|
||||
`evaluation_user_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '评标人姓名',
|
||||
`evaluation_time` DATETIME DEFAULT NULL COMMENT '评标时间',
|
||||
`comment` TEXT COMMENT '评语',
|
||||
`result` TINYINT NOT NULL DEFAULT 0 COMMENT '0待评 1合格 2不合格 3废标',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_tender` (`tenant_id`, `tender_id`),
|
||||
KEY `idx_supplier` (`tenant_id`, `supplier_id`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-开标评标';
|
||||
|
||||
-- ---------------- 6. 定标结果表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_tender_result` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`tender_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '招标项目ID',
|
||||
`result_type` TINYINT NOT NULL DEFAULT 1 COMMENT '1定标 2流标 3废标',
|
||||
`winner_supplier_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '中标供应商ID',
|
||||
`winner_supplier_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '中标供应商名称',
|
||||
`winner_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '中标金额',
|
||||
`result_date` DATETIME DEFAULT NULL COMMENT '定标日期',
|
||||
`announcement` TEXT COMMENT '定标公告',
|
||||
`contract_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '转合同ID',
|
||||
`project_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '关联项目ID',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_tender` (`tenant_id`, `tender_id`),
|
||||
KEY `idx_result_type` (`tenant_id`, `result_type`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-定标结果';
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 第二部分:我要投标(11 张)
|
||||
-- ============================================================
|
||||
|
||||
-- ---------------- 7. 投标项目主表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_no` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '投标编号',
|
||||
`title` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '投标标题/项目名称',
|
||||
`bid_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '投标类型: 1公开 2邀请 3竞争性谈判 4询比 5单一来源',
|
||||
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1立项审批中 2投标中 3已开标 4中标 5落标 6已转合同 7已归档',
|
||||
`source_type` TINYINT NOT NULL DEFAULT 0 COMMENT '0独立 1商机 2项目',
|
||||
`source_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '来源ID(商机/项目)',
|
||||
`business_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '关联商机ID',
|
||||
`business_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '商机名称',
|
||||
`project_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '关联项目ID(中转)',
|
||||
`project_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '项目名称',
|
||||
`tender_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '关联招标项目ID(如果从招标信息创建)',
|
||||
`tender_no` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '招标编号',
|
||||
`customer_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '招标方ID',
|
||||
`customer_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '招标方名称',
|
||||
`publish_org` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '发布单位',
|
||||
`bid_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '投标报价',
|
||||
`bid_deposit` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '保证金',
|
||||
`publish_date` DATE DEFAULT NULL COMMENT '招标发布日期',
|
||||
`register_end_date` DATETIME DEFAULT NULL COMMENT '报名截止',
|
||||
`bid_end_date` DATETIME DEFAULT NULL COMMENT '投标截止',
|
||||
`open_bid_date` DATETIME DEFAULT NULL COMMENT '开标时间',
|
||||
`contact_person` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '联系人',
|
||||
`contact_phone` VARCHAR(20) NOT NULL DEFAULT '',
|
||||
`bid_address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '投标地址',
|
||||
`open_address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '开标地址',
|
||||
`win_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0未开标 1中标 2未中标 3废标 4流标',
|
||||
`win_date` DATETIME DEFAULT NULL COMMENT '中标日期',
|
||||
`our_ranking` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '我方排名',
|
||||
`total_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '我方得分',
|
||||
`competitor_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '竞争对手数量',
|
||||
`contract_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '转合同ID',
|
||||
`new_project_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '中标后新建项目ID',
|
||||
`owner_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`owner_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`department_id` BIGINT UNSIGNED DEFAULT 0 COMMENT '部门ID',
|
||||
`description` TEXT COMMENT '项目描述',
|
||||
`remark` TEXT COMMENT '备注',
|
||||
`locked` TINYINT NOT NULL DEFAULT 0 COMMENT '0否 1是',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_status` (`tenant_id`, `status`),
|
||||
KEY `idx_bid_no` (`tenant_id`, `bid_no`),
|
||||
KEY `idx_owner` (`tenant_id`, `owner_user_id`),
|
||||
KEY `idx_business` (`tenant_id`, `business_id`),
|
||||
KEY `idx_project` (`tenant_id`, `project_id`),
|
||||
KEY `idx_tender` (`tenant_id`, `tender_id`),
|
||||
KEY `idx_customer` (`tenant_id`, `customer_id`),
|
||||
KEY `idx_win_status` (`tenant_id`, `win_status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标项目主表';
|
||||
|
||||
-- ---------------- 8. 投标文件表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_file` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`file_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1投标文件 2技术方案 3商务方案 4报价单 5保证金 7其他',
|
||||
`file_name` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`file_url` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`file_size` BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`file_ext` VARCHAR(20) NOT NULL DEFAULT '',
|
||||
`version` VARCHAR(20) NOT NULL DEFAULT '1.0' COMMENT '版本号',
|
||||
`upload_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`upload_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`remark` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标文件';
|
||||
|
||||
-- ---------------- 9. 投标立项审批表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_approval` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`approval_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1立项审批 2报价审批 3合同审批',
|
||||
`approval_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待审批 1已通过 2已拒绝 3已撤回',
|
||||
`approval_flow_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '审批流ID(复用现有引擎)',
|
||||
`current_user_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '当前审批人',
|
||||
`current_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`approval_users` TEXT COMMENT '审批人列表JSON',
|
||||
`submit_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`submit_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`submit_time` DATETIME DEFAULT NULL,
|
||||
`complete_time` DATETIME DEFAULT NULL,
|
||||
`comment` TEXT COMMENT '审批意见',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_flow` (`approval_flow_id`),
|
||||
KEY `idx_status` (`tenant_id`, `approval_status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标立项审批';
|
||||
|
||||
-- ---------------- 10. 投标任务表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_task` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`task_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '任务名称',
|
||||
`task_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1商务 2技术 3报价 4法务 5财务 6其他',
|
||||
`content` TEXT COMMENT '任务内容',
|
||||
`owner_user_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '负责人',
|
||||
`owner_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`start_date` DATE DEFAULT NULL,
|
||||
`end_date` DATE DEFAULT NULL,
|
||||
`complete_time` DATETIME DEFAULT NULL,
|
||||
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0未开始 1进行中 2已完成 3已逾期 4已取消',
|
||||
`progress` TINYINT NOT NULL DEFAULT 0 COMMENT '进度 0-100',
|
||||
`files` TEXT COMMENT '附件JSON',
|
||||
`remark` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_owner` (`tenant_id`, `owner_user_id`),
|
||||
KEY `idx_status` (`tenant_id`, `status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标任务';
|
||||
|
||||
-- ---------------- 11. 标书模板表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_template` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`template_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '模板名称',
|
||||
`template_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1技术标 2商务标 3综合标',
|
||||
`industry` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '适用行业',
|
||||
`description` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`content` LONGTEXT COMMENT '模板内容',
|
||||
`file_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '模板文件URL',
|
||||
`use_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用次数',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0禁用',
|
||||
`owner_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`owner_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_type` (`tenant_id`, `template_type`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-标书模板';
|
||||
|
||||
-- ---------------- 12. 标书版本表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_document` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`template_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '基于模板',
|
||||
`version` VARCHAR(20) NOT NULL DEFAULT 'v1.0' COMMENT '版本号',
|
||||
`title` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '版本标题',
|
||||
`content` LONGTEXT COMMENT '标书内容',
|
||||
`file_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '标书文件URL',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1编辑中 2待审核 3已定稿 4已提交',
|
||||
`editor_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`editor_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`review_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`review_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`review_comment` TEXT COMMENT '审核意见',
|
||||
`review_time` DATETIME DEFAULT NULL,
|
||||
`submit_time` DATETIME DEFAULT NULL,
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_template` (`template_id`),
|
||||
KEY `idx_status` (`tenant_id`, `status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-标书版本';
|
||||
|
||||
-- ---------------- 13. 资质文件库 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_qualification` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`qualification_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '资质名称',
|
||||
`qualification_type` VARCHAR(20) NOT NULL DEFAULT '1' COMMENT '1营业执照 2资质证书 3业绩证明 4认证证书 5其他',
|
||||
`qualification_no` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '证书编号',
|
||||
`issuing_authority` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '颁发机构',
|
||||
`issue_date` DATE DEFAULT NULL COMMENT '颁发日期',
|
||||
`expire_date` DATE DEFAULT NULL COMMENT '到期日期',
|
||||
`alert_days` INT UNSIGNED NOT NULL DEFAULT 30 COMMENT '提前提醒天数',
|
||||
`file_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '文件URL',
|
||||
`file_name` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`file_size` BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`description` TEXT COMMENT '备注',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1有效 2即将过期 0失效',
|
||||
`owner_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`owner_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_type` (`tenant_id`, `qualification_type`),
|
||||
KEY `idx_expire` (`tenant_id`, `expire_date`),
|
||||
KEY `idx_status` (`tenant_id`, `status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-资质文件库';
|
||||
|
||||
-- ---------------- 14. 报价表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_quote` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`quote_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '报价单名称',
|
||||
`total_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '总报价',
|
||||
`currency` VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
|
||||
`tax_rate` DECIMAL(6,4) NOT NULL DEFAULT 0 COMMENT '税率',
|
||||
`valid_date` DATE DEFAULT NULL COMMENT '报价有效期',
|
||||
`products` LONGTEXT COMMENT '产品清单JSON',
|
||||
`cost_analysis` TEXT COMMENT '成本分析',
|
||||
`profit_rate` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '利润率%',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1草稿 2待审批 3已审批 4已使用',
|
||||
`approval_flow_id` BIGINT UNSIGNED DEFAULT NULL,
|
||||
`submit_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`submit_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`submit_time` DATETIME DEFAULT NULL,
|
||||
`approval_time` DATETIME DEFAULT NULL,
|
||||
`remark` TEXT COMMENT '备注',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_status` (`tenant_id`, `status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-报价表';
|
||||
|
||||
-- ---------------- 15. 竞争对手报价表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_competitor` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`competitor_name` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '竞争对手名称',
|
||||
`competitor_type` TINYINT NOT NULL DEFAULT 1 COMMENT '1同类厂商 2关联企业 3其他',
|
||||
`quote_amount` DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT '报价',
|
||||
`advantages` TEXT COMMENT '优势',
|
||||
`weaknesses` TEXT COMMENT '劣势',
|
||||
`source` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '信息来源',
|
||||
`remark` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-竞争对手报价';
|
||||
|
||||
-- ---------------- 16. 投标结果表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_result` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`result_type` TINYINT NOT NULL DEFAULT 0 COMMENT '1中标 2未中标 3废标 4流标',
|
||||
`announce_date` DATETIME DEFAULT NULL COMMENT '公告日期',
|
||||
`announce_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '公告URL',
|
||||
`total_score` DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT '我方得分',
|
||||
`our_ranking` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '我方排名',
|
||||
`competitor_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '竞争对手数量',
|
||||
`reason` TEXT COMMENT '原因分析',
|
||||
`lessons` TEXT COMMENT '经验教训',
|
||||
`next_step` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '下一步行动',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_unique_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_result_type` (`tenant_id`, `result_type`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标结果';
|
||||
|
||||
-- ---------------- 17. 投标费用表 ----------------
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_crm_bidding_bid_expense` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tenant_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`bid_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '投标项目ID',
|
||||
`expense_type` TINYINT NOT NULL DEFAULT 1 COMMENT '1标书费 2保证金 3差旅费 4咨询费 5代理费 6其他',
|
||||
`expense_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '费用名称',
|
||||
`amount` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '金额',
|
||||
`currency` VARCHAR(10) NOT NULL DEFAULT 'CNY',
|
||||
`payment_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0未付 1已付 2已退还 3已抵扣',
|
||||
`payment_date` DATETIME DEFAULT NULL COMMENT '支付日期',
|
||||
`payment_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`payment_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`voucher_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '凭证URL',
|
||||
`description` TEXT COMMENT '说明',
|
||||
`create_user_id` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
`create_user_name` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`delete_time` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_bid` (`tenant_id`, `bid_id`),
|
||||
KEY `idx_payment_status` (`tenant_id`, `payment_status`),
|
||||
KEY `idx_delete_time` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM-投标费用';
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 菜单配置(投标管理)
|
||||
-- ============================================================
|
||||
-- 招标方菜单:path = 'apps/crm/bidding/tender/index.vue'
|
||||
-- 投标方菜单:path = 'apps/crm/bidding/bid/index.vue'
|
||||
-- 注意:菜单通常通过平台端 UI 配置,详见 docs/bidding_menus.sql
|
||||
@@ -97,6 +97,24 @@ func Init(_ string) {
|
||||
new(TenantCrmContract),
|
||||
new(TenantCrmPayback),
|
||||
new(TenantCrmPaybackRecord),
|
||||
// 招投标管理(docs/sql/create_crm_bidding.sql)
|
||||
new(TenantCrmBiddingTender),
|
||||
new(TenantCrmBiddingTenderFile),
|
||||
new(TenantCrmBiddingTenderAnnounce),
|
||||
new(TenantCrmBiddingTenderSupplier),
|
||||
new(TenantCrmBiddingTenderEvaluation),
|
||||
new(TenantCrmBiddingTenderResult),
|
||||
new(TenantCrmBiddingBid),
|
||||
new(TenantCrmBiddingBidFile),
|
||||
new(TenantCrmBiddingBidApproval),
|
||||
new(TenantCrmBiddingBidTask),
|
||||
new(TenantCrmBiddingBidTemplate),
|
||||
new(TenantCrmBiddingBidDocument),
|
||||
new(TenantCrmBiddingBidQualification),
|
||||
new(TenantCrmBiddingBidQuote),
|
||||
new(TenantCrmBiddingBidCompetitor),
|
||||
new(TenantCrmBiddingBidResult),
|
||||
new(TenantCrmBiddingBidExpense),
|
||||
// 平台更新通知(docs/sql/platform_upgrade.sql)
|
||||
new(PlatformUpgradeNotice),
|
||||
new(ErpAccountSet),
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// =============================================================
|
||||
// 投标方(我要投标):主表 + 文件 + 审批 + 任务(4 张)
|
||||
// =============================================================
|
||||
|
||||
// TenantCrmBiddingBid 投标项目主表: yz_backend_crm_bidding_bid
|
||||
//
|
||||
// 状态机:
|
||||
// 0草稿 -> 1立项审批中 -> 2投标中 -> 3已开标 -> (4中标 | 5落标) -> (6已转合同 | 7已归档)
|
||||
//
|
||||
// 来源关联:
|
||||
// - source_type: 0独立 1商机 2项目
|
||||
// - source_id: 来源ID(商机/项目)
|
||||
type TenantCrmBiddingBid struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidNo string `orm:"column(bid_no);size(50)" json:"bid_no"` // 投标编号
|
||||
Title string `orm:"column(title);size(200)" json:"title"` // 投标标题
|
||||
BidType string `orm:"column(bid_type);size(20);default(1)" json:"bid_type"`
|
||||
Status int8 `orm:"column(status);default(0)" json:"status"` // 状态
|
||||
SourceType int8 `orm:"column(source_type);default(0)" json:"source_type"`
|
||||
SourceID *uint64 `orm:"column(source_id);null" json:"source_id"`
|
||||
BusinessID *uint64 `orm:"column(business_id);null" json:"business_id"` // 关联商机
|
||||
BusinessName string `orm:"column(business_name);size(200)" json:"business_name"`
|
||||
ProjectID *uint64 `orm:"column(project_id);null" json:"project_id"` // 关联项目(中转)
|
||||
ProjectName string `orm:"column(project_name);size(200)" json:"project_name"`
|
||||
TenderID *uint64 `orm:"column(tender_id);null" json:"tender_id"` // 关联招标项目
|
||||
TenderNo string `orm:"column(tender_no);size(50)" json:"tender_no"`
|
||||
CustomerID *uint64 `orm:"column(customer_id);null" json:"customer_id"` // 招标方
|
||||
CustomerName string `orm:"column(customer_name);size(200)" json:"customer_name"`
|
||||
PublishOrg string `orm:"column(publish_org);size(200)" json:"publish_org"`
|
||||
BidAmount float64 `orm:"column(bid_amount);digits(14);decimals(2);default(0)" json:"bid_amount"`
|
||||
BidDeposit float64 `orm:"column(bid_deposit);digits(14);decimals(2);default(0)" json:"bid_deposit"`
|
||||
PublishDate *time.Time `orm:"column(publish_date);type(date);null" json:"publish_date"`
|
||||
RegisterEndDate *time.Time `orm:"column(register_end_date);type(datetime);null" json:"register_end_date"`
|
||||
BidEndDate *time.Time `orm:"column(bid_end_date);type(datetime);null" json:"bid_end_date"`
|
||||
OpenBidDate *time.Time `orm:"column(open_bid_date);type(datetime);null" json:"open_bid_date"`
|
||||
ContactPerson string `orm:"column(contact_person);size(50)" json:"contact_person"`
|
||||
ContactPhone string `orm:"column(contact_phone);size(20)" json:"contact_phone"`
|
||||
BidAddress string `orm:"column(bid_address);size(255)" json:"bid_address"`
|
||||
OpenAddress string `orm:"column(open_address);size(255)" json:"open_address"`
|
||||
WinStatus int8 `orm:"column(win_status);default(0)" json:"win_status"` // 0未开标 1中标 2未中标 3废标 4流标
|
||||
WinDate *time.Time `orm:"column(win_date);type(datetime);null" json:"win_date"`
|
||||
OurRanking uint32 `orm:"column(our_ranking);default(0)" json:"our_ranking"`
|
||||
TotalScore float64 `orm:"column(total_score);digits(6);decimals(2);default(0)" json:"total_score"`
|
||||
CompetitorCount uint32 `orm:"column(competitor_count);default(0)" json:"competitor_count"`
|
||||
ContractID *uint64 `orm:"column(contract_id);null" json:"contract_id"` // 转合同ID
|
||||
NewProjectID *uint64 `orm:"column(new_project_id);null" json:"new_project_id"` // 中标后新建项目ID
|
||||
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
|
||||
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
|
||||
DepartmentID uint64 `orm:"column(department_id);default(0)" json:"department_id"`
|
||||
Description string `orm:"column(description);type(text);null" json:"description"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
Locked int8 `orm:"column(locked);default(0)" json:"locked"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBid) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidFile 投标文件: yz_backend_crm_bidding_bid_file
|
||||
type TenantCrmBiddingBidFile struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
FileType string `orm:"column(file_type);size(20);default(1)" json:"file_type"`
|
||||
FileName string `orm:"column(file_name);size(255)" json:"file_name"`
|
||||
FileURL string `orm:"column(file_url);size(500)" json:"file_url"`
|
||||
FileSize uint64 `orm:"column(file_size);default(0)" json:"file_size"`
|
||||
FileExt string `orm:"column(file_ext);size(20)" json:"file_ext"`
|
||||
Version string `orm:"column(version);size(20);default(1.0)" json:"version"`
|
||||
UploadUserID string `orm:"column(upload_user_id);size(64)" json:"upload_user_id"`
|
||||
UploadUserName string `orm:"column(upload_user_name);size(128)" json:"upload_user_name"`
|
||||
Remark string `orm:"column(remark);size(500)" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidFile) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_file"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidApproval 投标立项审批: yz_backend_crm_bidding_bid_approval
|
||||
type TenantCrmBiddingBidApproval struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
ApprovalType string `orm:"column(approval_type);size(20);default(1)" json:"approval_type"`
|
||||
ApprovalStatus int8 `orm:"column(approval_status);default(0)" json:"approval_status"`
|
||||
ApprovalFlowID *uint64 `orm:"column(approval_flow_id);null" json:"approval_flow_id"`
|
||||
CurrentUserID string `orm:"column(current_user_id);size(64)" json:"current_user_id"`
|
||||
CurrentUserName string `orm:"column(current_user_name);size(128)" json:"current_user_name"`
|
||||
ApprovalUsers string `orm:"column(approval_users);type(text);null" json:"approval_users"`
|
||||
SubmitUserID string `orm:"column(submit_user_id);size(64)" json:"submit_user_id"`
|
||||
SubmitUserName string `orm:"column(submit_user_name);size(128)" json:"submit_user_name"`
|
||||
SubmitTime *time.Time `orm:"column(submit_time);type(datetime);null" json:"submit_time"`
|
||||
CompleteTime *time.Time `orm:"column(complete_time);type(datetime);null" json:"complete_time"`
|
||||
Comment string `orm:"column(comment);type(text);null" json:"comment"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidApproval) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_approval"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidTask 投标任务: yz_backend_crm_bidding_bid_task
|
||||
type TenantCrmBiddingBidTask struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
TaskName string `orm:"column(task_name);size(100)" json:"task_name"`
|
||||
TaskType string `orm:"column(task_type);size(20);default(1)" json:"task_type"`
|
||||
Content string `orm:"column(content);type(text);null" json:"content"`
|
||||
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
|
||||
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
|
||||
StartDate *time.Time `orm:"column(start_date);type(date);null" json:"start_date"`
|
||||
EndDate *time.Time `orm:"column(end_date);type(date);null" json:"end_date"`
|
||||
CompleteTime *time.Time `orm:"column(complete_time);type(datetime);null" json:"complete_time"`
|
||||
Status int8 `orm:"column(status);default(0)" json:"status"`
|
||||
Progress int8 `orm:"column(progress);default(0)" json:"progress"`
|
||||
Files string `orm:"column(files);type(text);null" json:"files"`
|
||||
Remark string `orm:"column(remark);size(500)" json:"remark"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidTask) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_task"
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// =============================================================
|
||||
// 投标方 - 公共库:模板 + 标书版本 + 资质文件 + 报价(4 张)
|
||||
// =============================================================
|
||||
|
||||
// TenantCrmBiddingBidTemplate 标书模板: yz_backend_crm_bidding_bid_template
|
||||
type TenantCrmBiddingBidTemplate struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TemplateName string `orm:"column(template_name);size(100)" json:"template_name"`
|
||||
TemplateType string `orm:"column(template_type);size(20);default(1)" json:"template_type"` // 1技术标 2商务标 3综合标
|
||||
Industry string `orm:"column(industry);size(50)" json:"industry"`
|
||||
Description string `orm:"column(description);size(500)" json:"description"`
|
||||
Content string `orm:"column(content);type(longtext);null" json:"content"`
|
||||
FileURL string `orm:"column(file_url);size(500)" json:"file_url"`
|
||||
UseCount uint32 `orm:"column(use_count);default(0)" json:"use_count"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用 0禁用
|
||||
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
|
||||
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidTemplate) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_template"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidDocument 标书版本: yz_backend_crm_bidding_bid_document
|
||||
type TenantCrmBiddingBidDocument struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
TemplateID *uint64 `orm:"column(template_id);null" json:"template_id"`
|
||||
Version string `orm:"column(version);size(20);default(v1.0)" json:"version"`
|
||||
Title string `orm:"column(title);size(200)" json:"title"`
|
||||
Content string `orm:"column(content);type(longtext);null" json:"content"`
|
||||
FileURL string `orm:"column(file_url);size(500)" json:"file_url"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"` // 1编辑中 2待审核 3已定稿 4已提交
|
||||
EditorUserID string `orm:"column(editor_user_id);size(64)" json:"editor_user_id"`
|
||||
EditorUserName string `orm:"column(editor_user_name);size(128)" json:"editor_user_name"`
|
||||
ReviewUserID string `orm:"column(review_user_id);size(64)" json:"review_user_id"`
|
||||
ReviewUserName string `orm:"column(review_user_name);size(128)" json:"review_user_name"`
|
||||
ReviewComment string `orm:"column(review_comment);type(text);null" json:"review_comment"`
|
||||
ReviewTime *time.Time `orm:"column(review_time);type(datetime);null" json:"review_time"`
|
||||
SubmitTime *time.Time `orm:"column(submit_time);type(datetime);null" json:"submit_time"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidDocument) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_document"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidQualification 资质文件库: yz_backend_crm_bidding_bid_qualification
|
||||
type TenantCrmBiddingBidQualification struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
QualificationName string `orm:"column(qualification_name);size(100)" json:"qualification_name"`
|
||||
QualificationType string `orm:"column(qualification_type);size(20);default(1)" json:"qualification_type"`
|
||||
QualificationNo string `orm:"column(qualification_no);size(100)" json:"qualification_no"`
|
||||
IssuingAuthority string `orm:"column(issuing_authority);size(200)" json:"issuing_authority"`
|
||||
IssueDate *time.Time `orm:"column(issue_date);type(date);null" json:"issue_date"`
|
||||
ExpireDate *time.Time `orm:"column(expire_date);type(date);null" json:"expire_date"`
|
||||
AlertDays uint32 `orm:"column(alert_days);default(30)" json:"alert_days"`
|
||||
FileURL string `orm:"column(file_url);size(500)" json:"file_url"`
|
||||
FileName string `orm:"column(file_name);size(255)" json:"file_name"`
|
||||
FileSize uint64 `orm:"column(file_size);default(0)" json:"file_size"`
|
||||
Description string `orm:"column(description);type(text);null" json:"description"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"` // 1有效 2即将过期 0失效
|
||||
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
|
||||
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidQualification) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_qualification"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidQuote 报价表: yz_backend_crm_bidding_bid_quote
|
||||
type TenantCrmBiddingBidQuote struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
QuoteName string `orm:"column(quote_name);size(100)" json:"quote_name"`
|
||||
TotalAmount float64 `orm:"column(total_amount);digits(14);decimals(2);default(0)" json:"total_amount"`
|
||||
Currency string `orm:"column(currency);size(10);default(CNY)" json:"currency"`
|
||||
TaxRate float64 `orm:"column(tax_rate);digits(6);decimals(4);default(0)" json:"tax_rate"`
|
||||
ValidDate *time.Time `orm:"column(valid_date);type(date);null" json:"valid_date"`
|
||||
Products string `orm:"column(products);type(longtext);null" json:"products"` // 产品清单JSON
|
||||
CostAnalysis string `orm:"column(cost_analysis);type(text);null" json:"cost_analysis"`
|
||||
ProfitRate float64 `orm:"column(profit_rate);digits(6);decimals(2);default(0)" json:"profit_rate"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
ApprovalFlowID *uint64 `orm:"column(approval_flow_id);null" json:"approval_flow_id"`
|
||||
SubmitUserID string `orm:"column(submit_user_id);size(64)" json:"submit_user_id"`
|
||||
SubmitUserName string `orm:"column(submit_user_name);size(128)" json:"submit_user_name"`
|
||||
SubmitTime *time.Time `orm:"column(submit_time);type(datetime);null" json:"submit_time"`
|
||||
ApprovalTime *time.Time `orm:"column(approval_time);type(datetime);null" json:"approval_time"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidQuote) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_quote"
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// =============================================================
|
||||
// 投标方 - 杂项:竞争对手 + 投标结果 + 投标费用(3 张)
|
||||
// =============================================================
|
||||
|
||||
// TenantCrmBiddingBidCompetitor 竞争对手报价: yz_backend_crm_bidding_bid_competitor
|
||||
type TenantCrmBiddingBidCompetitor struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
CompetitorName string `orm:"column(competitor_name);size(200)" json:"competitor_name"`
|
||||
CompetitorType int8 `orm:"column(competitor_type);default(1)" json:"competitor_type"` // 1同类厂商 2关联企业 3其他
|
||||
QuoteAmount float64 `orm:"column(quote_amount);digits(14);decimals(2);default(0)" json:"quote_amount"`
|
||||
Advantages string `orm:"column(advantages);type(text);null" json:"advantages"`
|
||||
Weaknesses string `orm:"column(weaknesses);type(text);null" json:"weaknesses"`
|
||||
Source string `orm:"column(source);size(200)" json:"source"`
|
||||
Remark string `orm:"column(remark);size(500)" json:"remark"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidCompetitor) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_competitor"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidResult 投标结果: yz_backend_crm_bidding_bid_result
|
||||
type TenantCrmBiddingBidResult struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
ResultType int8 `orm:"column(result_type);default(0)" json:"result_type"` // 1中标 2未中标 3废标 4流标
|
||||
AnnounceDate *time.Time `orm:"column(announce_date);type(datetime);null" json:"announce_date"`
|
||||
AnnounceURL string `orm:"column(announce_url);size(500)" json:"announce_url"`
|
||||
TotalScore float64 `orm:"column(total_score);digits(6);decimals(2);default(0)" json:"total_score"`
|
||||
OurRanking uint32 `orm:"column(our_ranking);default(0)" json:"our_ranking"`
|
||||
CompetitorCount uint32 `orm:"column(competitor_count);default(0)" json:"competitor_count"`
|
||||
Reason string `orm:"column(reason);type(text);null" json:"reason"`
|
||||
Lessons string `orm:"column(lessons);type(text);null" json:"lessons"`
|
||||
NextStep string `orm:"column(next_step);size(255)" json:"next_step"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidResult) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_result"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingBidExpense 投标费用: yz_backend_crm_bidding_bid_expense
|
||||
type TenantCrmBiddingBidExpense struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
BidID uint64 `orm:"column(bid_id);default(0)" json:"bid_id"`
|
||||
ExpenseType int8 `orm:"column(expense_type);default(1)" json:"expense_type"` // 1标书费 2保证金 3差旅费 4咨询费 5代理费 6其他
|
||||
ExpenseName string `orm:"column(expense_name);size(100)" json:"expense_name"`
|
||||
Amount float64 `orm:"column(amount);digits(12);decimals(2);default(0)" json:"amount"`
|
||||
Currency string `orm:"column(currency);size(10);default(CNY)" json:"currency"`
|
||||
PaymentStatus int8 `orm:"column(payment_status);default(0)" json:"payment_status"` // 0未付 1已付 2已退还 3已抵扣
|
||||
PaymentDate *time.Time `orm:"column(payment_date);type(datetime);null" json:"payment_date"`
|
||||
PaymentUserID string `orm:"column(payment_user_id);size(64)" json:"payment_user_id"`
|
||||
PaymentUserName string `orm:"column(payment_user_name);size(128)" json:"payment_user_name"`
|
||||
VoucherURL string `orm:"column(voucher_url);size(500)" json:"voucher_url"`
|
||||
Description string `orm:"column(description);type(text);null" json:"description"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingBidExpense) TableName() string {
|
||||
return "yz_backend_crm_bidding_bid_expense"
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// =============================================================
|
||||
// 招标方(我要招标):6 张表
|
||||
// =============================================================
|
||||
|
||||
// TenantCrmBiddingTender 招标项目主表: yz_backend_crm_bidding_tender
|
||||
//
|
||||
// 状态机:
|
||||
// 0草稿 -> 1公告中 -> 2报名中 -> 3投标中 -> 4开标中 -> 5评标中 -> 6已定标 -> 7已归档
|
||||
type TenantCrmBiddingTender struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TenderNo string `orm:"column(tender_no);size(50)" json:"tender_no"` // 招标编号
|
||||
Title string `orm:"column(title);size(200)" json:"title"` // 招标标题
|
||||
TenderType string `orm:"column(tender_type);size(20);default(1)" json:"tender_type"` // 招标类型
|
||||
Status int8 `orm:"column(status);default(0)" json:"status"` // 状态
|
||||
CustomerID *uint64 `orm:"column(customer_id);null" json:"customer_id"` // 招标方客户ID
|
||||
CustomerName string `orm:"column(customer_name);size(200)" json:"customer_name"`
|
||||
PublishOrg string `orm:"column(publish_org);size(200)" json:"publish_org"`
|
||||
BudgetAmount float64 `orm:"column(budget_amount);digits(14);decimals(2);default(0)" json:"budget_amount"`
|
||||
BidDeposit float64 `orm:"column(bid_deposit);digits(14);decimals(2);default(0)" json:"bid_deposit"`
|
||||
PublishDate *time.Time `orm:"column(publish_date);type(date);null" json:"publish_date"`
|
||||
RegisterStartDate *time.Time `orm:"column(register_start_date);type(datetime);null" json:"register_start_date"`
|
||||
RegisterEndDate *time.Time `orm:"column(register_end_date);type(datetime);null" json:"register_end_date"`
|
||||
BidStartDate *time.Time `orm:"column(bid_start_date);type(datetime);null" json:"bid_start_date"`
|
||||
BidEndDate *time.Time `orm:"column(bid_end_date);type(datetime);null" json:"bid_end_date"`
|
||||
OpenBidDate *time.Time `orm:"column(open_bid_date);type(datetime);null" json:"open_bid_date"`
|
||||
ContactPerson string `orm:"column(contact_person);size(50)" json:"contact_person"`
|
||||
ContactPhone string `orm:"column(contact_phone);size(20)" json:"contact_phone"`
|
||||
ContactEmail string `orm:"column(contact_email);size(100)" json:"contact_email"`
|
||||
BidAddress string `orm:"column(bid_address);size(255)" json:"bid_address"`
|
||||
OpenAddress string `orm:"column(open_address);size(255)" json:"open_address"`
|
||||
Description string `orm:"column(description);type(text);null" json:"description"`
|
||||
Requirement string `orm:"column(requirement);type(text);null" json:"requirement"`
|
||||
EvaluationMethod string `orm:"column(evaluation_method);size(50);default(1)" json:"evaluation_method"`
|
||||
WinnerSupplierID *uint64 `orm:"column(winner_supplier_id);null" json:"winner_supplier_id"`
|
||||
WinnerSupplierName string `orm:"column(winner_supplier_name);size(200)" json:"winner_supplier_name"`
|
||||
WinnerAmount float64 `orm:"column(winner_amount);digits(14);decimals(2);default(0)" json:"winner_amount"`
|
||||
WinnerDate *time.Time `orm:"column(winner_date);type(datetime);null" json:"winner_date"`
|
||||
ContractID *uint64 `orm:"column(contract_id);null" json:"contract_id"` // 转合同ID
|
||||
ProjectID *uint64 `orm:"column(project_id);null" json:"project_id"` // 关联项目ID
|
||||
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
|
||||
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
|
||||
DepartmentID uint64 `orm:"column(department_id);default(0)" json:"department_id"`
|
||||
Locked int8 `orm:"column(locked);default(0)" json:"locked"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingTender) TableName() string {
|
||||
return "yz_backend_crm_bidding_tender"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingTenderFile 招标文件: yz_backend_crm_bidding_tender_file
|
||||
type TenantCrmBiddingTenderFile struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TenderID uint64 `orm:"column(tender_id);default(0)" json:"tender_id"`
|
||||
FileType string `orm:"column(file_type);size(20);default(1)" json:"file_type"`
|
||||
FileName string `orm:"column(file_name);size(255)" json:"file_name"`
|
||||
FileURL string `orm:"column(file_url);size(500)" json:"file_url"`
|
||||
FileSize uint64 `orm:"column(file_size);default(0)" json:"file_size"`
|
||||
FileExt string `orm:"column(file_ext);size(20)" json:"file_ext"`
|
||||
UploadUserID string `orm:"column(upload_user_id);size(64)" json:"upload_user_id"`
|
||||
UploadUserName string `orm:"column(upload_user_name);size(128)" json:"upload_user_name"`
|
||||
Remark string `orm:"column(remark);size(500)" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingTenderFile) TableName() string {
|
||||
return "yz_backend_crm_bidding_tender_file"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingTenderAnnounce 招标公告: yz_backend_crm_bidding_tender_announce
|
||||
type TenantCrmBiddingTenderAnnounce struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TenderID uint64 `orm:"column(tender_id);default(0)" json:"tender_id"`
|
||||
AnnounceType string `orm:"column(announce_type);size(20);default(1)" json:"announce_type"`
|
||||
Title string `orm:"column(title);size(255)" json:"title"`
|
||||
Content string `orm:"column(content);type(longtext);null" json:"content"`
|
||||
PublishStatus int8 `orm:"column(publish_status);default(0)" json:"publish_status"`
|
||||
PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"`
|
||||
ViewCount uint32 `orm:"column(view_count);default(0)" json:"view_count"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingTenderAnnounce) TableName() string {
|
||||
return "yz_backend_crm_bidding_tender_announce"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingTenderSupplier 投标方/供应商: yz_backend_crm_bidding_tender_supplier
|
||||
type TenantCrmBiddingTenderSupplier struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TenderID uint64 `orm:"column(tender_id);default(0)" json:"tender_id"`
|
||||
SupplierID *uint64 `orm:"column(supplier_id);null" json:"supplier_id"`
|
||||
SupplierName string `orm:"column(supplier_name);size(200)" json:"supplier_name"`
|
||||
RegisterStatus int8 `orm:"column(register_status);default(1)" json:"register_status"`
|
||||
RegisterTime *time.Time `orm:"column(register_time);type(datetime);null" json:"register_time"`
|
||||
BidAmount float64 `orm:"column(bid_amount);digits(14);decimals(2);default(0)" json:"bid_amount"`
|
||||
BidFiles string `orm:"column(bid_files);type(text);null" json:"bid_files"`
|
||||
ContactPerson string `orm:"column(contact_person);size(50)" json:"contact_person"`
|
||||
ContactPhone string `orm:"column(contact_phone);size(20)" json:"contact_phone"`
|
||||
IsWinner int8 `orm:"column(is_winner);default(0)" json:"is_winner"`
|
||||
Ranking uint32 `orm:"column(ranking);default(0)" json:"ranking"`
|
||||
Remark string `orm:"column(remark);size(500)" json:"remark"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingTenderSupplier) TableName() string {
|
||||
return "yz_backend_crm_bidding_tender_supplier"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingTenderEvaluation 开标评标: yz_backend_crm_bidding_tender_evaluation
|
||||
type TenantCrmBiddingTenderEvaluation struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TenderID uint64 `orm:"column(tender_id);default(0)" json:"tender_id"`
|
||||
SupplierID *uint64 `orm:"column(supplier_id);null" json:"supplier_id"`
|
||||
SupplierName string `orm:"column(supplier_name);size(200)" json:"supplier_name"`
|
||||
TechnicalScore float64 `orm:"column(technical_score);digits(6);decimals(2);default(0)" json:"technical_score"`
|
||||
BusinessScore float64 `orm:"column(business_score);digits(6);decimals(2);default(0)" json:"business_score"`
|
||||
PriceScore float64 `orm:"column(price_score);digits(6);decimals(2);default(0)" json:"price_score"`
|
||||
ServiceScore float64 `orm:"column(service_score);digits(6);decimals(2);default(0)" json:"service_score"`
|
||||
TotalScore float64 `orm:"column(total_score);digits(6);decimals(2);default(0)" json:"total_score"`
|
||||
WeightConfig string `orm:"column(weight_config);size(255)" json:"weight_config"`
|
||||
Ranking uint32 `orm:"column(ranking);default(0)" json:"ranking"`
|
||||
EvaluationUserID string `orm:"column(evaluation_user_id);size(64)" json:"evaluation_user_id"`
|
||||
EvaluationUserName string `orm:"column(evaluation_user_name);size(128)" json:"evaluation_user_name"`
|
||||
EvaluationTime *time.Time `orm:"column(evaluation_time);type(datetime);null" json:"evaluation_time"`
|
||||
Comment string `orm:"column(comment);type(text);null" json:"comment"`
|
||||
Result int8 `orm:"column(result);default(0)" json:"result"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingTenderEvaluation) TableName() string {
|
||||
return "yz_backend_crm_bidding_tender_evaluation"
|
||||
}
|
||||
|
||||
// TenantCrmBiddingTenderResult 定标结果: yz_backend_crm_bidding_tender_result
|
||||
type TenantCrmBiddingTenderResult struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
TenderID uint64 `orm:"column(tender_id);default(0)" json:"tender_id"`
|
||||
ResultType int8 `orm:"column(result_type);default(1)" json:"result_type"`
|
||||
WinnerSupplierID *uint64 `orm:"column(winner_supplier_id);null" json:"winner_supplier_id"`
|
||||
WinnerSupplierName string `orm:"column(winner_supplier_name);size(200)" json:"winner_supplier_name"`
|
||||
WinnerAmount float64 `orm:"column(winner_amount);digits(14);decimals(2);default(0)" json:"winner_amount"`
|
||||
ResultDate *time.Time `orm:"column(result_date);type(datetime);null" json:"result_date"`
|
||||
Announcement string `orm:"column(announcement);type(text);null" json:"announcement"`
|
||||
ContractID *uint64 `orm:"column(contract_id);null" json:"contract_id"`
|
||||
ProjectID *uint64 `orm:"column(project_id);null" json:"project_id"`
|
||||
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
|
||||
CreateUserName string `orm:"column(create_user_name);size(128)" json:"create_user_name"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *TenantCrmBiddingTenderResult) TableName() string {
|
||||
return "yz_backend_crm_bidding_tender_result"
|
||||
}
|
||||
@@ -503,6 +503,102 @@ func registerOrganizationRoutes(module string) {
|
||||
// CRM操作日志
|
||||
beego.Router("/backend/crm/oplog/list", &controllers.BackendCrmOperateLogController{}, "get:List")
|
||||
|
||||
// ============================================================
|
||||
// CRM 招投标管理(我要招标 / 我要投标)
|
||||
// ============================================================
|
||||
// 投标方(Bid):从商机/项目发起 -> 投标 -> 中标 -> 转合同/项目
|
||||
beego.Router("/backend/crm/bidding/bid/list", &controllers.BackendCrmBiddingBidController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid/detail/:id", &controllers.BackendCrmBiddingBidController{}, "get:Detail")
|
||||
beego.Router("/backend/crm/bidding/bid", &controllers.BackendCrmBiddingBidController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid/:id", &controllers.BackendCrmBiddingBidController{}, "put:Update;delete:Delete")
|
||||
beego.Router("/backend/crm/bidding/bid/:id/contract", &controllers.BackendCrmBiddingBidController{}, "post:ConvertContract")
|
||||
beego.Router("/backend/crm/bidding/bid/:id/project", &controllers.BackendCrmBiddingBidController{}, "post:ConvertProject")
|
||||
beego.Router("/backend/crm/bidding/bid/:id/result", &controllers.BackendCrmBiddingBidController{}, "post:SubmitResult")
|
||||
beego.Router("/backend/crm/bidding/bid/from-business", &controllers.BackendCrmBiddingBidController{}, "post:FromBusiness")
|
||||
beego.Router("/backend/crm/bidding/bid/from-project", &controllers.BackendCrmBiddingBidController{}, "post:FromProject")
|
||||
// 投标文件
|
||||
beego.Router("/backend/crm/bidding/bid-file/list", &controllers.BackendCrmBiddingBidFileController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-file", &controllers.BackendCrmBiddingBidFileController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-file/:id", &controllers.BackendCrmBiddingBidFileController{}, "delete:Delete")
|
||||
// 投标任务
|
||||
beego.Router("/backend/crm/bidding/bid-task/list", &controllers.BackendCrmBiddingBidTaskController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-task", &controllers.BackendCrmBiddingBidTaskController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-task/:id", &controllers.BackendCrmBiddingBidTaskController{}, "put:Update;delete:Delete")
|
||||
// 投标结果
|
||||
beego.Router("/backend/crm/bidding/bid-result/list", &controllers.BackendCrmBiddingBidResultController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-result/detail/:id", &controllers.BackendCrmBiddingBidResultController{}, "get:Detail")
|
||||
// 投标报价
|
||||
beego.Router("/backend/crm/bidding/bid-quote/list", &controllers.BackendCrmBiddingBidQuoteController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-quote", &controllers.BackendCrmBiddingBidQuoteController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-quote/:id", &controllers.BackendCrmBiddingBidQuoteController{}, "delete:Delete")
|
||||
// 投标竞争对手
|
||||
beego.Router("/backend/crm/bidding/bid-competitor/list", &controllers.BackendCrmBiddingBidCompetitorController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-competitor", &controllers.BackendCrmBiddingBidCompetitorController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-competitor/:id", &controllers.BackendCrmBiddingBidCompetitorController{}, "delete:Delete")
|
||||
// 投标费用
|
||||
beego.Router("/backend/crm/bidding/bid-expense/list", &controllers.BackendCrmBiddingBidExpenseController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-expense", &controllers.BackendCrmBiddingBidExpenseController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-expense/:id", &controllers.BackendCrmBiddingBidExpenseController{}, "delete:Delete")
|
||||
|
||||
// 招标方(Tender):发布招标 -> 投标方报名 -> 评标 -> 定标 -> 转合同
|
||||
beego.Router("/backend/crm/bidding/tender/list", &controllers.BackendCrmBiddingTenderController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/tender/detail/:id", &controllers.BackendCrmBiddingTenderController{}, "get:Detail")
|
||||
beego.Router("/backend/crm/bidding/tender", &controllers.BackendCrmBiddingTenderController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/tender/:id", &controllers.BackendCrmBiddingTenderController{}, "put:Update;delete:Delete")
|
||||
beego.Router("/backend/crm/bidding/tender/:id/award", &controllers.BackendCrmBiddingTenderController{}, "post:Award")
|
||||
beego.Router("/backend/crm/bidding/tender/:id/contract", &controllers.BackendCrmBiddingTenderController{}, "post:AwardToContract")
|
||||
// 招标文件
|
||||
beego.Router("/backend/crm/bidding/tender-file/list", &controllers.BackendCrmBiddingTenderFileController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/tender-file", &controllers.BackendCrmBiddingTenderFileController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/tender-file/:id", &controllers.BackendCrmBiddingTenderFileController{}, "delete:Delete")
|
||||
|
||||
// 招标公告
|
||||
beego.Router("/backend/crm/bidding/tender-announce/list", &controllers.BackendCrmBiddingTenderAnnounceController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/tender-announce", &controllers.BackendCrmBiddingTenderAnnounceController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/tender-announce/:id/publish", &controllers.BackendCrmBiddingTenderAnnounceController{}, "post:Publish")
|
||||
beego.Router("/backend/crm/bidding/tender-announce/:id", &controllers.BackendCrmBiddingTenderAnnounceController{}, "delete:Delete")
|
||||
|
||||
// 投标方/供应商
|
||||
beego.Router("/backend/crm/bidding/tender-supplier/list", &controllers.BackendCrmBiddingTenderSupplierController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/tender-supplier", &controllers.BackendCrmBiddingTenderSupplierController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/tender-supplier/:id", &controllers.BackendCrmBiddingTenderSupplierController{}, "put:Update;delete:Delete")
|
||||
|
||||
// 开标评标
|
||||
beego.Router("/backend/crm/bidding/tender-evaluation/list", &controllers.BackendCrmBiddingTenderEvaluationController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/tender-evaluation", &controllers.BackendCrmBiddingTenderEvaluationController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/tender-evaluation/:id", &controllers.BackendCrmBiddingTenderEvaluationController{}, "delete:Delete")
|
||||
|
||||
// 定标结果
|
||||
beego.Router("/backend/crm/bidding/tender-result/list", &controllers.BackendCrmBiddingTenderResultController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/tender-result/detail/:id", &controllers.BackendCrmBiddingTenderResultController{}, "get:Detail")
|
||||
|
||||
// 投标立项审批
|
||||
beego.Router("/backend/crm/bidding/bid-approval/list", &controllers.BackendCrmBiddingBidApprovalController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-approval", &controllers.BackendCrmBiddingBidApprovalController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-approval/:id/approve", &controllers.BackendCrmBiddingBidApprovalController{}, "post:Approve")
|
||||
|
||||
// 标书版本
|
||||
beego.Router("/backend/crm/bidding/bid-document/list", &controllers.BackendCrmBiddingBidDocumentController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/bid-document", &controllers.BackendCrmBiddingBidDocumentController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/bid-document/:id", &controllers.BackendCrmBiddingBidDocumentController{}, "put:Update;delete:Delete")
|
||||
|
||||
// 标书模板库
|
||||
beego.Router("/backend/crm/bidding/template/list", &controllers.BackendCrmBiddingBidTemplateController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/template", &controllers.BackendCrmBiddingBidTemplateController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/template/:id", &controllers.BackendCrmBiddingBidTemplateController{}, "put:Update;delete:Delete")
|
||||
|
||||
// 资质文件库
|
||||
beego.Router("/backend/crm/bidding/qualification/list", &controllers.BackendCrmBiddingBidQualificationController{}, "get:List")
|
||||
beego.Router("/backend/crm/bidding/qualification", &controllers.BackendCrmBiddingBidQualificationController{}, "post:Create")
|
||||
beego.Router("/backend/crm/bidding/qualification/:id", &controllers.BackendCrmBiddingBidQualificationController{}, "put:Update;delete:Delete")
|
||||
|
||||
// 招投标统计
|
||||
beego.Router("/backend/crm/bidding/statistics/tender", &controllers.BackendCrmBiddingStatisticsController{}, "get:Tender")
|
||||
beego.Router("/backend/crm/bidding/statistics/bid", &controllers.BackendCrmBiddingStatisticsController{}, "get:Bid")
|
||||
|
||||
// 招投标到期提醒(开发计划 4.1,实时计算,无定时任务)
|
||||
beego.Router("/backend/crm/bidding/reminders", &controllers.BackendCrmBiddingReminderController{}, "get:List")
|
||||
|
||||
// ERP供应商管理
|
||||
beego.Router("/backend/erp/supplier/list", &controllers.BackendErpSupplierController{}, "get:List")
|
||||
beego.Router("/backend/erp/supplier", &controllers.BackendErpSupplierController{}, "post:Create")
|
||||
|
||||
Reference in New Issue
Block a user