diff --git a/common/model/staff_salary.go b/common/model/staff_salary.go index d80237b..4a3341e 100644 --- a/common/model/staff_salary.go +++ b/common/model/staff_salary.go @@ -13,6 +13,16 @@ var ( StaffSalaryStatusPayed = 2 ) +type SalaryBillLine struct { + Label string + Value string +} + +type SalaryBillSection struct { + Name string + Line []*SalaryBillLine +} + type StaffSalary struct { Id int64 CorpId int64 diff --git a/conf/template/salary_bill.html b/conf/template/salary_bill.html new file mode 100644 index 0000000..b700e2b --- /dev/null +++ b/conf/template/salary_bill.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<html lang="zh-CN"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>工资单</title> + <style> + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + margin: 0; + } + + .payroll-container { + background: white; + border-radius: 15px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + padding: 30px; + width: 90%; + max-width: 500px; + animation: fadeIn 0.6s ease; + } + + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + h1 { + text-align: center; + color: #00838f; + font-size: 28px; + margin-bottom: 20px; + } + + .section { + background: #f9f9f9; + border-radius: 10px; + padding: 20px; + margin-bottom: 20px; + } + + h2 { + color: #006064; + font-size: 20px; + margin-bottom: 15px; + } + + .info-row { + display: flex; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid #eee; + } + + .info-row:last-child { + border-bottom: none; + } + + .info-label { + font-weight: 600; + color: #333; + } + + .info-value { + color: #555; + } + + .total-row { + font-weight: bold; + font-size: 1.2em; + color: #00838f; + border-top: 2px solid #00838f; + padding-top: 15px; + } + </style> +</head> + +<body> +<div class="payroll-container"> + <h1>{{.title}}</h1> + + {{range $i, $data := .datas}} + <div class="section"> + <h2>{{.Name}}</h2> + + {{range $j, $v := .Line}} + <div class="info-row"> + <span class="info-label">{{ $v.Label }}</span> + <span class="info-value">{{ $v.Value }}</span> + </div> + {{end}} + </div> + {{end}} + + + <div class="info-row total-row"> + <span class="info-label">实发工资</span> + <span class="info-value">{{ .salary }}</span> + </div> +</div> +</body> + +</html> \ No newline at end of file diff --git a/go.mod b/go.mod index 0ddf035..58b5618 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( git.u8t.cn/open/gosdk v0.0.0-20250309163531-2f47649d3dbd github.com/ArtisanCloud/PowerWeChat/v3 v3.2.27 github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 + github.com/fogleman/gg v1.3.0 github.com/gin-gonic/gin v1.10.0 github.com/go-co-op/gocron v1.37.0 github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9 @@ -49,6 +50,7 @@ require ( github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -92,6 +94,7 @@ require ( go.uber.org/zap v1.21.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/image v0.14.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect diff --git a/server/controller/salary.go b/server/controller/salary.go index a887ac0..cbb636c 100644 --- a/server/controller/salary.go +++ b/server/controller/salary.go @@ -92,3 +92,9 @@ func (s *Salary) SyncStaffSalary(ctx *gin.Context) { go new(worker.Staff).SyncStaffSalary(corp.Id, "") ctx.JSON(http.StatusOK, session.NewRspOk()) } + +func (s *Salary) Bill(ctx *gin.Context) { + id := cast.ToInt64(ctx.Query("id")) + + service.NewStaffSalary().Bill(id, ctx) +} diff --git a/server/server.go b/server/server.go index d7931fe..6786443 100644 --- a/server/server.go +++ b/server/server.go @@ -38,6 +38,7 @@ func initRoutge(engine *gin.Engine) { apiGroup.POST("/salary/pay", controller.NewSalary().Pay) apiGroup.PUT("/staff/salary", controller.NewSalary().Update) apiGroup.DELETE("/staff/salary", controller.NewSalary().Delete) + noTokenGroup.GET("/staff/salary/bill", controller.NewSalary().Bill) apiGroup.GET("/staff/suggest", controller.NewStaff().Suggest) apiGroup.POST("/staff/pay", controller.NewStaff().Pay) diff --git a/server/service/staff_salary.go b/server/service/staff_salary.go index 7391207..38eedb1 100644 --- a/server/service/staff_salary.go +++ b/server/service/staff_salary.go @@ -426,3 +426,70 @@ func (s *StaffSalary) toExcel(filePath string, header []string, datas [][]string ctx.Writer.Header().Add("Content-Type", "application/msexcel") ctx.File(filePath) } + +func (s *StaffSalary) Bill(id int64, ctx *gin.Context) { + + salary, err := dao.NewStaffSalaryDao().Get(id) + session.CheckDBError(err) + session.CheckNilError(salary, "工资单不存在") + corp, err := dao.NewCorpDao().Get(salary.CorpId) + session.CheckDBError(err) + session.CheckNilError(corp, "企业不存在") + user, err := dao.NewStaffUserDao().Get(salary.UserId) + session.CheckDBError(err) + session.CheckNilError(user, "用户不存在") + + datas := make([]*model.SalaryBillSection, 0) + + baseDetail := make([]*model.SalaryBillLine, 0) + baseDetail = append(baseDetail, &model.SalaryBillLine{Label: "员工姓名", Value: fmt.Sprintf("%s", user.Realname)}) + baseDetail = append(baseDetail, &model.SalaryBillLine{Label: "基本工资", Value: fmt.Sprintf("%s", user.GetSalary().Base)}) + baseDetail = append(baseDetail, &model.SalaryBillLine{Label: "出勤工资", Value: fmt.Sprintf("%s", user.GetSalary().Target)}) + datas = append(datas, &model.SalaryBillSection{ + Name: "基本信息", + Line: baseDetail, + }) + + salaryDetail := make([]*model.SalaryBillLine, 0) + + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "出勤收入", Value: fmt.Sprintf("%.2f 元", salary.AttendSalary)}) + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "绩效收入", Value: fmt.Sprintf("%.2f 元", salary.TargetSalary)}) + + if salary.AwardSalary != 0 { + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "奖金收入", Value: fmt.Sprintf("%.2f 元", salary.AwardSalary)}) + } + if salary.OtherSalary != 0 { + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "其他收入", Value: fmt.Sprintf("%.2f 元", salary.OtherSalary)}) + } + if salary.SocialDeduct != 0 { + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "社保扣除", Value: fmt.Sprintf("%.2f 元", salary.SocialDeduct)}) + } + if salary.HouseDeduct != 0 { + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "公积金扣除", Value: fmt.Sprintf("%.2f 元", salary.HouseDeduct)}) + } + + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "个税扣除", Value: fmt.Sprintf("%.2f 元", salary.PersonalDeduct)}) + if salary.OtherSalary != 0 { + salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "其他扣除", Value: fmt.Sprintf("%.2f 元", salary.OtherSalary)}) + } + datas = append(datas, &model.SalaryBillSection{ + Name: "工资明细", + Line: salaryDetail, + }) + + attendDetail := make([]*model.SalaryBillLine, 0) + attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "应出勤", Value: fmt.Sprintf("%d 天", salary.ShouldDay)}) + attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "休假天数", Value: fmt.Sprintf("%.2f 天", salary.HolidayDay)}) + attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "缺勤天数", Value: fmt.Sprintf("%.2f 天", float64(salary.ShouldDay)-salary.AttendDay-salary.HolidayDay)}) + attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "实际出勤", Value: fmt.Sprintf("%.2f 天", salary.AttendDay)}) + datas = append(datas, &model.SalaryBillSection{ + Name: "出勤信息", + Line: attendDetail, + }) + + ctx.HTML(http.StatusOK, "salary_bill.html", gin.H{ + "title": "工资单", + "salary": fmt.Sprintf("%.2f 元", salary.GetRealSalary()), + "datas": datas, + }) +}