| | |
| | | PayrollProductionWeavers{}, |
| | | PayrollProductionGroup{}, |
| | | PayrollOtherSubsidies{}, |
| | | PayrollConstitute{}, |
| | | PayrollSalaryPlan{}, |
| | | ) |
| | | return err |
| | |
| | | models := []interface{}{ |
| | | NewAttendanceRuleSearch(), |
| | | //NewPayrollSalaryPlanSearch(), |
| | | NewPayrollWorkingHoursSearch(), |
| | | } |
| | | for _, model := range models { |
| | | if id, ok := model.(InitDefaultData); ok { |
| New file |
| | |
| | | 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:工种代码"` //工种代码 |
| | | 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 |
| | | } |
| | |
| | | // 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:工种代码"` //工种代码 |
| | | 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:工种代码"` //工种代码 |
| | | 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 { |
| | |
| | | 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()) |
| | | |
| | |
| | | |
| | | if slf.WorkerID != "" { |
| | | db = db.Where("worker_id = ?", slf.WorkerID) |
| | | } |
| | | |
| | | if slf.SubsidyType != "" { |
| | | db = db.Where("subsidy_type = ?", slf.SubsidyType) |
| | | } |
| | | |
| | | db.Where("1 = 1") |
| | |
| | | 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:车台关联挡车工ID"` // 车台关联挡车工ID |
| | |
| | | // 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:组别"` // 组别 |
| | |
| | | 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:车台号"` // 车台号 |
| | |
| | | ) |
| | | |
| | | func (slf PayrollProductionWeavers) TableName() string { |
| | | return "silk_payroll_production_employee" |
| | | return "silk_payroll_production_weavers" |
| | | } |
| | | |
| | | // NewPayrollProductionWeaversSearch 员工每天的产量统计 |
| | |
| | | |
| | | 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 |
| | | } |
| | |
| | | "fmt" |
| | | "github.com/shopspring/decimal" |
| | | "gorm.io/gorm" |
| | | "math/rand" |
| | | "silkserver/constvar" |
| | | "silkserver/pkg/mysqlx" |
| | | "silkserver/utils" |
| | | ) |
| | | |
| | | // 工时统计 |
| | |
| | | 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:车台号开始"` // 车台号列表 |
| | |
| | | |
| | | 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 |
| | | } |
| | |
| | | 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:所属车间ID" 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"` |
| | |
| | | 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:规格"` |
| | |
| | | "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"` |
| | | } |
| | | |
| | | // 薪资计算 |
| | | // 纤度登记:silk_fineness_register silk_fineness_register_item |
| | | // 纤度检验:silk_fineness_check silk_fineness_check_item |
| | | // 产量登记登记:silk_yield_register_circle |
| | | |
| | | // WorkingHours 存表:工时计算(日期(年月日)、工种、员工姓名、小组、车台、产量工资、上班超时(小时)、上班超时(天)、加班(单独)、加班(全车间)、出勤(天)、带徒(天)、产量(KG)、其它) |
| | | func WorkingHours(date string) error { |
| | |
| | | } |
| | | |
| | | 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 |
| | | }) |
| | | |
| | |
| | | } |
| | | |
| | | // 车台挡车工重复人员标记 |
| | | 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, |
| | |
| | | // 野纤统计 |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | |
| | | 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)) |
| | |
| | | |
| | | } |
| | | 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 |
| | |
| | | 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 |
| | |
| | | } |
| | | |
| | | } |
| | | 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 { |
| | |
| | | return nil |
| | | } |
| | | |
| | | // 根据上报计算: |
| | | // 1、车台当月全等级丝总量; |
| | | // 2、车台当月各等级丝总量; |
| | | // 3、车台每月等级占比=车台当月各等级丝总量/车台当月全等级丝总量; |
| | | // 4、车台每月每人平均丝量=车台当月全等级丝总量/车台挡车工人数; |
| | | // 5、车台当月全野纤扣除金额; |
| | | // 6、车台每月每人平均野纤扣除金额=当月全野纤扣除金额/车台挡车工人数; |
| | | // 7、车台每月丝量总金额 |
| | | // 8、车台每月丝量成品金额 |
| | | // 9、车台每月每人平均丝量金额=车台每月丝量成品金额/车台挡车工人数 |
| | | |
| | | // ProductionEmployee 存表: 每人每天的产量统计登记 |
| | | // ProductionWeavers 存表: 挡车工每人每天的产量统计登记 |
| | | func ProductionWeavers(date string) error { |
| | | workingHours, err := models.NewPayrollWorkingHoursSearch().SetWorkTypeCode(constvar.JobTypeWeavers).SetCycle(date).FindNotTotal() // 员工的工时统计 |
| | | if err != nil { |
| | |
| | | Cycle: date, |
| | | WorkTypeID: worker.WorkTypeID, |
| | | WorkerID: worker.WorkerID, |
| | | WorkshopId: worker.WorkshopId, |
| | | WorkshopNumber: worker.WorkshopNumber, |
| | | GroupNumber: worker.GroupNumber, |
| | | } |
| | |
| | | } |
| | | |
| | | 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 |
| | |
| | | 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 { |
| | |
| | | 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) |
| | | } |
| New file |
| | |
| | | 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, |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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]) |
| | | } |
| New file |
| | |
| | | /* |
| | | math-engine |
| | | |
| | | -------- |
| | | |
| | | 数学表达式解析计算引擎库 |
| | | |
| | | 使用 Go 实现的数学表达式解析计算引擎库,它小巧,无任何依赖,具有扩展性(比如可以注册自己的函数到引擎中),比较完整的完成了数学表达式解析执行,包括词法分析、语法分析、构建AST、运行。 |
| | | |
| | | `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 |
| New file |
| | |
| | | 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' |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | |
| | | |
| | | 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() |
| | | } |