309 lines
9.9 KiB
Go
309 lines
9.9 KiB
Go
|
// @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
|
|||
|
}
|