sunsheng 7 maanden geleden
bovenliggende
commit
4eec202823

+ 54 - 7
boxcost/api/aadiffupdatetest.go

@@ -24,11 +24,58 @@ func DiffUpdatePlanTest(c *gin.Context, apictx *ApiSession) (interface{}, error)
 		return nil, errors.New("id的为空")
 	}
 	plan.UpdateTime = time.Now()
-	// 查询更新前数据
-	// oldPlan := &model.ProductPlan{}
-	// repo.RepoSeachDoc(apictx.CreateRepoCtx(),&repo.DocSearchOptions{},oldPlan)
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), "product-plan_copy1", plan.Id.Hex(), &plan)
-	// 查询更新后数据
-	// 差异比较
-	// 记录到数据库中
+	// repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+	// 	CollectName: "product-plan_copy1",
+	// 	Query:       repo.Map{"_id": plan.Id.Hex()},
+	// }, &plan)
+	// return plan, nil
+
+	// ========================记录更新日志 查询更新前数据============================
+	// planModel1 := &model.ProductPlan{}
+	// oldPlan := getDataById(apictx, plan.Id.Hex(), planModel1, "product-plan_copy1")
+	// // ============================================================================
+
+	result, err1 := repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), "product-plan_copy1", plan.Id.Hex(), &plan, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: plan.Id.Hex(),
+	})
+
+	// ========================记录更新日志 查询更新后数据============================
+	// planModel2 := &model.ProductPlan{}
+	// newPlan := getDataById(apictx, plan.Id.Hex(), planModel2, "product-plan_copy1")
+	// // ============================================================================
+
+	// // ============================记录更新前后差异================================
+	// diffStr, err := diffUpdateData(oldPlan, newPlan)
+	// if err != nil {
+	// 	fmt.Println(err)
+	// }
+
+	// // 记录到数据库中
+	// changeLogs := &model.Logs{
+	// 	Path:       c.Request.URL.Path,
+	// 	UserId:     apictx.User.ID,
+	// 	TargetId:   plan.Id.Hex(),
+	// 	Diff:       diffStr,
+	// 	CreateTime: time.Now(),
+	// 	UpdateTime: time.Now(),
+	// }
+	// repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionLogs, changeLogs)
+	// ==========================================================================
+	return result, err1
+}
+
+func PrintDiff(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	id := c.Query("id")
+	changeLogs := model.Logs{}
+	_, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+		CollectName: repo.CollectionLogs,
+		Query:       repo.Map{"_id": id},
+	}, &changeLogs)
+	if err != nil {
+		return nil, err
+	}
+	fmt.Println(changeLogs.Diff)
+	return changeLogs, nil
 }

+ 278 - 0
boxcost/api/aadiffupdatetest.http

@@ -0,0 +1,278 @@
+@host = 127.0.0.1:8888
+
+
+###
+# 查询本地queenter库材质球组信息
+# 不存在时则创建
+POST http://{{host}}/boxcost/diffUpdatePlanTest HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjE4NzE1MDgsImlkIjoiNjQyYTUyNGY1ZjUwYmM5MDNmOTg2Mzk0Iiwia2V5IjoiYm94Y29zdCIsIm5hbWUiOiLlrZnog5wiLCJvcmlnX2lhdCI6MTcyMTI2NjcwOCwicGFyZW50IjoiNjQyYTUyNGY1ZjUwYmM5MDNmOTg2Mzk0IiwicGhvbmUiOiIxMzQwODU0NzgyMyIsInJvbGUiOiIiLCJzdGF0ZSI6MSwidXNlclR5cGUiOjJ9.oBKvW_adQUfzc5yuH_0dfHdsU559SmtfEq9UWQGJkfQ
+
+{
+    "_id": "64aa52935cca777202101884",
+    "name": "2023年-稻香锦绣内贴纸fdsds更换zzzz",
+    "pack": {
+      "_id": "646c37becfd68ee5e94335ef",
+      "name": "2023年-稻香rrrr锦绣",
+      "thumbnail": "",
+      "compCounts": 6,
+      "designer": "1",
+      "components": [
+        {
+          "id": "1685594042228",
+          "name": "内贴纸(1*4)",
+          "thumbnail": "",
+          "count": 1,
+          "uv": "",
+          "uvSize": "0",
+          "stages": [
+            {
+              "id": "1686109343729640b1add481f42e1f1219741",
+              "typeId": "640b1add481f42e1f1219741",
+              "orderPrice": 0.601,
+              "orderCount": 4700,
+              "isFix": null,
+              "confirmCount": 4700,
+              "deliveryTime": "2023-07-11T06:23:49.091Z",
+              "supplierInfo": {
+                "_id": "635f4afa769300c4a84bfd1b",
+                "name": "016-成都凯欣纸业",
+                "address": "崇州经开区霞光路51号",
+                "phone": "15982811956",
+                "categorys": [
+                  "纸张erererr",
+                  "cfdfdff"
+                ],
+                "category": "纸张类",
+                "createTime": "2022-10-31T04:11:38.249Z",
+                "updateTime": "2023-04-26T03:52:33.544Z"
+              },
+              "batchCount": 4,
+              "batchSizeWidth": 730,
+              "batchSizeHeight": 889,
+              "remark": "特规分切",
+              "size": "0",
+              "name": "铜版纸",
+              "category": "纸张",
+              "price": 5900,
+              "unit": "吨",
+              "norm": "157g",
+              "createTime": "2023-03-10T11:56:13.495Z",
+              "updateTime": "2023-06-07T03:33:00.164Z",
+              "type": 1,
+              "group": "",
+              "billId": "64aa52b45cca777202101885",
+              "billType": 1
+            },
+            {
+              "id": "1685594164090642bd76a26be7db05898efab",
+              "typeId": "642bd76a26be7db05898efab",
+              "orderPrice": 0.12,
+              "orderCount": 4600,
+              "isFix": null,
+              "confirmCount": 0,
+              "deliveryTime": "2023-07-12T06:23:53.291Z",
+              "supplierInfo": {
+                "_id": "63846b744d585ba68be6cba1",
+                "name": "008-成都宏川印务",
+                "address": "成都崇州经济开发区泗维路333号",
+                "phone": "13689052627",
+                "categorys": [
+                  "外协加工"
+                ],
+                "category": "普通印刷",
+                "createTime": "2022-11-28T08:04:04.791Z",
+                "updateTime": "2023-04-26T03:52:56.541Z"
+              },
+              "batchCount": 4,
+              "batchSizeWidth": 730,
+              "batchSizeHeight": 830,
+              "remark": "专红PT200C",
+              "size": "0",
+              "name": "普通印刷",
+              "category": "外协加工",
+              "price": 0.072,
+              "unit": "张",
+              "norm": "满版专色",
+              "createTime": "0001-01-01T00:00:00Z",
+              "updateTime": "0001-01-01T00:00:00Z",
+              "type": 2,
+              "group": "",
+              "billId": "64aa52bb5cca777202101886",
+              "billType": 2
+            },
+            {
+              "id": "168559418417563ef066d1031634bc6eeb844",
+              "typeId": "63ef066d1031634bc6eeb844",
+              "orderPrice": 0.297,
+              "orderCount": 4550,
+              "isFix": null,
+              "confirmCount": 0,
+              "deliveryTime": "2023-07-13T06:23:58.424Z",
+              "supplierInfo": {
+                "_id": "6388441551ba5b3307f8ab61",
+                "name": "018-覆膜 熊明军",
+                "address": "金鸡路556号 覆膜 熊明军",
+                "phone": "13980871668",
+                "categorys": [
+                  "外协加工"
+                ],
+                "category": "覆膜",
+                "createTime": "2022-12-01T06:05:09.079Z",
+                "updateTime": "2023-04-19T09:35:50.467Z"
+              },
+              "batchCount": 4,
+              "batchSizeWidth": 730,
+              "batchSizeHeight": 830,
+              "remark": " ",
+              "size": "0",
+              "name": "覆膜",
+              "category": "外协加工",
+              "price": 0.49,
+              "unit": "平方米",
+              "norm": "哑膜",
+              "createTime": "0001-01-01T00:00:00Z",
+              "updateTime": "0001-01-01T00:00:00Z",
+              "type": 2,
+              "group": "",
+              "billId": "64aa52be5cca777202101887",
+              "billType": 2
+            },
+            {
+              "id": "168559419589263ef10f91031634bc6eeb849",
+              "typeId": "63ef10f91031634bc6eeb849",
+              "orderPrice": 0.08,
+              "orderCount": 4500,
+              "isFix": null,
+              "confirmCount": 0,
+              "deliveryTime": "2023-07-14T06:24:03.474Z",
+              "supplierInfo": {
+                "_id": "63edbe951bd7c47e69698533",
+                "name": "019-压纹 吴宗祥",
+                "address": "",
+                "phone": "18280165844",
+                "categorys": [
+                  "外协加工"
+                ],
+                "category": "压纹",
+                "createTime": "2023-02-16T05:26:45.004Z",
+                "updateTime": "2023-04-19T09:36:07.663Z"
+              },
+              "batchCount": 884,
+              "batchSizeWidth": 0,
+              "batchSizeHeight": 0,
+              "remark": " ",
+              "size": "0",
+              "name": "压纹",
+              "category": "外协加工",
+              "price": 0.08,
+              "unit": "张",
+              "norm": "按文件要求",
+              "createTime": "0001-01-01T00:00:00Z",
+              "updateTime": "0001-01-01T00:00:00Z",
+              "type": 2,
+              "group": "",
+              "billId": "64aa52c65cca777202101888",
+              "billType": 2
+            },
+            {
+              "id": "168792259076963ef10b51031634bc6eeb848",
+              "typeId": "63ef10b51031634bc6eeb848",
+              "orderPrice": 0.12,
+              "orderCount": 8900,
+              "isFix": null,
+              "confirmCount": 0,
+              "deliveryTime": "2023-07-15T06:24:08.758Z",
+              "supplierInfo": {
+                "_id": "638eecd4f9039e0980fe5650",
+                "name": "020-烫金 温学刚",
+                "address": "崇州金鸡路556号 烫金 温学刚",
+                "phone": "13028138020",
+                "categorys": [
+                  "外协加工"
+                ],
+                "category": "烫金",
+                "createTime": "2022-12-06T07:18:44.322Z",
+                "updateTime": "2023-04-19T09:36:26.912Z"
+              },
+              "batchCount": 2,
+              "batchSizeWidth": 0,
+              "batchSizeHeight": 0,
+              "remark": " ",
+              "size": "0",
+              "name": "普通烫金eeeeeeeeeeeeee",
+              "category": "外协加工",
+              "price": 0.12,
+              "unit": "张",
+              "norm": "按文件要求",
+              "createTime": "0001-01-01T00:00:00Z",
+              "updateTime": "0001-01-01T00:00:00Z",
+              "type": 2,
+              "group": "",
+              "billId": "64aa52d65cca777202101889",
+              "billType": 2
+            }
+          ],
+          "remark": "",
+          "totalPrice": 6156.05
+        }
+      ],
+      "createTime": "2023-05-23T03:49:18.506Z",
+      "updateTime": "2023-05-23T13:36:11.432Z"
+    },
+    "thumbnail": "",
+    "createUser": "费用易展红图承担",
+    "total": 20000,
+    "status": "process",
+    "totalPrice": 6156.05,
+    "updateTime": "2024-07-23T04:33:44.858Z",
+    "createTime": "2023-07-09T06:24:19.305Z"
+  }
+
+
+###
+# 打印更改差异
+GET http://{{host}}/boxcost/printDiff?id=669e2e5a224892c0ab3a5048 HTTP/1.1
+Content-Type: application/json
+
+
+###
+# 打印更改差异
+# POST  http://{{host}}/boxcost/units/update HTTP/1.1
+# Content-Type: application/json
+# Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjE4NzE1MDgsImlkIjoiNjQyYTUyNGY1ZjUwYmM5MDNmOTg2Mzk0Iiwia2V5IjoiYm94Y29zdCIsIm5hbWUiOiLlrZnog5wiLCJvcmlnX2lhdCI6MTcyMTI2NjcwOCwicGFyZW50IjoiNjQyYTUyNGY1ZjUwYmM5MDNmOTg2Mzk0IiwicGhvbmUiOiIxMzQwODU0NzgyMyIsInJvbGUiOiIiLCJzdGF0ZSI6MSwidXNlclR5cGUiOjJ9.oBKvW_adQUfzc5yuH_0dfHdsU559SmtfEq9UWQGJkfQ
+
+# {
+#   "_id": "646cb8e1cfd68ee5e9433614",
+#   "name": "平方厘米1"
+# }
+
+###
+# 下载计划追踪表
+POST  http://{{host}}/boxcost/download/plan/track HTTP/1.1
+Content-Type: application/json
+
+{
+ "ids": [
+    "6648181a23830800fd7751fd"
+ ]
+}
+
+# {
+#  "ids": [
+#     "6648181a23830800fd7751fd",
+#     "664968ac23830800fd775203",
+#     "66498b5423830800fd775205",
+#     "6649535e23830800fd7751ff",
+#     "664989d823830800fd775204",
+#     "6649a02b23830800fd775216",
+#     "6649980723830800fd77520f",
+#     "66499a3423830800fd775211",
+#     "66499e2b23830800fd775215",
+#     "665dbdd623830800fd775416",
+#     "66499ae023830800fd775212",
+#     "6649990123830800fd775210",
+#     "66499c1d23830800fd775213",
+#     "66499cfd23830800fd775214"
+#  ]
+# }

+ 5 - 4
boxcost/api/api.go

@@ -4,6 +4,7 @@ import (
 	"box-cost/conf"
 	"box-cost/db"
 	"box-cost/db/repo"
+	"box-cost/middleware"
 	"context"
 	"fmt"
 	"net/http"
@@ -85,17 +86,17 @@ type RouterInterface interface {
 
 // GET http Get 请求
 func (g GinRouter) GET(path string, httpHandler Handler) {
-	g.group.GET(path, ResultWrapper(httpHandler, g.svc))
+	g.group.GET(path, middleware.Logger(), ResultWrapper(httpHandler, g.svc))
 }
 
 // POST http POST 请求
 func (g GinRouter) POST(path string, httpHandler Handler) {
-	g.group.POST(path, ResultWrapper(httpHandler, g.svc))
+	g.group.POST(path, middleware.Logger(), ResultWrapper(httpHandler, g.svc))
 }
 
 // GETJWT http Get 请求
 func (g GinRouter) GETJWT(path string, httpHandler JWTHander) {
-	g.group.GET(path, g.svc.JWT.MiddleFunc(), ResultJWTWrapper(httpHandler, g.svc))
+	g.group.GET(path, g.svc.JWT.MiddleFunc(), middleware.Logger(), ResultJWTWrapper(httpHandler, g.svc))
 }
 
 // GETJWTTest http Get 请求
@@ -105,7 +106,7 @@ func (g GinRouter) GETJWTTest(path string, httpHandler JWTHander) {
 
 // POSTJWT http POST 请求
 func (g GinRouter) POSTJWT(path string, httpHandler JWTHander) {
-	g.group.POST(path, g.svc.JWT.MiddleFunc(), ResultJWTWrapper(httpHandler, g.svc))
+	g.group.POST(path, g.svc.JWT.MiddleFunc(), middleware.Logger(), ResultJWTWrapper(httpHandler, g.svc))
 }
 
 // DeleteJWT http POST 请求

+ 15 - 5
boxcost/api/bill-produce.go

@@ -81,7 +81,12 @@ func ProduceReview(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		UpdateTime: time.Now(),
 		SignUsers:  signs,
 	}
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, _id, &produce)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, _id, &produce)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduce, _id, &produce, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: _id,
+	})
 
 }
 
@@ -106,7 +111,7 @@ func CreateProduceBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 		return nil, errors.New("类型为空")
 	}
 
-	bill.SerialNumber, err = generateSerial(apictx, bill.Type)
+	bill.SerialNumber, err = generateSerial(c, apictx, bill.Type)
 	if err != nil {
 		return nil, err
 	}
@@ -189,7 +194,7 @@ func UpdateProduceBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 			return nil, err
 		}
 		if billType != bill.Type {
-			bill.SerialNumber, err = generateSerial(apictx, bill.Type)
+			bill.SerialNumber, err = generateSerial(c, apictx, bill.Type)
 			if err != nil {
 				return nil, err
 			}
@@ -217,7 +222,7 @@ func UpdateProduceBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 			idCounts[produce.Id] = produce.ConfirmCount
 		}
 		fmt.Println(idCounts)
-		result, err := updateStageCount(bill.Id, bill.PlanId, idCounts, apictx)
+		result, err := updateStageCount(c, bill.PlanId, idCounts, apictx)
 		if err != nil {
 			fmt.Println(err)
 			log.Error(err)
@@ -226,7 +231,12 @@ func UpdateProduceBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 	}
 
 	bill.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, bill.Id.Hex(), &bill)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, bill.Id.Hex(), &bill)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduce, bill.Id.Hex(), &bill, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: bill.Id.Hex(),
+	})
 }
 
 // 删除单据

+ 16 - 5
boxcost/api/bill-product.go

@@ -81,7 +81,12 @@ func ProductReview(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		UpdateTime: time.Now(),
 		SignUsers:  signs,
 	}
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, _id, &product)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, _id, &product)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduct, _id, &product, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: _id,
+	})
 
 }
 
@@ -106,7 +111,7 @@ func CreateProductBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 		return nil, errors.New("类型为空")
 	}
 
-	bill.SerialNumber, err = generateSerial(apictx, bill.Type)
+	bill.SerialNumber, err = generateSerial(c, apictx, bill.Type)
 	if err != nil {
 		return nil, err
 	}
@@ -189,7 +194,7 @@ func UpdateProductBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 			return nil, err
 		}
 		if billType != bill.Type {
-			bill.SerialNumber, err = generateSerial(apictx, bill.Type)
+			bill.SerialNumber, err = generateSerial(c, apictx, bill.Type)
 			if err != nil {
 				return nil, err
 			}
@@ -218,7 +223,7 @@ func UpdateProductBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 			idCounts[product.Id] = product.ConfirmCount
 		}
 		fmt.Println(idCounts)
-		result, err := updateStageCount(bill.Id, bill.PlanId, idCounts, apictx)
+		result, err := updateStageCount(c, bill.PlanId, idCounts, apictx)
 		if err != nil {
 			fmt.Println(err)
 			log.Error(err)
@@ -227,7 +232,13 @@ func UpdateProductBill(c *gin.Context, apictx *ApiSession) (interface{}, error)
 	}
 
 	bill.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, bill.Id.Hex(), &bill)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, bill.Id.Hex(), &bill)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduct, bill.Id.Hex(), &bill, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: bill.Id.Hex(),
+	})
+
 }
 
 // 删除单据

+ 21 - 6
boxcost/api/bill.go

@@ -69,7 +69,12 @@ func BillRecord(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		return nil, errors.New("订单类型错误!")
 	}
 	update := bson.M{"isRecord": req.Record}
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), collection, req.Id.Hex(), &update)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), collection, req.Id.Hex(), &update)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), collection, req.Id.Hex(), &update, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: req.Id.Hex(),
+	})
 
 }
 
@@ -116,7 +121,12 @@ func PurchaseReview(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		SignUsers:  signs,
 	}
 
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, _id, &purchase)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, _id, &purchase)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, _id, &purchase, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: _id,
+	})
 
 }
 
@@ -149,7 +159,7 @@ func CreateBill(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		return nil, errors.New("类型为空")
 	}
 
-	bill.SerialNumber, err = generateSerial(apictx, bill.Type)
+	bill.SerialNumber, err = generateSerial(c, apictx, bill.Type)
 	if err != nil {
 		return nil, err
 	}
@@ -319,7 +329,7 @@ func UpdateBill(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 			return nil, err
 		}
 		if billType != bill.Type {
-			bill.SerialNumber, err = generateSerial(apictx, bill.Type)
+			bill.SerialNumber, err = generateSerial(c, apictx, bill.Type)
 			if err != nil {
 				return nil, err
 			}
@@ -345,7 +355,7 @@ func UpdateBill(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 			idCounts[paper.Id] = paper.ConfirmCount
 		}
 		fmt.Println(idCounts)
-		result, err := updateStageCount(bill.Id, bill.PlanId, idCounts, apictx)
+		result, err := updateStageCount(c, bill.PlanId, idCounts, apictx)
 		if err != nil {
 			fmt.Println(err)
 			log.Error(err)
@@ -354,7 +364,12 @@ func UpdateBill(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 	}
 
 	bill.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, bill.Id.Hex(), &bill)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, bill.Id.Hex(), &bill)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, bill.Id.Hex(), &bill, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: bill.Id.Hex(),
+	})
 }
 
 // 删除单据

+ 6 - 1
boxcost/api/craft.go

@@ -117,7 +117,12 @@ func UpdateCraft(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		craft.Remark = " "
 	}
 	craft.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionCraft, craft.Id.Hex(), &craft)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionCraft, craft.Id.Hex(), &craft)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionCraft, craft.Id.Hex(), &craft, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: craft.Id.Hex(),
+	})
 }
 
 // 删除工艺

+ 1 - 0
boxcost/api/jwt.go

@@ -52,6 +52,7 @@ func NewUitlsJwt(app *conf.AppConf) *UtilsJwt {
 		},
 		IdentityHandler: func(c *gin.Context) interface{} {
 			claims := jwt.ExtractClaims(c)
+			c.Set("userId", claims["id"])
 			// lg.Debug().Msgf("token: %v\n", claims)
 
 			u := &JWTUser{

+ 6 - 1
boxcost/api/material.go

@@ -111,7 +111,12 @@ func UpdateMaterial(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		return nil, errors.New("id的为空")
 	}
 	mat.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionMaterial, mat.Id.Hex(), &mat)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionMaterial, mat.Id.Hex(), &mat)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionMaterial, mat.Id.Hex(), &mat, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: mat.Id.Hex(),
+	})
 }
 
 // 删除材料

+ 6 - 1
boxcost/api/pack.go

@@ -108,7 +108,12 @@ func UpdatePack(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		return nil, errors.New("id的为空")
 	}
 	pack.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionPack, pack.Id.Hex(), &pack)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionPack, pack.Id.Hex(), &pack)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionPack, pack.Id.Hex(), &pack, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: pack.Id.Hex(),
+	})
 }
 
 // 删除包装

+ 281 - 0
boxcost/api/plan-process-track-excel.go

@@ -0,0 +1,281 @@
+package api
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/xuri/excelize/v2"
+)
+
+// 生产成本表
+type PlanProcessTrackExcel struct {
+	Row              int
+	Title            string
+	Excel            *excelize.File
+	SheetName        string
+	AlignCenterStyle int
+	Content          []*PlanStatusInfo
+	RowMap           map[string]int
+	RowWidthArray    []float64
+	RowsHeightArray  []map[int]float64
+}
+
+// 批量设置行高
+func (b *PlanProcessTrackExcel) setRowsHeight() {
+	for _, rowHeight := range b.RowsHeightArray {
+		for row, height := range rowHeight {
+			b.Excel.SetRowHeight(b.SheetName, row, height)
+		}
+	}
+
+}
+
+// 获取范围内单元格的宽度 A:F
+func (b *PlanProcessTrackExcel) getRangeWidth(r string) float64 {
+	rg := strings.Split(r, ":")
+
+	if len(rg) == 1 {
+		start := b.RowMap[rg[0]]
+		return b.RowWidthArray[start]
+	} else if len(rg) == 2 {
+		start := b.RowMap[rg[0]]
+		end := b.RowMap[rg[1]]
+		rowr := b.RowWidthArray[start : end+1]
+		width := 0.0
+		for _, v := range rowr {
+			width += v
+		}
+		return width
+	}
+	return 0.0
+}
+
+func (b *PlanProcessTrackExcel) drawTitle() error {
+	b.Row++
+	startCell := fmt.Sprintf("A%d", b.Row)
+	// A1:S2
+	b.Row++
+	err := b.Excel.MergeCell(b.SheetName, startCell, fmt.Sprintf("S%d", b.Row))
+	if err != nil {
+		return err
+	}
+
+	style, err := b.Excel.NewStyle(&excelize.Style{
+		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+		Font:      &excelize.Font{Bold: true, Size: 18}})
+	if err != nil {
+		return err
+	}
+	err = b.Excel.SetCellStyle(b.SheetName, startCell, startCell, style)
+	if err != nil {
+		return err
+	}
+	b.Excel.SetCellValue(b.SheetName, startCell, "2024月饼礼盒加工追踪表")
+	return nil
+}
+
+func (b *PlanProcessTrackExcel) drawTableTitle() error {
+	b.Row++
+	var drawCol = func(prefix string, value string) error {
+		left1Cell := fmt.Sprintf("%s%d", prefix, b.Row)
+		left2Cell := fmt.Sprintf("%s%d", prefix, b.Row+1)
+
+		err := b.Excel.MergeCell(b.SheetName, left1Cell, left2Cell)
+		if err != nil {
+			return err
+		}
+		err = b.Excel.SetCellStyle(b.SheetName, left1Cell, left2Cell, b.AlignCenterStyle)
+		if err != nil {
+			return err
+		}
+
+		return b.Excel.SetCellValue(b.SheetName, left1Cell, value)
+	}
+
+	var drawMergeCol = func(prefix1 string, prefix2 string, value string) error {
+		startCell := fmt.Sprintf("%s%d", prefix1, b.Row)
+		endCell := fmt.Sprintf("%s%d", prefix2, b.Row)
+		err := b.Excel.MergeCell(b.SheetName, startCell, endCell)
+		if err != nil {
+			return err
+		}
+		err = b.Excel.SetCellStyle(b.SheetName, startCell, endCell, b.AlignCenterStyle)
+		if err != nil {
+			return err
+		}
+		b.Excel.SetCellValue(b.SheetName, startCell, value)
+		return nil
+	}
+	var drawCell = func(prefix string, value string) error {
+		cell := fmt.Sprintf("%s%d", prefix, b.Row+1)
+		err := b.Excel.SetCellStyle(b.SheetName, cell, cell, b.AlignCenterStyle)
+		if err != nil {
+			return err
+		}
+		b.Excel.SetCellValue(b.SheetName, cell, value)
+		return nil
+
+	}
+
+	drawCol("A", "序号")
+	drawCol("B", "品名")
+	drawCol("C", "箱规")
+	drawCol("D", "数量合计(盒)")
+	drawCell("E", "部件")
+	drawCell("F", "下单")
+	drawCell("G", "纸张")
+	drawCell("H", "印刷")
+	drawCell("I", "覆膜")
+	drawCell("J", "烫金")
+	drawCell("K", "丝印")
+	drawCell("L", "对裱")
+	drawCell("M", "压纹")
+	drawCell("N", "裱瓦")
+	drawCell("O", "模切")
+	drawCell("P", "粘盒")
+	drawCell("Q", "组装")
+	drawMergeCol("E", "Q", "包装追踪")
+	drawCol("R", "交货")
+	drawCol("S", "备注")
+	b.Row++
+	return nil
+}
+
+func (b *PlanProcessTrackExcel) drawAllContent() error {
+	b.Row++
+
+	style, _ := b.Excel.NewStyle(&excelize.Style{
+		Border: []excelize.Border{
+			{
+				Type:  "left",
+				Color: "FF000000",
+				Style: 1,
+			},
+			{
+				Type:  "right",
+				Color: "FF000000",
+				Style: 1,
+			},
+			{
+				Type:  "top",
+				Color: "FF000000",
+				Style: 1,
+			},
+			{
+				Type:  "bottom",
+				Color: "FF000000",
+				Style: 1,
+			},
+		},
+		Fill: excelize.Fill{
+			Type:    "pattern",
+			Pattern: 1, // 1 表示实心填充
+			Color:   []string{CELL_BACKGROUND},
+		},
+	})
+
+	var drawRow = func(rowIndex int, values ...string) float64 {
+		charas := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S"}
+		// 获取改行最大行高
+		max := getRowHeight(values[0], b.getRangeWidth(charas[0]))
+		for i, c := range charas {
+			v := ""
+			if i < len(values) {
+				v = values[i]
+			}
+			// 设置背景色
+			if v == CELL_BACKGROUND {
+				b.Excel.SetCellStyle(b.SheetName, fmt.Sprintf("%s%d", c, rowIndex), fmt.Sprintf("%s%d", c, rowIndex), style)
+
+			} else {
+				// 填充cell
+				b.Excel.SetCellValue(b.SheetName, fmt.Sprintf("%s%d", c, rowIndex), v)
+				val2Cel := fmt.Sprintf("%s%d", c, rowIndex)
+				b.Excel.SetCellStyle(b.SheetName, val2Cel, val2Cel, b.AlignCenterStyle)
+
+			}
+
+			if getRowHeight(v, b.getRangeWidth(c)) > max {
+				max = getRowHeight(v, b.getRangeWidth(c))
+			}
+		}
+		return max
+	}
+
+	for _, planStatusInfo := range b.Content {
+		// 对应产品下部件's的状态
+		for _, compStatus := range planStatusInfo.PlanCompStatus {
+			_row := compStatus["行数"]
+			row, _ := strconv.Atoi(_row)
+
+			rowMaxHeight := drawRow(row, "", "", "", "", compStatus["部件"], compStatus["下单"], compStatus["纸张"], compStatus["印刷"], compStatus["覆膜"],
+				compStatus["烫金"], compStatus["丝印"], compStatus["对裱"], compStatus["压纹"], compStatus["裱瓦"], compStatus["模切"], compStatus["粘盒"],
+				compStatus["组装"], compStatus["交货"])
+			b.RowsHeightArray = append(b.RowsHeightArray, map[int]float64{row: rowMaxHeight})
+
+		}
+
+	}
+
+	for index, planStatusInfo := range b.Content {
+		fmt.Println(planStatusInfo.PlanRowStart)
+		fmt.Println(planStatusInfo.PlanRowEnd)
+		fmt.Println(planStatusInfo.PlanName)
+
+		b.Excel.MergeCell(b.SheetName, fmt.Sprintf("%s%d", "A", planStatusInfo.PlanRowStart), fmt.Sprintf("%s%d", "A", planStatusInfo.PlanRowEnd))
+		b.Excel.SetCellValue(b.SheetName, fmt.Sprintf("%s%d", "A", planStatusInfo.PlanRowStart), index+1)
+
+		// 品名
+		b.Excel.MergeCell(b.SheetName, fmt.Sprintf("%s%d", "B", planStatusInfo.PlanRowStart), fmt.Sprintf("%s%d", "B", planStatusInfo.PlanRowEnd))
+		b.Excel.SetCellValue(b.SheetName, fmt.Sprintf("%s%d", "B", planStatusInfo.PlanRowStart), planStatusInfo.PlanName)
+
+		// 箱规
+		b.Excel.MergeCell(b.SheetName, fmt.Sprintf("%s%d", "C", planStatusInfo.PlanRowStart), fmt.Sprintf("%s%d", "C", planStatusInfo.PlanRowEnd))
+		b.Excel.SetCellValue(b.SheetName, fmt.Sprintf("%s%d", "C", planStatusInfo.PlanRowStart), planStatusInfo.PlanUnit)
+
+		// 数量合计
+		b.Excel.MergeCell(b.SheetName, fmt.Sprintf("%s%d", "D", planStatusInfo.PlanRowStart), fmt.Sprintf("%s%d", "D", planStatusInfo.PlanRowEnd))
+		b.Excel.SetCellValue(b.SheetName, fmt.Sprintf("%s%d", "D", planStatusInfo.PlanRowStart), planStatusInfo.PlanCount)
+
+	}
+
+	return nil
+}
+
+func (b *PlanProcessTrackExcel) Draws() {
+	b.drawTitle()
+	b.drawTableTitle()
+	b.drawAllContent()
+}
+
+func NewPlanProcessTrackExcel(f *excelize.File) *PlanProcessTrackExcel {
+
+	border := []excelize.Border{
+		{Type: "top", Style: 1, Color: "000000"},
+		{Type: "left", Style: 1, Color: "000000"},
+		{Type: "right", Style: 1, Color: "000000"},
+		{Type: "bottom", Style: 1, Color: "000000"},
+	}
+
+	styleLeft, _ := f.NewStyle(&excelize.Style{
+		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
+		Border:    border,
+		Font:      &excelize.Font{Size: 10},
+	})
+
+	b := &PlanProcessTrackExcel{
+		Title:            "2024月饼礼盒加工追踪表",
+		Row:              0,
+		SheetName:        "Sheet1",
+		Excel:            f,
+		AlignCenterStyle: styleLeft,
+		Content:          make([]*PlanStatusInfo, 0),
+		RowMap:           map[string]int{"A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7, "I": 8, "J": 9, "K": 10, "L": 11, "M": 12, "N": 13, "O": 14, "P": 15, "Q": 16, "R": 17, "S": 18},
+		RowWidthArray:    []float64{8, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 12, 12},
+		RowsHeightArray:  make([]map[int]float64, 0),
+	}
+
+	f.SetPageMargins(b.SheetName, excelize.PageMarginTop(0), excelize.PageMarginLeft(0), excelize.PageMarginRight(0))
+	return b
+}

+ 331 - 0
boxcost/api/plan-process-track.go

@@ -0,0 +1,331 @@
+package api
+
+import (
+	"box-cost/db/model"
+	"box-cost/db/repo"
+	"box-cost/log"
+	"errors"
+	"fmt"
+	"regexp"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/xuri/excelize/v2"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type PlanIds struct {
+	Ids []primitive.ObjectID `json:"ids"`
+}
+
+func DownloadPlanTrack(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	var form PlanIds
+
+	err := c.ShouldBindJSON(&form)
+	if err != nil {
+		return nil, err
+	}
+	if len(form.Ids) == 0 {
+		return nil, errors.New("ids为空")
+	}
+
+	plans := []*model.ProductPlan{}
+	for _, objId := range form.Ids {
+		fmt.Println(objId.Hex())
+		plan := &model.ProductPlan{}
+		_, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+			CollectName: repo.CollectionProductPlan,
+			Query:       repo.Map{"_id": objId},
+		}, plan)
+		if err != nil {
+			fmt.Println(err)
+			log.Info(err)
+			continue
+		}
+		plans = append(plans, plan)
+	}
+
+	f := excelize.NewFile()
+	index := f.NewSheet("Sheet1")
+	f.SetActiveSheet(index)
+	f.SetDefaultFont("宋体")
+
+	planTrackExcel := NewPlanProcessTrackExcel(f)
+	planStatusInfos, err := handlPlanStatus(apictx, plans)
+	if err != nil {
+		return nil, err
+	}
+	planTrackExcel.Content = planStatusInfos
+
+	//设置对应的数据
+	planTrackExcel.Draws()
+	date := time.Now().Format("2006年01月02日_150405")
+	fileName := fmt.Sprintf("礼盒加工追踪表_%s.xlsx", date)
+
+	c.Header("Content-Type", "application/octet-stream")
+	c.Header("Content-Disposition", "attachment; filename="+fileName)
+	c.Header("Content-Transfer-Encoding", "binary")
+
+	err = f.Write(c.Writer)
+	if err != nil {
+		return nil, err
+	}
+
+	return nil, nil
+}
+
+const (
+	EXCEL_TMPLATE_FILE = "tmplate/tmplate.xlsx"
+	CELL_BACKGROUND    = "808080"
+)
+
+var needChangeCol = map[string]string{
+	"部件": "E",
+	"下单": "F",
+	"纸张": "G",
+	"印刷": "H",
+	"覆膜": "I",
+	"烫金": "J",
+	"丝印": "K",
+	"对裱": "L",
+	"压纹": "M",
+	"裱瓦": "N",
+	"模切": "O",
+	"粘盒": "P",
+	"组装": "Q",
+	"交货": "R",
+}
+
+func MatchString(targetStr string, regexPattern string) bool {
+	// 编译正则表达式
+	re := regexp.MustCompile(regexPattern)
+	// 检查目标字符串是否包含匹配的子串
+	return re.MatchString(targetStr)
+}
+
+type PlanStatusInfo struct {
+	PlanName       string              `json:"planName"`
+	PlanUnit       string              `json:"planUnit"`
+	PlanCount      int                 `json:"planCount"`
+	PlanRowStart   int                 `json:"planRowStart"`
+	PlanRowEnd     int                 `json:"planRowEnd"`
+	PlanCompStatus []map[string]string `json:"planCompStatus"`
+}
+
+func handlPlanStatus(apictx *ApiSession, plans []*model.ProductPlan) ([]*PlanStatusInfo, error) {
+	row := 5
+	planStatusInfos := make([]*PlanStatusInfo, 0)
+	planCompStatus := []map[string]string{}
+	for _, plan := range plans {
+		startRow := row
+		for _, comp := range plan.Pack.Components {
+			// ""代表该部件没有该工艺 "〇"代表正在进行的工艺
+			// "808080"背景颜色代表部件所含未进行工艺 √代表已完成工序
+			compStatus := map[string]string{
+				"部件": " ",
+				"行数": "5",
+				// 部件中只要有一个订单就说明下单了
+				"下单": " ",
+				// 采购单中type为纸张 订单对应状态为完成
+				"纸张": " ",
+				// 遍历工艺单,工序中包含印刷
+				"印刷": " ",
+				// 遍历工艺单,工序中包含覆膜
+				"覆膜": " ",
+				// 遍历工艺单,工序中包含烫金
+				"烫金": " ",
+				// 遍历工艺单,工序中包含丝印
+				"丝印": " ",
+				// 遍历工艺单,工序中包含对裱
+				"对裱": " ",
+				// 遍历工艺单,工序中包含压纹
+				"压纹": " ",
+				// 遍历工艺单,工序中包含裱瓦
+				"裱瓦": " ",
+				// 遍历工艺单,工序中包含模切
+				"模切": " ",
+				// 遍历工艺单,工序中包含粘盒
+				"粘盒": " ",
+				// 遍历工艺单,工序中包含组装
+				"组装": " ",
+				"交货": " ",
+			}
+			fmt.Println(plan.Name)
+			fmt.Println(comp.Name)
+			fmt.Println(row)
+			fmt.Println("------------------------------------")
+			compStatus["部件"] = comp.Name
+			compStatus["行数"] = fmt.Sprintf("%d", row)
+			row++
+			// 去重获取所有订单
+			seen := make(map[string]bool)
+			tbills := make([]string, 0, len(comp.Stages)) // 结果数组,容量初始化为原数组的长度
+
+			// ???:1 最后一个工序没下单
+			// isBill := true
+			// lastStage := comp.Stages[len(comp.Stages)-1]
+			// if len(lastStage.BillId) < 24 {
+			// 	isBill = false
+			// }
+
+			for _, stage := range comp.Stages {
+
+				if len(stage.BillId) > 0 {
+					value := fmt.Sprintf("%d_%s", stage.BillType, stage.BillId)
+					if _, ok := seen[value]; !ok {
+						// 标记为已出现
+						seen[value] = true
+						// 添加到结果数组
+						tbills = append(tbills, value)
+					}
+				} else {
+					// 产品中填写了这个工序但是没有下单 黑色背景
+					for k := range compStatus {
+						if k == "下单" || k == "交货" || k == "部件" || k == "行数" {
+							continue
+						}
+						// 纸张 只要是采购单就设置纸张状态
+						if stage.Type == 1 {
+							compStatus["纸张"] = CELL_BACKGROUND
+						}
+
+						// 匹配工艺关键字 匹配到就设置状态为黑背景
+						if MatchString(stage.Name, k) {
+							compStatus[k] = CELL_BACKGROUND
+						}
+
+					}
+				}
+
+			}
+			// fmt.Println(tbills)
+
+			// 如果tbills为空,说明该部件没有订单
+			if len(tbills) == 0 {
+				// 该部件没有订单,跳过
+				planCompStatus = append(planCompStatus, compStatus)
+				continue
+			}
+
+			// 查询数据库获取bill详细信息
+			// 最后工序订单号
+			lastBillType := tbills[len(tbills)-1]
+			for _, billType := range tbills {
+
+				compStatus["下单"] = "√"
+				bt := strings.Split(billType, "_")[0]
+				billId, _ := primitive.ObjectIDFromHex(strings.Split(billType, "_")[1])
+				if bt == "1" {
+					// 查询采购单
+					bill := &model.PurchaseBill{}
+					_, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+						CollectName: repo.CollectionBillPurchase,
+						Query:       repo.Map{"_id": billId},
+					}, bill)
+					if err != nil {
+						fmt.Printf("billId:%s---%s", billId.Hex(), err.Error())
+						return nil, errors.New("采购单不存在")
+					}
+
+					// if bill.Type == "纸张" {
+					// 	compStatus["纸张"] = "〇"
+					// 	if bill.Status == "complete" {
+					// 		compStatus["纸张"] = "√"
+					// 	}
+
+					// }
+
+					compStatus["纸张"] = "〇"
+					if bill.Status == "complete" {
+						compStatus["纸张"] = "√"
+					}
+
+					// if !isBill {
+					// 	continue
+					// }
+					if lastBillType == billType {
+						for _, paper := range bill.Paper {
+							compStatus["交货"] = fmt.Sprintf("%d", paper.ConfirmCount)
+						}
+					}
+
+				}
+				if bt == "2" {
+					// 查询工艺单
+					bill := &model.ProduceBill{}
+					_, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+						CollectName: repo.CollectionBillProduce,
+						Query:       repo.Map{"_id": billId},
+					}, bill)
+					if err != nil {
+						fmt.Printf("billId:%s---%s", billId.Hex(), err.Error())
+						return nil, errors.New("工艺单不存在")
+					}
+					for _, produce := range bill.Produces {
+						for k := range compStatus {
+							if k == "下单" || k == "纸张" || k == "交货" || k == "部件" || k == "行数" {
+								continue
+							}
+							if MatchString(produce.Name, k) {
+								compStatus[k] = "〇"
+								if bill.Status == "complete" {
+									compStatus[k] = "√"
+								}
+							}
+
+							// 直接赋值,如果这个订单是最后一个,则状态覆盖
+							// ???思考:如果最后一个工序没有生成订单的话,是按最后一个工序还是最后一个订单?这里是最后一个订单
+							// ???:1
+							// if !isBill {
+							// 	continue
+							// }
+							compStatus["交货"] = fmt.Sprintf("%d", produce.ConfirmCount)
+						}
+
+					}
+				}
+
+				// 暂时没有状态标定
+				if bt == "3" {
+					// 查询成品单
+					bill := &model.ProductBill{}
+					_, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+						CollectName: repo.CollectionBillProduct,
+						Query:       repo.Map{"_id": billId},
+					}, bill)
+					if err != nil {
+						fmt.Printf("billId:%s---%s", billId.Hex(), err.Error())
+						return nil, errors.New("成品单不存在")
+					}
+					// fmt.Println(bill)
+					// ?? 这里需不需要 影响正确数据吗?
+					// if !isBill {
+					// 	continue
+					// }
+					if lastBillType == billType {
+						for _, product := range bill.Products {
+							compStatus["交货"] = fmt.Sprintf("%d", product.ConfirmCount)
+						}
+					}
+				}
+			}
+
+			planCompStatus = append(planCompStatus, compStatus)
+		}
+		// fmt.Println(plan.Name)
+		// fmt.Println("rowstart:", startRow)
+		// fmt.Println("rowend:", row-1)
+		planStatusInfos = append(planStatusInfos, &PlanStatusInfo{
+			PlanName: plan.Name,
+			// !这个规格好像没有
+			PlanUnit:       "",
+			PlanCount:      plan.Total,
+			PlanRowStart:   startRow,
+			PlanRowEnd:     row - 1,
+			PlanCompStatus: planCompStatus,
+		})
+
+	}
+	return planStatusInfos, nil
+}

+ 31 - 18
boxcost/api/plan.go

@@ -115,17 +115,32 @@ func PlanAllocBatch(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		billId, _ := primitive.ObjectIDFromHex(tidArr[1])
 		if tidArr[0] == "1" {
 			billType = PURCHASE_BILL_TYPE
-			_, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, billId.Hex(), &model.PurchaseBill{IsSend: true, SendTime: time.Now()})
+			// _, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, billId.Hex(), &model.PurchaseBill{IsSend: true, SendTime: time.Now()})
+			_, err = repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, billId.Hex(), &model.PurchaseBill{IsSend: true, SendTime: time.Now()}, &repo.RecordLogReq{
+				Path:     c.Request.URL.Path,
+				UserId:   apictx.User.ID,
+				TargetId: billId.Hex(),
+			})
 		}
 		// 工艺
 		if tidArr[0] == "2" {
 			billType = PRODUCE_BILL_TYPE
-			_, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, billId.Hex(), &model.ProduceBill{IsSend: true, SendTime: time.Now()})
+			// _, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, billId.Hex(), &model.ProduceBill{IsSend: true, SendTime: time.Now()})
+			_, err = repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduce, billId.Hex(), &model.ProduceBill{IsSend: true, SendTime: time.Now()}, &repo.RecordLogReq{
+				Path:     c.Request.URL.Path,
+				UserId:   apictx.User.ID,
+				TargetId: billId.Hex(),
+			})
 		}
 		// 成品采购
 		if tidArr[0] == "3" {
 			billType = PRODUCT_BILL_TYPE
-			_, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, billId.Hex(), &model.ProductBill{IsSend: true, SendTime: time.Now()})
+			// _, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, billId.Hex(), &model.ProductBill{IsSend: true, SendTime: time.Now()})
+			_, err = repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduct, billId.Hex(), &model.ProductBill{IsSend: true, SendTime: time.Now()}, &repo.RecordLogReq{
+				Path:     c.Request.URL.Path,
+				UserId:   apictx.User.ID,
+				TargetId: billId.Hex(),
+			})
 		}
 
 		if err == nil {
@@ -147,7 +162,7 @@ func PlanAllocBatch(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 }
 
 // 更新供应商确定数量与plan中stage项的同步
-func updateStageCount(billId primitive.ObjectID, planId primitive.ObjectID, idCounts map[string]int, apictx *ApiSession) (interface{}, error) {
+func updateStageCount(c *gin.Context, planId primitive.ObjectID, idCounts map[string]int, apictx *ApiSession) (interface{}, error) {
 	if len(idCounts) == 0 {
 		return true, nil
 	}
@@ -172,13 +187,12 @@ func updateStageCount(billId primitive.ObjectID, planId primitive.ObjectID, idCo
 
 	}
 	plan.UpdateTime = time.Now()
-	result, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, planId.Hex(), &plan)
-	if err != nil {
-		log.Error(err)
-		fmt.Println(err)
-	}
-	return result, err
-
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, planId.Hex(), &plan)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionProductPlan, planId.Hex(), &plan, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: planId.Hex(),
+	})
 }
 
 type SupplierPlanCost struct {
@@ -1341,13 +1355,12 @@ func UpdateProductPlan(c *gin.Context, apictx *ApiSession) (interface{}, error)
 		return nil, errors.New("id的为空")
 	}
 	plan.UpdateTime = time.Now()
-	// 查询更新前数据
-	// oldPlan := &model.ProductPlan{}
-	// repo.RepoSeachDoc(apictx.CreateRepoCtx(),&repo.DocSearchOptions{},oldPlan)
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, plan.Id.Hex(), &plan)
-	// 查询更新后数据
-	// 差异比较
-	// 记录到数据库中
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, plan.Id.Hex(), &plan)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionProductPlan, plan.Id.Hex(), &plan, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: plan.Id.Hex(),
+	})
 }
 
 // 删除生产计划

+ 3 - 0
boxcost/api/router.go

@@ -18,6 +18,9 @@ func RegRouters(svc *Service) {
 	boxcost.GET("/printr", Printr)
 	boxcost.GET("/removeSyncBill", RemovePlanSyncBill)
 	boxcost.GET("/callback", callback)
+	boxcost.POSTJWT("/diffUpdatePlanTest", DiffUpdatePlanTest)
+	boxcost.GET("/printDiff", PrintDiff)
+	boxcost.POST("/download/plan/track", DownloadPlanTrack)
 
 	// 材料管理
 	Material(boxcost)

+ 7 - 2
boxcost/api/service.go

@@ -85,14 +85,19 @@ func CreateCRUD(router *GinRouter, prefix string, option *CRUDOption) {
 				option.OnUpdate(c, apictx, m)
 			}
 
-			out, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), option.Collection, objId, m)
+			// out, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), option.Collection, objId, m)
+			out, err := repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), option.Collection, objId, m, &repo.RecordLogReq{
+				Path:     c.Request.URL.Path,
+				UserId:   apictx.User.ID,
+				TargetId: objId,
+			})
+
 			if err != nil {
 				return nil, err
 			}
 			if out.MatchedCount != 1 {
 				return nil, NewError("文件不存在!")
 			}
-
 			return out, nil
 		})
 	}

+ 6 - 1
boxcost/api/signature.go

@@ -109,7 +109,12 @@ func SignatureUpdate(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 		return nil, errors.New("id的为空")
 	}
 	signature.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionSignature, signature.Id.Hex(), &signature)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionSignature, signature.Id.Hex(), &signature)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionSignature, signature.Id.Hex(), &signature, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: signature.Id.Hex(),
+	})
 }
 
 // 删除签名

+ 6 - 1
boxcost/api/supplier-price.go

@@ -337,7 +337,12 @@ func UpdateSupplierPrice(c *gin.Context, apictx *ApiSession) (interface{}, error
 		return nil, errors.New("id的为空")
 	}
 	supplierprice.UpdateTime = time.Now()
-	return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionSupplierPrice, supplierprice.Id.Hex(), &supplierprice)
+	// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionSupplierPrice, supplierprice.Id.Hex(), &supplierprice)
+	return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionSupplierPrice, supplierprice.Id.Hex(), &supplierprice, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   apictx.User.ID,
+		TargetId: supplierprice.Id.Hex(),
+	})
 }
 
 // 删除供应商价格

+ 36 - 6
boxcost/api/supplier.go

@@ -171,11 +171,26 @@ func SupplierBillAlloc(c *gin.Context, apictx *ApiSession) (interface{}, error)
 	var err error
 	switch billType {
 	case PURCHASE_BILL_TYPE:
-		result, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, billId.Hex(), &model.PurchaseBill{IsSend: true, SendTime: time.Now()})
+		// result, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, billId.Hex(), &model.PurchaseBill{IsSend: true, SendTime: time.Now()})
+		result, err = repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, billId.Hex(), &model.PurchaseBill{IsSend: true, SendTime: time.Now()}, &repo.RecordLogReq{
+			Path:     c.Request.URL.Path,
+			UserId:   apictx.User.ID,
+			TargetId: billId.Hex(),
+		})
 	case PRODUCE_BILL_TYPE:
-		result, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, billId.Hex(), &model.ProduceBill{IsSend: true, SendTime: time.Now()})
+		// result, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, billId.Hex(), &model.ProduceBill{IsSend: true, SendTime: time.Now()})
+		result, err = repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduce, billId.Hex(), &model.ProduceBill{IsSend: true, SendTime: time.Now()}, &repo.RecordLogReq{
+			Path:     c.Request.URL.Path,
+			UserId:   apictx.User.ID,
+			TargetId: billId.Hex(),
+		})
 	case PRODUCT_BILL_TYPE:
-		result, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, billId.Hex(), &model.ProductBill{IsSend: true, SendTime: time.Now()})
+		// result, err = repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, billId.Hex(), &model.ProductBill{IsSend: true, SendTime: time.Now()})
+		result, err = repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduct, billId.Hex(), &model.ProductBill{IsSend: true, SendTime: time.Now()}, &repo.RecordLogReq{
+			Path:     c.Request.URL.Path,
+			UserId:   apictx.User.ID,
+			TargetId: billId.Hex(),
+		})
 	default:
 		return result, nil
 	}
@@ -235,11 +250,26 @@ func SupplierBillAck(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 	isAck := true
 	switch billType {
 	case "purchase":
-		return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, _id, &model.PurchaseBill{IsAck: &isAck, AckTime: time.Now()})
+		// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, _id, &model.PurchaseBill{IsAck: &isAck, AckTime: time.Now()})
+		return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillPurchase, _id, &model.PurchaseBill{IsAck: &isAck, AckTime: time.Now()}, &repo.RecordLogReq{
+			Path:     c.Request.URL.Path,
+			UserId:   apictx.User.ID,
+			TargetId: _id,
+		})
 	case "produce":
-		return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, _id, &model.ProduceBill{IsAck: &isAck, AckTime: time.Now()})
+		// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduce, _id, &model.ProduceBill{IsAck: &isAck, AckTime: time.Now()})
+		return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduce, _id, &model.ProduceBill{IsAck: &isAck, AckTime: time.Now()}, &repo.RecordLogReq{
+			Path:     c.Request.URL.Path,
+			UserId:   apictx.User.ID,
+			TargetId: _id,
+		})
 	case "product":
-		return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, _id, &model.ProductBill{IsAck: &isAck, AckTime: time.Now()})
+		// return repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionBillProduct, _id, &model.ProductBill{IsAck: &isAck, AckTime: time.Now()})
+		return repo.RepoUpdateSetDoc1(apictx.CreateRepoCtx(), repo.CollectionBillProduct, _id, &model.ProductBill{IsAck: &isAck, AckTime: time.Now()}, &repo.RecordLogReq{
+			Path:     c.Request.URL.Path,
+			UserId:   apictx.User.ID,
+			TargetId: _id,
+		})
 	default:
 		return nil, errors.New("更新类型错误")
 	}

+ 8 - 25
boxcost/api/utils.go

@@ -5,7 +5,6 @@ import (
 	"box-cost/db/repo"
 	"box-cost/log"
 	"bytes"
-	"encoding/json"
 	"fmt"
 	"math"
 	"math/rand"
@@ -14,7 +13,7 @@ import (
 	"time"
 	"unsafe"
 
-	"github.com/sergi/go-diff/diffmatchpatch"
+	"github.com/gin-gonic/gin"
 	"github.com/thecodingmachine/gotenberg-go-client/v7"
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/bson/primitive"
@@ -22,27 +21,6 @@ import (
 
 var SignatureDir string = "https://www.3dqueen.cloud/box/v1/boxcost/public/"
 
-// 比较差异字符
-func diffUpdateData(oldObj any, newObj any) (diffStr string, err error) {
-	oldObjByte, err := json.Marshal(oldObj)
-	if err != nil {
-		return "", err
-	}
-	newObjByte, err := json.Marshal(newObj)
-	if err != nil {
-		return "", err
-	}
-
-	dmp := diffmatchpatch.New()
-	diffs := dmp.DiffMain(string(oldObjByte), string(newObjByte), false)
-	// 返回差异数据用于日志记录
-	if len(diffs) > 0 {
-		return dmp.DiffPrettyText(diffs), nil
-	}
-
-	return "", err
-}
-
 func makeBillQuery(query map[string]interface{}) map[string]interface{} {
 	if query["packId"] != nil {
 		query["packId"], _ = primitive.ObjectIDFromHex(query["packId"].(string))
@@ -137,7 +115,7 @@ func swap(arr []string, a, b int) {
 	arr[a], arr[b] = arr[b], arr[a]
 }
 
-func generateSerial(ctx *ApiSession, typeName string) (serial string, err error) {
+func generateSerial(c *gin.Context, ctx *ApiSession, typeName string) (serial string, err error) {
 	// 获取类型
 	cate := &model.Category{}
 	found, err := repo.RepoSeachDoc(ctx.CreateRepoCtx(), &repo.DocSearchOptions{
@@ -166,7 +144,12 @@ func generateSerial(ctx *ApiSession, typeName string) (serial string, err error)
 	}
 
 	index := increment.Index + 1
-	repo.RepoUpdateSetDoc(ctx.CreateRepoCtx(), repo.CollectionIncrement, increment.Id.Hex(), &model.Increment{Index: index})
+	// repo.RepoUpdateSetDoc(ctx.CreateRepoCtx(), repo.CollectionIncrement, increment.Id.Hex(), &model.Increment{Index: index})
+	repo.RepoUpdateSetDoc1(ctx.CreateRepoCtx(), repo.CollectionIncrement, increment.Id.Hex(), &model.Increment{Index: index}, &repo.RecordLogReq{
+		Path:     c.Request.URL.Path,
+		UserId:   ctx.User.ID,
+		TargetId: increment.Id.Hex(),
+	})
 
 	// 拼接为序号
 	return fmt.Sprintf("%s-%06d", cate.LetterName, index), nil

+ 2 - 2
boxcost/app.yaml

@@ -29,8 +29,8 @@ debug:
   UserRole: string
 
 nats:
-  # url: nats://124.71.139.24:14300
-  url: nats://127.0.0.1:14300
+  url: nats://124.71.139.24:14300
+  # url: nats://127.0.0.1:14300
   maxReconnect: 1000
   reconnDelaySecond: 5
 

+ 2 - 2
boxcost/build.sh

@@ -5,8 +5,8 @@ echo "building..."
 go build -o box-cost-service
 
 # 命名镜像
-local_imge="pack-box-cost:v1.0.3"
-repository_image="registry.cn-chengdu.aliyuncs.com/infish/pack-box-cost:v1.0.3"
+local_imge="pack-box-cost:v1.0.4"
+repository_image="registry.cn-chengdu.aliyuncs.com/infish/pack-box-cost:v1.0.4"
 
 # 删除本地已存在的镜像
 docker rmi $repository_image

+ 17 - 0
boxcost/db/model/logs.go

@@ -0,0 +1,17 @@
+package model
+
+import (
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type Logs struct {
+	Id         primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
+	Path       string             `bson:"path,omitempty" json:"path"`
+	UserId     string             `bson:"userId,omitempty" json:"userId"`
+	TargetId   string             `bson:"targetId,omitempty" json:"targetId"`
+	Diff       string             `bson:"diff,omitempty" json:"diff"`
+	CreateTime time.Time          `bson:"createTime,omitempty" json:"createTime"`
+	UpdateTime time.Time          `bson:"updateTime,omitempty" json:"updateTime"`
+}

+ 21 - 0
boxcost/db/model/request-logs.go

@@ -0,0 +1,21 @@
+package model
+
+import (
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type RequestLogs struct {
+	Id         primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
+	UserId     string             `bson:"userId,omitempty" json:"userId"`
+	Path       string             `bson:"path,omitempty" json:"path"`
+	Method     string             `bson:"method,omitempty" json:"method"`
+	Ip         string             `bson:"ip,omitempty" json:"ip"`
+	Code       int                `bson:"code,omitempty" json:"code"`
+	Time       float64            `bson:"time,omitempty" json:"time"`
+	Query      string             `bson:"query,omitempty" json:"query"`
+	Reqbody    string             `bson:"reqb,omitempty" json:"reqb"`
+	Resbody    string             `bson:"resb,omitempty" json:"resb"`
+	CreateTime time.Time          `bson:"createTime,omitempty" json:"createTime"`
+}

+ 77 - 0
boxcost/db/repo/repo.go

@@ -2,10 +2,14 @@ package repo
 
 import (
 	"box-cost/db"
+	dm "box-cost/db/model"
 	"box-cost/log"
 	"context"
+	"encoding/json"
 	"fmt"
+	"time"
 
+	"github.com/sergi/go-diff/diffmatchpatch"
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/bson/primitive"
 	"go.mongodb.org/mongo-driver/mongo"
@@ -35,6 +39,9 @@ const (
 	CollectionIncrement            = "increment"
 	CollectionSignature            = "signature"
 	CollectionUsers                = "users"
+	// 更改日志记录
+	CollectionLogs        = "logs"
+	CollectionRequestLogs = "request-logs"
 )
 
 type Map map[string]interface{}
@@ -128,6 +135,76 @@ func RepoUpdateSetDoc(ctx *RepoSession, collectName string, idstr string, model
 	return colls.UpdateByID(ctx.Ctx, uid, update)
 }
 
+type RecordLogReq struct {
+	Path     string
+	UserId   string
+	TargetId string
+}
+
+func RepoUpdateSetDoc1(ctx *RepoSession, collectName string, idstr string, model interface{}, recordLogReq *RecordLogReq) (*mongo.UpdateResult, error) {
+
+	colls := ctx.Client.GetCollection(collectName)
+	update := bson.M{"$set": model}
+
+	uid, _ := primitive.ObjectIDFromHex(idstr)
+
+	// ========================记录更新日志 查询更新前数据============================
+	var oldData Map
+
+	err := colls.FindOne(ctx.Ctx, bson.M{"_id": uid}).Decode(&oldData)
+	fmt.Println(err)
+
+	// ============================================================================
+	result, err1 := colls.UpdateByID(ctx.Ctx, uid, update)
+
+	// ========================记录更新日志 查询更新后数据============================
+	var newData Map
+	colls.FindOne(ctx.Ctx, bson.M{"_id": uid}).Decode(&newData)
+	// ============================================================================
+
+	// ============================记录更新前后差异================================
+	diffStr, err := diffUpdateData(oldData, newData)
+	if err != nil {
+		fmt.Println(err)
+	}
+	fmt.Println(diffStr)
+
+	// 记录到数据库中
+	changeLogs := &dm.Logs{
+		Path:       recordLogReq.Path,
+		UserId:     recordLogReq.UserId,
+		TargetId:   recordLogReq.TargetId,
+		Diff:       diffStr,
+		CreateTime: time.Now(),
+		UpdateTime: time.Now(),
+	}
+	RepoAddDoc(ctx, CollectionLogs, changeLogs)
+	// ==========================================================================
+	return result, err1
+
+}
+
+// 比较差异字符
+func diffUpdateData(oldObj any, newObj any) (diffStr string, err error) {
+	oldObjByte, err := json.Marshal(oldObj)
+	if err != nil {
+		return "", err
+	}
+	newObjByte, err := json.Marshal(newObj)
+	if err != nil {
+		return "", err
+	}
+
+	dmp := diffmatchpatch.New()
+	diffs := dmp.DiffMain(string(oldObjByte), string(newObjByte), false)
+	// 返回差异数据用于日志记录
+	if len(diffs) > 0 {
+		return dmp.DiffPrettyText(diffs), nil
+	}
+
+	return "", err
+}
+
 func RepoUpdateSeDbDoc(ctx *RepoSession, db string, collectName string, idstr string, model interface{}) (*mongo.UpdateResult, error) {
 
 	colls := ctx.Client.GetDbCollection(db, collectName)

+ 0 - 2
boxcost/go.mod

@@ -35,7 +35,6 @@ require (
 	github.com/jessevdk/go-flags v1.5.0 // indirect
 	github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
 	github.com/tjfoc/gmsm v1.3.2 // indirect
-	github.com/yusufpapurcu/wmi v1.2.2 // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 )
@@ -80,7 +79,6 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
-	github.com/shirou/gopsutil v3.21.11+incompatible
 	github.com/spf13/afero v1.6.0 // indirect
 	github.com/spf13/cast v1.4.1 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect

+ 0 - 4
boxcost/go.sum

@@ -1122,8 +1122,6 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW
 github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
 github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
 github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
-github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
-github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@@ -1257,8 +1255,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
-github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
 github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=

+ 94 - 0
boxcost/middleware/logger.go

@@ -0,0 +1,94 @@
+package middleware
+
+import (
+	"box-cost/db"
+	"box-cost/db/model"
+	"box-cost/db/repo"
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+// 记录日志
+func Logger() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		if strings.Contains(c.Request.URL.Path, "/update") {
+			c.Next()
+			return
+		}
+
+		// 处理请求
+		var requestBody bytes.Buffer
+		requestBodyBytes, err := io.ReadAll(c.Request.Body)
+		if err != nil {
+			log.Printf("Error reading body: %v", err)
+			return
+		}
+		requestBody.Write(requestBodyBytes)
+		c.Request.Body = io.NopCloser(&requestBody)
+		reqb := requestBody.String()
+
+		// 记录响应 body
+		// var responseBody bytes.Buffer
+		// writer := io.MultiWriter(c.Writer, &responseBody)
+		// c.Writer = &bodyLogWriter{body: &responseBody, ResponseWriter: c.Writer, writer: writer}
+
+		// 其他中间件执行,洋葱模型
+		// 开始时间
+		start := time.Now()
+		c.Next()
+		// 结束时间
+		// resb := responseBody.String()
+		session := &repo.RepoSession{
+			Ctx:    context.Background(),
+			Client: db.MongoClient,
+		}
+		id, err := CreateLog(start, reqb, "", session, c)
+
+		if err != nil {
+			fmt.Println(err)
+		}
+		fmt.Println("request_log_id: ", id)
+
+	}
+}
+
+func CreateLog(start time.Time, reqb string, resb string, session *repo.RepoSession, c *gin.Context) (string, error) {
+	_time := time.Since(start).Seconds()
+	path := c.Request.URL.Path
+	method := c.Request.Method
+	ip := c.ClientIP()
+	code := c.Writer.Status()
+	query := c.Request.URL.RawQuery
+	id, _ := c.Get("userId")
+	requesLogs := &model.RequestLogs{
+		UserId:     id.(string),
+		Path:       path,
+		Method:     method,
+		Ip:         ip,
+		Code:       code,
+		Time:       _time * 1000, // ms
+		Query:      query,
+		Reqbody:    reqb,
+		Resbody:    resb,
+		CreateTime: time.Now(),
+	}
+	return repo.RepoAddDoc(session, repo.CollectionRequestLogs, requesLogs)
+
+}
+
+// type bodyLogWriter struct {
+// 	gin.ResponseWriter
+// 	body   *bytes.Buffer
+// 	writer io.Writer
+// }
+
+// func (w bodyLogWriter) Write(b []byte) (int, error) {
+// 	return w.writer.Write(b)
+// }

+ 5 - 0
boxcost/readme.md

@@ -0,0 +1,5 @@
+## 更新记录
+
+- v1.0.4
+  - 添加记录日志功能,记录更新变更
+  - 添加计划追踪表下载

+ 184 - 184
boxcost/utils/reg-code.go

@@ -1,186 +1,186 @@
 package utils
 
-import (
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/md5"
-	"crypto/rand"
-	"crypto/sha1"
-	"encoding/base64"
-	"errors"
-	"fmt"
-	"io"
-	"strconv"
-	"strings"
-
-	"github.com/shirou/gopsutil/disk"
-	"github.com/shirou/gopsutil/net"
-)
-
-// 字符串生成md5
-func CreatMD5(s string) string {
-	data := []byte(s)
-	has := md5.Sum(data)
-	return fmt.Sprintf("%x", has)
-}
-
-func GetDiskId() string {
-	partitions, err := disk.Partitions(false)
-	if err != nil {
-		return ""
-	}
-	return disk.GetDiskSerialNumber(partitions[0].Device)
-
-}
-
-// 获取物理网卡mac地址
-func GetRealMacAddr() string {
-	netInterfaces, err := net.Interfaces()
-	if err != nil {
-		return ""
-	}
-	loindex := 0
-	for _, netInterface := range netInterfaces {
-		if netInterface.Name == "lo" {
-			loindex = netInterface.Index
-			break
-		}
-	}
-	if len(netInterfaces) < loindex+1 {
-		return ""
-	}
-	fmt.Println(netInterfaces[loindex].HardwareAddr)
-	return netInterfaces[loindex].HardwareAddr
-}
-
-func GetDeviceId() string {
-	// deviceId := fmt.Sprintf("%s:%s", GetRealMacAddr(), GetDiskId())
-	deviceId := GetRealMacAddr()
-	fmt.Println(deviceId)
-	return CreatMD5(deviceId)
-}
-
-// sha1()方法,返回的是byte转换的字符串,如果需要转成16进制,可以使用hex.EncodeToString
-func GetSha1(str string) string {
-	h := sha1.New()
-	io.WriteString(h, str)
-	return string(h.Sum(nil))
-}
-
-// 右边补全字符串实现方法,主要实现php的str_pad()方法
-func StrPadRight(input string, padLength int, padString string) string {
-	output := ""
-	inputLen := len(input)
-	if inputLen >= padLength {
-		return input
-	}
-	ll := padLength - inputLen
-	for i := 1; i <= ll; i = i + len(padString) {
-		output += padString
-	}
-	return input + output
-}
-
-// 生成密钥
-func GenSecretKey(appId string, appSecret string) (secretKey string) {
-	key := appId + "&" + appSecret
-	sha1_str := GetSha1(key)
-	str10 := "0"
-	pack64, _ := strconv.ParseUint(str10, 10, 32)
-	fmt.Println(pack64)
-	pack32 := uint32(pack64)
-	str_pad := StrPadRight(sha1_str, 32, string(rune(pack32)))
-	secretKey = base64.StdEncoding.EncodeToString([]byte(str_pad))
-	return secretKey
-}
-
-// 解密
-func GetCallbackData(secretKey string, encryptData string) (result_str string, err error) {
-	decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
-	decodeSecretKey := string(decodeSecretKeyByte)
-	orderSuccessPayInfoByte, err2 := DecodeAesGcm(encryptData, decodeSecretKey, "")
-	result_str = string(orderSuccessPayInfoByte)
-	if err2 != nil {
-		return result_str, errors.New("decode error")
-	}
-	return result_str, err
-}
-
-// 加密
-func SetCallbackData(secretKey string, data string) (result_str string, err error) {
-	decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
-	decodeSecretKey := string(decodeSecretKeyByte)
-	orderSuccessPayInfoByte, err2 := EncodeAesGcm(data, decodeSecretKey, "")
-	result_str = string(orderSuccessPayInfoByte)
-	if err2 != nil {
-		return result_str, errors.New("encode error")
-	}
-	return result_str, err
-}
-
-// url安全模式decode字符串
-func UrlSafeB64decode(str string) (result []byte) {
-	str = strings.Replace(str, "-", "+", -1)
-	str = strings.Replace(str, "_", "/", -1)
-	mod4 := len(str) % 4
-	if mod4 != 0 {
-		str = str + "===="[0:mod4]
-	}
-	result, _ = base64.StdEncoding.DecodeString(str)
-	return result
-}
-
-// base64字符串,替换转换为安全模式
-func UrlSafeB64encode(str string) (result string) {
-	str = strings.Replace(str, "+", "-", -1)
-	str = strings.Replace(str, "/", "_", -1)
-	return str
-}
-
-// 使用aes-256-gcm方式解密字符串
-func DecodeAesGcm(encryptData string, hex_key string, hex_add string) ([]byte, error) {
-	tagSize := 16 //nonceSize,tag的长度,用于open时候生成tag,默认12
-	key := []byte(hex_key)
-	add := []byte(hex_add)
-	block, err := aes.NewCipher(key) //生成加解密用的block
-	if err != nil {
-		return []byte(""), err
-	}
-	//根据不同加密算法,也有不同tag长度的方法设定和调用,比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
-	aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
-	if err != nil {
-		return []byte(""), err
-	}
-	decodeEncryptStr := UrlSafeB64decode(encryptData)
-	ciphertext := decodeEncryptStr
-	if len(ciphertext) <= aesgcm.NonceSize() { // 长度应该>iv
-		return []byte(""), errors.New("string: too short") //解密失败
-	}
-	iv := ciphertext[:aesgcm.NonceSize()]        //分离出IV
-	ciphertext = ciphertext[aesgcm.NonceSize():] // 密文,tag是调用open方法时候通过密文和前面new时候传的size来进行截取的
-	plaintext, err := aesgcm.Open(nil, iv, ciphertext, add)
-	return plaintext, err
-}
-
-// 使用aes-256-gcm加密数据
-func EncodeAesGcm(data string, hex_key string, hex_add string) (result string, error error) {
-	tagSize := 16 //nonceSize,tag的长度,用于open时候生成tag,默认12
-	key := []byte(hex_key)
-	add := []byte(hex_add)
-	block, err := aes.NewCipher(key) //生成加解密用的block
-	if err != nil {
-		return result, err
-	}
-	//根据不同加密算法,也有不同tag长度的方法设定和调用,比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
-	aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
-	if err != nil {
-		return result, err
-	}
-	plaintext := []byte(data)
-	iv := make([]byte, tagSize)                       // NonceSize=12
-	rand.Read(iv)                                     //获取随机值
-	ciphertext := aesgcm.Seal(iv, iv, plaintext, add) //加密,密文为:iv+密文+tag
-	result = base64.StdEncoding.EncodeToString(ciphertext)
-	result = UrlSafeB64encode(result)
-	return result, nil // 生成的BS64
-}
+// import (
+// 	"crypto/aes"
+// 	"crypto/cipher"
+// 	"crypto/md5"
+// 	"crypto/rand"
+// 	"crypto/sha1"
+// 	"encoding/base64"
+// 	"errors"
+// 	"fmt"
+// 	"io"
+// 	"strconv"
+// 	"strings"
+
+// 	"github.com/shirou/gopsutil/disk"
+// 	"github.com/shirou/gopsutil/net"
+// )
+
+// // 字符串生成md5
+// func CreatMD5(s string) string {
+// 	data := []byte(s)
+// 	has := md5.Sum(data)
+// 	return fmt.Sprintf("%x", has)
+// }
+
+// func GetDiskId() string {
+// 	partitions, err := disk.Partitions(false)
+// 	if err != nil {
+// 		return ""
+// 	}
+// 	return disk.GetDiskSerialNumber(partitions[0].Device)
+
+// }
+
+// // 获取物理网卡mac地址
+// func GetRealMacAddr() string {
+// 	netInterfaces, err := net.Interfaces()
+// 	if err != nil {
+// 		return ""
+// 	}
+// 	loindex := 0
+// 	for _, netInterface := range netInterfaces {
+// 		if netInterface.Name == "lo" {
+// 			loindex = netInterface.Index
+// 			break
+// 		}
+// 	}
+// 	if len(netInterfaces) < loindex+1 {
+// 		return ""
+// 	}
+// 	fmt.Println(netInterfaces[loindex].HardwareAddr)
+// 	return netInterfaces[loindex].HardwareAddr
+// }
+
+// func GetDeviceId() string {
+// 	// deviceId := fmt.Sprintf("%s:%s", GetRealMacAddr(), GetDiskId())
+// 	deviceId := GetRealMacAddr()
+// 	fmt.Println(deviceId)
+// 	return CreatMD5(deviceId)
+// }
+
+// // sha1()方法,返回的是byte转换的字符串,如果需要转成16进制,可以使用hex.EncodeToString
+// func GetSha1(str string) string {
+// 	h := sha1.New()
+// 	io.WriteString(h, str)
+// 	return string(h.Sum(nil))
+// }
+
+// // 右边补全字符串实现方法,主要实现php的str_pad()方法
+// func StrPadRight(input string, padLength int, padString string) string {
+// 	output := ""
+// 	inputLen := len(input)
+// 	if inputLen >= padLength {
+// 		return input
+// 	}
+// 	ll := padLength - inputLen
+// 	for i := 1; i <= ll; i = i + len(padString) {
+// 		output += padString
+// 	}
+// 	return input + output
+// }
+
+// // 生成密钥
+// func GenSecretKey(appId string, appSecret string) (secretKey string) {
+// 	key := appId + "&" + appSecret
+// 	sha1_str := GetSha1(key)
+// 	str10 := "0"
+// 	pack64, _ := strconv.ParseUint(str10, 10, 32)
+// 	fmt.Println(pack64)
+// 	pack32 := uint32(pack64)
+// 	str_pad := StrPadRight(sha1_str, 32, string(rune(pack32)))
+// 	secretKey = base64.StdEncoding.EncodeToString([]byte(str_pad))
+// 	return secretKey
+// }
+
+// // 解密
+// func GetCallbackData(secretKey string, encryptData string) (result_str string, err error) {
+// 	decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
+// 	decodeSecretKey := string(decodeSecretKeyByte)
+// 	orderSuccessPayInfoByte, err2 := DecodeAesGcm(encryptData, decodeSecretKey, "")
+// 	result_str = string(orderSuccessPayInfoByte)
+// 	if err2 != nil {
+// 		return result_str, errors.New("decode error")
+// 	}
+// 	return result_str, err
+// }
+
+// // 加密
+// func SetCallbackData(secretKey string, data string) (result_str string, err error) {
+// 	decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
+// 	decodeSecretKey := string(decodeSecretKeyByte)
+// 	orderSuccessPayInfoByte, err2 := EncodeAesGcm(data, decodeSecretKey, "")
+// 	result_str = string(orderSuccessPayInfoByte)
+// 	if err2 != nil {
+// 		return result_str, errors.New("encode error")
+// 	}
+// 	return result_str, err
+// }
+
+// // url安全模式decode字符串
+// func UrlSafeB64decode(str string) (result []byte) {
+// 	str = strings.Replace(str, "-", "+", -1)
+// 	str = strings.Replace(str, "_", "/", -1)
+// 	mod4 := len(str) % 4
+// 	if mod4 != 0 {
+// 		str = str + "===="[0:mod4]
+// 	}
+// 	result, _ = base64.StdEncoding.DecodeString(str)
+// 	return result
+// }
+
+// // base64字符串,替换转换为安全模式
+// func UrlSafeB64encode(str string) (result string) {
+// 	str = strings.Replace(str, "+", "-", -1)
+// 	str = strings.Replace(str, "/", "_", -1)
+// 	return str
+// }
+
+// // 使用aes-256-gcm方式解密字符串
+// func DecodeAesGcm(encryptData string, hex_key string, hex_add string) ([]byte, error) {
+// 	tagSize := 16 //nonceSize,tag的长度,用于open时候生成tag,默认12
+// 	key := []byte(hex_key)
+// 	add := []byte(hex_add)
+// 	block, err := aes.NewCipher(key) //生成加解密用的block
+// 	if err != nil {
+// 		return []byte(""), err
+// 	}
+// 	//根据不同加密算法,也有不同tag长度的方法设定和调用,比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
+// 	aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
+// 	if err != nil {
+// 		return []byte(""), err
+// 	}
+// 	decodeEncryptStr := UrlSafeB64decode(encryptData)
+// 	ciphertext := decodeEncryptStr
+// 	if len(ciphertext) <= aesgcm.NonceSize() { // 长度应该>iv
+// 		return []byte(""), errors.New("string: too short") //解密失败
+// 	}
+// 	iv := ciphertext[:aesgcm.NonceSize()]        //分离出IV
+// 	ciphertext = ciphertext[aesgcm.NonceSize():] // 密文,tag是调用open方法时候通过密文和前面new时候传的size来进行截取的
+// 	plaintext, err := aesgcm.Open(nil, iv, ciphertext, add)
+// 	return plaintext, err
+// }
+
+// // 使用aes-256-gcm加密数据
+// func EncodeAesGcm(data string, hex_key string, hex_add string) (result string, error error) {
+// 	tagSize := 16 //nonceSize,tag的长度,用于open时候生成tag,默认12
+// 	key := []byte(hex_key)
+// 	add := []byte(hex_add)
+// 	block, err := aes.NewCipher(key) //生成加解密用的block
+// 	if err != nil {
+// 		return result, err
+// 	}
+// 	//根据不同加密算法,也有不同tag长度的方法设定和调用,比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
+// 	aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
+// 	if err != nil {
+// 		return result, err
+// 	}
+// 	plaintext := []byte(data)
+// 	iv := make([]byte, tagSize)                       // NonceSize=12
+// 	rand.Read(iv)                                     //获取随机值
+// 	ciphertext := aesgcm.Seal(iv, iv, plaintext, add) //加密,密文为:iv+密文+tag
+// 	result = base64.StdEncoding.EncodeToString(ciphertext)
+// 	result = UrlSafeB64encode(result)
+// 	return result, nil // 生成的BS64
+// }