feat: add Z12/Z13 bind APIs, stock import, and sync docs

Enable auto default sync channels on agent activate, bind-code/phone confirm flows, publish ALTER, and align admin/yuheng docs with the production bind path.
This commit is contained in:
whm
2026-08-05 11:47:20 +08:00
parent b04b180d30
commit cb56e6847e
31 changed files with 1882 additions and 91 deletions

View File

@@ -3,6 +3,7 @@ package applogic
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"io"
"path/filepath"
@@ -13,6 +14,7 @@ import (
"aijianzhan/platform/internal/meta"
"aijianzhan/platform/internal/types"
"github.com/lib/pq"
"github.com/xuri/excelize/v2"
)
@@ -24,8 +26,10 @@ func (l *CrudLogic) ImportRows(slug, resource, filename string, r io.Reader) (*t
if err != nil {
return nil, err
}
// Z9f读路径内存兜底与 Z9e 扫库双保险)
ensureImportOpInMemory(ref)
if !hasOp(ref, "import") {
return nil, fmt.Errorf("operation import not allowed")
return nil, fmt.Errorf("蓝图未开启 importoperations 缺 import)。请管理员执行「一键开启全部业务表导入」或再发布模块;与账号权限 row.import 无关")
}
ext := strings.ToLower(filepath.Ext(filename))
@@ -86,7 +90,11 @@ func (l *CrudLogic) ImportRows(slug, resource, filename string, r io.Reader) (*t
if _, err := l.svcCtx.CRUD.Create(l.ctx, ref, tenantID, userID, body); err != nil {
resp.Skipped++
if len(resp.Errors) < 100 {
resp.Errors = append(resp.Errors, fmt.Sprintf("row %d: %v", rowNum+1, err))
msg := err.Error()
if isUndefinedColumnErr(err) {
msg = fmt.Sprintf("%v蓝图字段与库表不一致请重新发布模块以自动补列或缩小 Excel 表头至库已有列)", err)
}
resp.Errors = append(resp.Errors, fmt.Sprintf("row %d: %s", rowNum+1, msg))
}
continue
}
@@ -107,8 +115,9 @@ func (l *CrudLogic) ExportExcel(slug, resource string) ([]byte, string, error) {
if err != nil {
return nil, "", err
}
ensureImportOpInMemory(ref)
if !hasOp(ref, "export") {
return nil, "", fmt.Errorf("operation export not allowed")
return nil, "", fmt.Errorf("蓝图未开启 exportoperations 缺 export)。请管理员执行「一键开启全部业务表导入」或再发布模块")
}
items, _, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "")
if err != nil {
@@ -147,8 +156,9 @@ func (l *CrudLogic) ExportCSV(slug, resource string) ([]byte, error) {
if err != nil {
return nil, err
}
ensureImportOpInMemory(ref)
if !hasOp(ref, "export") {
return nil, fmt.Errorf("operation export not allowed")
return nil, fmt.Errorf("蓝图未开启 exportoperations 缺 export)。请管理员执行「一键开启全部业务表导入」或再发布模块")
}
items, _, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "")
if err != nil {
@@ -237,6 +247,9 @@ func rowEmpty(rec []string) bool {
}
func hasOp(ref *meta.ResourceRef, op string) bool {
if ref == nil {
return false
}
for _, o := range ref.Resource.Operations {
if o == op {
return true
@@ -245,6 +258,37 @@ func hasOp(ref *meta.ResourceRef, op string) bool {
return false
}
// ensureImportOpInMemory Z9f对已加载蓝图做与 EnsureDefaultImportExport 相同的内存补齐,并刷新 ref.Resource。
func ensureImportOpInMemory(ref *meta.ResourceRef) {
if ref == nil || ref.App == nil || ref.App.Blueprint == nil {
return
}
ref.App.Blueprint.EnsureDefaultImportExport()
want := strings.TrimSpace(ref.Resource.Path)
if len(want) > 0 && want[0] == '/' {
want = want[1:]
}
for _, r := range ref.App.Blueprint.Apis.Resources {
path := r.Path
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
if path == want {
ref.Resource = r
return
}
}
}
func isUndefinedColumnErr(err error) bool {
var pqErr *pq.Error
if errors.As(err, &pqErr) && pqErr.Code == "42703" {
return true
}
msg := err.Error()
return strings.Contains(msg, "42703") || strings.Contains(msg, "字段不存在") || strings.Contains(msg, "does not exist")
}
func mapHeaderToField(ref *meta.ResourceRef, header string) string {
h := strings.TrimSpace(header)
if h == "" {