From 7cc2f77503135e266264eb897e8f688e8ad216d5 Mon Sep 17 00:00:00 2001
From: zhangqian <zhangqian@123.com>
Date: 星期三, 09 八月 2023 19:27:38 +0800
Subject: [PATCH] 增加发票和合同产品的关联,发票新增和修改时更改对应合同已开票金额

---
 model/product.go                 |    2 
 model/serviceContract.go         |    2 
 model/invoiceProduct.go          |  131 ++++++++++++++++++++++++++
 model/request/serviceContract.go |   32 +++---
 service/products.go              |   50 ++++++++++
 model/invoice.go                 |    2 
 service/invoice.go               |   51 ++++++++--
 model/request/invoice.go         |   27 ++--
 8 files changed, 255 insertions(+), 42 deletions(-)

diff --git a/model/invoice.go b/model/invoice.go
index 8db5f13..9145a6a 100644
--- a/model/invoice.go
+++ b/model/invoice.go
@@ -28,7 +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;"`
+		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 20f0fc2..a61ee23 100644
--- a/model/product.go
+++ b/model/product.go
@@ -6,7 +6,7 @@
 )
 
 type Product struct {
-	Id         int             `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+	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:浜у搧缂栧彿"`
diff --git a/model/request/invoice.go b/model/request/invoice.go
index 5f9128f..526af5f 100644
--- a/model/request/invoice.go
+++ b/model/request/invoice.go
@@ -22,19 +22,20 @@
 }
 
 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 1ab30c4..65068e4 100644
--- a/model/request/serviceContract.go
+++ b/model/request/serviceContract.go
@@ -10,22 +10,22 @@
 }
 
 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"`
+	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 {
diff --git a/model/serviceContract.go b/model/serviceContract.go
index 4345d2c..651ad73 100644
--- a/model/serviceContract.go
+++ b/model/serviceContract.go
@@ -36,7 +36,7 @@
 		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;"`
+		Products                []*Product            `json:"products" gorm:"many2many:service_contract_product;"`
 		gorm.Model              `json:"-"`
 	}
 
diff --git a/service/invoice.go b/service/invoice.go
index c70450d..59301a9 100644
--- a/service/invoice.go
+++ b/service/invoice.go
@@ -18,12 +18,13 @@
 func (InvoiceService) AddInvoice(invoice *model.Invoice) int {
 
 	if invoice.SourceType == constvar.InvoiceSourceTypeServiceContract {
-		serviceContract, err := model.NewServiceContractSearch().SetId(invoice.SourceId).First()
+		serviceContract, err := model.NewServiceContractSearch().SetId(invoice.SourceId).SetPreload(true).First()
 		if err != nil {
 			return ecode.DBErr
 		}
 		var amountInvoiced decimal.Decimal
-		for _, product := range invoice.Products {
+		rightProducts := NewProductsService().PickRightProducts(invoice.Products, serviceContract.Products)
+		for _, product := range rightProducts {
 			amountInvoiced = serviceContract.AmountInvoiced.Add(product.Amount.Mul(product.Price))
 		}
 		amountInvoiced = amountInvoiced.Round(2)
@@ -35,13 +36,9 @@
 			if err != nil {
 				return err
 			}
-			err = model.NewServiceContractSearch().SetId(invoice.SourceId).UpdateByMap(map[string]interface{}{
+			return model.NewServiceContractSearch().SetId(invoice.SourceId).UpdateByMap(map[string]interface{}{
 				"amount_invoiced": amountInvoiced,
 			})
-			if err != nil {
-				return err
-			}
-			return nil
 		})
 		if err != nil {
 			return ecode.DBErr
@@ -86,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
+}

--
Gitblit v1.8.0