增加发票和合同产品的关联,发票新增和修改时更改对应合同已开票金额
| | |
| | | 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 销售发票搜索条件 |
New file |
| | |
| | | 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) |
| | | } |
| | |
| | | ) |
| | | |
| | | 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:产品编号"` |
| | |
| | | } |
| | | |
| | | 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"` // 销售负责人id |
| | | Subject string `gorm:"subject" json:"subject"` // 主题 |
| | | InvoiceStatusId int `gorm:"invoice_status_id" json:"invoiceStatusId"` // 发票状态id |
| | | 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"` // 销售负责人id |
| | | Subject string `gorm:"subject" json:"subject"` // 主题 |
| | | InvoiceStatusId int `gorm:"invoice_status_id" json:"invoiceStatusId"` // 发票状态id |
| | | 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 { |
| | |
| | | } |
| | | |
| | | 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 { |
| | |
| | | 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:"-"` |
| | | } |
| | | |
| | |
| | | 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) |
| | |
| | | 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 |
| | |
| | | } |
| | | |
| | | 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 |
| | | } |
New file |
| | |
| | | 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 |
| | | } |