增加CRM模块

This commit is contained in:
2025-11-13 17:24:59 +08:00
parent 117e2b3440
commit 695bf2194f
32 changed files with 5340 additions and 44 deletions
+90
View File
@@ -0,0 +1,90 @@
package controllers
import (
"encoding/json"
"strconv"
"server/models"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type ContactController struct {
beego.Controller
}
// List GET /api/crm/contact/list?tenant_id=&related_type=&related_id=
func (c *ContactController) List() {
tenantId := c.GetString("tenant_id")
relatedType, _ := strconv.Atoi(c.GetString("related_type"))
relatedId := c.GetString("related_id")
if tenantId == "" || relatedType == 0 || relatedId == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "缺少必要参数"}
c.ServeJSON()
return
}
list, err := services.ListContacts(tenantId, relatedType, relatedId)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": list}
}
c.ServeJSON()
}
// Add POST /api/crm/contact/add
func (c *ContactController) Add() {
var m models.Contact
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if err := services.CreateContact(&m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": m}
}
c.ServeJSON()
}
// Edit POST /api/crm/contact/edit
func (c *ContactController) Edit() {
var m models.Contact
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if m.Id == 0 {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
if err := services.UpdateContact(&m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
}
c.ServeJSON()
}
// Delete POST /api/crm/contact/delete
func (c *ContactController) Delete() {
var body struct {
Id int64 `json:"id"`
TenantId string `json:"tenant_id"`
}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if err := services.DeleteContact(body.Id, body.TenantId); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
}
c.ServeJSON()
}
+155
View File
@@ -0,0 +1,155 @@
package controllers
import (
"encoding/json"
"fmt"
"strconv"
"server/models"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type CustomerController struct {
beego.Controller
}
// List GET /api/crm/customer/list
func (c *CustomerController) List() {
tenantId := c.GetString("tenantId")
keyword := c.GetString("keyword")
status := c.GetString("status")
page, _ := strconv.Atoi(c.GetString("page"))
pageSize, _ := strconv.Atoi(c.GetString("pageSize"))
list, total, err := services.ListCustomers(tenantId, keyword, status, page, pageSize)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": map[string]interface{}{"list": list, "total": total}}
}
c.ServeJSON()
}
// Detail GET /api/crm/customer/detail?id=...
func (c *CustomerController) Detail() {
id := c.GetString("id")
if id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
m, err := services.GetCustomer(id)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": m}
}
c.ServeJSON()
}
// Add POST /api/crm/customer/add
func (c *CustomerController) Add() {
var m models.Customer
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if err := services.CreateCustomer(&m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": m}
}
c.ServeJSON()
}
// Edit POST /api/crm/customer/edit body: {id, ...}
func (c *CustomerController) Edit() {
var body map[string]interface{}
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
id, _ := body["id"].(string)
if id == "" {
// 也允许前端直接在JSON中传 id 字段,下面会再从结构体取
}
var m models.Customer
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
// 回退:从通用map中提取已知字段,避免类型不匹配导致失败
if body == nil {
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
}
toStr := func(v interface{}) string {
switch t := v.(type) {
case nil:
return ""
case string:
return t
case json.Number:
return t.String()
default:
return fmt.Sprint(v)
}
}
m = models.Customer{
Id: toStr(body["id"]),
TenantId: toStr(body["tenant_id"]),
CustomerName: toStr(body["customer_name"]),
CustomerType: toStr(body["customer_type"]),
ContactPerson: toStr(body["contact_person"]),
ContactPhone: toStr(body["contact_phone"]),
ContactEmail: toStr(body["contact_email"]),
CustomerLevel: toStr(body["customer_level"]),
Industry: toStr(body["industry"]),
Address: toStr(body["address"]),
Status: toStr(body["status"]),
Remark: toStr(body["remark"]),
}
if m.Id == "" {
if id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
m.Id = id
}
}
if m.Id == "" {
if id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
m.Id = id
}
if err := services.UpdateCustomer(&m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
}
c.ServeJSON()
}
// Delete POST /api/crm/customer/delete body: {id, tenantId}
func (c *CustomerController) Delete() {
var body struct {
Id string `json:"id"`
TenantId string `json:"tenantId"`
}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if body.Id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
if err := services.SoftDeleteCustomer(body.Id); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
}
c.ServeJSON()
}
+36 -8
View File
@@ -123,9 +123,9 @@ func (c *MenuController) UpdateMenu() {
return
}
// 直接使用 Beego 的 BindJSON 方法
var menu models.Menu
if err := c.BindJSON(&menu); err != nil {
// 解析请求体为通用map,进行部分字段更新,避免未提供字段被置零
var body map[string]interface{}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "请求参数错误",
@@ -135,11 +135,40 @@ func (c *MenuController) UpdateMenu() {
return
}
// 设置 ID
menu.Id = id
// 允许更新的字段映射:前端Key -> 数据库列名
allowed := map[string]string{
"Name": "name",
"Path": "path",
"ParentId": "parent_id",
"Icon": "icon",
"Order": "order",
"Status": "status",
"ComponentPath": "component_path",
"IsExternal": "is_external",
"ExternalUrl": "external_url",
"MenuType": "menu_type",
"Permission": "permission",
"IsShow": "is_show",
}
// 更新菜单
if err := models.UpdateMenu(&menu); err != nil {
params := orm.Params{}
for k, col := range allowed {
if v, ok := body[k]; ok {
params[col] = v
}
}
if len(params) == 0 {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "无可更新的字段",
}
c.ServeJSON()
return
}
o := orm.NewOrm()
if _, err := o.QueryTable("yz_menus").Filter("id", id).Update(params); err != nil {
c.Data["json"] = map[string]interface{}{
"success": false,
"message": "更新菜单失败",
@@ -149,7 +178,6 @@ func (c *MenuController) UpdateMenu() {
c.Data["json"] = map[string]interface{}{
"success": true,
"message": "更新菜单成功",
// "data": menu,
}
}
+110
View File
@@ -0,0 +1,110 @@
package controllers
import (
"encoding/json"
"strconv"
"server/models"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type SupplierController struct {
beego.Controller
}
func (c *SupplierController) List() {
tenantId := c.GetString("tenantId")
keyword := c.GetString("keyword")
status := c.GetString("status")
page, _ := strconv.Atoi(c.GetString("page"))
pageSize, _ := strconv.Atoi(c.GetString("pageSize"))
list, total, err := services.ListSuppliers(tenantId, keyword, status, page, pageSize)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": map[string]interface{}{"list": list, "total": total}}
}
c.ServeJSON()
}
func (c *SupplierController) Detail() {
id := c.GetString("id")
if id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
m, err := services.GetSupplier(id)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": m}
}
c.ServeJSON()
}
func (c *SupplierController) Add() {
var m models.Supplier
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if err := services.CreateSupplier(&m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok", "data": m}
}
c.ServeJSON()
}
func (c *SupplierController) Edit() {
var body map[string]interface{}
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
id, _ := body["id"].(string)
var m models.Supplier
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if m.Id == "" {
if id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
m.Id = id
}
if err := services.UpdateSupplier(&m); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
}
c.ServeJSON()
}
func (c *SupplierController) Delete() {
var body struct {
Id string `json:"id"`
TenantId string `json:"tenantId"`
}
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
c.ServeJSON()
return
}
if body.Id == "" {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
c.ServeJSON()
return
}
if err := services.SoftDeleteSupplier(body.Id); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
} else {
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
}
c.ServeJSON()
}
+24
View File
@@ -0,0 +1,24 @@
CREATE TABLE yz_tenant_crm_contact (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
tenant_id BIGINT NOT NULL COMMENT '租户ID(关联租户表,隔离数据)',
related_type TINYINT NOT NULL COMMENT '关联类型:1=客户,2=供应商(区分联系人归属)',
related_id BIGINT NOT NULL COMMENT '关联ID(关联yz_tenant_crm_customer.id或yz_tenant_crm_supplier.id',
contact_name VARCHAR(50) NOT NULL COMMENT '联系人姓名',
gender TINYINT DEFAULT 0 COMMENT '性别:0=未知,1=男,2=女',
mobile VARCHAR(20) COMMENT '手机号(唯一索引,避免重复)',
phone VARCHAR(20) COMMENT '固定电话',
email VARCHAR(100) COMMENT '邮箱',
position VARCHAR(50) COMMENT '职位(如:项目经理、采购负责人)',
department VARCHAR(50) COMMENT '所属部门',
is_primary TINYINT DEFAULT 0 COMMENT '是否主联系人:0=否,1=是(一个客户/供应商可设一个主联系人)',
remark VARCHAR(500) COMMENT '备注(如:关键决策人、对接优先级等)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by BIGINT COMMENT '创建人ID(关联用户表)',
update_by BIGINT COMMENT '更新人ID(关联用户表)',
is_deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0=正常,1=删除',
PRIMARY KEY (id),
UNIQUE KEY uk_tenant_mobile (tenant_id, mobile) COMMENT '同一租户内手机号唯一',
KEY idx_tenant_related (tenant_id, related_type, related_id) COMMENT '查询租户下某客户/供应商的所有联系人',
KEY idx_contact_name (tenant_id, contact_name) COMMENT '按姓名模糊查询联系人'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户CRM联系人表';
@@ -0,0 +1,26 @@
-- 客户管理表:yz_tenant_crm_customer(支持租户隔离、软删除、时间追踪)
CREATE TABLE `yz_tenant_crm_customer` (
`id` varchar(36) NOT NULL COMMENT 'ID',
`tenant_id` varchar(64) NOT NULL COMMENT '租户ID',
`customer_name` varchar(100) NOT NULL COMMENT '客户名称(企业/个人名称)',
`customer_type` varchar(20) NOT NULL COMMENT '客户类型',
`contact_person` varchar(50) NOT NULL COMMENT '联系人姓名',
`contact_phone` varchar(20) NOT NULL COMMENT '联系人电话',
`contact_email` varchar(100) DEFAULT '' COMMENT '联系人邮箱',
`customer_level` varchar(20) DEFAULT '3' COMMENT '客户等级(1-核心客户/2-重要客户/3-普通客户/4-潜在客户)',
`industry` varchar(50) DEFAULT '' COMMENT '所属行业',
`address` varchar(255) DEFAULT '' COMMENT '客户地址',
`register_time` date DEFAULT NULL COMMENT '客户注册/合作起始日期',
`expire_time` date DEFAULT NULL COMMENT '合作到期日期',
`status` varchar(20) NOT NULL DEFAULT '1' COMMENT '客户状态(0-禁用/1-正常/2-冻结/3-已注销)',
`remark` text COMMENT '客户备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化多租户隔离查询',
KEY `idx_customer_name` (`customer_name`) COMMENT '客户名称索引,优化按名称模糊查询',
KEY `idx_contact_phone` (`contact_phone`) COMMENT '联系人电话索引,优化按电话精准查询',
KEY `idx_status` (`status`) COMMENT '客户状态索引,优化按状态筛选(如:查询正常客户)',
KEY `idx_register_time` (`register_time`) COMMENT '注册时间索引,优化按合作时间范围查询'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户管理表(支持租户隔离、软删除、客户全生命周期追踪)';
@@ -0,0 +1,26 @@
-- 客户管理表:yz_tenant_crm_supplier(支持租户隔离、软删除、时间追踪)
CREATE TABLE `yz_tenant_crm_supplier` (
`id` varchar(36) NOT NULL COMMENT 'ID',
`tenant_id` varchar(64) NOT NULL COMMENT '租户ID',
`supplier_name` varchar(100) NOT NULL COMMENT '供应商名称(企业/个人名称)',
`supplier_type` varchar(20) NOT NULL COMMENT '供应商类型',
`contact_person` varchar(50) NOT NULL COMMENT '联系人姓名',
`contact_phone` varchar(20) NOT NULL COMMENT '联系人电话',
`contact_email` varchar(100) DEFAULT '' COMMENT '联系人邮箱',
`supplier_level` varchar(20) DEFAULT '3' COMMENT '供应商等级(1-核心供应商/2-重要供应商/3-普通供应商/4-潜在供应商)',
`industry` varchar(50) DEFAULT '' COMMENT '所属行业',
`address` varchar(255) DEFAULT '' COMMENT '供应商地址',
`register_time` date DEFAULT NULL COMMENT '供应商注册/合作起始日期',
`expire_time` date DEFAULT NULL COMMENT '合作到期日期',
`status` varchar(20) NOT NULL DEFAULT '1' COMMENT '供应商状态(0-禁用/1-正常/2-冻结/3-已注销)',
`remark` text COMMENT '供应商备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化多租户隔离查询',
KEY `idx_supplier_name` (`supplier_name`) COMMENT '供应商名称索引,优化按名称模糊查询',
KEY `idx_contact_phone` (`contact_phone`) COMMENT '联系人电话索引,优化按电话精准查询',
KEY `idx_status` (`status`) COMMENT '供应商状态索引,优化按状态筛选',
KEY `idx_register_time` (`register_time`) COMMENT '注册时间索引,优化按合作时间范围查询'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商管理表';
+37
View File
@@ -0,0 +1,37 @@
package models
import (
"time"
"github.com/beego/beego/v2/client/orm"
)
// Contact 联系人模型(对应表 yz_tenant_crm_contact
type Contact struct {
Id int64 `orm:"pk;auto" json:"id"`
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
RelatedType int `orm:"column(related_type)" json:"related_type"` // 1=客户 2=供应商
RelatedId string `orm:"column(related_id);size(64)" json:"related_id"`
ContactName string `orm:"column(contact_name);size(50)" json:"contact_name"`
Gender int8 `orm:"column(gender);null" json:"gender"`
Mobile string `orm:"column(mobile);size(20);null" json:"mobile"`
Phone string `orm:"column(phone);size(20);null" json:"phone"`
Email string `orm:"column(email);size(100);null" json:"email"`
Position string `orm:"column(position);size(50);null" json:"position"`
Department string `orm:"column(department);size(50);null" json:"department"`
IsPrimary int8 `orm:"column(is_primary);null" json:"is_primary"`
Remark string `orm:"column(remark);size(500);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
CreateBy int64 `orm:"column(create_by);null" json:"create_by"`
UpdateBy int64 `orm:"column(update_by);null" json:"update_by"`
IsDeleted int8 `orm:"column(is_deleted);null" json:"is_deleted"`
}
func (t *Contact) TableName() string {
return "yz_tenant_crm_contact"
}
func init() {
orm.RegisterModel(new(Contact))
}
+36
View File
@@ -0,0 +1,36 @@
package models
import (
"time"
"github.com/beego/beego/v2/client/orm"
)
// Customer 客户模型(对应表 yz_tenant_crm_customer
type Customer struct {
Id string `orm:"pk;size(36)" json:"id"`
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
CustomerName string `orm:"column(customer_name);size(100)" json:"customer_name"`
CustomerType string `orm:"column(customer_type);size(20)" json:"customer_type"`
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);null" json:"contact_email"`
CustomerLevel string `orm:"column(customer_level);size(20);null" json:"customer_level"`
Industry string `orm:"column(industry);size(50);null" json:"industry"`
Address string `orm:"column(address);size(255);null" json:"address"`
RegisterTime *time.Time `orm:"column(register_time);null;type(date)" json:"register_time"`
ExpireTime *time.Time `orm:"column(expire_time);null;type(date)" json:"expire_time"`
Status string `orm:"column(status);size(20)" json:"status"`
Remark string `orm:"column(remark);type(text);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
}
func (t *Customer) TableName() string {
return "yz_tenant_crm_customer"
}
func init() {
orm.RegisterModel(new(Customer))
}
+8 -2
View File
@@ -21,6 +21,7 @@ type Menu struct {
ExternalUrl string `orm:"size(1000);null"`
MenuType int8 `orm:"default(1)"`
Permission string `orm:"size(200);null"`
IsShow int8 `orm:"default(1)"`
CreateTime time.Time `orm:"auto_now_add;type(datetime)"`
UpdateTime time.Time `orm:"auto_now;type(datetime)"`
DeleteTime *time.Time `orm:"null;type(datetime)"`
@@ -53,6 +54,8 @@ func GetAllMenus() ([]map[string]interface{}, error) {
"isExternal": m.IsExternal,
"externalUrl": m.ExternalUrl,
"menuType": m.MenuType,
"permission": m.Permission,
"isShow": m.IsShow,
}
result = append(result, item)
}
@@ -129,11 +132,12 @@ func GetTenantMenus(roleId int) ([]map[string]interface{}, error) {
args[len(menuIds)] = 1 // menu_type=1 表示页面菜单
// 3. 查询菜单(只返回menu_type=1的页面菜单,且未删除的)
query := "SELECT id, name, path, parent_id, icon, `order`, status, component_path, is_external, external_url, menu_type, permission " +
query := "SELECT id, name, path, parent_id, icon, `order`, status, component_path, is_external, external_url, menu_type, permission, is_show " +
"FROM yz_menus " +
"WHERE id IN (" + strings.Join(placeholders, ",") + ") " +
"AND delete_time IS NULL " +
"AND menu_type = ? " +
"AND is_show = 1 " +
"ORDER BY `order`, id"
var menus []*Menu
@@ -184,11 +188,12 @@ func GetTenantMenus(roleId int) ([]map[string]interface{}, error) {
}
parentArgs[len(parentIdList)] = 1 // menu_type=1
parentQuery := "SELECT id, name, path, parent_id, icon, `order`, status, component_path, is_external, external_url, menu_type, permission " +
parentQuery := "SELECT id, name, path, parent_id, icon, `order`, status, component_path, is_external, external_url, menu_type, permission, is_show " +
"FROM yz_menus " +
"WHERE id IN (" + strings.Join(parentPlaceholders, ",") + ") " +
"AND delete_time IS NULL " +
"AND menu_type = ? " +
"AND is_show = 1 " +
"ORDER BY `order`, id"
var parentMenus []*Menu
@@ -224,6 +229,7 @@ func GetTenantMenus(roleId int) ([]map[string]interface{}, error) {
"externalUrl": m.ExternalUrl,
"menuType": m.MenuType,
"permission": m.Permission,
"isShow": m.IsShow,
}
result = append(result, item)
}
+36
View File
@@ -0,0 +1,36 @@
package models
import (
"time"
"github.com/beego/beego/v2/client/orm"
)
// Supplier 供应商模型(对应表 yz_tenant_crm_supplier
type Supplier struct {
Id string `orm:"pk;size(36)" json:"id"`
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
SupplierName string `orm:"column(supplier_name);size(100)" json:"supplier_name"`
SupplierType string `orm:"column(supplier_type);size(20)" json:"supplier_type"`
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);null" json:"contact_email"`
SupplierLevel string `orm:"column(supplier_level);size(20);null" json:"supplier_level"`
Industry string `orm:"column(industry);size(50);null" json:"industry"`
Address string `orm:"column(address);size(255);null" json:"address"`
RegisterTime *time.Time `orm:"column(register_time);null;type(date)" json:"register_time"`
ExpireTime *time.Time `orm:"column(expire_time);null;type(date)" json:"expire_time"`
Status string `orm:"column(status);size(20)" json:"status"`
Remark string `orm:"column(remark);type(text);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
}
func (t *Supplier) TableName() string {
return "yz_tenant_crm_supplier"
}
func init() {
orm.RegisterModel(new(Supplier))
}
+24 -4
View File
@@ -314,10 +314,30 @@ func init() {
// OA基础数据合并接口(一次性获取部门、职位、角色)
beego.Router("/api/oa/base-data/:tenantId", &controllers.OAController{}, "get:GetOABaseData")
// OA任务管理路由
beego.Router("/api/oa/tasks", &controllers.TaskController{}, "get:GetTasks;post:CreateTask")
beego.Router("/api/oa/tasks/:id", &controllers.TaskController{}, "get:GetTaskById;put:UpdateTask;delete:DeleteTask")
beego.Router("/api/oa/tasks/todo", &controllers.TaskController{}, "get:GetTodoTasks")
// OA任务管理路由
beego.Router("/api/oa/tasks", &controllers.TaskController{}, "get:GetTasks;post:CreateTask")
beego.Router("/api/oa/tasks/:id", &controllers.TaskController{}, "get:GetTaskById;put:UpdateTask;delete:DeleteTask")
beego.Router("/api/oa/tasks/todo", &controllers.TaskController{}, "get:GetTodoTasks")
// CRM 客户路由
beego.Router("/api/crm/customer/list", &controllers.CustomerController{}, "get:List")
beego.Router("/api/crm/customer/detail", &controllers.CustomerController{}, "get:Detail")
beego.Router("/api/crm/customer/add", &controllers.CustomerController{}, "post:Add")
beego.Router("/api/crm/customer/edit", &controllers.CustomerController{}, "post:Edit")
beego.Router("/api/crm/customer/delete", &controllers.CustomerController{}, "post:Delete")
// CRM 供应商路由
beego.Router("/api/crm/supplier/list", &controllers.SupplierController{}, "get:List")
beego.Router("/api/crm/supplier/detail", &controllers.SupplierController{}, "get:Detail")
beego.Router("/api/crm/supplier/add", &controllers.SupplierController{}, "post:Add")
beego.Router("/api/crm/supplier/edit", &controllers.SupplierController{}, "post:Edit")
beego.Router("/api/crm/supplier/delete", &controllers.SupplierController{}, "post:Delete")
// CRM 联系人路由
beego.Router("/api/crm/contact/list", &controllers.ContactController{}, "get:List")
beego.Router("/api/crm/contact/add", &controllers.ContactController{}, "post:Add")
beego.Router("/api/crm/contact/edit", &controllers.ContactController{}, "post:Edit")
beego.Router("/api/crm/contact/delete", &controllers.ContactController{}, "post:Delete")
// 权限管理路由
beego.Router("/api/permissions/menus", &controllers.PermissionController{}, "get:GetAllMenuPermissions")
+64
View File
@@ -0,0 +1,64 @@
package services
import (
"errors"
"github.com/beego/beego/v2/client/orm"
"server/models"
)
func ListContacts(tenantId string, relatedType int, relatedId string) ([]models.Contact, error) {
o := orm.NewOrm()
var list []models.Contact
qs := o.QueryTable(new(models.Contact)).Filter("tenant_id", tenantId).Filter("related_type", relatedType).Filter("related_id", relatedId).Filter("is_deleted", 0)
_, err := qs.All(&list)
return list, err
}
func CreateContact(m *models.Contact) error {
if m.TenantId == "" || m.RelatedType == 0 || m.RelatedId == "" || m.ContactName == "" {
return errors.New("缺少必填参数")
}
o := orm.NewOrm()
// 若设为主联系人,则先清理同归属其他主联系人
if m.IsPrimary == 1 {
_, _ = o.QueryTable(new(models.Contact)).Filter("tenant_id", m.TenantId).Filter("related_type", m.RelatedType).Filter("related_id", m.RelatedId).Filter("is_deleted", 0).Update(orm.Params{"is_primary": 0})
}
_, err := o.Insert(m)
return err
}
func UpdateContact(m *models.Contact) error {
if m.Id == 0 {
return errors.New("id不能为空")
}
o := orm.NewOrm()
// 若设为主联系人,则先清理同归属其他主联系人
if m.IsPrimary == 1 {
// 需要获取原始记录以得到 tenant/related
var origin models.Contact
origin.Id = m.Id
if err := o.Read(&origin); err == nil {
_, _ = o.QueryTable(new(models.Contact)).Filter("tenant_id", origin.TenantId).Filter("related_type", origin.RelatedType).Filter("related_id", origin.RelatedId).Filter("id__ne", m.Id).Filter("is_deleted", 0).Update(orm.Params{"is_primary": 0})
}
}
_, err := o.Update(m, "contact_name", "gender", "mobile", "phone", "email", "position", "department", "is_primary", "remark")
return err
}
func DeleteContact(id int64, tenantId string) error {
if id == 0 {
return errors.New("id不能为空")
}
o := orm.NewOrm()
m := models.Contact{Id: id}
if err := o.Read(&m); err != nil {
return err
}
if m.TenantId != tenantId {
return errors.New("租户不匹配")
}
m.IsDeleted = 1
_, err := o.Update(&m, "is_primary", "is_deleted")
return err
}
+79
View File
@@ -0,0 +1,79 @@
package services
import (
"time"
"server/models"
"github.com/beego/beego/v2/client/orm"
)
// ListCustomers 获取客户列表(可选租户过滤、关键词搜索、状态筛选、分页)
func ListCustomers(tenantId, keyword, status string, page, pageSize int) (list []*models.Customer, total int64, err error) {
o := orm.NewOrm()
qs := o.QueryTable(new(models.Customer)).Filter("delete_time__isnull", true)
if tenantId != "" {
qs = qs.Filter("tenant_id", tenantId)
}
if keyword != "" {
cond := orm.NewCondition()
cond1 := cond.Or("customer_name__icontains", keyword).
Or("contact_person__icontains", keyword).
Or("contact_phone__icontains", keyword)
qs = qs.SetCond(cond1)
}
if status != "" {
qs = qs.Filter("status", status)
}
total, err = qs.Count()
if err != nil {
return
}
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
_, err = qs.OrderBy("-create_time").Limit(pageSize, offset).All(&list)
return
}
// GetCustomer 通过ID获取客户
func GetCustomer(id string) (*models.Customer, error) {
o := orm.NewOrm()
m := models.Customer{Id: id}
if err := o.Read(&m); err != nil {
return nil, err
}
return &m, nil
}
// CreateCustomer 新增客户
func CreateCustomer(m *models.Customer) error {
o := orm.NewOrm()
_, err := o.Insert(m)
return err
}
// UpdateCustomer 更新客户(全量更新)
func UpdateCustomer(m *models.Customer, cols ...string) error {
o := orm.NewOrm()
if len(cols) == 0 {
_, err := o.Update(m)
return err
}
_, err := o.Update(m, cols...)
return err
}
// SoftDeleteCustomer 软删除客户
func SoftDeleteCustomer(id string) error {
o := orm.NewOrm()
now := time.Now()
m := models.Customer{Id: id, DeleteTime: &now}
_, err := o.Update(&m, "delete_time")
return err
}
+79
View File
@@ -0,0 +1,79 @@
package services
import (
"time"
"server/models"
"github.com/beego/beego/v2/client/orm"
)
// ListSuppliers 获取供应商列表(可选租户过滤、关键词搜索、状态筛选、分页)
func ListSuppliers(tenantId, keyword, status string, page, pageSize int) (list []*models.Supplier, total int64, err error) {
o := orm.NewOrm()
qs := o.QueryTable(new(models.Supplier)).Filter("delete_time__isnull", true)
if tenantId != "" {
qs = qs.Filter("tenant_id", tenantId)
}
if keyword != "" {
cond := orm.NewCondition()
cond1 := cond.Or("supplier_name__icontains", keyword).
Or("contact_person__icontains", keyword).
Or("contact_phone__icontains", keyword)
qs = qs.SetCond(cond1)
}
if status != "" {
qs = qs.Filter("status", status)
}
total, err = qs.Count()
if err != nil {
return
}
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
_, err = qs.OrderBy("-create_time").Limit(pageSize, offset).All(&list)
return
}
// GetSupplier 通过ID获取供应商
func GetSupplier(id string) (*models.Supplier, error) {
o := orm.NewOrm()
m := models.Supplier{Id: id}
if err := o.Read(&m); err != nil {
return nil, err
}
return &m, nil
}
// CreateSupplier 新增供应商
func CreateSupplier(m *models.Supplier) error {
o := orm.NewOrm()
_, err := o.Insert(m)
return err
}
// UpdateSupplier 更新供应商(全量更新)
func UpdateSupplier(m *models.Supplier, cols ...string) error {
o := orm.NewOrm()
if len(cols) == 0 {
_, err := o.Update(m)
return err
}
_, err := o.Update(m, cols...)
return err
}
// SoftDeleteSupplier 软删除供应商
func SoftDeleteSupplier(id string) error {
o := orm.NewOrm()
now := time.Now()
m := models.Supplier{Id: id, DeleteTime: &now}
_, err := o.Update(&m, "delete_time")
return err
}