From b94bef381946e22fd1038f24e6d9de911d194640 Mon Sep 17 00:00:00 2001
From: yinbentan <yinbentan@live.com>
Date: 星期三, 31 七月 2024 23:33:48 +0800
Subject: [PATCH] 功能修改,工资计算调整,通过前端配置公式进行计算

---
 utils/calculator/doc.go              |   22 
 utils/calculator/def.go              |  158 ++++
 models/payroll_production_car.go     |    7 
 utils/calculator/ast.go              |  267 ++++++++
 models/payroll_working_hours.go      |   92 ++
 models/yield_register.go             |    1 
 utils/calculator/util_test.go        |  240 +++++++
 models/payroll_production_group.go   |    3 
 models/payroll_production_weavers.go |   25 
 utils/timex.go                       |   23 
 models/payroll_other_subsidies.go    |   28 
 models/db.go                         |    2 
 utils/stringsx.go                    |   22 
 models/worker.go                     |    1 
 utils/calculator/util.go             |  141 ++++
 utils/calculator/parser.go           |  168 +++++
 models/payroll_constitute.go         |  306 +++++++++
 service/salary_plan.go               |  466 ++++++++-----
 18 files changed, 1,775 insertions(+), 197 deletions(-)

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

--
Gitblit v1.8.0