yinbentan
2024-07-31 b94bef381946e22fd1038f24e6d9de911d194640
功能修改,工资计算调整,通过前端配置公式进行计算
10个文件已修改
8个文件已添加
1972 ■■■■■ 已修改文件
models/db.go 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/payroll_constitute.go 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/payroll_other_subsidies.go 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/payroll_production_car.go 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/payroll_production_group.go 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/payroll_production_weavers.go 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/payroll_working_hours.go 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/worker.go 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
models/yield_register.go 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/salary_plan.go 466 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/calculator/ast.go 267 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/calculator/def.go 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/calculator/doc.go 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/calculator/parser.go 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/calculator/util.go 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/calculator/util_test.go 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/stringsx.go 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/timex.go 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 {
models/payroll_constitute.go
New file
@@ -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:工种代码"`           //工种代码
        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
}
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:工种代码"`      //工种代码
        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 {
@@ -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")
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:车台关联挡车工ID"`              // 车台关联挡车工ID
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:组别"`      // 组别
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
}
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
}
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:所属车间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"`
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:规格"`
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"`
}
// 薪资计算
// 纤度登记: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 {
@@ -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、车台当月全野纤扣除金额;
//    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(&parameter, 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(&parameter, 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)
}
utils/calculator/ast.go
New file
@@ -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,
        }
    }
}
utils/calculator/def.go
New file
@@ -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])
}
utils/calculator/doc.go
New file
@@ -0,0 +1,22 @@
/*
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
utils/calculator/parser.go
New file
@@ -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'
}
utils/calculator/util.go
New file
@@ -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
}
utils/calculator/util_test.go
New file
@@ -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
}
utils/stringsx.go
New file
@@ -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
}
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()
}