From b94bef381946e22fd1038f24e6d9de911d194640 Mon Sep 17 00:00:00 2001 From: yinbentan <yinbentan@live.com> Date: 星期三, 31 七月 2024 23:33:48 +0800 Subject: [PATCH] 功能修改,工资计算调整,通过前端配置公式进行计算 --- utils/calculator/doc.go | 22 utils/calculator/def.go | 158 ++++ models/payroll_production_car.go | 7 utils/calculator/ast.go | 267 ++++++++ models/payroll_working_hours.go | 92 ++ models/yield_register.go | 1 utils/calculator/util_test.go | 240 +++++++ models/payroll_production_group.go | 3 models/payroll_production_weavers.go | 25 utils/timex.go | 23 models/payroll_other_subsidies.go | 28 models/db.go | 2 utils/stringsx.go | 22 models/worker.go | 1 utils/calculator/util.go | 141 ++++ utils/calculator/parser.go | 168 +++++ models/payroll_constitute.go | 306 +++++++++ service/salary_plan.go | 466 ++++++++----- 18 files changed, 1,775 insertions(+), 197 deletions(-) diff --git a/models/db.go b/models/db.go index bc0b521..a8e5d54 100644 --- a/models/db.go +++ b/models/db.go @@ -99,6 +99,7 @@ PayrollProductionWeavers{}, PayrollProductionGroup{}, PayrollOtherSubsidies{}, + PayrollConstitute{}, PayrollSalaryPlan{}, ) return err @@ -112,6 +113,7 @@ models := []interface{}{ NewAttendanceRuleSearch(), //NewPayrollSalaryPlanSearch(), + NewPayrollWorkingHoursSearch(), } for _, model := range models { if id, ok := model.(InitDefaultData); ok { diff --git a/models/payroll_constitute.go b/models/payroll_constitute.go new file mode 100644 index 0000000..b920aec --- /dev/null +++ b/models/payroll_constitute.go @@ -0,0 +1,306 @@ +package models + +import ( + "fmt" + "github.com/shopspring/decimal" + "gorm.io/gorm" + "silkserver/constvar" + "silkserver/pkg/mysqlx" +) + +type ( + // PayrollConstitute 鍏跺畠琛ヨ创銆佸鎯� + PayrollConstitute struct { + BaseModelInt + Cycle string `json:"cycle" gorm:"index;size:20;not null;comment:缁熻鍛ㄦ湡(yyyy-MM)"` //鏈堜唤 + WorkerID string `json:"workerId" gorm:"size:200;not null;comment:鍛樺伐ID"` //鍛樺伐ID + Worker Worker `json:"worker" gorm:"foreignKey:WorkerID;references:ID"` + WorkTypeID uint `json:"workTypeID" gorm:"type:bigint(20);not null;comment:宸ョID"` //宸ョID + WorkType WorkTypeManage `json:"workType" gorm:"foreignKey:WorkTypeID;references:ID"` //宸ョID + WorkTypeCode constvar.JobType `json:"workTypeCode" gorm:"size:255;not null;comment:宸ョ浠g爜"` //宸ョ浠g爜 + WorkTypeName string `json:"workTypeName" gorm:"size:255;not null;comment:宸ョ鍚嶇О"` //宸ョ鍚嶇О + SalaryPlanId uint `json:"salaryPlanId" gorm:"type:bigint(20);not null;comment:钖祫鏂规ID"` //钖祫鏂规ID + SalaryPlan SalaryPlan `json:"subsidyTypeName" gorm:"foreignKey:SalaryPlanId;references:ID"` //钖祫鏂规 + SalaryFormula string `json:"salaryFormula" gorm:"size:255;not null;comment:钖祫鏂规(缈昏瘧)"` //钖祫鏂规 + Amount decimal.Decimal `json:"amount" gorm:"type:decimal(12,4);comment:閲戦"` // 閲戦 + CreatedBy string `json:"createdBy" gorm:"size:255;not null;comment:娣诲姞鑰�"` // 娣诲姞鑰�(auto,鐢ㄦ埛id) + } + + PayrollConstituteSearch struct { + PayrollConstitute + Monthly string + Order string + PageNum int + PageSize int + Preload bool + Orm *gorm.DB + } +) + +func (slf PayrollConstitute) TableName() string { + return "silk_payroll_constitute" +} + +// NewPayrollConstituteSearch 鍏跺畠琛ヨ创 +func NewPayrollConstituteSearch() *PayrollConstituteSearch { + return &PayrollConstituteSearch{Orm: mysqlx.GetDB()} +} + +func (slf *PayrollConstituteSearch) SetOrm(tx *gorm.DB) *PayrollConstituteSearch { + slf.Orm = tx + return slf +} + +func (slf *PayrollConstituteSearch) SetPage(page, size int) *PayrollConstituteSearch { + slf.PageNum, slf.PageSize = page, size + return slf +} + +func (slf *PayrollConstituteSearch) SetOrder(order string) *PayrollConstituteSearch { + slf.Order = order + return slf +} + +func (slf *PayrollConstituteSearch) SetID(id uint) *PayrollConstituteSearch { + slf.ID = id + return slf +} + +func (slf *PayrollConstituteSearch) SetCycle(cycle string) *PayrollConstituteSearch { + slf.Cycle = cycle + return slf +} + +func (slf *PayrollConstituteSearch) SetMonthly(monthly string) *PayrollConstituteSearch { + slf.Monthly = monthly + return slf +} + +func (slf *PayrollConstituteSearch) SetWorkTypeID(workTypeID uint) *PayrollConstituteSearch { + slf.WorkTypeID = workTypeID + return slf +} + +func (slf *PayrollConstituteSearch) SetWorkerID(workerID string) *PayrollConstituteSearch { + slf.WorkerID = workerID + return slf +} + +func (slf *PayrollConstituteSearch) SetCreatedBy(createdBy string) *PayrollConstituteSearch { + slf.CreatedBy = createdBy + return slf +} + +func (slf *PayrollConstituteSearch) build() *gorm.DB { + var db = slf.Orm.Table(slf.TableName()) + + if slf.Preload { + db = db.Model(&PayrollConstitute{}).Preload("Worker").Preload("WorkType").Preload("WorkType.SalaryPlan") + } + + if slf.ID > 0 { + db = db.Where("id = ?", slf.ID) + } + + if slf.Cycle != "" { + db = db.Where("cycle = ?", slf.Cycle) + } + + if slf.Monthly != "" { + db = db.Where("cycle like ?", slf.Monthly+"%") + } + + if slf.WorkTypeID > 0 { + db = db.Where("work_type_id = ?", slf.WorkTypeID) + } + + if slf.WorkerID != "" { + db = db.Where("worker_id = ?", slf.WorkerID) + } + + if slf.WorkerID != "" { + db = db.Where("worker_id = ?", slf.WorkerID) + } + + if slf.CreatedBy != "" { + db = db.Where("created_by = ?", slf.SalaryPlanId) + } + + db.Where("1 = 1") + if slf.Order != "" { + db = db.Order(slf.Order) + } + + return db +} + +// Create 鍗曟潯鎻掑叆 +func (slf *PayrollConstituteSearch) Create(record *PayrollConstitute) error { + var db = slf.build() + + if err := db.Create(record).Error; err != nil { + return fmt.Errorf("create err: %v, record: %+v", err, record) + } + + return nil +} + +// CreateBatch 鎵归噺鎻掑叆 +func (slf *PayrollConstituteSearch) CreateBatch(records []*PayrollConstitute) error { + var db = slf.build() + + if err := db.Create(&records).Error; err != nil { + return fmt.Errorf("create batch err: %v, records: %+v", err, records) + } + + return nil +} + +// Save 鍗曟潯鏇存柊 +func (slf *PayrollConstituteSearch) Save(record *PayrollConstitute) error { + var db = slf.build() + + if err := db.Omit("CreatedAt").Save(record).Error; err != nil { + return fmt.Errorf("save err: %v, record: %+v", err, record) + } + + return nil +} + +// SaveBatch 鎵归噺鏇存柊 +func (slf *PayrollConstituteSearch) SaveBatch(record []*PayrollConstitute) error { + var db = slf.build() + + if err := db.Omit("CreatedAt").Save(record).Error; err != nil { + return fmt.Errorf("save err: %v, record: %+v", err, record) + } + + return nil +} + +// UpdateByMap 鍗曟潯鏇存柊 +func (slf *PayrollConstituteSearch) UpdateByMap(upMap map[string]interface{}) error { + var ( + db = slf.build() + ) + + if err := db.Updates(upMap).Error; err != nil { + return fmt.Errorf("update by map err: %v, upMap: %+v", err, upMap) + } + + return nil +} + +// UpdateByQuery 鎵归噺鏇存柊 +func (slf *PayrollConstituteSearch) UpdateByQuery(query string, args []interface{}, upMap map[string]interface{}) error { + var ( + db = slf.Orm.Table(slf.TableName()).Where(query, args...) + ) + + if err := db.Updates(upMap).Error; err != nil { + return fmt.Errorf("update by query err: %v, query: %s, args: %+v, upMap: %+v", err, query, args, upMap) + } + + return nil +} + +// Delete 鍒犻櫎 +func (slf *PayrollConstituteSearch) Delete() error { + var db = slf.build() + + if err := db.Unscoped().Delete(&PayrollConstitute{}).Error; err != nil { + return err + } + + return nil +} + +// First 鏍规嵁鏉′欢鏌ヨ涓�鏉¤褰� +func (slf *PayrollConstituteSearch) First() (*PayrollConstitute, error) { + var ( + record = new(PayrollConstitute) + db = slf.build() + ) + + if err := db.First(record).Error; err != nil { + return record, err + } + + return record, nil +} + +// Find 鎸囧畾鏉′欢鏌ヨ锛堝寘鍚�绘潯鏁帮級 +func (slf *PayrollConstituteSearch) Find() ([]*PayrollConstitute, int64, error) { + var ( + records = make([]*PayrollConstitute, 0) + total int64 + db = slf.build() + ) + + if err := db.Count(&total).Error; err != nil { + return records, total, fmt.Errorf("find count err: %v", err) + } + if slf.PageNum*slf.PageSize > 0 { + db = db.Offset((slf.PageNum - 1) * slf.PageSize).Limit(slf.PageSize) + } + if err := db.Find(&records).Error; err != nil { + return records, total, fmt.Errorf("find records err: %v", err) + } + + return records, total, nil +} + +// FindNotTotal 鎸囧畾鏉′欢鏌ヨ +func (slf *PayrollConstituteSearch) FindNotTotal() ([]*PayrollConstitute, error) { + var ( + records = make([]*PayrollConstitute, 0) + db = slf.build() + ) + + if slf.PageNum*slf.PageSize > 0 { + db = db.Offset((slf.PageNum - 1) * slf.PageSize).Limit(slf.PageSize) + } + if err := db.Find(&records).Error; err != nil { + return records, fmt.Errorf("find records err: %v", err) + } + + return records, nil +} + +// FindByQuery 鎸囧畾鏉′欢鏌ヨ锛堝寘鍚�绘潯鏁帮級 +func (slf *PayrollConstituteSearch) FindByQuery(query string, args []interface{}) ([]*PayrollConstitute, int64, error) { + var ( + records = make([]*PayrollConstitute, 0) + total int64 + db = slf.Orm.Table(slf.TableName()).Where(query, args...) + ) + + if err := db.Count(&total).Error; err != nil { + return records, total, fmt.Errorf("find by query count err: %v", err) + } + if slf.PageNum*slf.PageSize > 0 { + db = db.Offset((slf.PageNum - 1) * slf.PageSize).Limit(slf.PageSize) + } + if err := db.Find(&records).Error; err != nil { + return records, total, fmt.Errorf("find by query records err: %v, query: %s, args: %+v", err, query, args) + } + + return records, total, nil +} + +// FindByQueryNotTotal 鎸囧畾鏉′欢鏌ヨ&涓嶆煡璇㈡�绘潯鏁�. +func (slf *PayrollConstituteSearch) FindByQueryNotTotal(query string, args []interface{}) ([]*PayrollConstitute, error) { + var ( + records = make([]*PayrollConstitute, 0) + db = slf.Orm.Table(slf.TableName()).Where(query, args...) + ) + + if slf.PageNum*slf.PageSize > 0 { + db = db.Offset((slf.PageNum - 1) * slf.PageSize).Limit(slf.PageSize) + } + if err := db.Find(&records).Error; err != nil { + return records, fmt.Errorf("find by query records err: %v, query: %s, args: %+v", err, query, args) + } + + return records, nil +} diff --git a/models/payroll_other_subsidies.go b/models/payroll_other_subsidies.go index f90429c..664cdef 100644 --- a/models/payroll_other_subsidies.go +++ b/models/payroll_other_subsidies.go @@ -12,15 +12,16 @@ // PayrollOtherSubsidies 鍏跺畠琛ヨ创銆佸鎯� PayrollOtherSubsidies struct { BaseModelInt - Cycle string `json:"cycle" gorm:"index;size:20;not null;comment:缁熻鍛ㄦ湡"` //鏈堜唤 - WorkerID string `json:"workerId" gorm:"size:200;not null;comment:鍛樺伐ID"` //鍛樺伐ID - Worker Worker `json:"worker" gorm:"foreignKey:WorkerID;references:ID"` - WorkTypeID uint `json:"workTypeID" gorm:"type:bigint(20);not null;comment:宸ョID"` //宸ョID - WorkTypeCode constvar.JobType `json:"workTypeCode" gorm:"size:255;not null;comment:宸ョ浠g爜"` //宸ョ浠g爜 - WorkTypeName string `json:"workTypeName" gorm:"size:255;not null;comment:宸ョ鍚嶇О"` //宸ョ鍚嶇О - SubsidyType constvar.PayrollSubsidyType `json:"subsidyType" gorm:"size:50;not null;comment:琛ヨ创绫诲瀷"` // 琛ヨ创绫诲瀷 - SubsidyTypeName string `json:"subsidyTypeName" gorm:"size:255;comment:琛ヨ创绫诲瀷鍚嶇О"` // 琛ヨ创绫诲瀷鍚嶇О - Amount decimal.Decimal `json:"amount" gorm:"type:decimal(12,4);comment:閲戦"` // 閲戦 + Cycle string `json:"cycle" gorm:"index;size:20;not null;comment:缁熻鍛ㄦ湡"` //鏈堜唤 + WorkerID string `json:"workerId" gorm:"size:200;not null;comment:鍛樺伐ID"` //鍛樺伐ID + Worker Worker `json:"worker" gorm:"foreignKey:WorkerID;references:ID"` + WorkTypeID uint `json:"workTypeID" gorm:"type:bigint(20);not null;comment:宸ョID"` //宸ョID + WorkTypeCode constvar.JobType `json:"workTypeCode" gorm:"size:255;not null;comment:宸ョ浠g爜"` //宸ョ浠g爜 + WorkTypeName string `json:"workTypeName" gorm:"size:255;not null;comment:宸ョ鍚嶇О"` //宸ョ鍚嶇О + SalaryPlanId uint `json:"salaryPlanId" gorm:"type:bigint(20);not null;comment:钖祫鏂规ID"` //钖祫鏂规ID + SalaryPlan SalaryPlan `json:"subsidyTypeName" gorm:"foreignKey:SalaryPlanId;references:ID"` //钖祫鏂规 + SalaryFormula string `json:"salaryFormula" gorm:"size:255;not null;comment:钖祫鏂规(缈昏瘧鍚�)"` //钖祫鏂规 + Amount decimal.Decimal `json:"amount" gorm:"type:decimal(12,4);comment:閲戦"` // 閲戦 } PayrollOtherSubsidiesSearch struct { @@ -83,11 +84,6 @@ return slf } -func (slf *PayrollOtherSubsidiesSearch) SetSubsidyType(subsidyType constvar.PayrollSubsidyType) *PayrollOtherSubsidiesSearch { - slf.SubsidyType = subsidyType - return slf -} - func (slf *PayrollOtherSubsidiesSearch) build() *gorm.DB { var db = slf.Orm.Table(slf.TableName()) @@ -113,10 +109,6 @@ if slf.WorkerID != "" { db = db.Where("worker_id = ?", slf.WorkerID) - } - - if slf.SubsidyType != "" { - db = db.Where("subsidy_type = ?", slf.SubsidyType) } db.Where("1 = 1") diff --git a/models/payroll_production_car.go b/models/payroll_production_car.go index 51f6e17..36e3b47 100644 --- a/models/payroll_production_car.go +++ b/models/payroll_production_car.go @@ -19,9 +19,10 @@ PayrollProductionCar struct { BaseModelInt Cycle string `json:"cycle" gorm:"index;size:20;not null;comment:缁熻鍛ㄦ湡锛堝勾鏈堟棩锛�"` //缁熻鍛ㄦ湡锛堝勾鏈堟棩锛� - WorkshopNumber string `json:"workshopNumber" gorm:"size:255;not null;comment:杞﹂棿缂栧彿"` // 杞﹂棿缂栧彿 - GroupNumber int `json:"groupNumber" gorm:"size:11;not null;comment:缁勫埆"` // 缁勫埆 - CarNumber int `json:"carNumber" gorm:"size:11;not null;comment:杞﹀彴鍙�"` // 杞﹀彴鍙� + WorkshopId uint `json:"workshopId" gorm:"type:int(11);comment:杞﹂棿Id"` + WorkshopNumber string `json:"workshopNumber" gorm:"size:255;not null;comment:杞﹂棿缂栧彿"` // 杞﹂棿缂栧彿 + GroupNumber int `json:"groupNumber" gorm:"size:11;not null;comment:缁勫埆"` // 缁勫埆 + CarNumber int `json:"carNumber" gorm:"size:11;not null;comment:杞﹀彴鍙�"` // 杞﹀彴鍙� CarWorkQuantity int `json:"carWorkQuantity" gorm:"size:11;default:1;comment:杞﹀彴鍏宠仈浜哄憳鏁伴噺"` // 杞﹀彴鍏宠仈浜哄憳鏁伴噺 CarWorkIds string `json:"carWorkIds" gorm:"size:255;comment:杞﹀彴鍏宠仈鎸¤溅宸D"` // 杞﹀彴鍏宠仈鎸¤溅宸D diff --git a/models/payroll_production_group.go b/models/payroll_production_group.go index 03a5da8..0c43b4e 100644 --- a/models/payroll_production_group.go +++ b/models/payroll_production_group.go @@ -11,7 +11,8 @@ // PayrollProductionGroup 宸ヨ祫璁$畻-灏忕粍姣忓ぉ鐨勪骇閲忕粺璁� PayrollProductionGroup struct { BaseModelInt - Cycle string `json:"cycle" gorm:"index;size:20;not null;comment:缁熻鍛ㄦ湡"` //缁熻鍛ㄦ湡锛堝勾鏈堟棩锛� + Cycle string `json:"cycle" gorm:"index;size:20;not null;comment:缁熻鍛ㄦ湡"` //缁熻鍛ㄦ湡锛堝勾鏈堟棩锛� + WorkshopId uint `json:"workshopId" gorm:"type:int(11);comment:杞﹂棿Id"` WorkshopNumber string `json:"workshopNumber" gorm:"size:50;not null;comment:杞﹂棿缂栧彿"` // 杞﹂棿缂栧彿 WorkshopName string `json:"workshopName" gorm:"size:50;comment:杞﹂棿鍚嶇О"` // 杞﹂棿鍚嶇О GroupNumber int `json:"groupNumber" gorm:"size:11;not null;comment:缁勫埆"` // 缁勫埆 diff --git a/models/payroll_production_weavers.go b/models/payroll_production_weavers.go index 9698f75..b865685 100644 --- a/models/payroll_production_weavers.go +++ b/models/payroll_production_weavers.go @@ -16,6 +16,7 @@ WorkType WorkTypeManage `json:"workType" gorm:"foreignKey:WorkTypeID;references:ID"` WorkerID string `json:"workerId" gorm:"size:200;not null;comment:鍛樺伐ID"` //鍛樺伐ID Worker Worker `json:"worker" gorm:"foreignKey:WorkerID;references:ID"` + WorkshopId uint `json:"workshopId" gorm:"type:int(11);comment:杞﹂棿Id"` WorkshopNumber string `json:"workshopNumber" gorm:"size:255;not null;comment:杞﹂棿缂栧彿"` // 杞﹂棿缂栧彿 GroupNumber int `json:"groupNumber" gorm:"size:11;not null;comment:缁勫埆"` // 缁勫埆 CarNumbers string `json:"carNumbers" gorm:"size:255;not null;comment:杞﹀彴鍙�"` // 杞﹀彴鍙� @@ -40,7 +41,7 @@ ) func (slf PayrollProductionWeavers) TableName() string { - return "silk_payroll_production_employee" + return "silk_payroll_production_weavers" } // NewPayrollProductionWeaversSearch 鍛樺伐姣忓ぉ鐨勪骇閲忕粺璁� @@ -320,3 +321,25 @@ return records, nil } + +type WeaversAmount struct { + WorkerID string `json:"workerID"` + SilkQuantity decimal.Decimal `json:"silkQuantity"` // 涓濋噺 + SilkTotalAmount decimal.Decimal `json:"silkTotalAmount"` // 涓濋噺鎬讳环 + BadSilkQuantity decimal.Decimal `json:"badSilkQuantity"` // 閲庣氦鏁伴噺 + BadSilkTotalAmount decimal.Decimal `json:"badSilkTotalAmount"` // 閲庣氦鎬讳环 + FinishTotalAmount decimal.Decimal `json:"finishTotalAmount"` // 鎴愬搧閲戦 +} + +// FindWeaversAmount 鎸¤溅宸ユ湀宸ヨ祫 +func (slf *PayrollProductionWeaversSearch) FindWeaversAmount(monthly string) ([]*WeaversAmount, error) { + var ( + records = make([]*WeaversAmount, 0) + db = slf.Orm.Table(slf.TableName()) + ) + db.Select("worker_id, sum(silk_quantity) as silk_quantity, sum(silk_total_amount) as silk_total_amount, sum(bad_silk_quantity) as bad_silk_quantity, sum(bad_silk_total_amount) as bad_silk_total_amount, sum(finish_total_amount) as finish_total_amount"). + Where("cycle like ?", monthly+"%"). + Group("worker_id") + + return records, db.Find(&records).Error +} diff --git a/models/payroll_working_hours.go b/models/payroll_working_hours.go index d9f04da..caff2b4 100644 --- a/models/payroll_working_hours.go +++ b/models/payroll_working_hours.go @@ -4,8 +4,10 @@ "fmt" "github.com/shopspring/decimal" "gorm.io/gorm" + "math/rand" "silkserver/constvar" "silkserver/pkg/mysqlx" + "silkserver/utils" ) // 宸ユ椂缁熻 @@ -19,7 +21,7 @@ WorkType WorkTypeManage `json:"workType" gorm:"foreignKey:WorkTypeID;references:ID"` WorkerID string `json:"workerId" gorm:"size:200;not null;comment:鍛樺伐ID"` //鍛樺伐ID Worker Worker `json:"worker" gorm:"foreignKey:WorkerID;references:ID"` - WorkshopId uint `json:"workshopId" gorm:"size:11;comment:杞﹂棿ID"` // 杞﹂棿ID + WorkshopId uint `json:"workshopId" gorm:"type:int(11);comment:杞﹂棿Id"` WorkshopNumber string `json:"workshopNumber" gorm:"size:255;not null;comment:杞﹂棿缂栧彿"` // 杞﹂棿缂栧彿 GroupNumber int `json:"groupNumber" gorm:"size:11;not null;comment:缁勫埆"` // 缁勫埆 StartCarNumber int `json:"startCarNumbers" gorm:"size:11;comment:杞﹀彴鍙峰紑濮�"` // 杞﹀彴鍙峰垪琛� @@ -321,3 +323,91 @@ return records, nil } + +type GroupWorker struct { + Cycle string `json:"cycle"` // 缁熻鍛ㄦ湡锛堝勾鏈堟棩锛� + WorkTypeCode constvar.JobType `json:"workTypeCode"` // 宸ョ缂栫爜 + WorkshopNumber string `json:"workshopNumber"` // 杞﹂棿缂栧彿 + GroupNumber int `json:"groupNumber"` // 缁勫埆 + WorkerIds string `json:"workerIds"` // 鍛樺伐ID + WorkerCount int `json:"workerCount"` // 鎸¤溅宸ユ暟閲� +} + +// GroupWorker 姣忓ぉ灏忕粍浜哄憳 +func (slf *PayrollWorkingHoursSearch) GroupWorker(monthly string, workTypeCode constvar.JobType) ([]*GroupWorker, error) { + var ( + records = make([]*GroupWorker, 0) + db = slf.Orm.Table(slf.TableName()) + ) + db.Select("cycle, work_type_code, workshop_number, group_number, group_concat(worker_id) as worker_ids, count(id) as worker_count") + db.Where("cycle like ?", monthly+"%") + if workTypeCode != "" { + db.Where("work_type_code = ?", workTypeCode) + } + db.Group("cycle, work_type_code, workshop_number, group_number") + + return records, db.Find(&records).Error +} + +// InitDefaultData 鍒濆鍖栨暟鎹� +func (slf *PayrollWorkingHoursSearch) InitDefaultData() error { + var ( + db = slf.Orm.Table(slf.TableName()) + total int64 = 0 + ) + + firstDay, lastDay := utils.GetLastMonthPeriod(utils.GetMonthByOffset(-1)) + + for i := 0; i < (lastDay.Day()); i++ { + date := firstDay.AddDate(0, 0, i) + if err := db.Where("cycle = ?", date.Format("2006-01-02")).Count(&total).Error; err != nil { + return err + } + if total != 0 { + return nil + } + + data := make([]*PayrollWorkingHours, 0) + workers, _ := NewWorkerSearch().FindNotTotal() + workshop := rand.Intn(10) + for _, record := range workers { + round := rand.Intn(10) + info := PayrollWorkingHours{ + Cycle: date.Format("2006-01-02"), + WorkTypeID: uint(round + 1), + WorkTypeCode: constvar.JobTypeArr[round], + WorkerID: record.ID, + WorkshopId: uint(workshop + 1), + WorkshopNumber: fmt.Sprintf("100%v", workshop), + GroupNumber: round, + StartCarNumber: round*10 + 1, + EndCarNumber: (round + 1) * 10, + ShiftTime: "08:00-18:00", + ShiftCrossDay: false, + ShiftClockInTime: fmt.Sprintf("07:5%v", round), + ShiftClockOutTime: fmt.Sprintf("20:0%v", round), + } + //if info.WorkTypeCode == constvar.JobTypeWeavers { + // info.StartCarNumber = i*10 + 1 + // info.EndCarNumber = (i + 1) * 10 + //} + + if date.Weekday() == 0 { + info.OvertimeType = constvar.ShiftOvertimeTypeOvertime + info.OvertimeDuration = decimal.NewFromInt32(1) + info.OvertimePay = decimal.NewFromInt32(1 * 90) + } else { + info.OvertimeType = constvar.ShiftOvertimeTypeTimeout + info.OvertimeDuration = decimal.NewFromInt32(int32(workshop)) + info.OvertimePay = decimal.NewFromInt32(int32(workshop * 12)) + } + data = append(data, &info) + } + + if err := slf.CreateBatch(data); err != nil { + return err + } + } + + return nil +} diff --git a/models/worker.go b/models/worker.go index ba2cc2a..a0ac604 100644 --- a/models/worker.go +++ b/models/worker.go @@ -15,6 +15,7 @@ Name string `gorm:"index;type:varchar(191);not null;comment:浜哄憳濮撳悕" json:"name"` PhoneNum string `gorm:"type:varchar(191);comment:鎵嬫満鍙�" json:"phoneNum"` ShopID string `gorm:"type:varchar(191);comment:鎵�灞炶溅闂碔D" json:"shopId"` + ShopNumber string `gorm:"type:varchar(191);comment:鎵�灞炶溅闂寸紪鍙�" json:"shopNumber"` ShopName string `gorm:"type:varchar(191);comment:杞﹂棿鍚嶇О锛屼粎鏌ヨ鐢�" json:"shopName"` Status constvar.WorkerStatus `gorm:"index;type:int(11);comment:浜哄憳鐘舵��" json:"status"` WorkTypeId uint `gorm:"type:int(11);comment:宸ョid" json:"workTypeId"` diff --git a/models/yield_register.go b/models/yield_register.go index 2b863b3..d44a37b 100644 --- a/models/yield_register.go +++ b/models/yield_register.go @@ -15,6 +15,7 @@ CreateTime string `json:"createTime" gorm:"type:varchar(255);comment:鍒涘缓鏃堕棿"` MarketId uint `json:"marketId" gorm:"type:int(11);comment:搴勫彛id"` MarketNumber string `gorm:"size:255;comment:搴勫彛" json:"marketNumber"` //搴勫彛缂栧彿 + WorkshopId uint `json:"workshopId" gorm:"type:int(11);comment:杞﹂棿Id"` WorkshopNumber string `json:"workshopNumber" gorm:"type:varchar(255);comment:杞﹂棿缂栫爜"` GroupNumber int `json:"groupNumber" gorm:"type:int(11);comment:缁勫埆"` Spec string `json:"spec" gorm:"type:varchar(255);comment:瑙勬牸"` diff --git a/service/salary_plan.go b/service/salary_plan.go index df0f491..c7f7aca 100644 --- a/service/salary_plan.go +++ b/service/salary_plan.go @@ -5,21 +5,14 @@ "fmt" "github.com/shopspring/decimal" "gorm.io/gorm" + "regexp" "silkserver/constvar" "silkserver/models" + "silkserver/pkg/logx" + "silkserver/utils/calculator" "strconv" "strings" ) - -type WeaversAmount struct { - WorkerID string `json:"workerID"` - Amount decimal.Decimal `json:"amount"` -} - -// 钖祫璁$畻 -// 绾ゅ害鐧昏锛歴ilk_fineness_register silk_fineness_register_item -// 绾ゅ害妫�楠岋細silk_fineness_check silk_fineness_check_item -// 浜ч噺鐧昏鐧昏锛歴ilk_yield_register_circle // WorkingHours 瀛樿〃锛氬伐鏃惰绠楋紙鏃ユ湡锛堝勾鏈堟棩锛夈�佸伐绉嶃�佸憳宸ュ鍚嶃�佸皬缁勩�佽溅鍙般�佷骇閲忓伐璧勩�佷笂鐝秴鏃讹紙灏忔椂锛夈�佷笂鐝秴鏃讹紙澶╋級銆佸姞鐝紙鍗曠嫭锛夈�佸姞鐝紙鍏ㄨ溅闂达級銆佸嚭鍕わ紙澶╋級銆佸甫寰掞紙澶╋級銆佷骇閲忥紙KG锛夈�佸叾瀹冿級 func WorkingHours(date string) error { @@ -77,10 +70,12 @@ } err = models.WithTransaction(func(db *gorm.DB) error { - - models.NewPayrollWorkingHoursSearch().SetOrm(db).SetCycle(date).Delete() - - models.NewPayrollWorkingHoursSearch().SetOrm(db).CreateBatch(list) + if err := models.NewPayrollWorkingHoursSearch().SetOrm(db).SetCycle(date).Delete(); err != nil { + return err + } + if err := models.NewPayrollWorkingHoursSearch().SetOrm(db).CreateBatch(list); err != nil { + return err + } return nil }) @@ -119,26 +114,47 @@ } // 杞﹀彴鎸¤溅宸ラ噸澶嶄汉鍛樻爣璁� - carEmployeeMap := make(map[string]map[string]bool) // map[杞﹂棿\缁勫埆\杞﹀彿]map[浜哄憳]true - for _, yield := range yieldRegisters { + carEmployeeMap := make(map[string]map[string]bool) // map[杞﹂棿\缁勫埆\杞﹀彿]map[浜哄憳ID]true + // 鏂规1 鏍规嵁鎵撳崱鍛樺伐鏉ユ煡浜烘暟 + workingHourArr := make([]models.PayrollWorkingHours, 0) + for _, workingHour := range workingHours { + for i := workingHour.StartCarNumber; i <= workingHour.EndCarNumber; i++ { + workingHourArr = append(workingHourArr, models.PayrollWorkingHours{ + Cycle: workingHour.Cycle, + WorkTypeID: workingHour.WorkTypeID, + WorkTypeCode: workingHour.WorkTypeCode, + WorkerID: workingHour.WorkerID, + WorkshopId: workingHour.WorkshopId, + WorkshopNumber: workingHour.WorkshopNumber, + GroupNumber: workingHour.GroupNumber, + StartCarNumber: i, + }) + } + } + for _, workingHour := range workingHourArr { + key := fmt.Sprintf("%v%v%v", workingHour.WorkshopNumber, workingHour.GroupNumber, workingHour.StartCarNumber) + carEmployeeMap[key][workingHour.WorkerID] = true + } + + // 鏂规2 鏍规嵁绾ゅ害鐧昏鏉ユ煡浜烘暟 + /* for _, yield := range yieldRegisters { for _, workingHour := range workingHours { if yield.WorkshopNumber == workingHour.WorkshopNumber && yield.GroupNumber == workingHour.GroupNumber { for _, circle := range yield.Circles { if circle.CarNumber >= workingHour.StartCarNumber && circle.CarNumber <= workingHour.EndCarNumber { key := fmt.Sprintf("%v%v%v", workingHour.WorkshopNumber, workingHour.GroupNumber, circle.CarNumber) - tempMap := carEmployeeMap[key] - tempMap[workingHour.WorkerID] = true - carEmployeeMap[key] = tempMap + carEmployeeMap[key][workingHour.WorkerID] = true } } } } - } + }*/ productionCar := make([]*models.PayrollProductionCar, 0) for _, yield := range yieldRegisters { info := models.PayrollProductionCar{ Cycle: date, + WorkshopId: yield.WorkshopId, WorkshopNumber: yield.WorkshopNumber, GroupNumber: yield.GroupNumber, MarketId: yield.MarketId, @@ -175,14 +191,9 @@ // 閲庣氦缁熻 badSilkQuantityMap := make(map[int]map[string]int) // map[杞﹀彿]map[绾ゅ害绛夌骇]鏁伴噺 for _, check := range finenessChecks { - if yield.MarketId == check.FinenessRegister.MarketId && - yield.WorkshopNumber == check.FinenessRegister.WorkshopNumber && - yield.GroupNumber == check.FinenessRegister.WorkshopGroup { - + if yield.MarketId == check.FinenessRegister.MarketId && yield.WorkshopNumber == check.FinenessRegister.WorkshopNumber && yield.GroupNumber == check.FinenessRegister.WorkshopGroup { if strings.Contains(check.FinenessGrade, "閲�") { - temp := badSilkQuantityMap[check.FinenessRegister.Position] - temp[check.FinenessGrade] = temp[check.FinenessGrade] + 1 - badSilkQuantityMap[check.FinenessRegister.Position] = temp + badSilkQuantityMap[check.FinenessRegister.Position][check.FinenessGrade] += 1 } } } @@ -195,23 +206,23 @@ if workIdMap, ok := carEmployeeMap[key]; ok { result.CarWorkQuantity = len(workIdMap) for workId := range workIdMap { - result.CarWorkIds += workId + "," + result.CarWorkIds += fmt.Sprintf("%v,", workId) } } // 閲庣氦 - if bad, ok := badSilkQuantityMap[carNumber]; ok { + if badSilk, ok := badSilkQuantityMap[carNumber]; ok { quantityTmp := 0 totalAmount := decimal.NewFromInt32(0) - for s := range bad { - quantityTmp = quantityTmp + bad[s] + for finenessGrade := range badSilk { + quantityTmp += badSilk[finenessGrade] for _, price := range priceStandards { - if price.MarketId == result.MarketId && price.RawSilkGrade == s { + if price.MarketId == result.MarketId && price.RawSilkGrade == finenessGrade { result.BadSilkUnitAmount = price.PayStandard - totalAmount = totalAmount.Add(result.BadSilkUnitAmount.Mul(decimal.NewFromInt32(int32(bad[s])))) + totalAmount = totalAmount.Add(result.BadSilkUnitAmount.Mul(decimal.NewFromInt32(int32(badSilk[finenessGrade])))) continue } } - result.BadSilkType += s + "," + result.BadSilkType += fmt.Sprintf("%v,", finenessGrade) } result.BadSilkQuantity = decimal.NewFromInt32(int32(quantityTmp)) @@ -230,12 +241,10 @@ } err = models.WithTransaction(func(db *gorm.DB) error { - err := models.NewPayrollProductionCarSearch().SetOrm(db).SetCycle(date).Delete() - if err != nil { + if err := models.NewPayrollProductionCarSearch().SetOrm(db).SetCycle(date).Delete(); err != nil { return err } - err = models.NewPayrollProductionCarSearch().SetOrm(db).CreateBatch(productionCar) - if err != nil { + if err = models.NewPayrollProductionCarSearch().SetOrm(db).CreateBatch(productionCar); err != nil { return err } return nil @@ -255,38 +264,47 @@ return err } + workingHours, err := models.NewPayrollWorkingHoursSearch().SetWorkTypeCode(constvar.JobTypeWeavers).SetCycle(date).FindNotTotal() // 鍛樺伐鐨勫伐鏃剁粺璁� + if err != nil { + return err + } + + groupWorkingHourMap := make(map[string]map[string]bool) + for _, workingHour := range workingHours { + key := fmt.Sprintf("%v%v", workingHour.WorkshopNumber, workingHour.GroupNumber) + groupWorkingHourMap[key][workingHour.WorkerID] = true + } + productionGroupList := make([]*models.PayrollProductionGroup, 0) - var counter int for i := 0; i < len(productionCars); i++ { - counter = 0 - silkQuantity := decimal.NewFromInt32(0) - silkAvgQuantity := decimal.NewFromInt32(0) - silkTotalAmount := decimal.NewFromInt32(0) - silkTotalAvgAmount := decimal.NewFromInt32(0) - badSilkQuantity := decimal.NewFromInt32(0) - badSilkTotalAmount := decimal.NewFromInt32(0) - badSilkTotalAvgAmount := decimal.NewFromInt32(0) - finishTotalAmount := decimal.NewFromInt32(0) - finishTotalAvgAmount := decimal.NewFromInt32(0) - fallingSilkBucket := decimal.NewFromInt32(0) + group := models.PayrollProductionGroup{ + Cycle: date, + WorkshopId: productionCars[i].WorkshopId, + WorkshopNumber: productionCars[i].WorkshopNumber, + GroupNumber: productionCars[i].GroupNumber, + } for j := i; j < len(productionCars); j++ { if productionCars[i].WorkshopNumber == productionCars[j].WorkshopNumber && productionCars[i].GroupNumber == productionGroupList[j].GroupNumber { + counter := 1 + key := fmt.Sprintf("%v%v", productionGroupList[j].WorkshopNumber, productionGroupList[j].GroupNumber) + if le := len(groupWorkingHourMap[key]); le > 0 { + counter = le + } // 涓�杞﹀浜猴紝绠楀钩鍧� - //population := decimal.NewFromInt32(int32(productionCars[j].CarWorkQuantity)) - silkQuantity = silkQuantity.Add(productionCars[j].SilkQuantity) - silkAvgQuantity = silkAvgQuantity.Add(productionCars[j].SilkAvgQuantity) - silkTotalAmount = silkTotalAmount.Add(productionCars[j].SilkTotalAmount) - silkTotalAvgAmount = silkTotalAvgAmount.Add(productionCars[j].SilkTotalAvgAmount) - badSilkQuantity = badSilkQuantity.Add(productionCars[j].BadSilkQuantity) - badSilkTotalAmount = badSilkTotalAmount.Add(productionCars[j].BadSilkTotalAmount) - badSilkTotalAvgAmount = badSilkTotalAvgAmount.Add(productionCars[j].BadSilkTotalAvgAmount) - finishTotalAmount = finishTotalAmount.Add(productionCars[j].FinishTotalAmount) - finishTotalAvgAmount = finishTotalAvgAmount.Add(productionCars[j].FinishTotalAvgAmount) - fallingSilkBucket = fallingSilkBucket.Add(productionCars[j].FallingSilkBucket) + counterD := decimal.NewFromInt32(int32(counter)) + group.SilkQuantity = group.SilkQuantity.Add(productionCars[j].SilkQuantity) + group.SilkAvgQuantity = group.SilkAvgQuantity.Add(productionCars[j].SilkAvgQuantity).Div(counterD) + group.SilkTotalAmount = group.SilkTotalAmount.Add(productionCars[j].SilkTotalAmount) + group.SilkTotalAvgAmount = group.SilkTotalAvgAmount.Add(productionCars[j].SilkTotalAvgAmount).Div(counterD) + group.BadSilkQuantity = group.BadSilkQuantity.Add(productionCars[j].BadSilkQuantity) + group.BadSilkTotalAmount = group.BadSilkTotalAmount.Add(productionCars[j].BadSilkTotalAmount) + group.BadSilkTotalAvgAmount = group.BadSilkTotalAvgAmount.Add(productionCars[j].BadSilkTotalAvgAmount).Div(counterD) + group.FinishTotalAmount = group.FinishTotalAmount.Add(productionCars[j].FinishTotalAmount) + group.FinishTotalAvgAmount = group.FinishTotalAvgAmount.Add(productionCars[j].FinishTotalAvgAmount).Div(counterD) + group.FallingSilkBucket = group.FallingSilkBucket.Add(productionCars[j].FallingSilkBucket) - counter += 1 // 璺宠繃閲嶅椤� if i != j { i += 1 @@ -294,22 +312,7 @@ } } - counterD := decimal.NewFromInt32(int32(counter)) - productionGroupList = append(productionGroupList, &models.PayrollProductionGroup{ - Cycle: date, - WorkshopNumber: productionCars[i].WorkshopNumber, - GroupNumber: productionCars[i].GroupNumber, - FallingSilkBucket: fallingSilkBucket, - SilkQuantity: silkQuantity, - SilkAvgQuantity: silkAvgQuantity.Div(counterD), - SilkTotalAmount: silkTotalAmount, - SilkTotalAvgAmount: silkTotalAmount.Div(counterD), - BadSilkQuantity: badSilkQuantity, - BadSilkTotalAmount: badSilkTotalAmount, - BadSilkTotalAvgAmount: badSilkTotalAmount.Div(counterD), - FinishTotalAmount: finishTotalAmount, - FinishTotalAvgAmount: finishTotalAmount.Div(counterD), - }) + productionGroupList = append(productionGroupList, &group) } err = models.WithTransaction(func(db *gorm.DB) error { @@ -330,18 +333,7 @@ return nil } -// 鏍规嵁涓婃姤璁$畻锛� -// 1銆佽溅鍙板綋鏈堝叏绛夌骇涓濇�婚噺锛� -// 2銆佽溅鍙板綋鏈堝悇绛夌骇涓濇�婚噺锛� -// 3銆佽溅鍙版瘡鏈堢瓑绾у崰姣�=杞﹀彴褰撴湀鍚勭瓑绾т笣鎬婚噺/杞﹀彴褰撴湀鍏ㄧ瓑绾т笣鎬婚噺锛� -// 4銆佽溅鍙版瘡鏈堟瘡浜哄钩鍧囦笣閲�=杞﹀彴褰撴湀鍏ㄧ瓑绾т笣鎬婚噺/杞﹀彴鎸¤溅宸ヤ汉鏁帮紱 -// 5銆佽溅鍙板綋鏈堝叏閲庣氦鎵i櫎閲戦锛� -// 6銆佽溅鍙版瘡鏈堟瘡浜哄钩鍧囬噹绾ゆ墸闄ら噾棰�=褰撴湀鍏ㄩ噹绾ゆ墸闄ら噾棰�/杞﹀彴鎸¤溅宸ヤ汉鏁帮紱 -// 7銆佽溅鍙版瘡鏈堜笣閲忔�婚噾棰� -// 8銆佽溅鍙版瘡鏈堜笣閲忔垚鍝侀噾棰� -// 9銆佽溅鍙版瘡鏈堟瘡浜哄钩鍧囦笣閲忛噾棰�=杞﹀彴姣忔湀涓濋噺鎴愬搧閲戦/杞﹀彴鎸¤溅宸ヤ汉鏁� - -// ProductionEmployee 瀛樿〃锛� 姣忎汉姣忓ぉ鐨勪骇閲忕粺璁$櫥璁� +// ProductionWeavers 瀛樿〃锛� 鎸¤溅宸ユ瘡浜烘瘡澶╃殑浜ч噺缁熻鐧昏 func ProductionWeavers(date string) error { workingHours, err := models.NewPayrollWorkingHoursSearch().SetWorkTypeCode(constvar.JobTypeWeavers).SetCycle(date).FindNotTotal() // 鍛樺伐鐨勫伐鏃剁粺璁� if err != nil { @@ -359,6 +351,7 @@ Cycle: date, WorkTypeID: worker.WorkTypeID, WorkerID: worker.WorkerID, + WorkshopId: worker.WorkshopId, WorkshopNumber: worker.WorkshopNumber, GroupNumber: worker.GroupNumber, } @@ -377,12 +370,10 @@ } err = models.WithTransaction(func(db *gorm.DB) error { - err := models.NewPayrollProductionWeaversSearch().SetOrm(db).SetCycle(date).Delete() - if err != nil { + if err := models.NewPayrollProductionWeaversSearch().SetOrm(db).SetCycle(date).Delete(); err != nil { return err } - err = models.NewPayrollProductionWeaversSearch().SetOrm(db).CreateBatch(productionEmployee) - if err != nil { + if err = models.NewPayrollProductionWeaversSearch().SetOrm(db).CreateBatch(productionEmployee); err != nil { return err } return nil @@ -391,23 +382,19 @@ return nil } -// 瀛樿〃锛氬伐璧勮绠楋紙鏃ユ湡锛堝勾鏈堬級銆佸伐绉嶃�佸憳宸ュ鍚嶃�佸皬缁勩�佽溅鍙般�佺敓浜у伐璧勩�佹弧鍕ゅ锛�=閰嶇疆锛夈�佽秴鏃跺伐璧勶紙=涓婄彮瓒呮椂灏忔椂*5+涓婄彮瓒呮椂澶�*6锛夈�佸姞鐝伐璧勶紙=鍗曠嫭鍔犵彮*80+鍏ㄨ溅闂村姞鐝�*75锛夈�佷氦閫氳ˉ璐达紙=1*鍑哄嫟澶╂暟锛夈�佸甫寰掕ˉ璐达紙=5*甯﹀緬澶╂暟锛夈�� -// 宀椾綅琛ヨ创锛�=閰嶇疆锛夈�佺ぞ淇濊ˉ璐达紙=閰嶇疆锛夈�佸伐榫勮ˉ璐达紙=閰嶇疆锛夈�佷笉杈句繚搴曪紙=閰嶇疆淇濆簳锛夈�佽川閲忓銆佸缃�1銆佸缃�2/娓呭噳琛ヨ创銆佸缃�3/鏃ュ父妫�鏌ャ�佸仠鏈鸿ˉ璐淬�佸簲鍙戝伐璧勩�佸娉級 +type SalaryParameter struct { + SilkQuantity decimal.Decimal // 鏃ヤ骇涓濋噺 + SilkUnitAmount decimal.Decimal // 鐢熶笣鍗曚环 + SilkTotalAmount decimal.Decimal // 鐢熶笣鎬讳环锛�=鏃ヤ骇涓濋噺*鐢熶笣鍗曚环锛� + FallingSilkBucket decimal.Decimal // 妗舵暟 + BadSilkQuantity decimal.Decimal // 閲庣氦鏁伴噺 + BadSilkUnitAmount decimal.Decimal // 閲庣氦鍗曚环 + BadSilkTotalAmount decimal.Decimal // 閲庣氦鎬讳环 -// OtherSubsidies 瀛樿〃锛� 鍏跺畠琛ヨ创 -func OtherSubsidies(date string) error { - - models.NewPayrollOtherSubsidiesSearch() // 鍏跺畠琛ヨ创 - - return nil + GroupCarHeadAvgAmount decimal.Decimal // 鍚岀粍杞﹀ご宸ュ伐璧� + GroupWeaversAvgAmount decimal.Decimal // 鍚岀粍鎸¤溅宸ユ湀骞冲潎宸ヨ祫 + JobDays decimal.Decimal // 鍑哄嫟澶╂暟 } - -// 瀛樿〃锛氳嚜鍔ㄧ极杞﹂棿鍚勬尅杞︺�佽溅澶淬�佷繚鍏ㄧ敓浜у伐璧勮绠� 锛堟棩鏈燂紙骞存湀锛夈�佽溅闂淬�佺粍鍒�佽溅鍙般�佷釜浜轰骇閲忥紙杞﹀彴姣忔湀姣忎汉骞冲潎涓濋噺锛夈�佹尅杞﹀伐宸ヨ祫锛堣溅鍙版瘡鏈堟瘡浜哄钩鍧囦笣閲忛噾棰濓級锛� -// 鏍规嵁涓婃姤璁$畻锛� -// 1銆佹尅杞﹀伐骞冲潎宸ヨ祫锛堜互缁勪负鍗曚綅锛�= 姣忔湀灏忕粍姣忚溅澶村伐璧勪箣鍜�/6锛�70缁級 -// 2銆佽溅澶村伐宸ヨ祫锛堜互缁勪负鍗曚綅锛�= 鎸¤溅宸ュ钩鍧囧伐璧�*1.09锛�1.09涓烘寚瀹氱郴鏁帮級 -// 3銆佷繚鍏ㄥ伐宸ヨ祫锛堜互缁勪负鍗曚綅锛�= 锛堟尅杞﹀伐骞冲潎宸ヨ祫+杞﹀ご宸ュ伐璧勶級/2*1.2锛�1.2涓烘寚瀹氱郴鏁帮級 -// 4銆佹姌100缁尅杞﹀钩鍧囧伐璧勶紙浠ョ粍涓哄崟浣嶏級= 姣忔湀灏忕粍姣忚溅澶村伐璧勪箣鍜�/4锛�100缁級 // SalaryPlan 鐢熶骇宸ヨ祫璁$畻 func SalaryPlan(date string) error { @@ -417,83 +404,216 @@ date = date[:7] } - hours, err := models.NewPayrollWorkingHoursSearch().SetMonthly(date).FindNotTotal() // 鍛樺伐鐨勫伐鏃剁粺璁� + hours, err := models.NewPayrollWorkingHoursSearch().SetOrder("worker_id").SetMonthly(date).FindNotTotal() // 鍛樺伐鐨勫伐鏃剁粺璁� if err != nil { return err } - groups, err := models.NewPayrollProductionGroupSearch().SetOrder("workshop_number,groupnumber"). - SetMonthly(date).FindNotTotal() // 灏忕粍姣忓ぉ鐨勪骇閲忕粺璁� - if err != nil { - return err + jobQuantityMap := make(map[string]int) // 鍛樺伐鍑哄嫟缁熻 + for _, hour := range hours { + jobQuantityMap[hour.WorkerID] += 1 } - // 姣忎釜灏忕粍鐨勫钩鍧囬噾棰� - groupAvgAmountMap := make(map[string]decimal.Decimal) // map[杞﹂棿灏忕粍]骞冲潎閲戦 - fallingSilkBucketMap := make(map[string]decimal.Decimal) // map[杞﹂棿灏忕粍] FallingSilkBucket - var counter int - for i := 0; i < len(groups); i++ { - counter = 0 - groupAvgAmount := decimal.NewFromInt32(0) - fallingSilkBucket := decimal.NewFromInt32(0) - for j := i; j < len(groups); j++ { - if groups[i].WorkshopNumber == groups[j].WorkshopNumber && groups[i].GroupNumber == groups[j].GroupNumber { - groupAvgAmount = groupAvgAmount.Add(groups[j].FinishTotalAvgAmount) - fallingSilkBucket = fallingSilkBucket.Add(groups[j].FallingSilkBucket) - counter += 1 - if i != j { - i += 1 - } - } - } - key := fmt.Sprintf("%v%v", groups[i].WorkshopNumber, groups[i].GroupNumber) - groupAvgAmountMap[key] = groupAvgAmount.Div(decimal.NewFromInt32(int32(counter))) - fallingSilkBucketMap[key] = fallingSilkBucket + + workers, err := models.NewWorkerSearch().FindNotTotal() + workerMap := make(map[string]*models.Worker) + for _, worker := range workers { + workerMap[worker.ID] = worker } // 鎸¤溅宸ュ伐璧� - models.NewPayrollProductionWeaversSearch().Orm.Model(&models.PayrollProductionWeavers{}). - Select("worker_id,sum()").Where("cycle like ?", date+"%") + weaversAmountArr, _ := models.NewPayrollProductionWeaversSearch().SetMonthly(date).FindNotTotal() + weaversAmountMap := make(map[string]*models.PayrollProductionWeavers, len(weaversAmountArr)) + for _, weaver := range weaversAmountArr { + key := fmt.Sprintf("%v%v", weaver.Cycle, weaver.WorkerID) + weaversAmountMap[key] = weaver + } - // 鏌ヨ鍗曚环 - for _, hour := range hours { - key := fmt.Sprintf("%v%v", hour.WorkshopNumber, hour.GroupNumber) + // 宸ョ宸ヨ祫鏂规 map[宸ョ]鏂规 + salaryPlans, _ := models.NewWorkTypeManageSearch().FindNotTotal() + salaryPlansMap := make(map[uint]*models.WorkTypeManage, len(salaryPlans)) + for _, salaryPlan := range salaryPlans { + salaryPlansMap[salaryPlan.ID] = salaryPlan + } - ready70 := decimal.NewFromInt32(6) - ready100 := decimal.NewFromInt32(4) - coefficient := decimal.NewFromInt32(1) - switch hour.WorkTypeCode { - case constvar.JobTypeWeavers: // 鏃ヤ骇閲忓伐璧�=锛堢敓涓濆崟浠�*鏃ヤ骇涓濋噺锛�-锛堥噹绾ゆ暟閲�*閲庣氦鍗曚环锛� 鏈堜骇閲忓伐璧�=鏃ヤ骇閲忓伐璧勪箣鍜� - coefficient.Mul(coefficient).Sub(coefficient.Mul(coefficient)) + groups, err := models.NewPayrollProductionGroupSearch().SetMonthly(date).FindNotTotal() + groupMap := make(map[string]*models.PayrollProductionGroup, len(groups)) + groupByMonthMap := make(map[string]*models.PayrollProductionGroup, len(groups)) + for _, group := range groups { + key := fmt.Sprintf("%v%v%v", group.Cycle, group.WorkshopNumber, group.GroupNumber) + groupMap[key] = group - case constvar.JobTypeCarHead: // 鏈堝伐璧�=70缁尅杞﹀伐鏈堝钩鍧囧伐璧�*绯绘暟 - groupAvgAmountMap[key].Div(ready70).Mul(coefficient) - - case constvar.JobTypeMaintenance: // 鏈堝伐璧�=锛�70缁尅杞﹀伐鏈堝钩鍧囧伐璧勶級+杞﹀ご宸ュ伐璧勶級/2*绯绘暟 锛燂紵锛燂紵 excel涓婄殑鏄�愭尅杞﹀伐鏈堝钩鍧囧伐璧�*绯绘暟銆� - groupAvgAmountMap[key].Div(ready70).Mul(coefficient) - - case constvar.JobTypeBoiled: // 鏃ュ伐璧�=妗舵暟*鐓導鍗曚环 鏈堝伐璧�=鏃ュ伐璧勪箣鍜� - fallingSilkBucketMap[key].Mul(coefficient) - - case constvar.JobTypeScoop: // 鏃ュ伐璧�=妗舵暟*鑸�鑼у崟浠� 鏈堝伐璧�=鏃ュ伐璧勪箣鍜� - fallingSilkBucketMap[key].Mul(coefficient) - - case constvar.JobTypeTransport: // 鏃ュ伐璧�=妗舵暟*閫佽導鍗曚环 鏈堝伐璧�=鏃ュ伐璧勪箣鍜� - fallingSilkBucketMap[key].Mul(coefficient) - - case constvar.JobTypeCleaner: // 鏈堝伐璧�=鍥哄畾宸ヨ祫*鍑哄嫟澶╂暟 - coefficient.Mul(coefficient) - - case constvar.JobTypeMachineCleaner: // 鏈堝伐璧�=鍥哄畾宸ヨ祫*鍑哄嫟澶╂暟 - coefficient.Mul(coefficient) - - case constvar.JobTypeAllPowerful: // 鏈堝伐璧�=鍥哄畾宸ヨ祫*鍑哄嫟澶╂暟 - coefficient.Mul(coefficient) - - case constvar.JobTypeMonitor: // 100缁尅杞﹀伐骞冲潎宸ヨ祫*绯绘暟 - groupAvgAmountMap[key].Div(ready100).Mul(coefficient) - + monthKey := fmt.Sprintf("%v%v", group.WorkshopNumber, group.GroupNumber) + if groupByM, ok := groupByMonthMap[monthKey]; ok { + groupByM.FallingSilkBucket = groupByM.FallingSilkBucket.Add(group.FallingSilkBucket) + groupByM.SilkQuantity = groupByM.SilkQuantity.Add(group.SilkQuantity) + groupByM.SilkAvgQuantity = groupByM.SilkAvgQuantity.Add(group.SilkAvgQuantity) + groupByM.SilkTotalAmount = groupByM.SilkTotalAmount.Add(group.SilkTotalAmount) + groupByM.SilkTotalAvgAmount = groupByM.SilkTotalAvgAmount.Add(group.SilkTotalAvgAmount) + groupByM.BadSilkQuantity = groupByM.BadSilkQuantity.Add(group.BadSilkQuantity) + groupByM.BadSilkTotalAmount = groupByM.BadSilkTotalAmount.Add(group.BadSilkTotalAmount) + groupByM.BadSilkTotalAvgAmount = groupByM.BadSilkTotalAvgAmount.Add(group.BadSilkTotalAvgAmount) + groupByM.FinishTotalAmount = groupByM.FinishTotalAmount.Add(group.FinishTotalAmount) + groupByM.FinishTotalAvgAmount = groupByM.FinishTotalAvgAmount.Add(group.FinishTotalAvgAmount) + groupByMonthMap[monthKey] = groupByM + } else { + groupByMonthMap[monthKey] = group } } + var productionByDay []*models.PayrollOtherSubsidies + // 鎸夊ぉ缁熻,姣忓ぉ鐨勫伐璧� + for _, hour := range hours { + production := models.PayrollOtherSubsidies{ + Cycle: hour.Cycle, + WorkerID: hour.WorkerID, + WorkTypeID: hour.WorkTypeID, + WorkTypeCode: hour.WorkTypeCode, + WorkTypeName: constvar.JobTypeMap[hour.WorkTypeCode], + //SalaryPlanIds: "", + //Amount: decimal.NewFromInt32(0), + } + + // 鎸夊ぉ绠楋細鏃ヤ骇涓濋噺銆佺敓涓濆崟浠枫�佹《鏁般�侀噹绾ゆ暟閲忋�侀噹绾ゅ崟浠� + groupKey := fmt.Sprintf("%v%v%v", hour.Cycle, hour.WorkshopNumber, hour.GroupNumber) + group := groupMap[groupKey] + weaversKey := fmt.Sprintf("%v%v", hour.Cycle, hour.WorkerID) + weavers := weaversAmountMap[weaversKey] + parameter := SalaryParameter{ + SilkQuantity: group.SilkQuantity, + SilkUnitAmount: decimal.NewFromInt32(1), + SilkTotalAmount: weavers.FinishTotalAmount, + FallingSilkBucket: group.FallingSilkBucket, + BadSilkQuantity: group.BadSilkQuantity, + BadSilkUnitAmount: decimal.NewFromInt32(1), + BadSilkTotalAmount: weavers.BadSilkTotalAmount, + } + if workType, ok := salaryPlansMap[hour.WorkTypeID]; ok { + for _, salaryPlan := range workType.SalaryPlans { + if matched, _ := regexp.MatchString("(鏃ヤ骇涓濋噺)|(鐢熶笣鍗曚环)|(妗舵暟)|(閲庣氦鏁伴噺)|(閲庣氦鍗曚环)", salaryPlan.SalaryFormula); matched { + temp := production + formula, s := salaryCalculate(¶meter, salaryPlan) + + temp.SalaryFormula = formula + temp.SalaryPlanId = salaryPlan.ID + temp.Amount = temp.Amount.Add(s) + productionByDay = append(productionByDay, &temp) // 姣忎釜浜虹殑鎵�鏈夋柟妗� + } + + } + } + + } + + err = models.WithTransaction(func(db *gorm.DB) error { + if err := models.NewPayrollOtherSubsidiesSearch().SetOrm(db).SetMonthly(date).Delete(); err != nil { + return err + } + if err = models.NewPayrollOtherSubsidiesSearch().SetOrm(db).CreateBatch(productionByDay); err != nil { + return err + } + return nil + }) + + var constituteByMonth []*models.PayrollConstitute + // 鎸夊ぉ绠楃殑鍚堝苟 + productionByMonthMap := make(map[string]*models.PayrollOtherSubsidies) + for _, production := range productionByDay { + key := fmt.Sprintf("%%", production.WorkerID, production.SalaryPlanId) // 鐢ㄦ埛id/鏂规ID + if groupByM, ok := productionByMonthMap[key]; ok { + productionByMonthMap[key].Amount = groupByM.Amount.Add(production.Amount) + } else { + productionByMonthMap[key] = production + } + } + for _, production := range productionByMonthMap { + constituteByMonth = append(constituteByMonth, &models.PayrollConstitute{ + Cycle: date, + WorkerID: production.WorkerID, + WorkTypeID: production.WorkTypeID, + WorkTypeCode: production.WorkTypeCode, + SalaryPlanId: production.SalaryPlanId, + SalaryFormula: "", + Amount: production.Amount, + CreatedBy: "auto", + }) // 姣忎釜浜虹殑鎵�鏈夋柟妗� + } + + // 鎸夋湀绠楃殑璁$畻 + for hourId, dayCount := range jobQuantityMap { + worker := workerMap[hourId] // 鍛樺伐淇℃伅 + ready70 := decimal.NewFromInt32(6) // 70缁� + //ready100 := decimal.NewFromInt32(4) // 100缁� + //coefficient := decimal.NewFromInt32(1) // 绯绘暟 + + constitute := models.PayrollConstitute{ + Cycle: date, + WorkerID: hourId, + WorkTypeID: worker.WorkTypeId, + WorkTypeCode: worker.WorkTypeCode, + //SalaryPlanId:, + //SalaryFormula:, + //Amount:, + CreatedBy: "auto", + } + + monthKey := fmt.Sprintf("%v%v", worker.ShopNumber, worker.GroupNumber) + group := groupByMonthMap[monthKey] + parameter := SalaryParameter{ + FallingSilkBucket: group.FallingSilkBucket, + GroupWeaversAvgAmount: group.FinishTotalAvgAmount, + GroupCarHeadAvgAmount: group.FinishTotalAvgAmount.Div(ready70), + JobDays: decimal.NewFromInt32(int32(dayCount)), + } + + // 鎸夋湀绠楋細鍚岀粍鎸¤溅宸ユ湀骞冲潎宸ヨ祫銆佸悓缁勮溅澶村伐宸ヨ祫銆佸嚭鍕ゅぉ鏁� + if workType, ok := salaryPlansMap[worker.WorkTypeId]; ok { + for _, salaryPlan := range workType.SalaryPlans { + if matched, _ := regexp.MatchString("(鍚岀粍鎸¤溅宸ユ湀骞冲潎宸ヨ祫)|(鍚岀粍杞﹀ご宸ュ伐璧�)|(鍑哄嫟澶╂暟)", salaryPlan.SalaryFormula); matched { + temp := constitute + formula, s := salaryCalculate(¶meter, salaryPlan) + temp.SalaryFormula = formula + temp.SalaryPlanId = salaryPlan.ID + temp.Amount = temp.Amount.Add(s) + constituteByMonth = append(constituteByMonth, &temp) // 姣忎釜浜虹殑鎵�鏈夋柟妗� + } + + } + } + } + + err = models.WithTransaction(func(db *gorm.DB) error { + if err := models.NewPayrollConstituteSearch().SetOrm(db).SetCycle(date).SetCreatedBy("auto").Delete(); err != nil { + return err + } + if err = models.NewPayrollConstituteSearch().SetOrm(db).CreateBatch(constituteByMonth); err != nil { + return err + } + return nil + }) + return nil } + +// 鏍规嵁鏂规璁$畻鍚勫伐绉嶈柂璧� +func salaryCalculate(parameter *SalaryParameter, salaryPlan *models.SalaryPlan) (string, decimal.Decimal) { + formula := strings.ReplaceAll(salaryPlan.SalaryFormula, " ", "") + //var SplitFixedField = []string{"鏃ヤ骇涓濋噺", "鐢熶笣鍗曚环", "妗舵暟", "閲庣氦鏁伴噺", "閲庣氦鍗曚环", "鍚岀粍鎸¤溅宸ユ湀骞冲潎宸ヨ祫", "鍚岀粍杞﹀ご宸ュ伐璧�", "鍑哄嫟澶╂暟"} + formula = strings.Replace(formula, "鏃ヤ骇涓濋噺*鐢熶笣鍗曚环", parameter.SilkTotalAmount.String(), -1) + formula = strings.Replace(formula, "閲庣氦鏁伴噺*閲庣氦鍗曚环", parameter.BadSilkTotalAmount.String(), -1) + formula = strings.Replace(formula, "鏃ヤ骇涓濋噺", parameter.SilkQuantity.String(), -1) + formula = strings.Replace(formula, "鐢熶笣鍗曚环", parameter.SilkTotalAmount.String(), -1) + formula = strings.Replace(formula, "妗舵暟", parameter.FallingSilkBucket.String(), -1) + formula = strings.Replace(formula, "閲庣氦鏁伴噺", parameter.BadSilkQuantity.String(), -1) + formula = strings.Replace(formula, "閲庣氦鍗曚环", parameter.BadSilkUnitAmount.String(), -1) + formula = strings.Replace(formula, "鍚岀粍鎸¤溅宸ユ湀骞冲潎宸ヨ祫", parameter.GroupWeaversAvgAmount.String(), -1) + formula = strings.Replace(formula, "鍚岀粍杞﹀ご宸ュ伐璧�", parameter.GroupCarHeadAvgAmount.String(), -1) + formula = strings.Replace(formula, "鍑哄嫟澶╂暟", parameter.JobDays.String(), -1) + + result, err := calculator.ParseAndExec(formula) + if err != nil { + logx.Errorf("%s : %v", formula, err) + } + logx.Debugf("%s = %v", formula, result) + + return formula, decimal.NewFromFloat(result) +} diff --git a/utils/calculator/ast.go b/utils/calculator/ast.go new file mode 100644 index 0000000..330f750 --- /dev/null +++ b/utils/calculator/ast.go @@ -0,0 +1,267 @@ +package calculator + +import ( + "errors" + "fmt" + "strconv" +) + +var precedence = map[string]int{"+": 20, "-": 20, "*": 40, "/": 40, "%": 40, "^": 60} + +type ExprAST interface { + toStr() string +} + +type NumberExprAST struct { + Val float64 + Str string +} + +type BinaryExprAST struct { + Op string + Lhs, + Rhs ExprAST +} + +type FunCallerExprAST struct { + Name string + Arg []ExprAST +} + +func (n NumberExprAST) toStr() string { + return fmt.Sprintf( + "NumberExprAST:%s", + n.Str, + ) +} + +func (b BinaryExprAST) toStr() string { + return fmt.Sprintf( + "BinaryExprAST: (%s %s %s)", + b.Op, + b.Lhs.toStr(), + b.Rhs.toStr(), + ) +} + +func (n FunCallerExprAST) toStr() string { + return fmt.Sprintf( + "FunCallerExprAST:%s", + n.Name, + ) +} + +type AST struct { + Tokens []*Token + + source string + currTok *Token + currIndex int + depth int + + Err error +} + +func NewAST(toks []*Token, s string) *AST { + a := &AST{ + Tokens: toks, + source: s, + } + if a.Tokens == nil || len(a.Tokens) == 0 { + a.Err = errors.New("empty token") + } else { + a.currIndex = 0 + a.currTok = a.Tokens[0] + } + return a +} + +func (a *AST) ParseExpression() ExprAST { + a.depth++ // called depth + lhs := a.parsePrimary() + r := a.parseBinOpRHS(0, lhs) + a.depth-- + if a.depth == 0 && a.currIndex != len(a.Tokens) && a.Err == nil { + a.Err = errors.New( + fmt.Sprintf("bad expression, reaching the end or missing the operator\n%s", + ErrPos(a.source, a.currTok.Offset))) + } + return r +} + +func (a *AST) getNextToken() *Token { + a.currIndex++ + if a.currIndex < len(a.Tokens) { + a.currTok = a.Tokens[a.currIndex] + return a.currTok + } + return nil +} + +func (a *AST) getTokPrecedence() int { + if p, ok := precedence[a.currTok.Tok]; ok { + return p + } + return -1 +} + +func (a *AST) parseNumber() NumberExprAST { + f64, err := strconv.ParseFloat(a.currTok.Tok, 64) + if err != nil { + a.Err = errors.New( + fmt.Sprintf("%v\nwant '(' or '0-9' but get '%s'\n%s", + err.Error(), + a.currTok.Tok, + ErrPos(a.source, a.currTok.Offset))) + return NumberExprAST{} + } + n := NumberExprAST{ + Val: f64, + Str: a.currTok.Tok, + } + a.getNextToken() + return n +} + +func (a *AST) parseFunCallerOrConst() ExprAST { + name := a.currTok.Tok + a.getNextToken() + // call func + if a.currTok.Tok == "(" { + f := FunCallerExprAST{} + if _, ok := defFunc[name]; !ok { + a.Err = errors.New( + fmt.Sprintf("function `%s` is undefined\n%s", + name, + ErrPos(a.source, a.currTok.Offset))) + return f + } + a.getNextToken() + exprs := make([]ExprAST, 0) + if a.currTok.Tok == ")" { + // function call without parameters + // ignore the process of parameter resolution + } else { + exprs = append(exprs, a.ParseExpression()) + for a.currTok.Tok != ")" && a.getNextToken() != nil { + if a.currTok.Type == COMMA { + continue + } + exprs = append(exprs, a.ParseExpression()) + } + } + def := defFunc[name] + if def.argc >= 0 && len(exprs) != def.argc { + a.Err = errors.New( + fmt.Sprintf("wrong way calling function `%s`, parameters want %d but get %d\n%s", + name, + def.argc, + len(exprs), + ErrPos(a.source, a.currTok.Offset))) + } + a.getNextToken() + f.Name = name + f.Arg = exprs + return f + } + // call const + if v, ok := defConst[name]; ok { + return NumberExprAST{ + Val: v, + Str: strconv.FormatFloat(v, 'f', 0, 64), + } + } else { + a.Err = errors.New( + fmt.Sprintf("const `%s` is undefined\n%s", + name, + ErrPos(a.source, a.currTok.Offset))) + return NumberExprAST{} + } +} + +func (a *AST) parsePrimary() ExprAST { + switch a.currTok.Type { + case Identifier: + return a.parseFunCallerOrConst() + case Literal: + return a.parseNumber() + case Operator: + if a.currTok.Tok == "(" { + t := a.getNextToken() + if t == nil { + a.Err = errors.New( + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", + ErrPos(a.source, a.currTok.Offset))) + return nil + } + e := a.ParseExpression() + if e == nil { + return nil + } + if a.currTok.Tok != ")" { + a.Err = errors.New( + fmt.Sprintf("want ')' but get %s\n%s", + a.currTok.Tok, + ErrPos(a.source, a.currTok.Offset))) + return nil + } + a.getNextToken() + return e + } else if a.currTok.Tok == "-" { + if a.getNextToken() == nil { + a.Err = errors.New( + fmt.Sprintf("want '0-9' but get '-'\n%s", + ErrPos(a.source, a.currTok.Offset))) + return nil + } + bin := BinaryExprAST{ + Op: "-", + Lhs: NumberExprAST{}, + Rhs: a.parsePrimary(), + } + return bin + } else { + return a.parseNumber() + } + case COMMA: + a.Err = errors.New( + fmt.Sprintf("want '(' or '0-9' but get %s\n%s", + a.currTok.Tok, + ErrPos(a.source, a.currTok.Offset))) + return nil + default: + return nil + } +} + +func (a *AST) parseBinOpRHS(execPrec int, lhs ExprAST) ExprAST { + for { + tokPrec := a.getTokPrecedence() + if tokPrec < execPrec { + return lhs + } + binOp := a.currTok.Tok + if a.getNextToken() == nil { + a.Err = errors.New( + fmt.Sprintf("want '(' or '0-9' but get EOF\n%s", + ErrPos(a.source, a.currTok.Offset))) + return nil + } + rhs := a.parsePrimary() + if rhs == nil { + return nil + } + nextPrec := a.getTokPrecedence() + if tokPrec < nextPrec { + rhs = a.parseBinOpRHS(tokPrec+1, rhs) + if rhs == nil { + return nil + } + } + lhs = BinaryExprAST{ + Op: binOp, + Lhs: lhs, + Rhs: rhs, + } + } +} diff --git a/utils/calculator/def.go b/utils/calculator/def.go new file mode 100644 index 0000000..9632cba --- /dev/null +++ b/utils/calculator/def.go @@ -0,0 +1,158 @@ +package calculator + +import ( + "errors" + "math" +) + +const ( + RadianMode = iota + AngleMode +) + +type defS struct { + argc int + fun func(expr ...ExprAST) float64 +} + +// enum "RadianMode", "AngleMode" +var TrigonometricMode = RadianMode + +var defConst = map[string]float64{ + "pi": math.Pi, +} + +var defFunc map[string]defS + +func init() { + defFunc = map[string]defS{ + "sin": {1, defSin}, + "cos": {1, defCos}, + "tan": {1, defTan}, + "cot": {1, defCot}, + "sec": {1, defSec}, + "csc": {1, defCsc}, + + "abs": {1, defAbs}, + "ceil": {1, defCeil}, + "floor": {1, defFloor}, + "round": {1, defRound}, + "sqrt": {1, defSqrt}, + "cbrt": {1, defCbrt}, + + "noerr": {1, defNoerr}, + + "max": {-1, defMax}, + "min": {-1, defMin}, + } +} + +// sin(pi/2) = 1 +func defSin(expr ...ExprAST) float64 { + return math.Sin(expr2Radian(expr[0])) +} + +// cos(0) = 1 +func defCos(expr ...ExprAST) float64 { + return math.Cos(expr2Radian(expr[0])) +} + +// tan(pi/4) = 1 +func defTan(expr ...ExprAST) float64 { + return math.Tan(expr2Radian(expr[0])) +} + +// cot(pi/4) = 1 +func defCot(expr ...ExprAST) float64 { + return 1 / defTan(expr...) +} + +// sec(0) = 1 +func defSec(expr ...ExprAST) float64 { + return 1 / defCos(expr...) +} + +// csc(pi/2) = 1 +func defCsc(expr ...ExprAST) float64 { + return 1 / defSin(expr...) +} + +// abs(-2) = 2 +func defAbs(expr ...ExprAST) float64 { + return math.Abs(ExprASTResult(expr[0])) +} + +// ceil(4.2) = ceil(4.8) = 5 +func defCeil(expr ...ExprAST) float64 { + return math.Ceil(ExprASTResult(expr[0])) +} + +// floor(4.2) = floor(4.8) = 4 +func defFloor(expr ...ExprAST) float64 { + return math.Floor(ExprASTResult(expr[0])) +} + +// round(4.2) = 4 +// round(4.6) = 5 +func defRound(expr ...ExprAST) float64 { + return math.Round(ExprASTResult(expr[0])) +} + +// sqrt(4) = 2 +// sqrt(4) = abs(sqrt(4)) +// returns only the absolute value of the result +func defSqrt(expr ...ExprAST) float64 { + return math.Sqrt(ExprASTResult(expr[0])) +} + +// cbrt(27) = 3 +func defCbrt(expr ...ExprAST) float64 { + return math.Cbrt(ExprASTResult(expr[0])) +} + +// max(2) = 2 +// max(2, 3) = 3 +// max(2, 3, 1) = 3 +func defMax(expr ...ExprAST) float64 { + if len(expr) == 0 { + panic(errors.New("calling function `max` must have at least one parameter.")) + } + if len(expr) == 1 { + return ExprASTResult(expr[0]) + } + maxV := ExprASTResult(expr[0]) + for i := 1; i < len(expr); i++ { + v := ExprASTResult(expr[i]) + maxV = math.Max(maxV, v) + } + return maxV +} + +// min(2) = 2 +// min(2, 3) = 2 +// min(2, 3, 1) = 1 +func defMin(expr ...ExprAST) float64 { + if len(expr) == 0 { + panic(errors.New("calling function `min` must have at least one parameter.")) + } + if len(expr) == 1 { + return ExprASTResult(expr[0]) + } + maxV := ExprASTResult(expr[0]) + for i := 1; i < len(expr); i++ { + v := ExprASTResult(expr[i]) + maxV = math.Min(maxV, v) + } + return maxV +} + +// noerr(1/0) = 0 +// noerr(2.5/(1-1)) = 0 +func defNoerr(expr ...ExprAST) (r float64) { + defer func() { + if e := recover(); e != nil { + r = 0 + } + }() + return ExprASTResult(expr[0]) +} diff --git a/utils/calculator/doc.go b/utils/calculator/doc.go new file mode 100644 index 0000000..f73a3ca --- /dev/null +++ b/utils/calculator/doc.go @@ -0,0 +1,22 @@ +/* +math-engine + +-------- + +鏁板琛ㄨ揪寮忚В鏋愯绠楀紩鎿庡簱 + +浣跨敤 Go 瀹炵幇鐨勬暟瀛﹁〃杈惧紡瑙f瀽璁$畻寮曟搸搴擄紝瀹冨皬宸э紝鏃犱换浣曚緷璧栵紝鍏锋湁鎵╁睍鎬�(姣斿鍙互娉ㄥ唽鑷繁鐨勫嚱鏁板埌寮曟搸涓�)锛屾瘮杈冨畬鏁寸殑瀹屾垚浜嗘暟瀛﹁〃杈惧紡瑙f瀽鎵ц锛屽寘鎷瘝娉曞垎鏋愩�佽娉曞垎鏋愩�佹瀯寤篈ST銆佽繍琛屻�� + +`go get -u github.com/dengsgo/math-engine` + +鑳藉澶勭悊鐨勮〃杈惧紡鏍蜂緥锛� +- `1+127-21+(3-4)*6/2.5` +- `(88+(1+8)*6)/2+99` +- `123_345_456 * 1.5 - 2 ^ 4` +- `-4 * 6 + 2e2 - 1.6e-3` +- `sin(pi/2)+cos(45-45*1)+tan(pi/4)` +- `99+abs(-1)-ceil(88.8)+floor(88.8)` +- `max(min(2^3, 3^2), 10*1.5-7)` +- `double(6) + 3` , `double`鏄竴涓嚜瀹氫箟鐨勫嚱鏁� +*/ +package calculator diff --git a/utils/calculator/parser.go b/utils/calculator/parser.go new file mode 100644 index 0000000..94f6271 --- /dev/null +++ b/utils/calculator/parser.go @@ -0,0 +1,168 @@ +package calculator + +import ( + "errors" + "fmt" + "strings" +) + +const ( + Identifier = iota + // e.g. 50 + Literal + // e.g. + - * / + Operator + // , + COMMA +) + +type Token struct { + // raw characters + Tok string + // type with Literal/Operator + Type, + Flag int + + Offset int +} + +type Parser struct { + Source string + + ch byte + offset int + + err error +} + +func Parse(s string) ([]*Token, error) { + p := &Parser{ + Source: s, + err: nil, + ch: s[0], + } + toks := p.parse() + if p.err != nil { + return nil, p.err + } + return toks, nil +} + +func (p *Parser) parse() []*Token { + toks := make([]*Token, 0) + for { + tok := p.nextTok() + if tok == nil { + break + } + toks = append(toks, tok) + } + return toks +} + +func (p *Parser) nextTok() *Token { + if p.offset >= len(p.Source) || p.err != nil { + return nil + } + var err error + for p.isWhitespace(p.ch) && err == nil { + err = p.nextCh() + } + start := p.offset + var tok *Token + switch p.ch { + case + '(', + ')', + '+', + '-', + '*', + '/', + '^', + '%': + tok = &Token{ + Tok: string(p.ch), + Type: Operator, + } + tok.Offset = start + err = p.nextCh() + + case + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9': + for p.isDigitNum(p.ch) && p.nextCh() == nil { + if (p.ch == '-' || p.ch == '+') && p.Source[p.offset-1] != 'e' { + break + } + } + tok = &Token{ + Tok: strings.ReplaceAll(p.Source[start:p.offset], "_", ""), + Type: Literal, + } + tok.Offset = start + + case ',': + tok = &Token{ + Tok: string(p.ch), + Type: COMMA, + } + tok.Offset = start + err = p.nextCh() + + default: + if p.isChar(p.ch) { + for p.isWordChar(p.ch) && p.nextCh() == nil { + } + tok = &Token{ + Tok: p.Source[start:p.offset], + Type: Identifier, + } + tok.Offset = start + } else if p.ch != ' ' { + s := fmt.Sprintf("symbol error: unknown '%v', pos [%v:]\n%s", + string(p.ch), + start, + ErrPos(p.Source, start)) + p.err = errors.New(s) + } + } + return tok +} + +func (p *Parser) nextCh() error { + p.offset++ + if p.offset < len(p.Source) { + p.ch = p.Source[p.offset] + return nil + } + return errors.New("EOF") +} + +func (p *Parser) isWhitespace(c byte) bool { + return c == ' ' || + c == '\t' || + c == '\n' || + c == '\v' || + c == '\f' || + c == '\r' +} + +func (p *Parser) isDigitNum(c byte) bool { + return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+' +} + +func (p *Parser) isChar(c byte) bool { + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' +} + +func (p *Parser) isWordChar(c byte) bool { + return p.isChar(c) || '0' <= c && c <= '9' +} diff --git a/utils/calculator/util.go b/utils/calculator/util.go new file mode 100644 index 0000000..7dbb079 --- /dev/null +++ b/utils/calculator/util.go @@ -0,0 +1,141 @@ +package calculator + +import ( + "errors" + "fmt" + "math" + "math/big" + "strconv" + "strings" +) + +// ParseAndExec : Top level function +// Analytical expression and execution +// err is not nil if an error occurs (including arithmetic runtime errors) +func ParseAndExec(s string) (r float64, err error) { + toks, err := Parse(s) + if err != nil { + return 0, err + } + ast := NewAST(toks, s) + if ast.Err != nil { + return 0, ast.Err + } + ar := ast.ParseExpression() + if ast.Err != nil { + return 0, ast.Err + } + defer func() { + if e := recover(); e != nil { + err = e.(error) + } + }() + return ExprASTResult(ar), err +} + +func ErrPos(s string, pos int) string { + r := strings.Repeat("-", len(s)) + "\n" + s += "\n" + for i := 0; i < pos; i++ { + s += " " + } + s += "^\n" + return r + s + r +} + +// Pow the integer power of a number +func Pow(x float64, n float64) float64 { + return math.Pow(x, n) +} + +func expr2Radian(expr ExprAST) float64 { + r := ExprASTResult(expr) + if TrigonometricMode == AngleMode { + r = r / 180 * math.Pi + } + return r +} + +// Float64ToStr float64 -> string +func Float64ToStr(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} + +// RegFunction is Top level function +// register a new function to use in expressions +// name: be register function name. the same function name only needs to be registered once. +// argc: this is a number of parameter signatures. should be -1, 0, or a positive integer +// +// -1 variable-length argument; >=0 fixed numbers argument +// +// fun: function handler +func RegFunction(name string, argc int, fun func(...ExprAST) float64) error { + if len(name) == 0 { + return errors.New("RegFunction name is not empty") + } + if argc < -1 { + return errors.New("RegFunction argc should be -1, 0, or a positive integer") + } + if _, ok := defFunc[name]; ok { + return errors.New("RegFunction name is already exist") + } + defFunc[name] = defS{argc, fun} + return nil +} + +// ExprASTResult is a Top level function +// AST traversal +// if an arithmetic runtime error occurs, a panic exception is thrown +func ExprASTResult(expr ExprAST) float64 { + var l, r float64 + switch expr.(type) { + case BinaryExprAST: + ast := expr.(BinaryExprAST) + l = ExprASTResult(ast.Lhs) + r = ExprASTResult(ast.Rhs) + switch ast.Op { + case "+": + lh, _ := new(big.Float).SetString(Float64ToStr(l)) + rh, _ := new(big.Float).SetString(Float64ToStr(r)) + f, _ := new(big.Float).Add(lh, rh).Float64() + return f + case "-": + lh, _ := new(big.Float).SetString(Float64ToStr(l)) + rh, _ := new(big.Float).SetString(Float64ToStr(r)) + f, _ := new(big.Float).Sub(lh, rh).Float64() + return f + case "*": + f, _ := new(big.Float).Mul(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64() + return f + case "/": + if r == 0 { + panic(errors.New( + fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g/%g]", + l, + r))) + } + f, _ := new(big.Float).Quo(new(big.Float).SetFloat64(l), new(big.Float).SetFloat64(r)).Float64() + return f + case "%": + if r == 0 { + panic(errors.New( + fmt.Sprintf("violation of arithmetic specification: a division by zero in ExprASTResult: [%g%%%g]", + l, + r))) + } + return float64(int(l) % int(r)) + case "^": + return Pow(l, r) + default: + + } + case NumberExprAST: + return expr.(NumberExprAST).Val + case FunCallerExprAST: + f := expr.(FunCallerExprAST) + def := defFunc[f.Name] + return def.fun(f.Arg...) + } + + return 0.0 +} diff --git a/utils/calculator/util_test.go b/utils/calculator/util_test.go new file mode 100644 index 0000000..aac26aa --- /dev/null +++ b/utils/calculator/util_test.go @@ -0,0 +1,240 @@ +package calculator + +import ( + "math/rand" + "testing" + "time" +) + +func TestParseAndExecSimple(t *testing.T) { + type U struct { + Expr string + R float64 + } + exprs := []U{ + {"1", 1}, + {"--1", 1}, + {"1+2", 3}, + {"-1+2", 1}, + {"-(1+2)", -3}, + {"-(1+2)*5", -15}, + {"-(1+2)*5/3", -5}, + {"1+(-(1+2)*5/3)", -4}, + {"3^4", 81}, + {"3^4.5", 140.29611541307906}, + {"3.5^4.5", 280.7412308013823}, + {"8%2", 0}, + {"8%3", 2}, + {"8%3.5", 2}, + {"1e2", 100}, + {"1e+2", 100}, + {"1e-2", 0.01}, + {"1e-2+1e2", 100.01}, + {"1e-2+1e2*6/3", 200.01}, + {"(1e-2+1e2)*6/3", 200.02}, + {"(88*8)+(1+1+1+1)+(6/1.5)-(99%9*(2^4))", 712}, + {"1/3*3", 1}, + {"123_456_789", 123456789}, + {"123_456_789___", 123456789}, + {"pi", 3.141592653589793}, + {"abs(1)", 1}, + {"abs(-1)", 1}, + {"ceil(90.2)", 91}, + {"ceil(90.8)", 91}, + {"ceil(90.0)", 90}, + {"floor(90.2)", 90}, + {"floor(90.8)", 90}, + {"floor(90.0)", 90}, + {"round(90.0)", 90}, + {"round(90.4)", 90}, + {"round(90.5)", 91}, + {"round(90.9)", 91}, + {"sqrt(4)", 2}, + {"cbrt(27)", 3}, + {"sqrt(4) + cbrt(27)", 5}, + {"sqrt(2^2) + cbrt(3^3)", 5}, + {"127^2+5/2-sqrt(2^2) + cbrt(3^3)", 16132.5}, + {"max(2)", 2}, + {"max(abs(1)+10)", 11}, + {"max(abs(1)+10)*2-1", 21}, + {"max(2,3.5)", 3.5}, + {"max(2^3,3+abs(-1)*6)", 9}, + {"max(2^3,3+abs(-1)*6, 20)", 20}, + {"max(2^3,3+abs(-1)*6,ceil(9.4))", 10}, + {"max(1,2,3,4,5,6,10,7,4,5,6,9.8)", 10}, + {"min(3.5)", 3.5}, + {"min(ceil(1.2))", 2}, + {"min(2,3.5)", 2}, + {"min(2^3,3+abs(-1)*6)", 8}, + {"min(2^3,3+abs(-1)*6,1^10)", 1}, + {"min(99.1,0.2,3,4,5,6,10,7,4,5,6,9.8)", 0.2}, + {"max(2^3,3^2)", 9}, + {"min(2^3,3^2)", 8}, + {"noerr(1/0)", 0}, + {"noerr(1/(1-1))", 0}, + {"0.1+0.2", 0.3}, + {"0.3-0.1", 0.2}, + {"10^-1", 0.1}, + {"10^-2", 0.01}, + {"10^-1*100", 10}, + {"10%0", 0}, + } + for _, e := range exprs { + r, _ := ParseAndExec(e.Expr) + if r != e.R { + t.Error(e, " ParseAndExec:", r) + } + } +} + +func TestParseAndExecTrigonometric(t *testing.T) { + type U struct { + Expr string + RadianMode float64 + AngleMode float64 + } + exprs := []U{ + {"sin(pi/2)", 1, 0.027412133592044294}, + {"csc(pi/2)", 1, 36.48019577324057}, + {"cos(0)", 1, 1}, + {"sec(0)", 1, 1}, + {"tan(pi/4)", 1, 0.013708642534394057}, + {"cot(pi/4)", 1, 72.94668290394674}, + + {"sin(90)", 0.893996663600558, 1}, + {"csc(90)", 1.1185724071637082, 1}, + {"cos(0)", 1, 1}, + {"sec(0)", 1, 1}, + {"tan(45)", 1.6197751905438615, 1}, + {"cot(45)", 0.6173696237835551, 1}, + } + for _, e := range exprs { + TrigonometricMode = RadianMode + r, _ := ParseAndExec(e.Expr) + if r != e.RadianMode { + t.Error(e, " ParseAndExec RadianMode:", r) + } + TrigonometricMode = AngleMode + r, _ = ParseAndExec(e.Expr) + if r != e.AngleMode { + t.Error(e, " ParseAndExec AngleMode:", r) + } + } +} + +func TestRegFunction(t *testing.T) { + funs := []struct { + Name string + Argc int + Fun func(expr ...ExprAST) float64 + Exp string + R float64 + }{ + { + "double", + 1, + func(expr ...ExprAST) float64 { + return ExprASTResult(expr[0]) * 2 + }, + "double(6)", + 12, + }, + { + "percentage50", + 1, + func(expr ...ExprAST) float64 { + return ExprASTResult(expr[0]) / 2 + }, + "percentage50(6)", + 3, + }, + { + "range", + 0, + func(expr ...ExprAST) float64 { + return 10.0 + }, + "range()", + 10, + }, + { + "choice", + -1, + func(expr ...ExprAST) float64 { + rand.Seed(time.Now().UnixNano()) + return ExprASTResult(expr[rand.Intn(len(expr))]) + }, + "choice(1.1, 9.8, 2.5, 100)", + 10, + }, + } + for _, f := range funs { + _ = RegFunction(f.Name, f.Argc, f.Fun) + r, err := ParseAndExec(f.Exp) + if f.Name == "choice" { + if !inSlices(r, []float64{1.1, 9.8, 2.5, 100}) { + t.Error(err, "RegFunction errors when register new function: ", f.Name) + } + continue + } else if r != f.R { + t.Error(err, "RegFunction errors when register new function: ", f.Name) + } + } + +} + +func TestParseAndExecError(t *testing.T) { + exprs := []string{ + "(", + "((((((", + "((xscdfddff", + "(1", + "(1+", + "1+", + "1*", + "+2344", + "3+(", + "4+(90-", + "3-(4*7-2)+", + "3-(4*7-2)+98*", + "1#1", + "_123_456_789___", + "1ee3+3", + "sin()", + "sin", + "pi(", + "sin(1, 50)", + "max", + "max()", + "max(1,)", + "max(1,4,6,7,5,)", + "min", + "min(,)", + "min()", + "min(1,)", + "min(1,998,4,23,234,2,)", + "min(1,998,4,23,234,2,,,)", + "1/0", + "99.9 / (2-1-1)", + "(1+2)3", + "1+1 111", + "1+1 111+2", + "1 3", + "1 3-", + } + for _, e := range exprs { + _, err := ParseAndExec(e) + if err == nil { + t.Error(e, " this is error expr!") + } + } +} + +func inSlices(target float64, s []float64) bool { + for _, v := range s { + if v == target { + return true + } + } + return false +} diff --git a/utils/stringsx.go b/utils/stringsx.go new file mode 100644 index 0000000..0e96416 --- /dev/null +++ b/utils/stringsx.go @@ -0,0 +1,22 @@ +package utils + +import ( + "strings" +) + +var SplitOperatorSymbol = []rune{'+', '-', '*', '/'} +var SplitBrackets = []rune{'(', ')'} +var SplitFixedField = []string{"鏃ヤ骇涓濋噺", "鐢熶笣鍗曚环", "妗舵暟", "閲庣氦鏁伴噺", "閲庣氦鍗曚环", "鍚岀粍鎸¤溅宸ユ湀骞冲潎宸ヨ祫", "鍚岀粍杞﹀ご宸ュ伐璧�", "鍑哄嫟澶╂暟"} + +func SplitString(str string, splitArr []rune) []string { + split := func(r rune) bool { + for _, v := range splitArr { + if v == r { + return true + } + } + return false + } + a := strings.FieldsFunc(str, split) + return a +} diff --git a/utils/timex.go b/utils/timex.go index 230c96e..98ca7b1 100644 --- a/utils/timex.go +++ b/utils/timex.go @@ -17,3 +17,26 @@ return true } + +func GetMonthByOffset(offset int) time.Time { + return time.Now().AddDate(0, offset, 0) +} + +// GetLastMonthPeriod 杩斿洖涓婁釜鏈堢殑鏈堝垵鏃堕棿鍜屾湀鏈椂闂� +func GetLastMonthPeriod(now time.Time) (firstDay time.Time, lastDay time.Time) { + // 鑾峰彇鏈釜鏈堢殑绗竴澶╃殑鏃ユ湡锛堝嵆鏈湀鏈堝垵锛� + firstDayOfThisMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + + nextMonth := now.AddDate(0, 1, 0) + // 鑾峰彇涓嬫湀绗竴澶╃殑鏃ユ湡锛堝嵆涓嬩釜鏈堟湀鍒濓級 + firstDayOfNextMonth := time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, now.Location()) + + // 涓嬩釜鏈堟湀鏈椂闂村嵆涓烘湰鏈堟湀鍒濆噺鍘讳竴绉� + lastDayOfThisMonth := firstDayOfNextMonth.Add(-time.Second) + + return firstDayOfThisMonth, lastDayOfThisMonth +} + +func GetMonthDuration(d time.Time) (duration int) { + return d.AddDate(0, 1, -1).Day() +} -- Gitblit v1.8.0