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
|
||
}
|