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