diff --git a/pc/src/api/contact.js b/pc/src/api/contact.js new file mode 100644 index 0000000..334e768 --- /dev/null +++ b/pc/src/api/contact.js @@ -0,0 +1,33 @@ +import request from '@/utils/request' + +export function listContacts(params) { + return request({ + url: '/api/crm/contact/list', + method: 'get', + params, + }) +} + +export function createContact(data) { + return request({ + url: '/api/crm/contact/add', + method: 'post', + data, + }) +} + +export function updateContact(data) { + return request({ + url: '/api/crm/contact/edit', + method: 'post', + data, + }) +} + +export function deleteContact(data) { + return request({ + url: '/api/crm/contact/delete', + method: 'post', + data, + }) +} diff --git a/pc/src/api/customer.js b/pc/src/api/customer.js new file mode 100644 index 0000000..aab0be2 --- /dev/null +++ b/pc/src/api/customer.js @@ -0,0 +1,72 @@ +import request from '@/utils/request' + +/** + * 获取客户列表 + * @param {Object} params 查询参数 + * @param {number} params.tenantId 租户ID + * @returns {Promise} + */ +export function listCustomers(params) { + return request({ + url: '/api/crm/customer/list', + method: 'get', + params + }) +} + +/** + * 获取客户详情 + * @param {number|string} id 客户ID + * @param {number} params.tenantId 租户ID + * @returns {Promise} + */ +export function getCustomer(id) { + return request({ + url: '/api/crm/customer/detail', + method: 'get', + params: { id } + }) +} + +/** + * 创建客户 + * @param {Object} data 客户数据 + * @param {number} data.tenantId 租户ID + * @returns {Promise} + */ +export function createCustomer(data) { + return request({ + url: '/api/crm/customer/add', + method: 'post', + data + }) +} + +/** + * 更新客户 + * @param {number|string} id 客户ID + * @param {Object} data 更新的数据 + * @param {number} data.tenantId 租户ID + * @returns {Promise} + */ +export function updateCustomer(id, data) { + return request({ + url: '/api/crm/customer/edit', + method: 'post', + data: { id, ...data } + }) +} + +/** + * 删除客户 + * @param {number|string} id 客户ID + * @param {number} tenantId 租户ID + * @returns {Promise} + */ +export function deleteCustomer(id, tenantId) { + return request({ + url: '/api/crm/customer/delete', + method: 'post', + data: { id, tenantId } + }) +} diff --git a/pc/src/api/supplier.js b/pc/src/api/supplier.js new file mode 100644 index 0000000..3464bba --- /dev/null +++ b/pc/src/api/supplier.js @@ -0,0 +1,72 @@ +import request from '@/utils/request' + +/** + * 获取供应商列表 + * @param {Object} params 查询参数 + * @param {number} params.tenantId 租户ID + * @returns {Promise} + */ +export function listSuppliers(params) { + return request({ + url: '/api/crm/supplier/list', + method: 'get', + params + }) +} + +/** + * 获取供应商详情 + * @param {number|string} id 供应商ID + * @param {number} params.tenantId 租户ID + * @returns {Promise} + */ +export function getSupplier(id) { + return request({ + url: '/api/crm/supplier/detail', + method: 'get', + params: { id } + }) +} + +/** + * 创建供应商 + * @param {Object} data 供应商数据 + * @param {number} data.tenantId 租户ID + * @returns {Promise} + */ +export function createSupplier(data) { + return request({ + url: '/api/crm/supplier/add', + method: 'post', + data + }) +} + +/** + * 更新供应商 + * @param {number|string} id 供应商ID + * @param {Object} data 更新的数据 + * @param {number} data.tenantId 租户ID + * @returns {Promise} + */ +export function updateSupplier(id, data) { + return request({ + url: '/api/crm/supplier/edit', + method: 'post', + data: { id, ...data } + }) +} + +/** + * 删除供应商 + * @param {number|string} id 供应商ID + * @param {number} tenantId 租户ID + * @returns {Promise} + */ +export function deleteSupplier(id, tenantId) { + return request({ + url: '/api/apps/crm/supplier/delete', + method: 'post', + data: { id, tenantId } + }) +} diff --git a/pc/src/stores/menu.js b/pc/src/stores/menu.js index 9bde12f..ea382f0 100644 --- a/pc/src/stores/menu.js +++ b/pc/src/stores/menu.js @@ -139,10 +139,11 @@ export const useMenuStore = defineStore('menu', () => { if (res.data !== undefined && res.data !== null) { // 确保 data 是数组 const menuData = Array.isArray(res.data) ? res.data : []; - menus.value = menuData; + const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); + menus.value = filtered; // 保存到缓存 - saveToCache(menuData); - return menuData; + saveToCache(filtered); + return filtered; } else { // data 为 null 或 undefined,使用空数组 console.warn('菜单数据为空,使用空数组'); @@ -155,9 +156,10 @@ export const useMenuStore = defineStore('menu', () => { // 如果响应格式不符合预期,尝试直接使用 res.data if (res.data !== undefined) { const menuData = Array.isArray(res.data) ? res.data : []; - menus.value = menuData; - saveToCache(menuData); - return menuData; + const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); + menus.value = filtered; + saveToCache(filtered); + return filtered; } // 如果都不符合,抛出错误 diff --git a/pc/src/views/apps/crm/customer/components/contact.vue b/pc/src/views/apps/crm/customer/components/contact.vue new file mode 100644 index 0000000..013c08b --- /dev/null +++ b/pc/src/views/apps/crm/customer/components/contact.vue @@ -0,0 +1,201 @@ + + + + + 新增联系人 + + + + + + + + + + 编辑 + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + diff --git a/pc/src/views/apps/crm/customer/components/detail.vue b/pc/src/views/apps/crm/customer/components/detail.vue new file mode 100644 index 0000000..575cae7 --- /dev/null +++ b/pc/src/views/apps/crm/customer/components/detail.vue @@ -0,0 +1,72 @@ + + + + {{ model.name || '-' }} + {{ model.contact || '-' }} + {{ model.phone || '-' }} + {{ model.email || '-' }} + {{ getLabel(customerTypeOptions, model.customer_type) }} + {{ getLabel(customerLevelOptions, model.customer_level) }} + {{ getLabel(industryOptions, model.industry) }} + + + {{ getLabel(customerStatusOptions, String(model.status ?? '')) || ((model.status===1||model.status==='1') ? '正常' : '停用') }} + + + {{ model.address || '-' }} + + + 关闭 + + + + + + + diff --git a/pc/src/views/apps/crm/customer/components/edit.vue b/pc/src/views/apps/crm/customer/components/edit.vue new file mode 100644 index 0000000..3ca483e --- /dev/null +++ b/pc/src/views/apps/crm/customer/components/edit.vue @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + diff --git a/pc/src/views/apps/crm/customer/components/invoice.vue b/pc/src/views/apps/crm/customer/components/invoice.vue new file mode 100644 index 0000000..9d1f3fe --- /dev/null +++ b/pc/src/views/apps/crm/customer/components/invoice.vue @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 保存 + + + + + + + diff --git a/pc/src/views/apps/crm/customer/index.vue b/pc/src/views/apps/crm/customer/index.vue new file mode 100644 index 0000000..e583ee9 --- /dev/null +++ b/pc/src/views/apps/crm/customer/index.vue @@ -0,0 +1,318 @@ + + + + + + 新增客户 + 刷新 + + + + + + + + + + + + + + + + + + + + {{ row.status === 1 ? '正常' : '停用' }} + + + + + + 联系人 + 详情 + 开票信息 + 编辑 + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pc/src/views/apps/crm/index.vue b/pc/src/views/apps/crm/index.vue new file mode 100644 index 0000000..9024bbe --- /dev/null +++ b/pc/src/views/apps/crm/index.vue @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pc/src/views/apps/crm/supplier/components/contact.vue b/pc/src/views/apps/crm/supplier/components/contact.vue new file mode 100644 index 0000000..1b9be80 --- /dev/null +++ b/pc/src/views/apps/crm/supplier/components/contact.vue @@ -0,0 +1,201 @@ + + + + + 新增联系人 + + + + + + + + + + 编辑 + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + + diff --git a/pc/src/views/apps/crm/supplier/components/detail.vue b/pc/src/views/apps/crm/supplier/components/detail.vue new file mode 100644 index 0000000..3130a2c --- /dev/null +++ b/pc/src/views/apps/crm/supplier/components/detail.vue @@ -0,0 +1,72 @@ + + + + {{ model.name || '-' }} + {{ model.contact || '-' }} + {{ model.phone || '-' }} + {{ model.email || '-' }} + {{ getLabel(supplierTypeOptions, model.supplier_type) }} + {{ getLabel(supplierLevelOptions, model.supplier_level) }} + {{ getLabel(industryOptions, model.industry) }} + + + {{ getLabel(supplierStatusOptions, String(model.status ?? '')) || ((model.status===1||model.status==='1') ? '正常' : '停用') }} + + + {{ model.address || '-' }} + + + 关闭 + + + + + + + diff --git a/pc/src/views/apps/crm/supplier/components/edit.vue b/pc/src/views/apps/crm/supplier/components/edit.vue new file mode 100644 index 0000000..2dc5d81 --- /dev/null +++ b/pc/src/views/apps/crm/supplier/components/edit.vue @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确定 + + + + + + + diff --git a/pc/src/views/apps/crm/supplier/components/invoice.vue b/pc/src/views/apps/crm/supplier/components/invoice.vue new file mode 100644 index 0000000..9d1f3fe --- /dev/null +++ b/pc/src/views/apps/crm/supplier/components/invoice.vue @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 保存 + + + + + + + diff --git a/pc/src/views/apps/crm/supplier/index.vue b/pc/src/views/apps/crm/supplier/index.vue new file mode 100644 index 0000000..4c79b79 --- /dev/null +++ b/pc/src/views/apps/crm/supplier/index.vue @@ -0,0 +1,310 @@ + + + + + + 新增供应商 + 刷新 + + + + + + + + + + + + + + + + + + + + {{ row.status === 1 ? '正常' : '停用' }} + +function handleContactView(row) { + currentRow.value = { ...row } + contactVisible.value = true + getSupplier(row.id).then((res) => { + const resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res) + if (resp && resp.code === 0 && resp.data) { + const m = resp.data + currentRow.value = { + id: m.id, + name: m.supplier_name || m.name || '', + contact: m.contact_person || m.contact || '', + phone: m.contact_phone || m.phone || '', + email: m.contact_email || m.email || '', + address: m.address || '', + status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1), + supplier_type: m.supplier_type, + supplier_level: m.supplier_level, + industry: m.industry, + _raw: m, + } + } + }) +} + +function handleInvoiceView(row) { + currentRow.value = { ...row } + invoiceVisible.value = true + getSupplier(row.id).then((res) => { + const resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res) + if (resp && resp.code === 0 && resp.data) { + const m = resp.data + currentRow.value = { + id: m.id, + name: m.supplier_name || m.name || '', + contact: m.contact_person || m.contact || '', + phone: m.contact_phone || m.phone || '', + email: m.contact_email || m.email || '', + address: m.address || '', + status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1), + supplier_type: m.supplier_type, + supplier_level: m.supplier_level, + industry: m.industry, + // 开票相关(如果后端返回则透传给抽屉) + invoice_title: m.invoice_title, + tax_number: m.tax_number, + bank_name: m.bank_name, + bank_account: m.bank_account, + registered_address: m.registered_address, + registered_phone: m.registered_phone, + _raw: m, + } + } + }) +} + + + + + + 联系人 + 详情 + 开票信息 + 编辑 + 删除 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pc/src/views/apps/crm/workbench/index.vue b/pc/src/views/apps/crm/workbench/index.vue new file mode 100644 index 0000000..7a8dc80 --- /dev/null +++ b/pc/src/views/apps/crm/workbench/index.vue @@ -0,0 +1,2460 @@ + + + + + + + + + + + + + + 审批 + + + + + + + + + + + + + + + + + + {{ app.name }} + + {{ app.count > 99 ? "99+" : app.count }} + + + + + + + + + + + 知识库 + + + + + + + + + + + + + + + + + + + {{ knowledge.title }} + + {{ + knowledge.category + }} + {{ knowledge.date }} + + + + + + + + + + + + + + + 我的待办 + + + + + + 待审批 + + {{ task.title }} + + 处理节点: {{ task.node }} + 申请时间: {{ task.applyTime }} + + + + + + + + + + + + 常用功能 + + + + + + + + + + + {{ func.name }} + + + + + + + + + + + + 行业资讯 + + 查看更多 + + + + + + {{ news.title }} + + {{ news.source }} + {{ news.date }} + + + + + + + + + + + + + + + + 我的日程 + + + 新增 + 查看 + + + + + + + + + + + {{ currentMonth }} + + + + + + + + + + {{ day }} + + + + + + + {{ getDayNumber(date) }} + + + + + + + + {{ formatDate(date) }} + + + + 添加日程 + + + + + + + + + + + {{ schedule.time }} + + + + {{ schedule.title }} + + + {{ schedule.description }} + + + + {{ schedule.typeLabel }} + + + + + + + + + + + + + + + + + 帮助与服务 + + 查看更多 + + + + + + {{ help.tag }} + + {{ help.title }} + + + + + + + + + + + diff --git a/pc/src/views/system/menus/manager.vue b/pc/src/views/system/menus/manager.vue index 3b8c134..cdb2f4f 100644 --- a/pc/src/views/system/menus/manager.vue +++ b/pc/src/views/system/menus/manager.vue @@ -85,6 +85,18 @@ + + + + + + + + + + >({ ExternalUrl: "", MenuType: 1, Permission: "", + IsShow: 1, }); // 表单验证规则 @@ -303,14 +325,15 @@ const fetchMenus = async () => { ParentId: item.parentId, Icon: item.icon, Order: item.order, - Status: 1, + Status: item.status ?? 1, ComponentPath: item.componentPath || "", IsExternal: item.isExternal || 0, ExternalUrl: item.externalUrl || "", - MenuType: item.menuType, - Permission: item.permission || "", - CreateTime: "", - UpdateTime: "", + MenuType: item.menuType, + Permission: item.permission || "", + IsShow: item.isShow ?? 1, + CreateTime: "", + UpdateTime: "", })); // 按照参数Order的大小从小到大排序 @@ -447,25 +470,19 @@ const handleStatusChange = async (menu: Menu) => { } }; -// 添加顶级菜单 -const handleAddMenu = () => { - dialogTitle.value = "添加菜单"; - currentMenu.value = { - Id: 0, - Name: "", - Path: "", - ParentId: 0, - Icon: "", - Order: 0, - Status: 1, - ComponentPath: "", - IsExternal: 0, - ExternalUrl: "", - MenuType: 1, - Permission: "", - }; - dialogVisible.value = true; -}; +// 处理是否显示变更 +const handleShowChange = async (menu: Menu) => { + try { + const result = await updateMenu(menu.Id, { IsShow: menu.IsShow } as any) + if (!result.success) { + ElMessage.error(result.message) + menu.IsShow = menu.IsShow === 1 ? 0 : 1 + } + } catch (error) { + ElMessage.error('更新是否显示失败: ' + (error as Error).message) + menu.IsShow = menu.IsShow === 1 ? 0 : 1 + } +} // 添加子菜单 const handleAddSubMenu = (parentMenu: Menu) => { diff --git a/server/controllers/contact.go b/server/controllers/contact.go new file mode 100644 index 0000000..6e5a7a4 --- /dev/null +++ b/server/controllers/contact.go @@ -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() +} diff --git a/server/controllers/customer.go b/server/controllers/customer.go new file mode 100644 index 0000000..3cd6da3 --- /dev/null +++ b/server/controllers/customer.go @@ -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() +} diff --git a/server/controllers/menu.go b/server/controllers/menu.go index aca4ea9..6e47a25 100644 --- a/server/controllers/menu.go +++ b/server/controllers/menu.go @@ -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, } } diff --git a/server/controllers/supplier.go b/server/controllers/supplier.go new file mode 100644 index 0000000..1fcb835 --- /dev/null +++ b/server/controllers/supplier.go @@ -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() +} diff --git a/server/database/yz_tenant_crm_contact.sql b/server/database/yz_tenant_crm_contact.sql new file mode 100644 index 0000000..eb95f0b --- /dev/null +++ b/server/database/yz_tenant_crm_contact.sql @@ -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联系人表'; \ No newline at end of file diff --git a/server/database/yz_tenant_crm_customer.sql b/server/database/yz_tenant_crm_customer.sql new file mode 100644 index 0000000..ba5af7d --- /dev/null +++ b/server/database/yz_tenant_crm_customer.sql @@ -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='客户管理表(支持租户隔离、软删除、客户全生命周期追踪)'; \ No newline at end of file diff --git a/server/database/yz_tenant_crm_supplier.sql b/server/database/yz_tenant_crm_supplier.sql new file mode 100644 index 0000000..07e117d --- /dev/null +++ b/server/database/yz_tenant_crm_supplier.sql @@ -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='供应商管理表'; \ No newline at end of file diff --git a/server/models/contact.go b/server/models/contact.go new file mode 100644 index 0000000..23b5588 --- /dev/null +++ b/server/models/contact.go @@ -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)) +} diff --git a/server/models/customer.go b/server/models/customer.go new file mode 100644 index 0000000..3982895 --- /dev/null +++ b/server/models/customer.go @@ -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)) +} \ No newline at end of file diff --git a/server/models/menu.go b/server/models/menu.go index cfc8530..14e2d91 100644 --- a/server/models/menu.go +++ b/server/models/menu.go @@ -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) } diff --git a/server/models/supplier.go b/server/models/supplier.go new file mode 100644 index 0000000..f0e8826 --- /dev/null +++ b/server/models/supplier.go @@ -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)) +} \ No newline at end of file diff --git a/server/routers/router.go b/server/routers/router.go index 862283d..eef2d5d 100644 --- a/server/routers/router.go +++ b/server/routers/router.go @@ -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") diff --git a/server/services/contact.go b/server/services/contact.go new file mode 100644 index 0000000..3d3dfa0 --- /dev/null +++ b/server/services/contact.go @@ -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 +} diff --git a/server/services/customer.go b/server/services/customer.go new file mode 100644 index 0000000..81f0418 --- /dev/null +++ b/server/services/customer.go @@ -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 +} diff --git a/server/services/supplier.go b/server/services/supplier.go new file mode 100644 index 0000000..e99150b --- /dev/null +++ b/server/services/supplier.go @@ -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 +}