Просмотр исходного кода

广誉源商户端-登录-我的技能,城市管理

jinwenhai 5 дней назад
Родитель
Сommit
90602a2cb4
20 измененных файлов с 982 добавлено и 239 удалено
  1. 20 21
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java
  2. 87 0
      nightFragrance-massage/src/main/java/com/ylx/massage/controller/MerchantDailyAttendanceController.java
  3. 2 1
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaProject.java
  4. 2 2
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTeProject.java
  5. 58 58
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTechnician.java
  6. 74 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MerchantApplyFile.java
  7. 82 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MerchantDailyAttendance.java
  8. 1 1
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MaTechnicianMerchantAddDTO.java
  9. 21 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantApplyFileDto.java
  10. 59 59
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MaTechnicianAppAddVo.java
  11. 6 2
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MaTechnicianCertificateVO.java
  12. 112 0
      nightFragrance-massage/src/main/java/com/ylx/massage/enums/FileTypeEnum.java
  13. 24 0
      nightFragrance-massage/src/main/java/com/ylx/massage/enums/TechnicianStatusEnum.java
  14. 1 1
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MaTeProjectMapper.java
  15. 18 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MerchantApplyFileMapper.java
  16. 17 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MerchantDailyAttendanceMapper.java
  17. 13 1
      nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java
  18. 18 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/MerchantDailyAttendanceService.java
  19. 348 93
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java
  20. 19 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MerchantDailyAttendanceServiceImpl.java

+ 20 - 21
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java

@@ -21,6 +21,7 @@ import com.ylx.common.core.domain.model.aliyun.SendSmsComponents;
 import com.ylx.common.core.domain.model.aliyun.SendSmsEnum;
 import com.ylx.common.utils.StringUtils;
 import com.ylx.massage.domain.MaProject;
+import com.ylx.massage.domain.dto.*;
 import com.ylx.massage.domain.dto.MaProjectSaveDto;
 import com.ylx.massage.domain.dto.MaProjectUpdateDto;
 import com.ylx.massage.domain.dto.MaTechnicianAuditQueryDTO;
@@ -199,7 +200,24 @@ public class MaTechnicianController extends BaseController {
         maTechnicianService.apply(req);
         return Result.ok("提交成功,进入审核流程");
     }
-
+    /**
+     * 申请技师文件
+     * @param req
+     */
+    @PostMapping("/applyFile")
+    public  Result applyFile(MerchantApplyFileDto req){
+        maTechnicianService.applyFile(req);
+        return Result.ok("上传成功");
+    }
+    /**
+     * 技师状态切换
+     *
+     * @param
+     */
+    @GetMapping("/switchToOffline")
+    public Result switchToOffline(@RequestParam Long userId, @RequestParam Boolean forceConfirm){
+        return maTechnicianService.switchToOffline(userId, forceConfirm);
+    }
     /**
      * 查询商户信息接口
      */
@@ -219,30 +237,11 @@ public class MaTechnicianController extends BaseController {
         if (req.getAuditStatus() == 0 || req.getAuditStatus() == 3) {
             //修改基本信息
             updateMaTechnician(req);
-        } else if (req.getAuditStatus() == 1 || req.getAuditStatus() == 2) {
-            //上传商户资料信息
-            extractedUpdate(req);
         }
         return Result.ok("修改成功");
     }
 
-    private void extractedUpdate(MaTechnicianAppAddVo req) {
-        LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();
-        updateWrapper.eq(MaTechnician::getId, req.getId());
-        updateWrapper.set(MaTechnician::getTeAvatar, req.getTeAvatar());
-        updateWrapper.set(MaTechnician::getTeNickName, req.getTeNickName());
-        updateWrapper.set(MaTechnician::getTeBrief, req.getTeBrief());
-        updateWrapper.set(MaTechnician::getLifePhotos, req.getLifePhotos());
-        updateWrapper.set(MaTechnician::getIdCard, req.getIdCard());
-        updateWrapper.set(MaTechnician::getHealthCertificate, req.getHealthCertificate());
-        updateWrapper.set(MaTechnician::getQualificationCertificate, req.getQualificationCertificate());
-        updateWrapper.set(MaTechnician::getNoCrimeRecord, req.getNoCrimeRecord());
-        updateWrapper.set(MaTechnician::getPromoVideo, req.getPromoVideo());
-        updateWrapper.set(MaTechnician::getCommitmentAudio, req.getCommitmentAudio());
-        updateWrapper.set(MaTechnician::getCommitmentPdf, req.getCommitmentPdf());
-        updateWrapper.set(MaTechnician::getCommitmentVideo, req.getCommitmentVideo());
-        maTechnicianService.update(updateWrapper);
-    }
+
 
     private void updateMaTechnician(MaTechnicianAppAddVo req) {
         LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();

+ 87 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/controller/MerchantDailyAttendanceController.java

@@ -0,0 +1,87 @@
+package com.ylx.massage.controller;
+
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.common.core.domain.R;
+import com.ylx.massage.domain.MerchantDailyAttendance;
+import com.ylx.massage.service.MerchantDailyAttendanceService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 商户每日考勤统计表(MerchantDailyAttendance)表控制层
+ *
+ * @author makejava
+ * @since 2026-06-05 11:16:04
+ */
+@RestController
+@RequestMapping("merchantDailyAttendance")
+public class MerchantDailyAttendanceController  {
+    /**
+     * 服务对象
+     */
+    @Resource
+    private MerchantDailyAttendanceService merchantDailyAttendanceService;
+
+    /**
+     * 分页查询所有数据
+     *
+     * @param page 分页对象
+     * @param merchantDailyAttendance 查询实体
+     * @return 所有数据
+     */
+    @GetMapping
+    public R selectAll(Page<MerchantDailyAttendance> page, MerchantDailyAttendance merchantDailyAttendance) {
+        return R.ok(this.merchantDailyAttendanceService.page(page, new QueryWrapper<>(merchantDailyAttendance)));
+    }
+
+    /**
+     * 通过主键查询单条数据
+     *
+     * @param id 主键
+     * @return 单条数据
+     */
+    @GetMapping("{id}")
+    public R selectOne(@PathVariable Serializable id) {
+        return R.ok(this.merchantDailyAttendanceService.getById(id));
+    }
+
+    /**
+     * 新增数据
+     *
+     * @param merchantDailyAttendance 实体对象
+     * @return 新增结果
+     */
+    @PostMapping
+    public R insert(@RequestBody MerchantDailyAttendance merchantDailyAttendance) {
+        return R.ok(this.merchantDailyAttendanceService.save(merchantDailyAttendance));
+    }
+
+    /**
+     * 修改数据
+     *
+     * @param merchantDailyAttendance 实体对象
+     * @return 修改结果
+     */
+    @PutMapping
+    public R update(@RequestBody MerchantDailyAttendance merchantDailyAttendance) {
+        return R.ok(this.merchantDailyAttendanceService.updateById(merchantDailyAttendance));
+    }
+
+    /**
+     * 删除数据
+     *
+     * @param idList 主键结合
+     * @return 删除结果
+     */
+    @DeleteMapping
+    public R delete(@RequestParam("idList") List<Long> idList) {
+        return R.ok(this.merchantDailyAttendanceService.removeByIds(idList));
+    }
+}
+

+ 2 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaProject.java

@@ -7,6 +7,7 @@ import io.swagger.annotations.ApiModelProperty;
 import com.ylx.common.annotation.Excel;
 import com.ylx.common.core.domain.BaseEntity;
 import lombok.Data;
+import java.util.Date;
 
 /**
  * 服务项目对象 ma_project
@@ -147,7 +148,7 @@ public class MaProject extends BaseEntity {
     /** 申请时间 */
     @Excel(name = "申请时间")
     @ApiModelProperty("申请时间")
-    private Data applyTime;
+    private Date applyTime;
     /**
      * 修改人
      */

+ 2 - 2
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTeProject.java

@@ -15,11 +15,11 @@ public class MaTeProject {
      * 商户ID
      */
     @TableField("te_id")
-    private Long teId;
+    private Integer teId;
 
     /**
      * 项目ID
      */
     @TableField("project_id")
-    private Long projectId;
+    private Integer projectId;
 }

+ 58 - 58
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTechnician.java

@@ -124,12 +124,12 @@ public class MaTechnician extends BaseEntity {
     @TableField("te_project")
     private String teProject;
 
-    /**
-     * 生活照
-     */
-    @Excel(name = "生活照")
-    @TableField("life_photos")
-    private String lifePhotos;
+//    /**
+//     * 生活照
+//     */
+//    @Excel(name = "生活照")
+//    @TableField("life_photos")
+//    private String lifePhotos;
 
     /**
      * 形象照
@@ -137,58 +137,58 @@ public class MaTechnician extends BaseEntity {
     @Excel(name = "形象照")
     @TableField("avatar")
     private String avatar;
-
-    /**
-     * 身份证
-     */
-    @Excel(name = "身份证")
-    @TableField("id_card")
-    private String idCard;
-
-    /**
-     * 健康证
-     */
-    @Excel(name = "健康证")
-    @TableField("health_certificate")
-    private String healthCertificate;
-
-    /**
-     * 从业资格证
-     */
-    @Excel(name = "从业资格证")
-    @TableField("qualification_certificate")
-    private String qualificationCertificate;
-
-    /**
-     * 无犯罪证明
-     */
-    @Excel(name = "无犯罪证明")
-    @TableField("no_crime_record")
-    private String noCrimeRecord;
-    /**
-     * 宣传视频
-     */
-    @Excel(name = "宣传视频")
-    @TableField("promo_video")
-    private String promoVideo;
-    /**
-     * 承诺书
-     */
-    @Excel(name = "承诺书")
-    @TableField("commitment_pdf")
-    private String commitmentPdf;
-    /**
-     * 承诺录音
-     */
-    @Excel(name = "承诺录音")
-    @TableField("commitment_audio")
-    private String commitmentAudio;
-    /**
-     * 承诺录像
-     */
-    @Excel(name = "承诺录像")
-    @TableField("commitment_video")
-    private String commitmentVideo;
+//
+//    /**
+//     * 身份证
+//     */
+//    @Excel(name = "身份证")
+//    @TableField("id_card")
+//    private String idCard;
+//
+//    /**
+//     * 健康证
+//     */
+//    @Excel(name = "健康证")
+//    @TableField("health_certificate")
+//    private String healthCertificate;
+//
+//    /**
+//     * 从业资格证
+//     */
+//    @Excel(name = "从业资格证")
+//    @TableField("qualification_certificate")
+//    private String qualificationCertificate;
+//
+//    /**
+//     * 无犯罪证明
+//     */
+//    @Excel(name = "无犯罪证明")
+//    @TableField("no_crime_record")
+//    private String noCrimeRecord;
+//    /**
+//     * 宣传视频
+//     */
+//    @Excel(name = "宣传视频")
+//    @TableField("promo_video")
+//    private String promoVideo;
+//    /**
+//     * 承诺书
+//     */
+//    @Excel(name = "承诺书")
+//    @TableField("commitment_pdf")
+//    private String commitmentPdf;
+//    /**
+//     * 承诺录音
+//     */
+//    @Excel(name = "承诺录音")
+//    @TableField("commitment_audio")
+//    private String commitmentAudio;
+//    /**
+//     * 承诺录像
+//     */
+//    @Excel(name = "承诺录像")
+//    @TableField("commitment_video")
+//    private String commitmentVideo;
 
     /**
      * 简介

+ 74 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MerchantApplyFile.java

@@ -0,0 +1,74 @@
+package com.ylx.massage.domain;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * 入驻申请附件表(MerchantApplyFile)表实体类
+ *
+ * @author makejava
+ * @since 2026-06-05 16:12:22
+ */
+@SuppressWarnings("serial")
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName(value = "merchant_apply_file", autoResultMap = true)
+public class MerchantApplyFile implements Serializable {
+    //主键ID
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    //商户ID
+    @TableField("merchant_id")
+    private Integer merchantId;
+    //文件类型//文件类型1-形象照,2-生活照,3-身份证正面
+    //    // 4-身份证反面 5-手持身份证 6-健康证,
+    //    // 7-从业资格证,8-无犯罪证明,9-承诺书,10-承诺录音,11-承诺录像,12-其他 默认为:'',
+    @TableField("file_type")
+    private String fileType;
+    //原始文件名
+    @TableField("file_name")
+    private String fileName;
+    //文件访问地址
+    @TableField("file_url")
+    private String fileUrl;
+    //文件大小,单位字节
+    @TableField("file_size")
+    private Long fileSize;
+    //文件MIME类型,如 image/jpeg
+    @TableField("content_type")
+    private String contentType;
+    //创建人
+    @TableField("create_by")
+    private String createBy;
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime updateTime;
+    //修改人
+    @TableField("update_by")
+    private String updateBy;
+    //是否删除(0否1是)
+    @TableField("is_delete")
+    private Integer isDelete;
+
+}
+

+ 82 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MerchantDailyAttendance.java

@@ -0,0 +1,82 @@
+package com.ylx.massage.domain;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * 商户每日考勤统计表(MerchantDailyAttendance)表实体类
+ *
+ * @author makejava
+ * @since 2026-06-05 11:16:08
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName(value = "merchant_daily_attendance", autoResultMap = true)
+public class MerchantDailyAttendance implements Serializable {
+//主键ID
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    //关联商户ID
+    @TableField("merchant_id")
+    private Integer merchantId;
+    //关联商户姓名
+    @TableField("merchant_name")
+    private String merchantName;
+    //考勤日期 (对应UI中的"日期")
+    @TableField("attendance_date")
+    private Date attendanceDate;
+    //当日首次打卡/上线时间
+    @TableField("attendance_start_time")
+    private Date attendanceStartTime;
+    //当日末次打卡/下线时间
+    @TableField("attendance_end_time")
+    private Date attendanceEndTime;
+    //日累计工作时长(分钟),方便精确计算
+    @TableField("total_work_minutes")
+    private Integer totalWorkMinutes;
+    //考勤状态: 1-正常, 2-异常
+    @TableField("attendance_status")
+    private Integer attendanceStatus;
+    //扣款金额
+    @TableField("deduction_amount")
+    private Double deductionAmount;
+    //备注
+    @TableField("remark")
+    private String remark;
+    //创建人
+    @TableField("create_by")
+    private String createBy;
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime updateTime;
+    //修改人
+    @TableField("update_by")
+    private String updateBy;
+    //是否删除(0否1是)
+    @TableField("is_delete")
+    private Integer isDelete;
+}
+

+ 1 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MaTechnicianMerchantAddDTO.java

@@ -47,7 +47,7 @@ public class MaTechnicianMerchantAddDTO {
      * 商户开通的服务项目ID集合。
      */
     @ApiModelProperty("服务项目ID集合")
-    private List<Long> projectIds;
+    private List<Integer> projectIds;
 
     /**
      * 商户类型:0-真实商户,1-虚拟商户。

+ 21 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantApplyFileDto.java

@@ -0,0 +1,21 @@
+package com.ylx.massage.domain.dto;
+
+import lombok.Data;
+
+@Data
+public class MerchantApplyFileDto {
+    //商户ID
+    private Integer merchantId;
+    //文件类型1-形象照,2-生活照,3-身份证正面
+    // 4-身份证反面 5-手持身份证 6-健康证,
+    // 7-从业资格证,8-无犯罪证明,9-承诺书,10-承诺录音,11-承诺录像,12-其他 默认为:'',
+    private String fileType;
+    //原始文件名
+    private String fileName;
+    //文件访问地址
+    private String fileUrl;
+    //文件大小,单位字节
+    private Long fileSize;
+    //文件MIME类型,如 image/jpeg
+    private String contentType;
+}

+ 59 - 59
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MaTechnicianAppAddVo.java

@@ -87,12 +87,12 @@ public class MaTechnicianAppAddVo {
     @ApiModelProperty("头像")
     private String teAvatar;
 
-    /**
-     * 生活照
-     */
-    @Excel(name = "生活照")
-    @ApiModelProperty("生活照")
-    private String lifePhotos;
+//    /**
+//     * 生活照
+//     */
+//    @Excel(name = "生活照")
+//    @ApiModelProperty("生活照")
+//    private String lifePhotos;
 
     /**
      * 简介
@@ -107,58 +107,58 @@ public class MaTechnicianAppAddVo {
     @Excel(name = "形象照")
     @ApiModelProperty("形象照")
     private String avatar;
-
-    /**
-     * 身份证
-     */
-    @Excel(name = "身份证")
-    @ApiModelProperty("身份证")
-    private String idCard;
-    /**
-     * 宣传视频
-     */
-    @Excel(name = "宣传视频")
-    @ApiModelProperty("宣传视频")
-    private String promoVideo;
-    /**
-     * 健康证
-     */
-    @Excel(name = "健康证")
-    @ApiModelProperty("健康证")
-    private String healthCertificate;
-
-    /**
-     * 从业资格证
-     */
-    @Excel(name = "从业资格证")
-    @ApiModelProperty("从业资格证")
-    private String qualificationCertificate;
-
-    /**
-     * 无犯罪证明
-     */
-    @Excel(name = "无犯罪证明")
-    @ApiModelProperty("无犯罪证明")
-    private String noCrimeRecord;
-
-    /**
-     * 承诺书
-     */
-    @Excel(name = "承诺书")
-    @ApiModelProperty("承诺书")
-    private String commitmentPdf;
-    /**
-     * 承诺录音
-     */
-    @Excel(name = "承诺录音")
-    @ApiModelProperty("承诺录音")
-    private String commitmentAudio;
-    /**
-     * 承诺录像
-     */
-    @Excel(name = "承诺录像")
-    @ApiModelProperty("承诺录像")
-    private String commitmentVideo;
+//
+//    /**
+//     * 身份证
+//     */
+//    @Excel(name = "身份证")
+//    @ApiModelProperty("身份证")
+//    private String idCard;
+//    /**
+//     * 宣传视频
+//     */
+//    @Excel(name = "宣传视频")
+//    @ApiModelProperty("宣传视频")
+//    private String promoVideo;
+//    /**
+//     * 健康证
+//     */
+//    @Excel(name = "健康证")
+//    @ApiModelProperty("健康证")
+//    private String healthCertificate;
+//
+//    /**
+//     * 从业资格证
+//     */
+//    @Excel(name = "从业资格证")
+//    @ApiModelProperty("从业资格证")
+//    private String qualificationCertificate;
+//
+//    /**
+//     * 无犯罪证明
+//     */
+//    @Excel(name = "无犯罪证明")
+//    @ApiModelProperty("无犯罪证明")
+//    private String noCrimeRecord;
+//
+//    /**
+//     * 承诺书
+//     */
+//    @Excel(name = "承诺书")
+//    @ApiModelProperty("承诺书")
+//    private String commitmentPdf;
+//    /**
+//     * 承诺录音
+//     */
+//    @Excel(name = "承诺录音")
+//    @ApiModelProperty("承诺录音")
+//    private String commitmentAudio;
+//    /**
+//     * 承诺录像
+//     */
+//    @Excel(name = "承诺录像")
+//    @ApiModelProperty("承诺录像")
+//    private String commitmentVideo;
     /**
      * 审核状态:0-待审核,1-待审核,2-审核通过,3-审核驳回
      */
@@ -172,6 +172,6 @@ public class MaTechnicianAppAddVo {
     @ApiModelProperty("审批时间")
     private Date approveTime;
     @ApiModelProperty("项目id集合")
-    private ArrayList<Long> projectIds;
+    private ArrayList<Integer> projectIds;
 
 }

+ 6 - 2
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MaTechnicianCertificateVO.java

@@ -20,8 +20,12 @@ public class MaTechnicianCertificateVO {
     @ApiModelProperty("生活照")
     private String lifePhotos;
 
-    @ApiModelProperty("身份证")
-    private String idCard;
+    @ApiModelProperty("身份证人像面")
+    private String idCardFrout;
+    @ApiModelProperty("身份证国徽面")
+    private String idCardBack;
+    @ApiModelProperty("手持身份证照片")
+    private String idCardHandheld;
 
     @ApiModelProperty("健康证")
     private String healthCertificate;

+ 112 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/enums/FileTypeEnum.java

@@ -0,0 +1,112 @@
+package com.ylx.massage.enums;
+
+import com.alibaba.druid.sql.dialect.odps.ast.OdpsAddFileStatement;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 商户申请附件 - 文件类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum FileTypeEnum {
+
+    /**
+     * 1-形象照
+     */
+    PORTRAIT("1", "形象照"),
+
+    /**
+     * 2-生活照
+     */
+    LIFE_PHOTO("2", "生活照"),
+
+    /**
+     * 3-身份证正面
+     */
+    ID_CARD_FRONT("3", "身份证正面"),
+
+    /**
+     * 4-身份证反面
+     */
+    ID_CARD_BACK("4", "身份证反面"),
+
+    /**
+     * 5-手持身份证
+     */
+    ID_CARD_HANDHELD("5", "手持身份证"),
+
+    /**
+     * 6-健康证
+     */
+    HEALTH_CERT("6", "健康证"),
+
+    /**
+     * 7-从业资格证
+     */
+    QUALIFICATION_CERT("7", "从业资格证"),
+
+    /**
+     * 8-无犯罪证明
+     */
+    NO_CRIME_RECORD("8", "无犯罪证明"),
+
+    /**
+     * 9-承诺书
+     */
+    COMMITMENT_LETTER("9", "承诺书"),
+
+    /**
+     * 10-承诺录音
+     */
+    COMMITMENT_AUDIO("10", "承诺录音"),
+
+    /**
+     * 11-承诺录像
+     */
+    COMMITMENT_VIDEO("11", "承诺录像"),
+
+    /**
+     * 12-其他
+     */
+    OTHER("12", "其他");
+
+    /**
+     * 类型代码 (String)
+     */
+    private  String code;
+
+    /**
+     * 描述信息
+     */
+    private  String desc;
+
+    /**
+     * 根据 code 获取对应的枚举对象
+     *
+     * @param code 类型代码
+     * @return 对应的枚举对象,如果未找到则返回 null
+     */
+    public static FileTypeEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (FileTypeEnum type : FileTypeEnum.values()) {
+            if (type.getCode().equals(code)) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 根据 code 获取对应的描述信息
+     *
+     * @param code 类型代码
+     * @return 描述信息,如果未找到则返回空字符串或原值
+     */
+    public static String getDescByCode(String code) {
+       FileTypeEnum type = getByCode(code);
+        return type != null ? type.getDesc() : "";
+    }
+}

+ 24 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/enums/TechnicianStatusEnum.java

@@ -0,0 +1,24 @@
+package com.ylx.massage.enums;
+
+import lombok.Data;
+
+
+public enum TechnicianStatusEnum {
+    ONLINE(1,"接单中"),
+    RESTING(0,"休息中");
+
+    private final Integer code;
+    private final String desc;
+
+    TechnicianStatusEnum(Integer code,String desc) {
+        this.desc = desc;
+        this.code = code;
+    }
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}

+ 1 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MaTeProjectMapper.java

@@ -28,7 +28,7 @@ public interface MaTeProjectMapper extends BaseMapper<MaTeProject> {
      * @param technicianId 商户ID
      * @return 影响行数
      */
-    default int deleteByTechnicianId(Long technicianId) {
+    default int deleteByTechnicianId(Integer technicianId) {
         return delete(new LambdaQueryWrapper<MaTeProject>().eq(MaTeProject::getTeId, technicianId));
     }
 }

+ 18 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MerchantApplyFileMapper.java

@@ -0,0 +1,18 @@
+package com.ylx.massage.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ylx.massage.domain.MerchantApplyFile;
+import org.apache.ibatis.annotations.Mapper;
+
+
+/**
+ * 入驻申请附件表(MerchantApplyFile)表数据库访问层
+ *
+ * @author makejava
+ * @since 2026-06-05 16:26:47
+ */
+@Mapper
+public interface MerchantApplyFileMapper extends BaseMapper<MerchantApplyFile> {
+
+}
+

+ 17 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MerchantDailyAttendanceMapper.java

@@ -0,0 +1,17 @@
+package com.ylx.massage.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ylx.massage.domain.MerchantDailyAttendance;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 商户每日考勤统计表(MerchantDailyAttendance)表数据库访问层
+ *
+ * @author makejava
+ * @since 2026-06-05 11:16:04
+ */
+@Mapper
+public interface MerchantDailyAttendanceMapper extends BaseMapper<MerchantDailyAttendance> {
+
+}
+

+ 13 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java

@@ -8,6 +8,8 @@ import com.ylx.common.core.domain.AjaxResult;
 import com.ylx.common.core.domain.model.LoginUser;
 import com.ylx.massage.domain.MaProject;
 import com.ylx.massage.domain.MaTechnician;
+import com.ylx.massage.domain.dto.*;
+import com.ylx.massage.domain.vo.*;
 import com.ylx.massage.domain.dto.MaProjectSaveDto;
 import com.ylx.massage.domain.dto.MaTechnicianAuditQueryDTO;
 import com.ylx.massage.domain.dto.MaTechnicianAuditSubmitDTO;
@@ -169,7 +171,17 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
      * @param req
      */
     void apply(MaTechnicianAppAddVo req);
-
+    /**
+     * 申请技师文件
+     * @param req
+     */
+    void applyFile(MerchantApplyFileDto req);
+    /**
+     * 技师状态切换
+     *
+     * @param
+     */
+    Result switchToOffline(Long userId, Boolean forceConfirm);
     /**
      * 技师列表
      *

+ 18 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/MerchantDailyAttendanceService.java

@@ -0,0 +1,18 @@
+package com.ylx.massage.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ylx.massage.domain.MerchantDailyAttendance;
+import org.springframework.stereotype.Service;
+
+
+/**
+ * 商户每日考勤统计表(MerchantDailyAttendance)表服务接口
+ *
+ * @author makejava
+ * @since 2026-06-05 11:16:10
+ */
+@Service
+public interface MerchantDailyAttendanceService extends IService<MerchantDailyAttendance> {
+
+}
+

+ 348 - 93
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java

@@ -1,17 +1,32 @@
 package com.ylx.massage.service.impl;
 
+import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.attendanceconfig.domain.AttendanceRule;
+import com.ylx.attendanceconfig.mapper.AttendanceRuleMapper;
 import com.ylx.common.core.domain.AjaxResult;
 import com.ylx.common.core.domain.model.LoginUser;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.common.utils.DateUtils;
 import com.ylx.common.utils.StringUtils;
+import com.ylx.massage.controller.CityOperationApplicationController;
+import com.ylx.massage.domain.*;
+import com.ylx.massage.domain.dto.*;
+import com.ylx.massage.domain.vo.*;
+import com.ylx.massage.enums.ProjectCategoryEnum;
+import com.ylx.massage.enums.TechnicianStatusEnum;
+import com.ylx.massage.mapper.*;
 import com.ylx.massage.domain.ContractRecord;
 import com.ylx.massage.domain.MaProject;
 import com.ylx.massage.domain.MaTeProject;
@@ -37,12 +52,14 @@ import lombok.Data;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.ylx.massage.mapper.MaTechnicianMapper;
-import com.ylx.massage.domain.MaTechnician;
 import com.ylx.massage.service.IMaTechnicianService;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.annotation.Resource;
+
+import static com.ylx.massage.enums.FileTypeEnum.*;
+
 /**
  * 技师Service业务层处理
  *
@@ -65,12 +82,12 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
     private static final Integer NOT_DELETED = 0;
     private static final String MERCHANT_STATUS_NORMAL = "0";
 
-    @Autowired
+    @Resource
     private MaTechnicianMapper maTechnicianMapper;
 
-    @Autowired
+    @Resource
     private MaTeProjectMapper maTeProjectMapper;
-    @Autowired
+    @Resource
     private MaProjectMapper maProjectMapper;
 
     @Autowired
@@ -79,8 +96,17 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
     @Autowired
     private TbFileService fileService;
 
-    @Autowired
+    @Resource
     private ContractRecordMapper contractRecordMapper;
+    @Resource
+    private MerchantDailyAttendanceMapper merchantDailyAttendanceMapper;
+    @Resource
+    private AttendanceRuleMapper attendanceRuleMapper;
+    @Resource
+    private TAddressMapper addressMapper;
+    @Resource
+    private MerchantApplyFileMapper merchantApplyFileMapper;
+
 
     /**
      * 商户入驻申请注册
@@ -101,6 +127,18 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         maTechnicianMapper.insert(maTechnician);
 
     }
+    /**
+     * 商户入驻申请文件上传
+     * @param req
+     */
+    @Override
+    public  void applyFile(MerchantApplyFileDto req){
+        MerchantApplyFile maTechnician = new MerchantApplyFile();
+        BeanUtils.copyProperties(req, maTechnician);
+        maTechnician.setCreateBy(req.getMerchantId().toString());
+        maTechnician.setUpdateBy(req.getMerchantId().toString());
+        merchantApplyFileMapper.insert(maTechnician);
+    }
 
     /**
      * 商户入住前置条件校验
@@ -146,7 +184,7 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         queryWrapper.eq(MaTechnician::getIsDelete, 0);
         queryWrapper.eq(MaTechnician::getAuditStatus, 2);
         //queryWrapper.eq(MaTechnician::getOpenService, req.getOpenService());
-        queryWrapper.eq(MaTechnician::getServiceTag,req.getServiceTag());
+        queryWrapper.eq(MaTechnician::getServiceTag, req.getServiceTag());
         MaTechnician userProfile = maTechnicianMapper.selectOne(queryWrapper);
         return userProfile;
     }
@@ -282,7 +320,7 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         if (id == null) {
             throw new ServiceException("商户ID不能为空");
         }
-        MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id);
+        MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id.intValue());
         if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
             throw new ServiceException("商户不存在或已删除");
         }
@@ -357,45 +395,6 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         }
         return uploadResult;
     }
-
-    /**
-     * 全量替换商户与服务项目关联关系。
-     *
-     * @param technicianId 商户ID
-     * @param projectIds   服务项目ID集合
-     */
-    private void replaceProjectRelations(Integer technicianId, Set<Long> projectIds) {
-        if (technicianId == null) {
-            throw new ServiceException("商户ID不能为空");
-        }
-        maTeProjectMapper.deleteByTechnicianId(technicianId.longValue());
-        for (Long projectId : projectIds) {
-            MaTeProject relation = new MaTeProject();
-            relation.setTeId(technicianId.longValue());
-            relation.setProjectId(projectId);
-            int rows = maTeProjectMapper.insert(relation);
-            if (rows <= 0) {
-                throw new ServiceException("编辑商户服务项目失败");
-            }
-        }
-    }
-
-    /**
-     * 后台查询商户入驻审核列表
-     *
-     * @param page 分页参数
-     * @param dto  查询条件
-     * @return 商户入驻审核分页列表
-     */
-    @Override
-    public Page<MaTechnicianAuditListVO> selectMerchantAuditList(Page<MaTechnicianAuditListVO> page, MaTechnicianAuditQueryDTO dto) {
-        if (dto != null && dto.getAuditStatus() != null) {
-            checkEnumValue(dto.getAuditStatus(), "审核状态", 0, 1, 2, 3);
-        }
-        Page<MaTechnicianAuditListVO> pageParam = page == null ? new Page<>(1, 10) : page;
-        return maTechnicianMapper.selectMerchantAuditList(pageParam, dto);
-    }
-
     /**
      * 商户入驻审核。
      *
@@ -448,6 +447,92 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         }
         return rows;
     }
+    /**
+     * 后台查询商户证照
+     *
+     * @param id 商户ID
+     * @return 商户证照
+     */
+    @Override
+    public MaTechnicianCertificateVO selectMerchantCertificate(Integer id) {
+        if (id == null) {
+            throw new ServiceException("商户ID不能为空");
+        }
+        LambdaQueryWrapper<MerchantApplyFile> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(MerchantApplyFile::getMerchantId, id);
+        List<MerchantApplyFile> merchantApplyFiles = merchantApplyFileMapper.selectList(queryWrapper);
+        if (merchantApplyFiles == null) {
+            throw new ServiceException("商户不存在或已删除");
+        }
+        MaTechnicianCertificateVO certificate = new MaTechnicianCertificateVO();
+        certificate.setMerchantId(merchantApplyFiles.get(0).getMerchantId());
+        merchantApplyFiles.forEach(merchant -> {
+            certificate.setAvatar( typeFIleUrl(merchant,PORTRAIT.getCode()));
+            certificate.setLifePhotos( typeFIleUrl(merchant,LIFE_PHOTO.getCode()));
+            certificate.setIdCardFrout( typeFIleUrl(merchant,ID_CARD_FRONT.getCode()));
+            certificate.setIdCardBack( typeFIleUrl(merchant,ID_CARD_BACK.getCode()));
+            certificate.setIdCardHandheld( typeFIleUrl(merchant,ID_CARD_HANDHELD.getCode()));
+            certificate.setHealthCertificate( typeFIleUrl(merchant,HEALTH_CERT.getCode()));
+            certificate.setQualificationCertificate( typeFIleUrl(merchant,QUALIFICATION_CERT.getCode()));
+            certificate.setNoCrimeRecord( typeFIleUrl(merchant,NO_CRIME_RECORD.getCode()));
+            certificate.setCommitmentPdf( typeFIleUrl(merchant,COMMITMENT_LETTER.getCode()));
+            certificate.setCommitmentVideo( typeFIleUrl(merchant,COMMITMENT_VIDEO.getCode()));
+            certificate.setCommitmentAudio( typeFIleUrl(merchant,COMMITMENT_AUDIO.getCode()));
+
+        });
+
+        return certificate;
+    }
+
+    private String typeFIleUrl(MerchantApplyFile merchant, String type) {
+
+        LambdaQueryWrapper<MerchantApplyFile> queryWrapper1 = Wrappers.lambdaQuery();
+        queryWrapper1.eq(MerchantApplyFile::getMerchantId, merchant.getMerchantId());
+        queryWrapper1.eq(MerchantApplyFile::getFileType, type);
+        MerchantApplyFile merchantApplyFiles = merchantApplyFileMapper.selectOne(queryWrapper1);
+        if (merchantApplyFiles == null) {
+            return null;
+        }
+        return merchantApplyFiles.getFileUrl();
+    }
+
+    /**
+     * 全量替换商户与服务项目关联关系。
+     *
+     * @param technicianId 商户ID
+     * @param projectIds   服务项目ID集合
+     */
+    private void replaceProjectRelations(Integer technicianId, Set<Integer> projectIds) {
+        if (technicianId == null) {
+            throw new ServiceException("商户ID不能为空");
+        }
+        maTeProjectMapper.deleteByTechnicianId(technicianId);
+        for (Integer projectId : projectIds) {
+            MaTeProject relation = new MaTeProject();
+            relation.setTeId(technicianId);
+            relation.setProjectId(projectId);
+            int rows = maTeProjectMapper.insert(relation);
+            if (rows <= 0) {
+                throw new ServiceException("编辑商户服务项目失败");
+            }
+        }
+    }
+
+    /**
+     * 后台查询商户入驻审核列表
+     *
+     * @param page 分页参数
+     * @param dto  查询条件
+     * @return 商户入驻审核分页列表
+     */
+    @Override
+    public Page<MaTechnicianAuditListVO> selectMerchantAuditList(Page<MaTechnicianAuditListVO> page, MaTechnicianAuditQueryDTO dto) {
+        if (dto != null && dto.getAuditStatus() != null) {
+            checkEnumValue(dto.getAuditStatus(), "审核状态", 0, 1, 2, 3);
+        }
+        Page<MaTechnicianAuditListVO> pageParam = page == null ? new Page<>(1, 10) : page;
+        return maTechnicianMapper.selectMerchantAuditList(pageParam, dto);
+    }
 
     /**
      * 后台查询商户列表
@@ -481,36 +566,6 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         return detail;
     }
 
-    /**
-     * 后台查询商户证照
-     *
-     * @param id 商户ID
-     * @return 商户证照
-     */
-    @Override
-    public MaTechnicianCertificateVO selectMerchantCertificate(Integer id) {
-        if (id == null) {
-            throw new ServiceException("商户ID不能为空");
-        }
-        MaTechnician merchant = maTechnicianMapper.selectMerchantCertificateById(id);
-        if (merchant == null || !NOT_DELETED.equals(merchant.getIsDelete())) {
-            throw new ServiceException("商户不存在或已删除");
-        }
-
-        MaTechnicianCertificateVO certificate = new MaTechnicianCertificateVO();
-        certificate.setMerchantId(merchant.getId());
-        certificate.setAvatar(merchant.getAvatar());
-        certificate.setLifePhotos(merchant.getLifePhotos());
-        certificate.setIdCard(merchant.getIdCard());
-        certificate.setHealthCertificate(merchant.getHealthCertificate());
-        certificate.setQualificationCertificate(merchant.getQualificationCertificate());
-        certificate.setNoCrimeRecord(merchant.getNoCrimeRecord());
-        certificate.setCommitmentPdf(merchant.getCommitmentPdf());
-        certificate.setCommitmentVideo(merchant.getCommitmentVideo());
-        certificate.setCommitmentAudio(merchant.getCommitmentAudio());
-        return certificate;
-    }
-
     /**
      * 修改技师
      *
@@ -622,13 +677,13 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
      * @param categoryIds 服务类目ID集合
      * @return 有效服务项目ID集合
      */
-    private MerchantProjectSelection checkProjectIds(List<Long> projectIds, Set<Integer> categoryIds) {
+    private MerchantProjectSelection checkProjectIds(List<Integer> projectIds, Set<Integer> categoryIds) {
         if (projectIds == null || projectIds.isEmpty()) {
             throw new ServiceException("服务项目不能为空");
         }
 
-        Set<Long> distinctProjectIds = new LinkedHashSet<>();
-        for (Long projectId : projectIds) {
+        Set<Integer> distinctProjectIds = new LinkedHashSet<>();
+        for (Integer projectId : projectIds) {
             if (projectId == null) {
                 throw new ServiceException("服务项目ID不能为空");
             }
@@ -642,10 +697,10 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
             throw new ServiceException("服务项目不存在或已删除");
         }
 
-        Map<Long, Project> projectMap = projects.stream()
-                                                .collect(Collectors.toMap(project -> project.getId().longValue(), Function.identity(), (left, right) -> left));
+        Map<Integer, Project> projectMap = projects.stream()
+                                                .collect(Collectors.toMap(project -> project.getId(), Function.identity(), (left, right) -> left));
         Set<Integer> projectCategoryIds = new LinkedHashSet<>();
-        for (Long projectId : distinctProjectIds) {
+        for (Integer projectId : distinctProjectIds) {
             Project project = projectMap.get(projectId);
             if (project == null || project.getCategoryId() == null) {
                 throw new ServiceException("服务项目类目不能为空");
@@ -687,7 +742,7 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
                        .collect(Collectors.joining(","));
     }
 
-    private String joinProjectTitles(Set<Long> projectIds, Map<Long, Project> projectMap) {
+    private String joinProjectTitles(Set<Integer> projectIds, Map<Integer, Project> projectMap) {
         return projectIds.stream()
                        .map(projectMap::get)
                        .map(Project::getTitle)
@@ -701,15 +756,15 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
      * @param technicianId
      * @param projectIds
      */
-    private void insertProjectRelations(Integer technicianId, Set<Long> projectIds) {
+    private void insertProjectRelations(Integer technicianId, Set<Integer> projectIds) {
         if (technicianId == null) {
             throw new ServiceException("商户ID不能为空");
         }
 
         List<MaTeProject> relations = new ArrayList<>();
-        for (Long projectId : projectIds) {
+        for (Integer projectId : projectIds) {
             MaTeProject relation = new MaTeProject();
-            relation.setTeId(technicianId.longValue());
+            relation.setTeId(technicianId);
             relation.setProjectId(projectId);
             relations.add(relation);
         }
@@ -721,10 +776,10 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
 
     private static class MerchantProjectSelection {
         private final Set<Integer> categoryIds;
-        private final Set<Long> projectIds;
-        private final Map<Long, Project> projectMap;
+        private final Set<Integer> projectIds;
+        private final Map<Integer, Project> projectMap;
 
-        private MerchantProjectSelection(Set<Integer> categoryIds, Set<Long> projectIds, Map<Long, Project> projectMap) {
+        private MerchantProjectSelection(Set<Integer> categoryIds, Set<Integer> projectIds, Map<Integer, Project> projectMap) {
             this.categoryIds = categoryIds;
             this.projectIds = projectIds;
             this.projectMap = projectMap;
@@ -734,11 +789,11 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
             return categoryIds;
         }
 
-        private Set<Long> getProjectIds() {
+        private Set<Integer> getProjectIds() {
             return projectIds;
         }
 
-        private Map<Long, Project> getProjectMap() {
+        private Map<Integer, Project> getProjectMap() {
             return projectMap;
         }
     }
@@ -799,9 +854,209 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
             maProject.setProjectLowestPrice(project.getPriceMin());
             maProject.setCreateBy(dto.getUserId());
             maProject.setMerchantId(dto.getUserId());
-            maProject.setApplyTime((Data) new Date());
+            maProject.setApplyTime(DateUtils.getNowDate());
             maProject.setMerchantPhone(dto.getMerchantPhone());
+            maProject.setCreateTime(DateUtils.getNowDate());
             maProjectMapper.insert(maProject);
         }
     }
+
+    /**
+     * 状态切换
+     *
+     * @param userId
+     * @param forceConfirm
+     * @return
+     */
+    @Override
+    public Result switchToOffline(Long userId, Boolean forceConfirm) {
+        MaTechnician technician = getTechnician(userId);
+
+        // 1. 基础权限校验 (对应流程图左下角)
+        if (!hasActiveSkills(userId)) {
+            throw new RuntimeException("请先申请开通技能");
+        }
+        if (!hasHomeAddress(userId)) {
+            // 这里通常会触发前端弹窗要求添加地址,或者直接阻断
+            throw new RuntimeException("请完善家庭地址");
+        }
+        if (ProjectCategoryEnum.MASSAGE.getCode().equals(technician.getServiceTag())) {
+            // 2. 状态判断逻辑 (对应流程图中间的菱形判断)
+            // 只有处于“在线接单”状态才需要检查疲劳度/时长限制
+            if (TechnicianStatusEnum.ONLINE.getCode().equals(technician.getPostState())) {
+                // 获取今日的商户考勤记录
+                MerchantDailyAttendance attendance = getTodayAttendance(userId);
+
+                long minutesOnline = 0;
+                if (attendance != null && attendance.getAttendanceStartTime() != null) {
+                    LocalDateTime localDateTime = attendance.getAttendanceStartTime().toInstant()
+                                                          .atZone(ZoneId.systemDefault())
+                                                          .toLocalDateTime();
+                    //计算截止现在的时长,单位为分钟
+                    minutesOnline = Duration.between(localDateTime, LocalDateTime.now()).toMinutes();
+                    // 计算今日的累加在线时长
+                    minutesOnline = minutesOnline + getWorkDuration(userId);
+                }
+                AttendanceRule rule = getAttendanceRule();
+                if (rule != null) {
+                    // 将小时转换为分钟进行比较
+                    BigDecimal minutes = rule.getBasicWorkHours().multiply(new BigDecimal(60));
+                    // 2. 精确转换成 long(无小数、无溢出才成功)
+                    long requiredMinutes = minutes.longValueExact();
+                    // 判断是否超过了平台规定的在线时间 (X小时)
+                    if (minutesOnline < requiredMinutes) {
+                        // 情况 A: 超时了,且用户还没有点击“确认下线”(forceConfirm=false)
+                        if (!forceConfirm) {
+                            // 返回特定错误码或数据结构,告诉前端弹出“我在想想/确认下线”的模态框
+                            return Result.ok("平台对您的在线时间做了约定,每日在线需满足"
+                                                     + (requiredMinutes / 60) + "小时,距离您下线时间还剩余" + ((requiredMinutes / 60) - minutesOnline) + "小时"
+                                                     + "不满足在线时间将收到平台处罚,是否确认下线?");
+                        }
+                        // 情况 B: 超时了,但用户已经点击了“确认下线”,允许通过
+                    }
+                }
+            }
+
+            // 3. 执行状态更新 (更新为休息中状态)
+            updateStatus(userId, TechnicianStatusEnum.RESTING);
+            if (!forceConfirm) {
+                // 查询商户今日的最近考勤记录
+                LambdaQueryWrapper<MerchantDailyAttendance> query = new LambdaQueryWrapper<>();
+                query.eq(MerchantDailyAttendance::getMerchantId, userId)
+                        .eq(MerchantDailyAttendance::getAttendanceDate, DateUtils.getNowDate())
+                        .orderByDesc(MerchantDailyAttendance::getCreateTime);
+                MerchantDailyAttendance update = merchantDailyAttendanceMapper.selectOne(query);
+                if (update != null) {
+                    LocalDateTime localDateTime = update.getAttendanceStartTime().toInstant()
+                                                          .atZone(ZoneId.systemDefault())
+                                                          .toLocalDateTime();
+                    LambdaUpdateWrapper<MerchantDailyAttendance> updateWrapper = new LambdaUpdateWrapper<>();
+                    updateWrapper.eq(MerchantDailyAttendance::getId, update.getId())
+                            .set(MerchantDailyAttendance::getAttendanceEndTime, DateUtils.getNowDate())
+                            .set(MerchantDailyAttendance::getTotalWorkMinutes, Duration.between(localDateTime, LocalDateTime.now()).toMinutes())
+                            .set(MerchantDailyAttendance::getUpdateTime, DateUtils.getNowDate());
+                    merchantDailyAttendanceMapper.update(update, updateWrapper);
+                }
+
+
+            }
+        } else {
+            //更新状态为在线接单
+            updateStatus(userId, TechnicianStatusEnum.ONLINE);
+            // 插入今日考勤记录
+            MerchantDailyAttendance merchantDailyAttendance = new MerchantDailyAttendance()
+                                                                      .setMerchantId(userId.intValue())
+                                                                      .setAttendanceDate(DateUtils.getNowDate())
+                                                                      .setMerchantName(technician.getTeName())
+                                                                      .setAttendanceStartTime(DateUtils.getNowDate())
+                                                                      .setCreateBy(technician.getTeName())
+                                                                      .setCreateTime(LocalDateTime.now());
+            merchantDailyAttendanceMapper.insert(merchantDailyAttendance);
+        }
+        return Result.ok("状态已切换成功");
+    }
+
+    /**
+     * 获取今天商户的考勤记录
+     *
+     * @param userId 技师ID
+     * @return 考勤记录
+     */
+    private MerchantDailyAttendance getTodayAttendance(Long userId) {
+        LambdaQueryWrapper<MerchantDailyAttendance> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(MerchantDailyAttendance::getMerchantId, userId)
+                .eq(MerchantDailyAttendance::getAttendanceDate, DateUtils.getNowDate())
+                .orderByDesc(MerchantDailyAttendance::getCreateTime)
+                .last("LIMIT 1");
+
+        return merchantDailyAttendanceMapper.selectOne(wrapper);
+    }
+
+    /**
+     * 获取商户的累计工作时长
+     *
+     * @param userId 技师ID
+     * @return 工作时长(分钟)
+     */
+    private long getWorkDuration(Long userId) {
+        LambdaQueryWrapper<MerchantDailyAttendance> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(MerchantDailyAttendance::getMerchantId, userId)
+                .eq(MerchantDailyAttendance::getAttendanceDate, DateUtils.getNowDate());
+        List<MerchantDailyAttendance> attendances = merchantDailyAttendanceMapper.selectList(wrapper);
+        if (attendances == null || attendances.isEmpty()) return 0;
+        // 计算指定日期的总分钟数
+        long totalMinutes = attendances.stream()
+                                    .filter(attendance -> DateUtils.getNowDate().toString().equals(attendance.getAttendanceDate().toString()))
+                                    .mapToLong(MerchantDailyAttendance::getTotalWorkMinutes)
+                                    .sum();
+        return totalMinutes;
+    }
+
+    /**
+     * 获取商户的考勤规则
+     *
+     * @return 考勤规则
+     */
+    private AttendanceRule getAttendanceRule() {
+        LambdaQueryWrapper<AttendanceRule> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(AttendanceRule::getIsDelete, 0);
+        wrapper.eq(AttendanceRule::getWorkDurationRuleEnabled, 1);
+        wrapper.last("LIMIT 1");
+        return attendanceRuleMapper.selectOne(wrapper);
+    }
+
+    /**
+     * 判断用户是否有生效中的技能
+     *
+     * @param userId 技师ID
+     * @return true: 有可用技能, false: 无
+     */
+    public boolean hasActiveSkills(Long userId) {
+        if (userId == null) return false;
+
+        // 构建查询条件:用户ID匹配 AND 状态为已发布/生效中
+        LambdaQueryWrapper<MaProject> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(MaProject::getMerchantId, userId)
+                .eq(MaProject::getAuditStatus, 1); // 假设 1 代表 "生效/已审核"
+        // 只要查到一条记录即返回 true
+        return maProjectMapper.selectCount(wrapper) > 0;
+    }
+
+    /**
+     * 判断用户是否有家庭地址(通常指设置为默认的地址)
+     *
+     * @param userId 技师ID
+     * @return true: 有地址, false: 无
+     */
+    public boolean hasHomeAddress(Long userId) {
+        if (userId == null) return false;
+
+        // 构建查询条件:用户ID匹配 AND 是默认地址(可选) AND 未删除
+        LambdaQueryWrapper<TAddress> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TAddress::getMerchantId, userId)
+                .eq(TAddress::getUserType, 2)  // 商户类型
+                .eq(TAddress::getIsDelete, 0);  // 逻辑未删除
+
+        // 统计数量是否大于0
+        long count = addressMapper.selectCount(wrapper);
+        return count > 0;
+    }
+
+    // 辅助方法:模拟获取用户
+    private MaTechnician getTechnician(Long userId) {
+        // ... DB查询逻辑
+        LambdaQueryWrapper<MaTechnician> query = new LambdaQueryWrapper<>();
+        query.eq(MaTechnician::getId, userId);
+        return maTechnicianMapper.selectOne(query);
+    }
+
+    // 辅助方法:更新状态
+    private void updateStatus(Long userId, TechnicianStatusEnum status) {
+        LambdaUpdateWrapper<MaTechnician> update = new LambdaUpdateWrapper<>();
+        update.eq(MaTechnician::getId, userId);
+        update.set(MaTechnician::getPostState, status.getCode());
+        // ... DB Update逻辑,同时记录上线/下线时间
+        maTechnicianMapper.update(null, update);
+
+    }
 }

+ 19 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MerchantDailyAttendanceServiceImpl.java

@@ -0,0 +1,19 @@
+package com.ylx.massage.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.massage.domain.MerchantDailyAttendance;
+import com.ylx.massage.mapper.MerchantDailyAttendanceMapper;
+import com.ylx.massage.service.MerchantDailyAttendanceService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 商户每日考勤统计表(MerchantDailyAttendance)表服务实现类
+ *
+ * @author makejava
+ * @since 2026-06-05 11:16:10
+ */
+@Service("merchantDailyAttendanceService")
+public class MerchantDailyAttendanceServiceImpl extends ServiceImpl<MerchantDailyAttendanceMapper, MerchantDailyAttendance> implements MerchantDailyAttendanceService {
+
+}
+