Files
zmkgC/api/attendanceMachine/service.go
2025-07-07 20:11:59 +08:00

309 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @Author cory 2025/3/5 10:27:00
package attendanceMachine
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
wxDao "github.com/tiger1103/gfast/v3/internal/app/wxApplet/dao"
wxBusAttendance "github.com/tiger1103/gfast/v3/internal/app/wxApplet/logic/busAttendance"
wxModel "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model"
wxDo "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model/do"
"github.com/tiger1103/gfast/v3/library/liberr"
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
"golang.org/x/net/context"
"io/ioutil"
"math"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
func (w AttendanceMachineApi) TaskCreate(ctx context.Context, req *EquipmentTimeClockReq) (res *EquipmentTimeClockRes, err error) {
res = new(EquipmentTimeClockRes)
err = AttendanceMachine(ctx, req)
res.Result = "0"
res.Msg = ""
r := ghttp.RequestFromCtx(ctx)
r.Response.ClearBuffer()
r.Response.WriteJson(res)
return
}
func AttendanceMachine(ctx context.Context, req *EquipmentTimeClockReq) (err error) {
logs := req.Logs[0]
f1 := logs.Location.Longitude
f2 := logs.Location.Latitude
res, err := wxBusAttendance.ThisInverseGeocodingFunc(f1 + "," + f2)
liberr.ErrIsNil(ctx, err)
// 上次打卡距离此次打卡不超过3分支那么提示“你刚刚已打上/下班卡了哦!”
currentTime := time.Now()
date := tool.New().GetFormattedDate(currentTime)
var bair *wxModel.BusAttendanceInfoRes
err = wxDao.BusAttendance.Ctx(ctx).
Where("openid", logs.UserID).
Where("printing_date", date).
Fields("clock_on,commuter").OrderDesc("clock_on").Limit().Scan(&bair)
liberr.ErrIsNil(ctx, err, "打卡失败!")
if err == nil && bair != nil {
//判断t1和t2是否相差180秒如果小于180秒就直接return
flag, err := IsWithinThreeMinutes(currentTime, bair.ClockOn)
if err != nil {
return err
}
if flag {
txt := ""
if bair.Commuter == "1" {
txt = "您已上班打卡成功!杜绝重复打卡!"
} else {
txt = "您已下班打卡成功!杜绝重复打卡!"
}
err = errors.New(txt)
return err
}
}
lng, err := strconv.ParseFloat(f1, 64)
if err != nil {
err = errors.New("精准度转换错误!")
return
}
lat, err := strconv.ParseFloat(f2, 64)
if err != nil {
err = errors.New("纬度转换错误!")
return
}
lng, lat = coryCommon.GCJ02toWGS84(lng, lat)
// 0-0、判断是否开启打卡功能 (禁止打卡和离职都不允许打卡)
var userInfo *wxModel.BusConstructionUserInfoRes
err = dao.BusConstructionUser.Ctx(ctx).Where("openid", logs.UserID).Scan(&userInfo)
if userInfo.TeamId == 0 {
err = errors.New("当前用户暂无班组,无法打卡!")
return
}
// 查看当前用户的打卡时间是否请假,如果请假就提示不用打卡
count, err := wxDao.BusAskforleave.Ctx(ctx).
Where(wxDao.BusAskforleave.Columns().ProjectId, userInfo.ProjectId).
Where(wxDao.BusAskforleave.Columns().Openid, userInfo.Openid).
Where("(" +
"DATE_FORMAT(" + date + ",'%Y-%m-%d') BETWEEN DATE_FORMAT(start_time,'%Y-%m-%d') AND DATE_FORMAT(end_time,'%Y-%m-%d')" +
" or " +
"DATE_FORMAT(" + date + ",'%Y-%m-%d') BETWEEN DATE_FORMAT(start_time,'%Y-%m-%d') AND DATE_FORMAT(end_time,'%Y-%m-%d')" +
")").Count()
liberr.ErrIsNil(ctx, err)
if count > 0 {
liberr.ErrIsNil(ctx, errors.New("您已请假,无需打卡!"))
return
}
// 部分班组可以直接跳过在范围内打卡,获取当前用户班组,看看是否是可以在任何地点打卡
value, errvalue := wxDao.SysProjectTeam.Ctx(ctx).Where("id", userInfo.TeamId).Fields("is_clock_in").Value()
if errvalue != nil {
err = errors.New("获取班组打卡状态失败!")
return err
}
if value.String() == "2" {
goto breakHere
}
// 计算是否在打卡范围中
if true {
// 查询当前用户是否有加入班组,有的话看看是什么项目,再根据项目获取到方正,最后根据方正数据得出打卡范围
projectId, _ := wxDao.SysProjectTeamMember.Ctx(ctx).As("a").
LeftJoin("sys_project_team as b on a.team_id = b.id").
Fields("a.project_id").
Where("a.openid", logs.UserID).Value()
if projectId != nil {
var qf []*wxBusAttendance.ProjectPunchRangeRes
err = g.DB().Model("sys_project_punch_range").Where("project_id", projectId).Fields("punch_range").Scan(&qf)
if err != nil {
err = errors.New("系统错误,请联系管理员!")
return err
}
if len(qf) == 0 {
err = errors.New("未设置打卡范围!请联系管理员设置!")
return err
}
countI := len(qf)
countII := 0
for _, dakaStr := range qf {
countII = countII + 1
var dataInfo coryCommon.DetailedMap
err = json.Unmarshal([]byte(fmt.Sprint(dakaStr.PunchRange)), &dataInfo.Positions)
flag := coryCommon.RectangularFrameRange(dataInfo, lng, lat)
if flag {
goto breakHere
} else {
// 没有一次范围匹配上,那就直接返回
if countII == countI {
err = errors.New("不在范围内,打卡无效!")
return err
}
}
}
} else {
err = errors.New("未指定打卡范围,请联系管理员!")
return err
}
}
breakHere:
if userInfo.Status == "1" {
err = errors.New("已离职,请联系管理员!")
return
}
if userInfo.Clock == "2" {
err = errors.New("已禁止打卡,请联系管理员!")
return
}
if len(strings.Trim(userInfo.LeaveDate.String(), "")) != 0 {
err = errors.New("已退场,请联系管理员!")
return
}
clockStatus := ""
// 1、获取当日时间
// 2、查询今日打卡记录
gm := wxDao.BusAttendance.Ctx(ctx).
Where(wxDao.BusAttendance.Columns().Openid, logs.UserID).
Where(wxDao.BusAttendance.Columns().PrintingDate, date)
// 3、如果有判断是否打了上班卡如果没有就打上班卡
count1, _ := gm.Where("commuter", "1").Count()
if count1 == 0 {
// 打上班卡
clockStatus = "1"
}
// 4、如果有判断是否打了下班卡如果没有就打下班卡
count2, _ := gm.Where("commuter", "2").Count()
if clockStatus == "" && count2 == 0 {
// 打下班卡
clockStatus = "2"
}
// 5、上下班卡都打了那么就提示今日打卡完成
if count1 != 0 && count2 != 0 {
// 已完成当日打卡
err = errors.New("当日打卡已完成!")
return err
}
// 提交数据
name := userInfo.NickName
if userInfo.UserName != "" {
name = userInfo.UserName
}
// 将base64人脸换成图片存储到本地
imagePath, err := DecodeBase64Image(logs.Photo)
if err != nil {
return err
}
rpath := coryCommon.ResourcePublicToFunc(imagePath, 1)
attendance := wxDo.BusAttendance{
PacePhoto: rpath,
UserName: name,
ProjectId: userInfo.ProjectId,
Openid: userInfo.Openid,
Commuter: clockStatus,
Lng: lng,
Lat: lat,
PrintingDate: date,
}
// 判断此次打卡状态 默认就为缺勤
dateTime := tool.New().GetFormattedDateTime(time.Now())
if userInfo.ProjectId > 0 {
value, err := dao.SysProject.Ctx(ctx).WherePri(userInfo.ProjectId).Fields("punch_range as punchRange").Value()
if err != nil {
err = errors.New("获取项目打卡范围失败!")
}
split := strings.Split(value.String(), ",")
strType := tool.New().TimeWithin(dateTime, split[0], split[1], clockStatus)
attendance.IsPinch = strType
attendance.PunchRange = value.String() // 记录项目的打卡时间,保留字段,后期有大用
}
// 获取此次打卡的日薪
vl, err := dao.BusConstructionUser.Ctx(ctx).As("a").
LeftJoin("bus_type_of_wage as f on a.type_of_work = f.type_of_work").
Fields("if (a.salary>0, a.salary,f.standard) as salary").
Where("a.openid", logs.UserID).Value()
liberr.ErrIsNil(ctx, err, "获取薪资失败")
// 记录打卡时间上下班打卡时间统一用clock_on
attendance.ClockOn = dateTime
attendance.DailyWage = vl
attendance.Lng = lng
attendance.Lat = lat
attendance.Location = res.Regeocode.FormattedAddress
_, err = wxDao.BusAttendance.Ctx(ctx).Insert(attendance)
liberr.ErrIsNil(ctx, err, "添加失败")
return
}
// DecodeBase64Image 函数用于解码 Base64 编码的图片数据并保存为图片
func DecodeBase64Image(encodedStr string) (string, error) {
// 进行 URL 解码
decodedURLStr, err := url.QueryUnescape(encodedStr)
if err != nil {
return "", fmt.Errorf("URL 解码失败: %w", err)
}
// 提取真正的 Base64 数据
parts := strings.SplitN(decodedURLStr, ",", 2)
if len(parts) != 2 {
return "", fmt.Errorf("无效的 Base64 编码图片数据格式")
}
base64Data := parts[1]
// 进行 Base64 解码
decodedData, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
return "", fmt.Errorf("Base64 解码失败: %w", err)
}
ht := coryCommon.Helmet
str := coryCommon.Ynr(ht)
fn := coryCommon.FileName("face")
dir, err := os.Getwd()
str = dir + "/" + str + fn + ".jpg"
str = filepath.ToSlash(str)
// 保存解码后的图片到文件
err = ioutil.WriteFile(str, decodedData, 0644)
if err != nil {
fmt.Println("保存图片文件出错:", err)
return "", fmt.Errorf("保存解码后的图片失败: %w", err)
}
return str, nil
}
// 判断两个时间是否相差超过 3 分钟
func isMinutesDifferenceGreaterThanThree(startTime, endTime time.Time) bool {
// 计算时间差(单位:秒)
diff := endTime.Sub(startTime).Seconds()
// 判断是否超过 180 秒3 分钟)
return diff > 180
}
// 判断两个时间的秒数差是否 <= 180 秒
func IsWithinThreeMinutes(currentTime time.Time, strTime string) (bool, error) {
// 解析字符串时间,使用本地时区
loc, _ := time.LoadLocation("Local") // 获取本地时区
parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", strTime, loc)
if err != nil {
fmt.Println("时间解析错误:", err)
return false, err
}
// 计算时间差(秒)
diff := math.Abs(currentTime.Sub(parsedTime).Seconds())
// 判断是否相差 180 秒
return diff <= 180, nil
}