From 12e09639dcbfa63b5a23cde8642b72829d4f3a3b Mon Sep 17 00:00:00 2001
From: zhangqian <zhangqian@123.com>
Date: 星期四, 10 八月 2023 11:42:31 +0800
Subject: [PATCH] Merge branch 'zq'

---
 service/products.go              |   50 ++
 pkg/ecode/code.go                |   15 
 docs/swagger.yaml                |   40 +
 docs/docs.go                     |   59 +-
 pkg/contextx/contextx.go         |  203 ++++----
 service/invoice.go               |   88 +++-
 docs/swagger.json                |   59 +-
 model/request/invoice.go         |   29 
 model/product.go                 |   19 
 model/serviceContract.go         |   84 ++-
 model/invoiceProduct.go          |  131 ++++++
 api/v1/serviceContract.go        |  322 ++++++--------
 model/request/serviceContract.go |   90 ++--
 model/invoice.go                 |    1 
 service/serviceContract.go       |   28 +
 pkg/ecode/msg.go                 |    3 
 16 files changed, 748 insertions(+), 473 deletions(-)

diff --git a/api/v1/serviceContract.go b/api/v1/serviceContract.go
index 38b78f2..d354aa2 100644
--- a/api/v1/serviceContract.go
+++ b/api/v1/serviceContract.go
@@ -1,176 +1,146 @@
-package v1
-
-import (
-	"aps_crm/model"
-	"aps_crm/model/request"
-	"aps_crm/model/response"
-	"aps_crm/pkg/contextx"
-	"aps_crm/pkg/ecode"
-	"github.com/gin-gonic/gin"
-)
-
-type ServiceContractApi struct{}
-
-// Add
-//
-//	@Tags		ServiceContract
-//	@Summary	娣诲姞鏈嶅姟鍚堝悓
-//	@Produce	application/json
-//	@Param		object	body		request.AddServiceContract	true	"鏌ヨ鍙傛暟"
-//	@Success	200		{object}	contextx.Response{}
-//	@Router		/api/serviceContract/add [post]
-func (s *ServiceContractApi) Add(c *gin.Context) {
-	var params request.AddServiceContract
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	errCode, serviceContract := checkServiceContractParams(params.ServiceContract)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	errCode = serviceContractService.AddServiceContract(&serviceContract)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.Ok()
-}
-
-// Delete
-//
-//	@Tags		ServiceContract
-//	@Summary	鍒犻櫎鏈嶅姟鍚堝悓
-//	@Produce	application/json
-//	@Param		object	body		request.DeleteserviceContract true	"鏌ヨ鍙傛暟"
-//	@Success	200	{object}	contextx.Response{}
-//	@Router		/api/serviceContract/delete [delete]
-func (s *ServiceContractApi) Delete(c *gin.Context) {
-	var params request.DeleteserviceContract
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	errCode := serviceContractService.DeleteServiceContract(params.Ids)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.Ok()
-}
-
-// Update
-//
-//	@Tags		ServiceContract
-//	@Summary	鏇存柊鏈嶅姟鍚堝悓
-//	@Produce	application/json
-//	@Param		object	body		request.UpdateServiceContract	true	"鏌ヨ鍙傛暟"
-//	@Success	200		{object}	contextx.Response{}
-//	@Router		/api/serviceContract/update [put]
-func (s *ServiceContractApi) Update(c *gin.Context) {
-	var params request.UpdateServiceContract
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	errCode, serviceContract := checkServiceContractParams(params.ServiceContract)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	serviceContract.Id = params.Id
-
-	errCode = serviceContractService.UpdateServiceContract(&serviceContract)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.Ok()
-}
-
-// check params
-func checkServiceContractParams(serviceContract request.ServiceContract) (errCode int, result model.ServiceContract) {
-	//if serviceContract.SignTime == "" {
-	//	return ecode.InvalidParams, result
-	//}
-	//
-	//if serviceContract.Number == "" {
-	//	return ecode.InvalidParams, result
-	//}
-	//
-	//if serviceContract.MemberId <= 0 {
-	//	return ecode.InvalidParams, result
-	//}
-
-	t, err := checkTimeFormat(serviceContract.SignTime)
-	if err != nil {
-		return ecode.InvalidParams, result
-	}
-
-	result.SignTime = t
-
-	t, err = checkTimeFormat(serviceContract.StartTime)
-	if err != nil {
-		return ecode.InvalidParams, result
-	}
-
-	result.StartTime = t
-
-	t, err = checkTimeFormat(serviceContract.EndTime)
-	if err != nil {
-		return ecode.InvalidParams, result
-	}
-
-	result.EndTime = t
-
-	result.Number = serviceContract.Number
-	result.MemberId = serviceContract.MemberId
-	result.Remark = serviceContract.Remark
-	result.ClientId = serviceContract.ClientId
-	result.ContactId = serviceContract.ContactId
-	result.SaleChanceId = serviceContract.SaleChanceId
-	result.QuotationId = serviceContract.QuotationId
-	result.ServiceContractTypeId = serviceContract.TypeId
-	result.ServiceContractStatusId = serviceContract.StatusId
-	result.ServiceTimes = serviceContract.ServiceTimes
-	result.Terms = serviceContract.Terms
-	result.Products = serviceContract.Products
-
-	return ecode.OK, result
-}
-
-// List
-//
-// @Tags		ServiceContract
-// @Summary	鐢熸垚璁″垝鍒楄〃
-// @Produce	application/json
-// @Param		object	body		request.GetServiceContractList	true	"鍙傛暟"
-// @Success	200		{object}	contextx.Response{data=response.ServiceContractsResponse}
-// @Router		/api/serviceContract/list [post]
-func (con *ServiceContractApi) List(c *gin.Context) {
-	var params request.GetServiceContractList
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	serviceContracts, total, errCode := serviceContractService.GetServiceContractList(params.Page, params.PageSize, params.QueryClass, params.KeywordType, params.Keyword)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.OkWithDetailed(response.ServiceContractsResponse{
-		List:  serviceContracts,
-		Count: int(total),
-	})
-}
+package v1
+
+import (
+	"aps_crm/model"
+	"aps_crm/model/request"
+	"aps_crm/model/response"
+	"aps_crm/pkg/contextx"
+	"aps_crm/pkg/ecode"
+	"github.com/gin-gonic/gin"
+)
+
+type ServiceContractApi struct{}
+
+// Add
+//
+//	@Tags		ServiceContract
+//	@Summary	娣诲姞鏈嶅姟鍚堝悓
+//	@Produce	application/json
+//	@Param		object	body		request.AddServiceContract	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/serviceContract/add [post]
+func (s *ServiceContractApi) Add(c *gin.Context) {
+	var params request.AddServiceContract
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	errCode, serviceContract := checkServiceContractParams(params.ServiceContract)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	errCode = serviceContractService.AddServiceContract(&serviceContract)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// Delete
+//
+//	@Tags		ServiceContract
+//	@Summary	鍒犻櫎鏈嶅姟鍚堝悓
+//	@Produce	application/json
+//	@Param		object	body		request.DeleteServiceContract true	"鏌ヨ鍙傛暟"
+//	@Success	200	{object}	contextx.Response{}
+//	@Router		/api/serviceContract/delete [delete]
+func (s *ServiceContractApi) Delete(c *gin.Context) {
+	var params request.DeleteServiceContract
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	errCode := serviceContractService.DeleteServiceContract(params.Ids)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// Update
+//
+//	@Tags		ServiceContract
+//	@Summary	鏇存柊鏈嶅姟鍚堝悓
+//	@Produce	application/json
+//	@Param		object	body		request.UpdateServiceContract	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/serviceContract/update [put]
+func (s *ServiceContractApi) Update(c *gin.Context) {
+	var params request.UpdateServiceContract
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	errCode, serviceContract := checkServiceContractParams(params.ServiceContract)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	serviceContract.Id = params.Id
+
+	errCode = serviceContractService.UpdateServiceContract(&serviceContract)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// check params
+func checkServiceContractParams(serviceContract request.ServiceContract) (errCode int, result model.ServiceContract) {
+	result.Number = serviceContract.Number
+	result.MemberId = serviceContract.MemberId
+	result.Remark = serviceContract.Remark
+	result.ClientId = serviceContract.ClientId
+	result.SalesDetailsId = serviceContract.SalesDetailsId
+	result.SaleChanceId = serviceContract.SaleChanceId
+	result.QuotationId = serviceContract.QuotationId
+	result.ServiceContractTypeId = serviceContract.TypeId
+	result.ServiceContractStatusId = serviceContract.StatusId
+	result.ServiceTimes = serviceContract.ServiceTimes
+	result.Terms = serviceContract.Terms
+	result.Products = serviceContract.Products
+	result.SignTime = serviceContract.SignTime
+	result.StartTime = serviceContract.StartTime
+	result.EndTime = serviceContract.EndTime
+
+	return ecode.OK, result
+}
+
+// List
+//
+// @Tags   ServiceContract
+// @Summary	鏈嶅姟鍚堝悓鍒楄〃
+// @Produce	application/json
+// @Param		object	body		request.GetServiceContractList	true	"鍙傛暟"
+// @Success	200		{object}	contextx.Response{data=response.ServiceContractsResponse}
+// @Router		/api/serviceContract/list [post]
+func (con *ServiceContractApi) List(c *gin.Context) {
+	var params request.GetServiceContractList
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	serviceContracts, total, errCode := serviceContractService.GetServiceContractList(params.Page, params.PageSize, params.QueryClass, params.KeywordType, params.Keyword)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.OkWithDetailed(response.ServiceContractsResponse{
+		List:  serviceContracts,
+		Count: int(total),
+	})
+}
diff --git a/docs/docs.go b/docs/docs.go
index 9a17b79..e39c3f4 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -7620,7 +7620,7 @@
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/request.DeleteserviceContract"
+                            "$ref": "#/definitions/request.DeleteServiceContract"
                         }
                     }
                 ],
@@ -7642,7 +7642,7 @@
                 "tags": [
                     "ServiceContract"
                 ],
-                "summary": "鐢熸垚璁″垝鍒楄〃",
+                "summary": "鏈嶅姟鍚堝悓鍒楄〃",
                 "parameters": [
                     {
                         "description": "鍙傛暟",
@@ -11366,6 +11366,9 @@
         "model.ServiceContract": {
             "type": "object",
             "properties": {
+                "SaleChance": {
+                    "$ref": "#/definitions/model.SaleChance"
+                },
                 "amountInvoiced": {
                     "description": "宸插紑绁ㄩ噾棰�",
                     "type": "number"
@@ -11382,9 +11385,6 @@
                     "type": "integer"
                 },
                 "contactId": {
-                    "type": "integer"
-                },
-                "contractId": {
                     "type": "integer"
                 },
                 "endTime": {
@@ -11405,6 +11405,9 @@
                         "$ref": "#/definitions/model.Product"
                     }
                 },
+                "quotation": {
+                    "$ref": "#/definitions/model.Quotation"
+                },
                 "quotationId": {
                     "type": "integer"
                 },
@@ -11414,8 +11417,20 @@
                 "saleChanceId": {
                     "type": "integer"
                 },
+                "salesDetails": {
+                    "$ref": "#/definitions/model.SalesDetails"
+                },
+                "salesDetailsId": {
+                    "type": "integer"
+                },
+                "serviceContractStatus": {
+                    "$ref": "#/definitions/model.ServiceContractStatus"
+                },
                 "serviceContractStatusId": {
                     "type": "integer"
+                },
+                "serviceContractType": {
+                    "$ref": "#/definitions/model.ServiceContractType"
                 },
                 "serviceContractTypeId": {
                     "type": "integer"
@@ -12889,9 +12904,6 @@
                 "contactId": {
                     "type": "integer"
                 },
-                "contractId": {
-                    "type": "integer"
-                },
                 "endTime": {
                     "type": "string"
                 },
@@ -12914,6 +12926,9 @@
                     "type": "string"
                 },
                 "saleChanceId": {
+                    "type": "integer"
+                },
+                "salesDetailsId": {
                     "type": "integer"
                 },
                 "serviceTimes": {
@@ -13536,6 +13551,17 @@
                 }
             }
         },
+        "request.DeleteServiceContract": {
+            "type": "object",
+            "properties": {
+                "ids": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                }
+            }
+        },
         "request.DeleteServiceFeeManage": {
             "type": "object",
             "properties": {
@@ -13564,17 +13590,6 @@
                 "userId": {
                     "description": "鐢ㄦ埛ID",
                     "type": "string"
-                }
-            }
-        },
-        "request.DeleteserviceContract": {
-            "type": "object",
-            "properties": {
-                "ids": {
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
                 }
             }
         },
@@ -15888,9 +15903,6 @@
                 "contactId": {
                     "type": "integer"
                 },
-                "contractId": {
-                    "type": "integer"
-                },
                 "endTime": {
                     "type": "string"
                 },
@@ -15918,6 +15930,9 @@
                 "saleChanceId": {
                     "type": "integer"
                 },
+                "salesDetailsId": {
+                    "type": "integer"
+                },
                 "serviceTimes": {
                     "type": "integer"
                 },
diff --git a/docs/swagger.json b/docs/swagger.json
index d95a065..97e2fca 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -7608,7 +7608,7 @@
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/request.DeleteserviceContract"
+                            "$ref": "#/definitions/request.DeleteServiceContract"
                         }
                     }
                 ],
@@ -7630,7 +7630,7 @@
                 "tags": [
                     "ServiceContract"
                 ],
-                "summary": "鐢熸垚璁″垝鍒楄〃",
+                "summary": "鏈嶅姟鍚堝悓鍒楄〃",
                 "parameters": [
                     {
                         "description": "鍙傛暟",
@@ -11354,6 +11354,9 @@
         "model.ServiceContract": {
             "type": "object",
             "properties": {
+                "SaleChance": {
+                    "$ref": "#/definitions/model.SaleChance"
+                },
                 "amountInvoiced": {
                     "description": "宸插紑绁ㄩ噾棰�",
                     "type": "number"
@@ -11370,9 +11373,6 @@
                     "type": "integer"
                 },
                 "contactId": {
-                    "type": "integer"
-                },
-                "contractId": {
                     "type": "integer"
                 },
                 "endTime": {
@@ -11393,6 +11393,9 @@
                         "$ref": "#/definitions/model.Product"
                     }
                 },
+                "quotation": {
+                    "$ref": "#/definitions/model.Quotation"
+                },
                 "quotationId": {
                     "type": "integer"
                 },
@@ -11402,8 +11405,20 @@
                 "saleChanceId": {
                     "type": "integer"
                 },
+                "salesDetails": {
+                    "$ref": "#/definitions/model.SalesDetails"
+                },
+                "salesDetailsId": {
+                    "type": "integer"
+                },
+                "serviceContractStatus": {
+                    "$ref": "#/definitions/model.ServiceContractStatus"
+                },
                 "serviceContractStatusId": {
                     "type": "integer"
+                },
+                "serviceContractType": {
+                    "$ref": "#/definitions/model.ServiceContractType"
                 },
                 "serviceContractTypeId": {
                     "type": "integer"
@@ -12877,9 +12892,6 @@
                 "contactId": {
                     "type": "integer"
                 },
-                "contractId": {
-                    "type": "integer"
-                },
                 "endTime": {
                     "type": "string"
                 },
@@ -12902,6 +12914,9 @@
                     "type": "string"
                 },
                 "saleChanceId": {
+                    "type": "integer"
+                },
+                "salesDetailsId": {
                     "type": "integer"
                 },
                 "serviceTimes": {
@@ -13524,6 +13539,17 @@
                 }
             }
         },
+        "request.DeleteServiceContract": {
+            "type": "object",
+            "properties": {
+                "ids": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                }
+            }
+        },
         "request.DeleteServiceFeeManage": {
             "type": "object",
             "properties": {
@@ -13552,17 +13578,6 @@
                 "userId": {
                     "description": "鐢ㄦ埛ID",
                     "type": "string"
-                }
-            }
-        },
-        "request.DeleteserviceContract": {
-            "type": "object",
-            "properties": {
-                "ids": {
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
                 }
             }
         },
@@ -15876,9 +15891,6 @@
                 "contactId": {
                     "type": "integer"
                 },
-                "contractId": {
-                    "type": "integer"
-                },
                 "endTime": {
                     "type": "string"
                 },
@@ -15906,6 +15918,9 @@
                 "saleChanceId": {
                     "type": "integer"
                 },
+                "salesDetailsId": {
+                    "type": "integer"
+                },
                 "serviceTimes": {
                     "type": "integer"
                 },
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 632c09c..058fde4 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1270,6 +1270,8 @@
     type: object
   model.ServiceContract:
     properties:
+      SaleChance:
+        $ref: '#/definitions/model.SaleChance'
       amountInvoiced:
         description: 宸插紑绁ㄩ噾棰�
         type: number
@@ -1283,8 +1285,6 @@
         type: integer
       contactId:
         type: integer
-      contractId:
-        type: integer
       endTime:
         type: string
       id:
@@ -1297,14 +1297,24 @@
         items:
           $ref: '#/definitions/model.Product'
         type: array
+      quotation:
+        $ref: '#/definitions/model.Quotation'
       quotationId:
         type: integer
       remark:
         type: string
       saleChanceId:
         type: integer
+      salesDetails:
+        $ref: '#/definitions/model.SalesDetails'
+      salesDetailsId:
+        type: integer
+      serviceContractStatus:
+        $ref: '#/definitions/model.ServiceContractStatus'
       serviceContractStatusId:
         type: integer
+      serviceContractType:
+        $ref: '#/definitions/model.ServiceContractType'
       serviceContractTypeId:
         type: integer
       serviceTimes:
@@ -2296,8 +2306,6 @@
         type: integer
       contactId:
         type: integer
-      contractId:
-        type: integer
       endTime:
         type: string
       memberId:
@@ -2313,6 +2321,8 @@
       remark:
         type: string
       saleChanceId:
+        type: integer
+      salesDetailsId:
         type: integer
       serviceTimes:
         type: integer
@@ -2746,6 +2756,13 @@
           type: integer
         type: array
     type: object
+  request.DeleteServiceContract:
+    properties:
+      ids:
+        items:
+          type: integer
+        type: array
+    type: object
   request.DeleteServiceFeeManage:
     properties:
       ids:
@@ -2765,13 +2782,6 @@
       userId:
         description: 鐢ㄦ埛ID
         type: string
-    type: object
-  request.DeleteserviceContract:
-    properties:
-      ids:
-        items:
-          type: integer
-        type: array
     type: object
   request.DownloadFile:
     properties:
@@ -4341,8 +4351,6 @@
         type: integer
       contactId:
         type: integer
-      contractId:
-        type: integer
       endTime:
         type: string
       id:
@@ -4360,6 +4368,8 @@
       remark:
         type: string
       saleChanceId:
+        type: integer
+      salesDetailsId:
         type: integer
       serviceTimes:
         type: integer
@@ -9980,7 +9990,7 @@
         name: object
         required: true
         schema:
-          $ref: '#/definitions/request.DeleteserviceContract'
+          $ref: '#/definitions/request.DeleteServiceContract'
       produces:
       - application/json
       responses:
@@ -10012,7 +10022,7 @@
                 data:
                   $ref: '#/definitions/response.ServiceContractsResponse'
               type: object
-      summary: 鐢熸垚璁″垝鍒楄〃
+      summary: 鏈嶅姟鍚堝悓鍒楄〃
       tags:
       - ServiceContract
   /api/serviceContract/update:
diff --git a/model/invoice.go b/model/invoice.go
index ef97ed8..9145a6a 100644
--- a/model/invoice.go
+++ b/model/invoice.go
@@ -28,6 +28,7 @@
 		CourierNumber    string                     `gorm:"courier_number" json:"courierNumber"`        // 鐗╂祦鍗曞彿
 		CourierCompanyId int                        `gorm:"courier_company_id" json:"courierCompanyId"` // 鐗╂祦鍏徃
 		CourierCompany   CourierCompany             `gorm:"foreignKey:CourierCompanyId"`
+		Products         []*Product                 `json:"products" gorm:"many2many:invoice_product;"`
 	}
 
 	// InvoiceSearch 閿�鍞彂绁ㄦ悳绱㈡潯浠�
diff --git a/model/invoiceProduct.go b/model/invoiceProduct.go
new file mode 100644
index 0000000..6a8bb50
--- /dev/null
+++ b/model/invoiceProduct.go
@@ -0,0 +1,131 @@
+package model
+
+import (
+	"aps_crm/pkg/mysqlx"
+	"gorm.io/gorm"
+)
+
+type (
+	// InvoiceProduct 鍚堝悓浜у搧
+	InvoiceProduct struct {
+		InvoiceId int `gorm:"invoice_id" json:"invoiceId"`
+		ProductId int `gorm:"product_id" json:"productId"`
+	}
+
+	// InvoiceProductSearch 鍚堝悓浜у搧鎼滅储鏉′欢
+	InvoiceProductSearch struct {
+		InvoiceProduct
+		Orm        *gorm.DB
+		Keyword    string
+		PageNum    int
+		PageSize   int
+		ProductIds []uint
+	}
+)
+
+func (InvoiceProduct) TableName() string {
+	return "invoice_product"
+}
+
+func NewInvoiceProductSearch() *InvoiceProductSearch {
+	return &InvoiceProductSearch{
+		Orm: mysqlx.GetDB(),
+	}
+}
+
+func (slf *InvoiceProductSearch) build() *gorm.DB {
+	var db = slf.Orm.Model(&InvoiceProduct{})
+	if len(slf.ProductIds) != 0 {
+		db = db.Where("product_id in ?", slf.ProductIds)
+	}
+	if slf.InvoiceId != 0 {
+		db = db.Where("invoice_id = ?", slf.InvoiceId)
+	}
+
+	return db
+}
+
+func (slf *InvoiceProductSearch) Create(record *InvoiceProduct) error {
+	var db = slf.build()
+	return db.Create(record).Error
+}
+
+func (slf *InvoiceProductSearch) CreateBatch(records []*InvoiceProduct) error {
+	var db = slf.build()
+	return db.Create(records).Error
+}
+
+func (slf *InvoiceProductSearch) Delete() error {
+	var db = slf.build()
+	return db.Delete(&InvoiceProduct{}).Error
+}
+
+func (slf *InvoiceProductSearch) Update(record *InvoiceProduct) error {
+	var db = slf.build()
+	return db.Updates(record).Error
+}
+
+func (slf *InvoiceProductSearch) FindAll() ([]*InvoiceProduct, error) {
+	var db = slf.build()
+	var record = make([]*InvoiceProduct, 0)
+	err := db.Find(&record).Error
+	return record, err
+}
+
+func (slf *InvoiceProductSearch) SetProductIds(ids []uint) *InvoiceProductSearch {
+	slf.ProductIds = ids
+	return slf
+}
+
+func (slf *InvoiceProductSearch) SetInvoiceId(id int) *InvoiceProductSearch {
+	slf.InvoiceId = id
+	return slf
+}
+
+func (slf *InvoiceProductSearch) SetOrm(tx *gorm.DB) *InvoiceProductSearch {
+	slf.Orm = tx
+	return slf
+}
+
+func (slf *InvoiceProductSearch) First() (*InvoiceProduct, error) {
+	var db = slf.build()
+	var record = new(InvoiceProduct)
+	err := db.First(record).Error
+	return record, err
+}
+
+func (slf *InvoiceProductSearch) Updates(values interface{}) error {
+	var db = slf.build()
+	return db.Updates(values).Error
+}
+
+func (slf *InvoiceProductSearch) Find() ([]*InvoiceProduct, int64, error) {
+	var db = slf.build()
+	var records = make([]*InvoiceProduct, 0)
+	var total int64
+	if err := db.Count(&total).Error; err != nil {
+		return records, total, err
+	}
+	if slf.PageNum > 0 && slf.PageSize > 0 {
+		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
+	}
+
+	err := db.Find(&records).Error
+	return records, total, err
+}
+
+// InitDefaultData 鍒濆鍖栨暟鎹�
+func (slf *InvoiceProductSearch) InitDefaultData() error {
+	var (
+		db          = slf.Orm.Table(slf.TableName())
+		total int64 = 0
+	)
+	if err := db.Count(&total).Error; err != nil {
+		return err
+	}
+	if total != 0 {
+		return nil
+	}
+	records := []*InvoiceProduct{}
+	return slf.CreateBatch(records)
+}
diff --git a/model/product.go b/model/product.go
index 029857b..a61ee23 100644
--- a/model/product.go
+++ b/model/product.go
@@ -1,15 +1,18 @@
 package model
 
-import "gorm.io/gorm"
+import (
+	"github.com/shopspring/decimal"
+	"gorm.io/gorm"
+)
 
 type Product struct {
-	Id         int     `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-	Name       string  `json:"name" gorm:"column:name;type:varchar(255);comment:浜у搧鍚嶇О"`
-	Price      float64 `json:"price" gorm:"column:price;type:decimal(10,2);comment:浜у搧浠锋牸"`
-	Number     string  `json:"number" gorm:"column:number;type:varchar(255);comment:浜у搧缂栧彿"`
-	Amount     int     `json:"amount" gorm:"column:amount;type:int;comment:浜у搧鏁伴噺"`
-	Total      float64 `json:"total" gorm:"column:total;type:decimal(10,2);comment:浜у搧鎬讳环"`
-	Desc       string  `json:"desc" gorm:"column:desc;type:varchar(255);comment:浜у搧鎻忚堪"`
+	Id         uint            `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+	Name       string          `json:"name" gorm:"column:name;type:varchar(255);comment:浜у搧鍚嶇О"`
+	Price      decimal.Decimal `json:"price" gorm:"column:price;type:decimal(10,2);comment:浜у搧浠锋牸"`
+	Number     string          `json:"number" gorm:"column:number;type:varchar(255);comment:浜у搧缂栧彿"`
+	Amount     decimal.Decimal `json:"amount" gorm:"column:amount;type:int;comment:浜у搧鏁伴噺"`
+	Total      decimal.Decimal `json:"total" gorm:"column:total;type:decimal(10,2);comment:浜у搧鎬讳环"`
+	Desc       string          `json:"desc" gorm:"column:desc;type:varchar(255);comment:浜у搧鎻忚堪"`
 	gorm.Model `json:"-"`
 }
 
diff --git a/model/request/invoice.go b/model/request/invoice.go
index 1b1eddd..526af5f 100644
--- a/model/request/invoice.go
+++ b/model/request/invoice.go
@@ -2,6 +2,7 @@
 
 import (
 	"aps_crm/constvar"
+	"aps_crm/model"
 )
 
 type AddInvoice struct {
@@ -17,22 +18,24 @@
 	InvoiceDate      string                     `gorm:"invoice_date" json:"invoiceDate"`            // 寮�绁ㄦ棩鏈�
 	CourierNumber    string                     `gorm:"courier_number" json:"courierNumber"`        // 鐗╂祦鍗曞彿
 	CourierCompanyId int                        `gorm:"courier_company_id" json:"courierCompanyId"` // 鐗╂祦鍏徃
+	Products         []model.Product            `json:"products"`                                   //鍙戠エ瀵瑰簲浜у搧锛屼粠鐩稿簲婧愬崟閲岃幏鍙�
 }
 
 type UpdateInvoice struct {
-	Id               int    `json:"id"`
-	ClientId         int    `gorm:"client_id" json:"clientId"`                  // 瀹㈡埛id
-	InvoiceTypeId    int    `gorm:"invoice_type_id" json:"invoiceTypeId"`       // 鍙戠エ绫诲瀷id
-	PrincipalId      int    `gorm:"principal_id" json:"principalId"`            // 閿�鍞礋璐d汉id
-	Subject          string `gorm:"subject" json:"subject"`                     // 涓婚
-	InvoiceStatusId  int    `gorm:"invoice_status_id" json:"invoiceStatusId"`   // 鍙戠エ鐘舵�乮d
-	SourceType       int    `gorm:"source_type" json:"sourceType"`              // 婧愬崟绫诲瀷(1閿�鍞槑缁嗗崟2鏈嶅姟鍚堝悓)
-	SourceId         int    `gorm:"source_id" json:"sourceId"`                  // 婧愬崟id
-	TaxpayerIdNumber string `gorm:"taxpayer_id_number" json:"taxpayerIdNumber"` // 绾崇◣璇嗗埆鍙�
-	InvoiceNumber    string `gorm:"invoice_number" json:"invoiceNumber"`        // 鍙戠エ鍙风爜
-	InvoiceDate      int    `gorm:"invoice_date" json:"invoiceDate"`            // 寮�绁ㄦ棩鏈�
-	CourierNumber    string `gorm:"courier_number" json:"courierNumber"`        // 鐗╂祦鍗曞彿
-	CourierCompanyId int    `gorm:"courier_company_id" json:"courierCompanyId"` // 鐗╂祦鍏徃
+	Id               int             `json:"id" binding:"required"`
+	ClientId         int             `gorm:"client_id" json:"clientId"`                  // 瀹㈡埛id
+	InvoiceTypeId    int             `gorm:"invoice_type_id" json:"invoiceTypeId"`       // 鍙戠エ绫诲瀷id
+	PrincipalId      int             `gorm:"principal_id" json:"principalId"`            // 閿�鍞礋璐d汉id
+	Subject          string          `gorm:"subject" json:"subject"`                     // 涓婚
+	InvoiceStatusId  int             `gorm:"invoice_status_id" json:"invoiceStatusId"`   // 鍙戠エ鐘舵�乮d
+	SourceType       int             `gorm:"source_type" json:"sourceType"`              // 婧愬崟绫诲瀷(1閿�鍞槑缁嗗崟2鏈嶅姟鍚堝悓)
+	SourceId         int             `gorm:"source_id" json:"sourceId"`                  // 婧愬崟id
+	TaxpayerIdNumber string          `gorm:"taxpayer_id_number" json:"taxpayerIdNumber"` // 绾崇◣璇嗗埆鍙�
+	InvoiceNumber    string          `gorm:"invoice_number" json:"invoiceNumber"`        // 鍙戠エ鍙风爜
+	InvoiceDate      int             `gorm:"invoice_date" json:"invoiceDate"`            // 寮�绁ㄦ棩鏈�
+	CourierNumber    string          `gorm:"courier_number" json:"courierNumber"`        // 鐗╂祦鍗曞彿
+	CourierCompanyId int             `gorm:"courier_company_id" json:"courierCompanyId"` // 鐗╂祦鍏徃
+	Products         []model.Product `json:"products"`                                   //鍙戠エ瀵瑰簲浜у搧锛屼粠鐩稿簲婧愬崟閲岃幏鍙�
 }
 
 type GetInvoiceList struct {
diff --git a/model/request/serviceContract.go b/model/request/serviceContract.go
index 2ad8995..65068e4 100644
--- a/model/request/serviceContract.go
+++ b/model/request/serviceContract.go
@@ -1,45 +1,45 @@
-package request
-
-import (
-	"aps_crm/constvar"
-	"aps_crm/model"
-)
-
-type AddServiceContract struct {
-	ServiceContract
-}
-
-type ServiceContract struct {
-	ClientId     int             `json:"clientId"`
-	Number       string          `json:"number"`
-	MemberId     int             `json:"memberId"`
-	ContactId    int             `json:"contactId"`
-	SaleChanceId int             `json:"saleChanceId"`
-	ContractId   int             `json:"contractId"`
-	QuotationId  int             `json:"quotationId"`
-	TypeId       int             `json:"typeId"`
-	SignTime     string          `json:"signTime"`
-	StartTime    string          `json:"startTime"`
-	EndTime      string          `json:"endTime"`
-	StatusId     int             `json:"statusId"`
-	ServiceTimes int             `json:"serviceTimes"`
-	Terms        string          `json:"terms"`
-	Remark       string          `json:"remark"`
-	Products     []model.Product `json:"products"`
-}
-
-type UpdateServiceContract struct {
-	Id int `json:"id"`
-	ServiceContract
-}
-
-type GetServiceContractList struct {
-	PageInfo
-	QueryClass  constvar.ServiceContractQueryClass  `json:"queryClass"`
-	KeywordType constvar.ServiceContractKeywordType `json:"keywordType"`
-	Keyword     string                              `json:"keyword"`
-}
-
-type DeleteserviceContract struct {
-	Ids []int `json:"ids"`
-}
+package request
+
+import (
+	"aps_crm/constvar"
+	"aps_crm/model"
+)
+
+type AddServiceContract struct {
+	ServiceContract
+}
+
+type ServiceContract struct {
+	ClientId       int              `json:"clientId"`
+	Number         string           `json:"number"`
+	MemberId       int              `json:"memberId"`
+	ContactId      int              `json:"contactId"`
+	SaleChanceId   int              `json:"saleChanceId"`
+	SalesDetailsId int              `json:"salesDetailsId"`
+	QuotationId    int              `json:"quotationId"`
+	TypeId         int              `json:"typeId"`
+	SignTime       string           `json:"signTime" binding:"datetime=2006-01-02"`
+	StartTime      string           `json:"startTime" binding:"datetime=2006-01-02"`
+	EndTime        string           `json:"endTime" binding:"datetime=2006-01-02"`
+	StatusId       int              `json:"statusId"`
+	ServiceTimes   int              `json:"serviceTimes"`
+	Terms          string           `json:"terms"`
+	Remark         string           `json:"remark"`
+	Products       []*model.Product `json:"products"`
+}
+
+type UpdateServiceContract struct {
+	Id int `json:"id"`
+	ServiceContract
+}
+
+type GetServiceContractList struct {
+	PageInfo
+	QueryClass  constvar.ServiceContractQueryClass  `json:"queryClass"`
+	KeywordType constvar.ServiceContractKeywordType `json:"keywordType"`
+	Keyword     string                              `json:"keyword"`
+}
+
+type DeleteServiceContract struct {
+	Ids []int `json:"ids"`
+}
diff --git a/model/serviceContract.go b/model/serviceContract.go
index 5c5a497..651ad73 100644
--- a/model/serviceContract.go
+++ b/model/serviceContract.go
@@ -11,26 +11,32 @@
 
 type (
 	ServiceContract struct {
-		Id                      int             `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		ClientId                int             `json:"clientId" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
-		Number                  string          `json:"number" gorm:"column:number;type:varchar(255);comment:鍚堝悓缂栧彿"`
-		MemberId                int             `json:"memberId" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
-		ContactId               int             `json:"contactId" gorm:"column:contact_id;type:int;comment:鑱旂郴浜篿d"`
-		SaleChanceId            int             `json:"saleChanceId" gorm:"column:sale_chance_id;type:int;comment:閿�鍞満浼歩d"`
-		ContractId              int             `json:"contractId" gorm:"column:contract_id;type:int;comment:鍚堝悓id"`
-		QuotationId             int             `json:"quotationId" gorm:"column:quotation_id;type:int;comment:鎶ヤ环鍗昳d"`
-		ServiceContractTypeId   int             `json:"serviceContractTypeId" gorm:"column:service_contract_type_id;type:int;comment:鍚堝悓绫诲瀷id"`
-		SignTime                time.Time       `json:"signTime" gorm:"column:sign_time;type:datetime;comment:绛剧害鏃堕棿"`
-		StartTime               time.Time       `json:"startTime" gorm:"column:start_time;type:datetime;comment:寮�濮嬫椂闂�"`
-		EndTime                 time.Time       `json:"endTime" gorm:"column:end_time;type:datetime;comment:缁撴潫鏃堕棿"`
-		ServiceContractStatusId int             `json:"serviceContractStatusId" gorm:"column:service_contract_status_id;type:int;comment:鍚堝悓鐘舵�乮d"`
-		ServiceTimes            int             `json:"serviceTimes" gorm:"column:service_times;type:int;comment:鏈嶅姟娆℃暟"`
-		Terms                   string          `json:"terms" gorm:"column:terms;type:text;comment:鏉℃"`
-		Remark                  string          `json:"remark" gorm:"column:remark;type:text;comment:澶囨敞"`
-		AmountReceivable        decimal.Decimal `gorm:"amount_receivable" json:"amountReceivable"` // 搴旀敹閲戦
-		AmountReceived          decimal.Decimal `gorm:"amount_received" json:"amountReceived"`     // 宸叉敹閲戦
-		AmountInvoiced          decimal.Decimal `gorm:"amount_invoiced" json:"amountInvoiced"`     // 宸插紑绁ㄩ噾棰�
-		Products                []Product       `json:"products" gorm:"many2many:serviceContract_product;"`
+		Id                      int                   `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		ClientId                int                   `json:"clientId" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
+		Number                  string                `json:"number" gorm:"column:number;type:varchar(255);comment:鍚堝悓缂栧彿"`
+		MemberId                int                   `json:"memberId" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
+		ContactId               int                   `json:"contactId" gorm:"column:contact_id;type:int;comment:鑱旂郴浜篿d"`
+		SaleChanceId            int                   `json:"saleChanceId" gorm:"column:sale_chance_id;type:int;comment:閿�鍞満浼歩d"`
+		SaleChance              SaleChance            `json:"SaleChance" gorm:"foreignKey:SaleChanceId"`
+		SalesDetailsId          int                   `json:"salesDetailsId" gorm:"column:sales_details_id;type:int;comment:鍚堝悓璁㈠崟id"`
+		SalesDetails            SalesDetails          `json:"salesDetails" gorm:"foreignKey:SalesDetailsId"`
+		QuotationId             int                   `json:"quotationId" gorm:"column:quotation_id;type:int;comment:鎶ヤ环鍗昳d"`
+		Quotation               Quotation             `json:"quotation" gorm:"foreignKey:QuotationId"`
+		ServiceContractTypeId   int                   `json:"serviceContractTypeId" gorm:"column:service_contract_type_id;type:int;comment:鍚堝悓绫诲瀷id"`
+		ServiceContractType     ServiceContractType   `json:"serviceContractType" gorm:"foreignKey:ServiceContractTypeId"`
+		SignTime                string                `json:"signTime" gorm:"column:sign_time;type:datetime;comment:绛剧害鏃堕棿"`
+		StartTime               string                `json:"startTime" gorm:"column:start_time;type:datetime;comment:寮�濮嬫椂闂�"`
+		EndTime                 string                `json:"endTime" gorm:"column:end_time;type:datetime;comment:缁撴潫鏃堕棿"`
+		ServiceContractStatusId int                   `json:"serviceContractStatusId" gorm:"column:service_contract_status_id;type:int;comment:鍚堝悓鐘舵�乮d"`
+		ServiceContractStatus   ServiceContractStatus `json:"serviceContractStatus" gorm:"foreignKey:ServiceContractStatusId"`
+		ServiceTimes            int                   `json:"serviceTimes" gorm:"column:service_times;type:int;comment:鏈嶅姟娆℃暟"`
+		Terms                   string                `json:"terms" gorm:"column:terms;type:text;comment:鏉℃"`
+		Remark                  string                `json:"remark" gorm:"column:remark;type:text;comment:澶囨敞"`
+		AmountReceivable        decimal.Decimal       `gorm:"amount_receivable" json:"amountReceivable"` // 搴旀敹閲戦
+		AmountReceived          decimal.Decimal       `gorm:"amount_received" json:"amountReceived"`     // 宸叉敹閲戦
+		AmountInvoiced          decimal.Decimal       `gorm:"amount_invoiced" json:"amountInvoiced"`     // 宸插紑绁ㄩ噾棰�
+		AmountUnInvoiced        decimal.Decimal       `gorm:"-" json:"amountUnInvoiced"`                 // 鏈紑绁ㄩ噾棰�
+		Products                []*Product            `json:"products" gorm:"many2many:service_contract_product;"`
 		gorm.Model              `json:"-"`
 	}
 
@@ -44,6 +50,7 @@
 		OrderBy     string
 		PageNum     int
 		PageSize    int
+		Preload     bool
 	}
 )
 
@@ -64,13 +71,13 @@
 	}
 	switch slf.QueryClass {
 	case constvar.ServiceContractQueryClassExpireAfter30Day:
-		db = db.Where("end_time > ?", time.Now(), time.Now().AddDate(0, 0, 30))
+		db = db.Where("end_time > ?", time.Now().AddDate(0, 0, 30).Format("2006-01-02"))
 	case constvar.ServiceContractQueryClassExpireAfter60Day:
-		db = db.Where("end_time > ?", time.Now(), time.Now().AddDate(0, 0, 60))
+		db = db.Where("end_time > ?", time.Now().AddDate(0, 0, 60).Format("2006-01-02"))
 	case constvar.ServiceContractQueryClassExpiredBefore15Day:
-		db = db.Where("end_time < ?", time.Now().AddDate(0, 0, -15))
+		db = db.Where("end_time < ?", time.Now().AddDate(0, 0, -15).Format("2006-01-02"))
 	case constvar.ServiceContractQueryClassExpiredBefore60Day:
-		db = db.Where("end_time < ?", time.Now().AddDate(0, 0, -60))
+		db = db.Where("end_time < ?", time.Now().AddDate(0, 0, -60).Format("2006-01-02"))
 
 	}
 	switch slf.KeywordType {
@@ -97,6 +104,15 @@
 		//todo
 
 	}
+	if slf.Preload {
+		db = db.
+			Preload("SaleChance").
+			Preload("SalesDetails").
+			Preload("Quotation").
+			Preload("ServiceContractType").
+			Preload("ServiceContractStatus").
+			Preload("Products")
+	}
 
 	return db
 }
@@ -116,14 +132,7 @@
 	return db.Delete(&ServiceContract{}).Error
 }
 
-func (slf *ServiceContractSearch) Find() (*ServiceContract, error) {
-	var db = slf.build()
-	var record = &ServiceContract{}
-	err := db.First(record).Error
-	return record, err
-}
-
-func (slf *ServiceContractSearch) FindAll() ([]*ServiceContract, int64, error) {
+func (slf *ServiceContractSearch) Find() ([]*ServiceContract, int64, error) {
 	var db = slf.build()
 	var records = make([]*ServiceContract, 0)
 	var total int64
@@ -134,11 +143,7 @@
 		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
 	}
 
-	if slf.PageNum > 0 && slf.PageSize > 0 {
-		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
-	}
-
-	err := db.Preload("Products").Find(&records).Error
+	err := db.Find(&records).Error
 	return records, total, err
 }
 
@@ -176,6 +181,11 @@
 	return slf
 }
 
+func (slf *ServiceContractSearch) SetPreload(preload bool) *ServiceContractSearch {
+	slf.Preload = preload
+	return slf
+}
+
 func (slf *ServiceContractSearch) UpdateByMap(upMap map[string]interface{}) error {
 	var (
 		db = slf.build()
diff --git a/pkg/contextx/contextx.go b/pkg/contextx/contextx.go
index 95a29c6..77a9b64 100644
--- a/pkg/contextx/contextx.go
+++ b/pkg/contextx/contextx.go
@@ -1,101 +1,102 @@
-package contextx
-
-import (
-	"aps_crm/pkg/ecode"
-	"aps_crm/pkg/logx"
-	"github.com/gin-gonic/gin"
-	"net/http"
-)
-
-type (
-	Context struct {
-		ctx       *gin.Context
-		paramsMap map[string]interface{}
-	}
-
-	Response struct {
-		Code int         `json:"code"`
-		Data interface{} `json:"data"`
-		Msg  string      `json:"msg"`
-	}
-)
-
-func NewContext(ctx *gin.Context, params interface{}) (r *Context, isAllow bool) {
-	r = &Context{
-		ctx: ctx,
-	}
-	if r.ctx.Request.Method == "OPTIONS" {
-		r.ctx.String(http.StatusOK, "")
-		return
-	}
-
-	defer func() {
-		query := r.ctx.Request.URL.RawQuery
-		if query != "" {
-			query = "?" + query
-		}
-		urlPath := r.ctx.Request.URL.Path
-		logx.Infof("%s | %s %s | uid: %s | %+v", ctx.ClientIP(), r.ctx.Request.Method, urlPath+query, r.GetUserId(), params)
-	}()
-
-	// validate params
-	if params != nil {
-		if err := r.ctx.ShouldBind(params); err != nil {
-			r.Fail(ecode.ParamsErr)
-			return
-		}
-	}
-	isAllow = true
-	return
-}
-
-func (slf *Context) GetRequestPath() (r string) {
-	r = slf.ctx.Request.URL.Path
-	return
-}
-
-func (slf *Context) GetUserId() (r string) {
-	v := slf.paramsMap["userId"]
-	switch v.(type) {
-	case string:
-		r = v.(string)
-	}
-	return
-}
-
-func (slf *Context) Result(code int, data interface{}, msg string) {
-	slf.ctx.JSON(http.StatusOK, Response{
-		Code: code,
-		Data: data,
-		Msg:  msg,
-	})
-}
-
-func (slf *Context) Ok() {
-	slf.Result(ecode.OK, map[string]interface{}{}, "")
-}
-
-func (slf *Context) OkWithDetailed(data interface{}) {
-	slf.Result(ecode.OK, data, "")
-}
-
-func (slf *Context) Fail(errCode int) {
-	slf.Result(errCode, map[string]interface{}{}, ecode.GetMsg(errCode))
-}
-
-func (slf *Context) FailWithMsg(errCode int, msg string) {
-	slf.Result(errCode, map[string]interface{}{}, msg)
-}
-
-func (slf *Context) FailWithDetailed(errCode int, data interface{}) {
-	slf.Result(errCode, data, ecode.GetMsg(errCode))
-}
-
-func (slf *Context) GetCtx() *gin.Context {
-	return slf.ctx
-}
-
-func (slf *Context) SetCtx(c *gin.Context) *Context {
-	slf.ctx = c
-	return slf
-}
+package contextx
+
+import (
+	"aps_crm/pkg/ecode"
+	"aps_crm/pkg/logx"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+type (
+	Context struct {
+		ctx       *gin.Context
+		paramsMap map[string]interface{}
+	}
+
+	Response struct {
+		Code int         `json:"code"`
+		Data interface{} `json:"data"`
+		Msg  string      `json:"msg"`
+	}
+)
+
+func NewContext(ctx *gin.Context, params interface{}) (r *Context, isAllow bool) {
+	r = &Context{
+		ctx: ctx,
+	}
+	if r.ctx.Request.Method == "OPTIONS" {
+		r.ctx.String(http.StatusOK, "")
+		return
+	}
+
+	defer func() {
+		query := r.ctx.Request.URL.RawQuery
+		if query != "" {
+			query = "?" + query
+		}
+		urlPath := r.ctx.Request.URL.Path
+		logx.Infof("%s | %s %s | uid: %s | %+v", ctx.ClientIP(), r.ctx.Request.Method, urlPath+query, r.GetUserId(), params)
+	}()
+
+	// validate params
+	if params != nil {
+		if err := r.ctx.ShouldBind(params); err != nil {
+			logx.Errorf("bind param error: %v", err.Error())
+			r.Fail(ecode.ParamsErr)
+			return
+		}
+	}
+	isAllow = true
+	return
+}
+
+func (slf *Context) GetRequestPath() (r string) {
+	r = slf.ctx.Request.URL.Path
+	return
+}
+
+func (slf *Context) GetUserId() (r string) {
+	v := slf.paramsMap["userId"]
+	switch v.(type) {
+	case string:
+		r = v.(string)
+	}
+	return
+}
+
+func (slf *Context) Result(code int, data interface{}, msg string) {
+	slf.ctx.JSON(http.StatusOK, Response{
+		Code: code,
+		Data: data,
+		Msg:  msg,
+	})
+}
+
+func (slf *Context) Ok() {
+	slf.Result(ecode.OK, map[string]interface{}{}, "")
+}
+
+func (slf *Context) OkWithDetailed(data interface{}) {
+	slf.Result(ecode.OK, data, "")
+}
+
+func (slf *Context) Fail(errCode int) {
+	slf.Result(errCode, map[string]interface{}{}, ecode.GetMsg(errCode))
+}
+
+func (slf *Context) FailWithMsg(errCode int, msg string) {
+	slf.Result(errCode, map[string]interface{}{}, msg)
+}
+
+func (slf *Context) FailWithDetailed(errCode int, data interface{}) {
+	slf.Result(errCode, data, ecode.GetMsg(errCode))
+}
+
+func (slf *Context) GetCtx() *gin.Context {
+	return slf.ctx
+}
+
+func (slf *Context) SetCtx(c *gin.Context) *Context {
+	slf.ctx = c
+	return slf
+}
diff --git a/pkg/ecode/code.go b/pkg/ecode/code.go
index a574c94..6a3d1a2 100644
--- a/pkg/ecode/code.go
+++ b/pkg/ecode/code.go
@@ -235,12 +235,15 @@
 	PlanUpdateErr = 3200005 // 鏇存柊璁″垝澶辫触
 	PlanDeleteErr = 3200006 // 鍒犻櫎璁″垝澶辫触
 
-	SContractExist     = 3300001 // 鏈嶅姟鍚堝悓宸插瓨鍦�
-	SContractNotExist  = 3300002 // 鏈嶅姟鍚堝悓涓嶅瓨鍦�
-	SContractListErr   = 3300003 // 鑾峰彇鏈嶅姟鍚堝悓鍒楄〃澶辫触
-	SContractSetErr    = 3300004 // 璁剧疆鏈嶅姟鍚堝悓澶辫触
-	SContractUpdateErr = 3300005 // 鏇存柊鏈嶅姟鍚堝悓澶辫触
-	SContractDeleteErr = 3300006 // 鍒犻櫎鏈嶅姟鍚堝悓澶辫触
+	SContractExist                                             = 3300001 // 鏈嶅姟鍚堝悓宸插瓨鍦�
+	SContractNotExist                                          = 3300002 // 鏈嶅姟鍚堝悓涓嶅瓨鍦�
+	SContractListErr                                           = 3300003 // 鑾峰彇鏈嶅姟鍚堝悓鍒楄〃澶辫触
+	SContractSetErr                                            = 3300004 // 璁剧疆鏈嶅姟鍚堝悓澶辫触
+	SContractUpdateErr                                         = 3300005 // 鏇存柊鏈嶅姟鍚堝悓澶辫触
+	SContractDeleteErr                                         = 3300006 // 鍒犻櫎鏈嶅姟鍚堝悓澶辫触
+	SContractProductPriceLowerThanInvoiceAmountErr             = 3300007 //浜у搧鎬讳环浣庝簬宸插紑绁ㄩ噾棰�
+	SContractProductPriceLowerThanReceivedAmountErr            = 3300008 //浜у搧鎬讳环浣庝簬宸叉敹閲戦
+	SContractInvoiceProductPriceGreaterThanReceivableAmountErr = 3300009 //寮�绁ㄦ�婚楂樹簬搴旀敹閲戦
 
 	OrderManageExist     = 3400001 // 璁㈠崟绠$悊宸插瓨鍦�
 	OrderManageNotExist  = 3400002 // 璁㈠崟绠$悊涓嶅瓨鍦�
diff --git a/pkg/ecode/msg.go b/pkg/ecode/msg.go
index 5b43237..deeff57 100644
--- a/pkg/ecode/msg.go
+++ b/pkg/ecode/msg.go
@@ -20,6 +20,9 @@
 	ChildrenExistErr:      "瀛樺湪瀛愯彍鍗�",
 	MenuNotExist:          "鑿滃崟涓嶅瓨鍦�",
 	MenuNameExistErr:      "鑿滃崟鍚嶅凡瀛樺湪",
+	SContractProductPriceLowerThanInvoiceAmountErr:             "浜у搧鎬讳环浣庝簬宸插紑绁ㄩ噾棰�",
+	SContractProductPriceLowerThanReceivedAmountErr:            "浜у搧鎬讳环浣庝簬宸叉敹閲戦",
+	SContractInvoiceProductPriceGreaterThanReceivableAmountErr: "寮�绁ㄦ�婚楂樹簬搴旀敹閲戦",
 }
 
 func GetMsg(errCode int) (errMsg string) {
diff --git a/service/invoice.go b/service/invoice.go
index 9bd1b91..59301a9 100644
--- a/service/invoice.go
+++ b/service/invoice.go
@@ -1,9 +1,11 @@
 package service
 
 import (
+	"aps_crm/constvar"
 	"aps_crm/model"
 	"aps_crm/model/request"
 	"aps_crm/pkg/ecode"
+	"github.com/shopspring/decimal"
 	"gorm.io/gorm"
 )
 
@@ -15,30 +17,32 @@
 
 func (InvoiceService) AddInvoice(invoice *model.Invoice) int {
 
-	err := model.WithTransaction(func(db *gorm.DB) error {
-		err := model.NewInvoiceSearch().Create(invoice)
+	if invoice.SourceType == constvar.InvoiceSourceTypeServiceContract {
+		serviceContract, err := model.NewServiceContractSearch().SetId(invoice.SourceId).SetPreload(true).First()
 		if err != nil {
-			return err
+			return ecode.DBErr
 		}
-		//if invoice.SourceType == constvar.InvoiceSourceTypeServiceContract {
-		//	contract,err := model.NewServiceContractSearch().SetId(invoice.SourceId).First()
-		//	if err != nil {
-		//		return err
-		//	}
-		//	AmountInvoiced := contract.AmountReceived.Add()
-		//	err := model.NewServiceContractSearch().SetId(invoice.SourceId).UpdateByMap(map[string]interface{}{
-		//		"amount_received" :
-		//	})
-		//	if err != nil {
-		//		return err
-		//	}
-		//}
-
-		return nil
-	})
-
-	if err != nil {
-		return ecode.DBErr
+		var amountInvoiced decimal.Decimal
+		rightProducts := NewProductsService().PickRightProducts(invoice.Products, serviceContract.Products)
+		for _, product := range rightProducts {
+			amountInvoiced = serviceContract.AmountInvoiced.Add(product.Amount.Mul(product.Price))
+		}
+		amountInvoiced = amountInvoiced.Round(2)
+		if amountInvoiced.GreaterThan(serviceContract.AmountReceivable) {
+			return ecode.SContractInvoiceProductPriceGreaterThanReceivableAmountErr
+		}
+		err = model.WithTransaction(func(db *gorm.DB) error {
+			err = model.NewInvoiceSearch().Create(invoice)
+			if err != nil {
+				return err
+			}
+			return model.NewServiceContractSearch().SetId(invoice.SourceId).UpdateByMap(map[string]interface{}{
+				"amount_invoiced": amountInvoiced,
+			})
+		})
+		if err != nil {
+			return ecode.DBErr
+		}
 	}
 
 	return ecode.OK
@@ -79,9 +83,43 @@
 }
 
 func (InvoiceService) UpdateInvoice(invoice *model.Invoice) int {
-	err := model.NewInvoiceSearch().SetId(invoice.Id).Save(invoice)
-	if err != nil {
-		return ecode.DBErr
+	if invoice.SourceType == constvar.InvoiceSourceTypeServiceContract {
+		serviceContract, err := model.NewServiceContractSearch().SetId(invoice.SourceId).SetPreload(true).First()
+		if err != nil {
+			return ecode.DBErr
+		}
+		var amountInvoiced decimal.Decimal
+		newProducts, removedProducts := NewProductsService().PickDiffProducts(invoice.Products, serviceContract.Products)
+		for _, product := range newProducts {
+			amountInvoiced = serviceContract.AmountInvoiced.Add(product.Amount.Mul(product.Price))
+		}
+		removedProductIds := make([]uint, 0, len(removedProducts))
+		for _, product := range removedProducts {
+			amountInvoiced = serviceContract.AmountInvoiced.Sub(product.Amount.Mul(product.Price))
+			removedProductIds = append(removedProductIds, product.Id)
+		}
+		amountInvoiced = amountInvoiced.Round(2)
+		if amountInvoiced.GreaterThan(serviceContract.AmountReceivable) {
+			return ecode.SContractInvoiceProductPriceGreaterThanReceivableAmountErr
+		}
+		err = model.WithTransaction(func(db *gorm.DB) error {
+			err = model.NewInvoiceSearch().SetId(invoice.Id).Save(invoice)
+			if err != nil {
+				return err
+			}
+			if len(removedProductIds) > 0 {
+				err = model.NewInvoiceProductSearch().SetInvoiceId(invoice.Id).SetProductIds(removedProductIds).Delete()
+				if err != nil {
+					return err
+				}
+			}
+			return model.NewServiceContractSearch().SetId(invoice.SourceId).UpdateByMap(map[string]interface{}{
+				"amount_invoiced": amountInvoiced,
+			})
+		})
+		if err != nil {
+			return ecode.DBErr
+		}
 	}
 	return ecode.OK
 }
diff --git a/service/products.go b/service/products.go
new file mode 100644
index 0000000..e8d46e5
--- /dev/null
+++ b/service/products.go
@@ -0,0 +1,50 @@
+package service
+
+import (
+	"aps_crm/model"
+)
+
+type ProductsService struct{}
+
+func NewProductsService() ProductsService {
+	return ProductsService{}
+}
+
+func (slf ProductsService) PickRightProducts(products, sourceProducts []*model.Product) (rightProducts []*model.Product) {
+	productIdMap, productNumberMap := slf.getMappedProducts(sourceProducts)
+	for _, product := range products {
+		if p, ok := productIdMap[product.Id]; ok {
+			rightProducts = append(rightProducts, p)
+		} else if p, ok = productNumberMap[product.Number]; ok {
+			rightProducts = append(rightProducts, p)
+		}
+	}
+	return
+}
+
+func (slf ProductsService) PickDiffProducts(products, sourceProducts []*model.Product) (newProducts, removedProducts []*model.Product) {
+	productIdMap, productNumberMap := slf.getMappedProducts(sourceProducts)
+	productNumberMap2 := make(map[string]*model.Product, len(products))
+	for _, product := range products {
+		if productIdMap[product.Id] == nil && productNumberMap[product.Number] == nil {
+			newProducts = append(newProducts, product)
+		}
+		productNumberMap2[product.Number] = product
+	}
+	for productNumber, product := range productNumberMap {
+		if productNumberMap2[productNumber] == nil {
+			removedProducts = append(removedProducts, product)
+		}
+	}
+	return
+}
+
+func (slf ProductsService) getMappedProducts(sourceProducts []*model.Product) (map[uint]*model.Product, map[string]*model.Product) {
+	productIdMap := make(map[uint]*model.Product, len(sourceProducts))
+	productNumberMap := make(map[string]*model.Product, len(sourceProducts))
+	for _, product := range sourceProducts {
+		productIdMap[product.Id] = product
+		productNumberMap[product.Number] = product
+	}
+	return productIdMap, productNumberMap
+}
diff --git a/service/serviceContract.go b/service/serviceContract.go
index 45907c8..eb901f2 100644
--- a/service/serviceContract.go
+++ b/service/serviceContract.go
@@ -4,11 +4,19 @@
 	"aps_crm/constvar"
 	"aps_crm/model"
 	"aps_crm/pkg/ecode"
+	"github.com/shopspring/decimal"
 )
 
 type SContractService struct{}
 
 func (SContractService) AddServiceContract(serviceContract *model.ServiceContract) int {
+	serviceContract.AmountReceivable = decimal.Zero.Round(2)
+	serviceContract.AmountInvoiced = decimal.Zero.Round(2)
+	serviceContract.AmountReceived = decimal.Zero.Round(2)
+	for _, product := range serviceContract.Products {
+		serviceContract.AmountReceivable = serviceContract.AmountReceivable.Add(product.Amount.Mul(product.Price))
+	}
+	serviceContract.AmountReceivable = serviceContract.AmountReceivable.Round(2)
 	err := model.NewServiceContractSearch().Create(serviceContract)
 	if err != nil {
 		return ecode.SContractExist
@@ -19,11 +27,23 @@
 
 func (SContractService) UpdateServiceContract(serviceContract *model.ServiceContract) int {
 	// check serviceContract exist
-	_, err := model.NewServiceContractSearch().SetId(serviceContract.Id).Find()
+	old, err := model.NewServiceContractSearch().SetId(serviceContract.Id).First()
 	if err != nil {
 		return ecode.SContractNotExist
 	}
-
+	var amountReceivable decimal.Decimal
+	for _, product := range serviceContract.Products {
+		amountReceivable = serviceContract.AmountReceivable.Add(product.Amount.Mul(product.Price))
+	}
+	if amountReceivable.LessThan(serviceContract.AmountInvoiced) {
+		return ecode.SContractProductPriceLowerThanInvoiceAmountErr
+	}
+	if amountReceivable.LessThan(serviceContract.AmountReceived) {
+		return ecode.SContractProductPriceLowerThanReceivedAmountErr
+	}
+	serviceContract.AmountInvoiced = old.AmountReceived
+	serviceContract.AmountReceived = old.AmountReceived
+	serviceContract.AmountReceivable = amountReceivable.Round(2)
 	err = model.NewServiceContractSearch().SetId(serviceContract.Id).Update(serviceContract)
 	if err != nil {
 		return ecode.SContractSetErr
@@ -56,7 +76,9 @@
 		SetKeyword(keyword).
 		SetKeywordType(keywordType).
 		SetQueryClass(queryClass).
-		SetPage(page, pageSize).FindAll()
+		SetPage(page, pageSize).
+		SetPreload(true).
+		Find()
 	if err != nil {
 		return nil, 0, ecode.SContractListErr
 	}

--
Gitblit v1.8.0