Parcourir la source

Merge remote-tracking branch 'origin/dev' into dev

郭子栋 il y a 6 jours
Parent
commit
accccbe4b9
22 fichiers modifiés avec 1357 ajouts et 46 suppressions
  1. 360 11
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java
  2. 87 0
      nightFragrance-massage/src/main/java/com/ylx/massage/controller/CityOperationApplicationController.java
  3. 88 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/CityOperationApplication.java
  4. 69 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/ContractRecord.java
  5. 89 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/ContractRecords.java
  6. 3 3
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MaTechnician.java
  7. 16 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/CityOperationApplicationMapper.java
  8. 8 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/ContractRecordMapper.java
  9. 13 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MaTeProjectMapper.java
  10. 30 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MaTechnicianMapper.java
  11. 18 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/CityOperationApplicationService.java
  12. 68 2
      nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java
  13. 19 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CityOperationApplicationServiceImpl.java
  14. 299 15
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java
  15. 16 13
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TFareFreeRuleServiceImpl.java
  16. 10 0
      nightFragrance-massage/src/main/java/com/ylx/project/controller/ProjectController.java
  17. 26 0
      nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/dto/BookMerchantDTO.java
  18. 42 0
      nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/vo/BookMerchantVO.java
  19. 21 0
      nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/vo/BookProjectDetailVO.java
  20. 36 0
      nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/vo/ProjectInfoVO.java
  21. 4 0
      nightFragrance-massage/src/main/java/com/ylx/project/service/ProjectService.java
  22. 35 2
      nightFragrance-massage/src/main/java/com/ylx/project/service/impl/ProjectServiceImpl.java

+ 360 - 11
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java

@@ -1,28 +1,42 @@
 package com.ylx.web.controller.massage;
 
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import cn.hutool.json.JSONObject;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.core.domain.R;
 import com.ylx.common.core.domain.model.LoginUser;
+import com.ylx.common.core.domain.model.aliyun.SMSVerificationCode;
+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.MaProjectSaveDto;
+import com.ylx.massage.domain.dto.MaProjectUpdateDto;
 import com.ylx.massage.domain.dto.MaTechnicianMerchantAddDTO;
 import com.ylx.massage.domain.dto.MaTechnicianMerchantQueryDTO;
-import com.ylx.massage.domain.vo.MaTechnicianAppAddVo;
-import com.ylx.massage.domain.vo.MaTechnicianMerchantDetailVO;
-import com.ylx.massage.domain.vo.MaTechnicianMerchantListVO;
+import com.ylx.massage.domain.vo.*;
+import com.ylx.massage.service.IMaProjectService;
+import com.ylx.project.domain.Project;
+import com.ylx.servicecategory.domain.ServiceCategory;
+import com.ylx.servicecategory.service.ServiceCategoryService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
 import com.ylx.common.annotation.Log;
 import com.ylx.common.core.controller.BaseController;
 import com.ylx.common.core.domain.AjaxResult;
@@ -31,6 +45,7 @@ import com.ylx.massage.domain.MaTechnician;
 import com.ylx.massage.service.IMaTechnicianService;
 import com.ylx.common.utils.poi.ExcelUtil;
 import com.ylx.common.core.page.TableDataInfo;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 技师Controller
@@ -44,6 +59,194 @@ import com.ylx.common.core.page.TableDataInfo;
 public class MaTechnicianController extends BaseController {
     @Autowired
     private IMaTechnicianService maTechnicianService;
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    @Autowired
+    private SendSmsComponents sendSms;
+    @Autowired
+    private ServiceCategoryService serviceCategoryService;
+    @Autowired
+    private IMaProjectService maProjectService;
+
+    public static final String PHONE_THREEUSERPARTCLIENT_CODE_KEY = "sys:threeUserPartClient:phone:";
+
+    @GetMapping("/sendMsg")
+    @ApiOperation(value = "短信发送", notes = "短信发送")
+    public Result sendMsg(@RequestParam String phone, HttpServletRequest request) {
+        if (org.apache.commons.lang3.StringUtils.isEmpty(phone)) {
+            return Result.error("手机号不能为空");
+        }
+        Random rand = new Random();
+        // randNumber 将被赋值为一个 MIN 和 MAX 范围内的随机数
+        int randNumber = rand.nextInt(9999 - 1000 + 1) + 1000;
+        // 保存验证码到redis
+        redisTemplate.opsForValue()
+                .set("userH5:order:phone:" + phone, String.valueOf(randNumber), 5L
+                        , TimeUnit.MINUTES);
+        try {
+            SMSVerificationCode smsVerificationCode = new SMSVerificationCode(String.valueOf(randNumber));
+            String jsonString = JSON.toJSONString(smsVerificationCode);
+            sendSms.sendSms(phone, SendSmsEnum.SMS_220650024, jsonString);
+            return Result.ok("发送成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return Result.ok("发送失败");
+    }
+
+    /**
+     * 商户登录接口
+     *
+     * @param thirdPartyLoginsVo
+     * @return
+     */
+    @ApiOperation(value = "商户登录", notes = "商户登录")
+    @PostMapping(value = "/clientLogin")
+    @Transactional
+    public Result<JSONObject> login(@RequestBody ThirdPartyLoginsVo thirdPartyLoginsVo) throws Exception {
+        // 获取登录用户信息
+        Result<JSONObject> result = new Result<>();
+
+        // 校验手机号是否为空
+        if (StringUtils.isEmpty(thirdPartyLoginsVo.getPhone())) {
+            return result.error500("请输入手机号");
+        }
+
+        // 校验用户是否存在且有效
+        LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(MaTechnician::getTePhone, thirdPartyLoginsVo.getPhone());
+        MaTechnician maTechnician = maTechnicianService.getBaseMapper().selectOne(queryWrapper);
+        // 校验用户是否有效
+        if (ObjectUtils.isEmpty(maTechnician)) {
+            return result.error500("商户不存在,请先注册");
+
+        }
+
+        if (thirdPartyLoginsVo.getCodeSwitch()) {
+            // 短信验证
+            String msg = redisTemplate.opsForValue().get(PHONE_THREEUSERPARTCLIENT_CODE_KEY + thirdPartyLoginsVo.getPhone());
+            if (StringUtils.isEmpty(msg)) {
+                return Result.error("验证码已失效");
+            }
+
+            if (!thirdPartyLoginsVo.getPhoneMsg().equals(msg)) {
+                return Result.error("短信验证码不正确");
+            }
+        } else {
+            if (!thirdPartyLoginsVo.getPassWord().equals(maTechnician.getTePassword())) {
+                return Result.error("密码错误");
+            }
+
+        }
+
+        // 登录成功删除验证码
+        redisTemplate.delete(PHONE_THREEUSERPARTCLIENT_CODE_KEY + thirdPartyLoginsVo.getPhone());
+        result.success("登录成功");
+        return result;
+    }
+
+    /**
+     * 商户忘记密码接口
+     */
+    @PostMapping("/resetPassword")
+    public Result<?> resetPassword(@RequestBody ThirdPartyLoginsVo thirdPartyLoginsVo) {
+        // 核心正则表达式:
+        // ^ 表示开头,$ 表示结尾
+        // [a-zA-Z0-9] 表示只能是字母或数字
+        // {8,20} 表示长度必须在8到20之间
+        String regex = "^[a-zA-Z0-9]{8,20}$";
+        boolean isMatch = Pattern.matches(regex, thirdPartyLoginsVo.getPhoneMsg());
+        if (!isMatch) {
+            // 根据需求返回指定的异常提示
+            return Result.error("请输入8-20位数字/字母组合");
+        }
+        // 短信验证
+        String msg = redisTemplate.opsForValue().get(PHONE_THREEUSERPARTCLIENT_CODE_KEY + thirdPartyLoginsVo.getPhone());
+        if (StringUtils.isEmpty(msg)) {
+            return Result.error("验证码已失效");
+        }
+        if (!thirdPartyLoginsVo.getPhoneMsg().equals(msg)) {
+            return Result.error("短信验证码不正确");
+        }
+        // 重置密码逻辑
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(MaTechnician::getTePhone, thirdPartyLoginsVo.getPhone());
+        updateWrapper.set(MaTechnician::getTePassword, thirdPartyLoginsVo.getPassWord());
+        maTechnicianService.update(updateWrapper);
+        redisTemplate.delete(PHONE_THREEUSERPARTCLIENT_CODE_KEY + thirdPartyLoginsVo.getPhone());
+        return Result.ok("重置密码成功");
+    }
+
+    /**
+     * 商户入驻申请接口
+     */
+    @PostMapping("/apply")
+    public Result<?> apply(@RequestBody MaTechnicianAppAddVo req) {
+        // 1. 基础参数校验
+        if (StringUtils.isAnyBlank(req.getTeName(), req.getTePhone(), req.getTeAddress(), req.getTeAvatar(), req.getLifePhotos(), req.getTeBrief(), req.getAvatar(), req.getIdCard())) {
+            return Result.error("必填项不能为空");
+        }
+        // 2. 调用业务层处理入驻申请
+        maTechnicianService.apply(req);
+        return Result.ok("提交成功,进入审核流程");
+    }
+
+    /**
+     * 查询商户信息接口
+     */
+    @GetMapping("/getTechnician")
+    public Result<?> getTechnician(@RequestParam String phone) {
+        LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(MaTechnician::getTePhone, phone);
+        MaTechnician maTechnician = maTechnicianService.getBaseMapper().selectOne(queryWrapper);
+        return Result.ok(maTechnician);
+    }
+
+    /**
+     * 修改和上传商户信息接口
+     */
+    @PostMapping("/updateTechnician")
+    public Result<?> updateTechnician(@RequestBody MaTechnicianAppAddVo req) {
+        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<>();
+        updateWrapper.eq(MaTechnician::getId, req.getId());
+        updateWrapper.set(MaTechnician::getTePhone, req.getTePhone());
+        updateWrapper.set(MaTechnician::getTeName, req.getTeName());
+        updateWrapper.set(MaTechnician::getOpenService, req.getOpenService());
+        updateWrapper.set(MaTechnician::getTeAddress, req.getTeAddress());
+        updateWrapper.set(MaTechnician::getTeAge, req.getTeAge());
+        updateWrapper.set(MaTechnician::getAvatar, req.getAvatar());
+        maTechnicianService.update(updateWrapper);
+    }
 
     /**
      * 查询技师列表
@@ -108,6 +311,48 @@ public class MaTechnicianController extends BaseController {
         }
     }
 
+    /**
+     * 后台编辑商户
+     *
+     * @param id 商户ID
+     * @param dto 商户编辑DTO
+     * @return AjaxResult 结果
+     */
+    @ApiOperation("后台编辑商户")
+    @PreAuthorize("@ss.hasPermi('technician:technician:edit')")
+    @Log(title = "商户", businessType = BusinessType.UPDATE)
+    @PutMapping("/merchant/{id}")
+    public AjaxResult editMerchant(@PathVariable("id") Long id, @RequestBody MaTechnicianMerchantAddDTO dto) {
+        try {
+            LoginUser loginUser = getLoginUser();
+            return toAjax(maTechnicianService.updateMerchant(id, dto, loginUser));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 后台上传商户合同文件
+     *
+     * @param id 商户ID
+     * @param file 合同文件
+     * @return AjaxResult 上传结果
+     */
+    @ApiOperation("后台上传商户合同文件")
+    @PreAuthorize("@ss.hasPermi('technician:technician:edit')")
+    @Log(title = "商户合同", businessType = BusinessType.UPDATE)
+    @PostMapping("/merchant/{id}/contract")
+    public AjaxResult uploadMerchantContract(@PathVariable("id") Integer id, @RequestParam("file") MultipartFile file) {
+        try {
+            LoginUser loginUser = getLoginUser();
+            return maTechnicianService.uploadMerchantContract(id, file, loginUser);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * 查询商户列表
      *
@@ -164,4 +409,108 @@ public class MaTechnicianController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(maTechnicianService.deleteMaTechnicianByIds(ids));
     }
+
+    /**
+     * 1. 获取服务类目列表 (对应图1、图3)
+     */
+    @GetMapping("/getServiceCategoryList")
+    public AjaxResult getServiceCategoryList() {
+        List<ServiceCategory> list = serviceCategoryService.listH5ServiceCategory();
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 1. 获取技能列表 (对应图1、图3)
+     * 支持 Tab 切换:all(全部), active(已开通), applying(申请中), rejected(驳回)
+     */
+    @PostMapping("/getSkillList")
+    public TableDataInfo getSkillList(@RequestBody MaProjectGetVo req) {
+        startPage();
+        List<MaProject> list = maTechnicianService.selectMaTechnicianListBy(req.getUserId(), req.getAuditStatus());
+        if (ObjectUtils.isEmpty(list)) {
+            List<Project> projectslist = maTechnicianService.selectTechnicianListBy(req.getTypeId());
+            return getDataTable(projectslist);
+        } else {
+            return getDataTable(list);
+        }
+    }
+
+    /**
+     * 查询未开通的服务项目列表
+     *
+     * @param req
+     * @return
+     */
+    @PostMapping("/getNotApplyList")
+    public Result<?> getNotApplyList(@RequestBody MaProjectGetVo req) {
+
+        return Result.ok(maTechnicianService.getNotApplyList(req.getUserId(), req.getTypeId()));
+
+    }
+
+    /**
+     * 申请开通新服务
+     */
+    @PostMapping("/applyForService")
+    public AjaxResult applyForService(@RequestBody MaProjectSaveDto dto) {
+
+        return toAjax(maTechnicianService.applyForService(dto));
+    }
+
+    /**
+     * 重新申请开通新服务
+     *
+     * @param req
+     * @return
+     */
+    @PostMapping("/updateApply")
+    public Result<?> updateApply(@RequestBody MaProjectUpdateDto req) {
+        if (StringUtils.isNotEmpty(req.getProjectId()) && StringUtils.isNotEmpty(req.getApplyReason())) {
+            LambdaUpdateWrapper<MaProject> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(MaProject::getId, req.getProjectId());
+            updateWrapper.set(MaProject::getApplyReason, req.getApplyReason());
+            updateWrapper.set(MaProject::getAuditStatus, 0);
+            maProjectService.update(updateWrapper);
+        }
+        return Result.ok("重新申请成功,提交到审核阶段");
+
+    }
+
+    /**
+     * 申请下架,删除服务项目,编辑售价价格
+     *
+     * @param req
+     * @return
+     */
+    @PostMapping("/updateMaProject")
+    public Result<?> updateMaProject(@RequestBody MaProjectUpdateDto req) {
+        String message = "";
+        if (StringUtils.isNotEmpty(req.getProjectId())) {
+            if (req.getIsDelete()) {
+                LambdaUpdateWrapper<MaProject> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(MaProject::getId, req.getProjectId());
+                updateWrapper.set(MaProject::getIsDelete, 1);
+                maProjectService.update(updateWrapper);
+                message = "删除成功";
+            }
+            if (req.getIsPass()) {
+                LambdaUpdateWrapper<MaProject> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(MaProject::getId, req.getProjectId());
+                updateWrapper.set(MaProject::getProjectIsEnable, 1);
+                maProjectService.update(updateWrapper);
+                message = "申请下架成功";
+            }
+            if (req.getProjectCurrentPrice() != null) {
+
+                LambdaUpdateWrapper<MaProject> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(MaProject::getId, req.getProjectId());
+                updateWrapper.set(MaProject::getProjectCurrentPrice, req.getProjectCurrentPrice());
+                maProjectService.update(updateWrapper);
+                message = "修改价格完成";
+
+            }
+        }
+        return Result.ok(message);
+    }
+
 }

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

@@ -0,0 +1,87 @@
+package com.ylx.massage.controller;
+
+
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.Area;
+import com.ylx.massage.domain.CityOperationApplication;
+import com.ylx.massage.service.CityOperationApplicationService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 城市管理表(CityOperationApplication)表控制层
+ *
+ * @author makejava
+ * @since 2026-06-04 14:19:49
+ */
+@RestController
+@RequestMapping("cityOperationApplication")
+public class CityOperationApplicationController {
+    /**
+     * 服务对象
+     */
+    @Autowired
+    private CityOperationApplicationService cityOperationApplicationService;
+
+    /**
+     * 分页查询所有数据
+     *
+     * @param page 分页对象
+     * @param cityOperationApplication 查询实体
+     * @return 所有数据
+     */
+    @GetMapping("/list")
+    public R selectAll(Page<CityOperationApplication> page, CityOperationApplication cityOperationApplication) {
+        return R.ok(this.cityOperationApplicationService.page(page, new QueryWrapper<>(cityOperationApplication)));
+    }
+
+    /**
+     * 通过主键查询单条数据
+     *
+     * @param id 主键
+     * @return 单条数据
+     */
+    @GetMapping("{id}")
+    public R selectOne(@PathVariable Serializable id) {
+        return R.ok(this.cityOperationApplicationService.getById(id));
+    }
+
+    /**
+     * 新增数据
+     *
+     * @param cityOperationApplication 实体对象
+     * @return 新增结果
+     */
+    @PostMapping("/saveCity")
+    public R insert(@RequestBody CityOperationApplication cityOperationApplication) {
+        return R.ok(this.cityOperationApplicationService.save(cityOperationApplication));
+    }
+    /**
+     * 根据商户ID查询城市管理列表
+     * @param merchantId
+     * @return R
+     */
+    @GetMapping("/getCityList")
+    @ApiOperation("根据商户ID查询城市管理列表")
+    public R getCityList(@RequestParam String merchantId) {
+        LambdaQueryWrapper<CityOperationApplication> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(CityOperationApplication::getMerchantId, merchantId);
+        List<CityOperationApplication> list = this.cityOperationApplicationService.list(queryWrapper);
+        if (CollectionUtil.isEmpty(list)) {
+            return R.fail("未找到此数据");
+        }
+        return R.ok(list);
+    }
+
+
+
+}
+

+ 88 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/CityOperationApplication.java

@@ -0,0 +1,88 @@
+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;
+
+/**
+ * 城市管理表(CityOperationApplication)表实体类
+ *
+ * @author makejava
+ * @since 2026-06-04 14:27:32
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName(value = "city_operation_application",autoResultMap = true)
+public class CityOperationApplication  implements Serializable {
+//主键ID
+@TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+//城市编码 (例如: 110000)
+@TableField("city_code")
+    private String cityCode;
+//城市名称 (冗余字段,方便查询,例如: 北京市)
+@TableField("city_name")
+    private String cityName;
+//运营中心ID
+@TableField("operation_center_id")
+    private Long operationCenterId;
+//运营中心名称
+@TableField("operation_center_name")
+    private String operationCenterName;
+//实际审批通过时间
+@TableField("actual_approval_time")
+    private Date actualApprovalTime;
+//申请原因 (限制500字以内)
+    @TableField("apply_reason")
+    private String applyReason;
+
+//状态 (0:待审核, 1:已通过, 2:已驳回)
+    @TableField("status")
+    private Integer status;
+//商户ID
+    @TableField("merchant_id")
+    private String merchantId;
+    /**
+     * 创建时间
+     */
+    @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(value = "create_by")
+    private String createBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_by")
+    private String updateBy;
+
+    /**
+     * 逻辑删除:1已删除 0未删除
+     */
+    @TableField("is_delete")
+    private Integer isDelete;
+
+
+
+}
+

+ 69 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/ContractRecord.java

@@ -0,0 +1,69 @@
+package com.ylx.massage.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 合同记录实体类。
+ */
+@Data
+@TableName("contract_records")
+@ApiModel(value = "MerchantContractRecord", description = "合同记录")
+public class ContractRecord {
+
+    /**
+     * 主键ID。
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    /**
+     * 商户ID。
+     */
+    @TableField("merchant_id")
+    @ApiModelProperty("商户ID")
+    private Integer merchantId;
+
+    /**
+     * 合同名称。
+     */
+    @TableField("contract_name")
+    @ApiModelProperty("合同名称")
+    private String contractName;
+
+    /**
+     * 上传文件URL。
+     */
+    @TableField("file_url")
+    @ApiModelProperty("上传文件URL")
+    private String fileUrl;
+
+    /**
+     * 合同签定时间。
+     */
+    @TableField("sign_time")
+    @ApiModelProperty("合同签定时间")
+    private Date signTime;
+
+    /**
+     * 签订人姓名。
+     */
+    @TableField("signer_name")
+    @ApiModelProperty("签订人姓名")
+    private String signerName;
+
+    /**
+     * 创建时间。
+     */
+    @TableField("create_time")
+    @ApiModelProperty("创建时间")
+    private Date createTime;
+}

+ 89 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/ContractRecords.java

@@ -0,0 +1,89 @@
+package com.ylx.massage.domain;
+
+
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 合同记录表(ContractRecords)表实体类
+ *
+ * @author makejava
+ * @since 2026-06-04 16:45:28
+ */
+@SuppressWarnings("serial")
+public class ContractRecords {
+//主键ID
+    private Object id;
+//商户ID
+    private Integer merchantId;
+//合同名称
+    private String contractName;
+//上传文件URL
+    private String fileUrl;
+//合同签定时间
+    private Date signTime;
+//签订人姓名
+    private String signerName;
+//创建时间
+    private Date createTime;
+
+
+    public Object getId() {
+        return id;
+    }
+
+    public void setId(Object id) {
+        this.id = id;
+    }
+
+    public Integer getMerchantId() {
+        return merchantId;
+    }
+
+    public void setMerchantId(Integer merchantId) {
+        this.merchantId = merchantId;
+    }
+
+    public String getContractName() {
+        return contractName;
+    }
+
+    public void setContractName(String contractName) {
+        this.contractName = contractName;
+    }
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+    public Date getSignTime() {
+        return signTime;
+    }
+
+    public void setSignTime(Date signTime) {
+        this.signTime = signTime;
+    }
+
+    public String getSignerName() {
+        return signerName;
+    }
+
+    public void setSignerName(String signerName) {
+        this.signerName = signerName;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+}
+

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

@@ -113,7 +113,7 @@ public class MaTechnician extends BaseEntity {
     private String openService;
 
     /**
-     * 可服务项目
+     * 可服务项目名称
      */
     @TableField("te_project")
     private String teProject;
@@ -163,7 +163,7 @@ public class MaTechnician extends BaseEntity {
      * 宣传视频
      */
     @Excel(name = "宣传视频")
-    @TableField("c_mr_address_id")
+    @TableField("promo_video")
     private String promoVideo;
     /**
      * 承诺书
@@ -242,7 +242,7 @@ public class MaTechnician extends BaseEntity {
      * 是否删除(0否1是)
      */
     @Excel(name = "是否删除(0否1是)")
-    private Long isDelete;
+    private Integer isDelete;
 
     /**
      * 评分

+ 16 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/CityOperationApplicationMapper.java

@@ -0,0 +1,16 @@
+package com.ylx.massage.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ylx.massage.domain.CityOperationApplication;
+
+
+/**
+ * 城市管理表(CityOperationApplication)表数据库访问层
+ *
+ * @author makejava
+ * @since 2026-06-04 15:53:41
+ */
+public interface CityOperationApplicationMapper extends BaseMapper<CityOperationApplication> {
+
+}
+

+ 8 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/ContractRecordMapper.java

@@ -0,0 +1,8 @@
+package com.ylx.massage.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ylx.massage.domain.ContractRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ContractRecordMapper extends BaseMapper<ContractRecord> {
+}

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

@@ -1,7 +1,9 @@
 package com.ylx.massage.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ylx.massage.domain.MaTeProject;
+import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -9,6 +11,7 @@ import java.util.List;
 /**
  * 商户服务项目关联Mapper接口
  */
+@Mapper
 public interface MaTeProjectMapper extends BaseMapper<MaTeProject> {
 
     /**
@@ -18,4 +21,14 @@ public interface MaTeProjectMapper extends BaseMapper<MaTeProject> {
      * @return 影响行数
      */
     int insertBatch(@Param("entities") List<MaTeProject> entities);
+
+    /**
+     * 根据商户ID删除服务项目关联。
+     *
+     * @param technicianId 商户ID
+     * @return 影响行数
+     */
+    default int deleteByTechnicianId(Long technicianId) {
+        return delete(new LambdaQueryWrapper<MaTeProject>().eq(MaTeProject::getTeId, technicianId));
+    }
 }

+ 30 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MaTechnicianMapper.java

@@ -89,6 +89,36 @@ public interface MaTechnicianMapper extends BaseMapper<MaTechnician>
      */
     MaTechnicianMerchantDetailVO selectMerchantDetailById(@Param("id") Long id);
 
+    /**
+     * 根据商户ID查询商户。
+     *
+     * @param id 商户ID
+     * @return 商户信息
+     */
+    default MaTechnician selectMerchantById(Integer id) {
+        return selectById(id);
+    }
+
+    /**
+     * 根据商户ID更新商户。
+     *
+     * @param maTechnician 商户信息
+     * @return 影响行数
+     */
+    default int updateMerchantById(MaTechnician maTechnician) {
+        return updateById(maTechnician);
+    }
+
+    /**
+     * 根据商户ID更新合同文件。
+     *
+     * @param maTechnician 商户合同文件信息
+     * @return 影响行数
+     */
+    default int updateMerchantContractById(MaTechnician maTechnician) {
+        return updateById(maTechnician);
+    }
+
     List<MerchantVo> getMerchantRecommend(@Param("dto") MassageMerchantRecommendDto dto);
 
     /**

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

@@ -0,0 +1,18 @@
+package com.ylx.massage.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ylx.massage.domain.CityOperationApplication;
+import org.springframework.stereotype.Service;
+
+
+/**
+ * 城市管理表(CityOperationApplication)表服务接口
+ *
+ * @author makejava
+ * @since 2026-06-04 14:23:52
+ */
+@Service("cityOperationApplicationService")
+public interface CityOperationApplicationService extends IService<CityOperationApplication> {
+
+}
+

+ 68 - 2
nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java

@@ -4,8 +4,11 @@ import java.util.List;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+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.MaProjectSaveDto;
 import com.ylx.massage.domain.dto.MassageMerchantRecommendDto;
 import com.ylx.massage.domain.dto.MaTechnicianMerchantAddDTO;
 import com.ylx.massage.domain.dto.MaTechnicianMerchantQueryDTO;
@@ -13,6 +16,9 @@ import com.ylx.massage.domain.vo.MaTechnicianAppAddVo;
 import com.ylx.massage.domain.vo.MaTechnicianMerchantDetailVO;
 import com.ylx.massage.domain.vo.MaTechnicianMerchantListVO;
 import com.ylx.massage.domain.vo.MerchantVo;
+import com.ylx.project.domain.Project;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 技师Service接口
@@ -40,7 +46,7 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
     /**
      * 新增技师
      *
-     * @param maTechnician 技师
+     * @param
      * @return 结果
      */
     public int insertMaTechnician(MaTechnicianAppAddVo maTechnicianAppAddVo);
@@ -54,6 +60,26 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
      */
     public int insertMerchant(MaTechnicianMerchantAddDTO dto, LoginUser loginUser);
 
+    /**
+     * 后台编辑商户
+     *
+     * @param id        商户ID
+     * @param dto       编辑商户参数
+     * @param loginUser 当前登录用户
+     * @return 结果
+     */
+    public int updateMerchant(Long id, MaTechnicianMerchantAddDTO dto, LoginUser loginUser);
+
+    /**
+     * 后台上传商户合同文件
+     *
+     * @param id        商户ID
+     * @param file      合同文件
+     * @param loginUser 当前登录用户
+     * @return 上传结果
+     */
+    AjaxResult uploadMerchantContract(Integer id, MultipartFile file, LoginUser loginUser);
+
     /**
      * 后台查询商户列表
      *
@@ -75,7 +101,7 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
     /**
      * 修改技师
      *
-     * @param maTechnician 技师
+     * @param
      * @return 结果
      */
     public int updateMaTechnician(MaTechnicianAppAddVo maTechnicianAppAddVo);
@@ -105,4 +131,44 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
     Boolean isHasMerchantCity(String areaCode);
 
     List<MerchantVo> getMerchantRecommend(MassageMerchantRecommendDto dto);
+
+    /**
+     * 申请技师
+     *
+     * @param req
+     */
+    void apply(MaTechnicianAppAddVo req);
+
+    /**
+     * 技师列表
+     *
+     * @param userId
+     * @param auditStatus
+     * @return
+     */
+    List<MaProject> selectMaTechnicianListBy(String userId, String auditStatus);
+
+    /**
+     * 服务项目列表
+     *
+     * @param typeId
+     * @return
+     */
+    List<Project> selectTechnicianListBy(String typeId);
+
+    /**
+     * 技师未申请项目列表
+     *
+     * @param userId
+     * @param typeId
+     * @return
+     */
+    List<Project> getNotApplyList(String userId, String typeId);
+
+    /**
+     * 申请开通新服务
+     * @param dto
+     * @return
+     */
+    int applyForService(MaProjectSaveDto dto);
 }

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

@@ -0,0 +1,19 @@
+package com.ylx.massage.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.massage.domain.CityOperationApplication;
+import com.ylx.massage.mapper.CityOperationApplicationMapper;
+import com.ylx.massage.service.CityOperationApplicationService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 城市管理表(CityOperationApplication)表服务实现类
+ *
+ * @author makejava
+ * @since 2026-06-04 14:24:35
+ */
+@Service("cityOperationApplicationService")
+public class CityOperationApplicationServiceImpl extends ServiceImpl<CityOperationApplicationMapper, CityOperationApplication> implements CityOperationApplicationService {
+
+}
+

+ 299 - 15
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java

@@ -1,20 +1,21 @@
 package com.ylx.massage.service.impl;
 
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+
 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.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.domain.ContractRecord;
+import com.ylx.massage.domain.MaProject;
 import com.ylx.massage.domain.MaTeProject;
+import com.ylx.massage.domain.dto.MaProjectSaveDto;
 import com.ylx.massage.domain.dto.MaTechnicianMerchantAddDTO;
 import com.ylx.massage.domain.dto.MaTechnicianMerchantQueryDTO;
 import com.ylx.massage.domain.dto.MassageMerchantRecommendDto;
@@ -22,9 +23,13 @@ import com.ylx.massage.domain.vo.MaTechnicianAppAddVo;
 import com.ylx.massage.domain.vo.MaTechnicianMerchantDetailVO;
 import com.ylx.massage.domain.vo.MaTechnicianMerchantListVO;
 import com.ylx.massage.domain.vo.MerchantVo;
+import com.ylx.massage.mapper.ContractRecordMapper;
+import com.ylx.massage.mapper.MaProjectMapper;
 import com.ylx.massage.mapper.MaTeProjectMapper;
+import com.ylx.massage.service.TbFileService;
 import com.ylx.project.domain.Project;
 import com.ylx.project.mapper.ProjectMapper;
+import lombok.Data;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -32,6 +37,7 @@ 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;
 
 /**
  * 技师Service业务层处理
@@ -48,7 +54,7 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
     private static final int AUDIT_APPROVED = 2;
     private static final int NS_STATUS_NOT_ON_DUTY = -1;
     private static final int DEFAULT_STAT_VALUE = 0;
-    private static final long NOT_DELETED = 0L;
+    private static final Integer NOT_DELETED = 0;
     private static final String MERCHANT_STATUS_NORMAL = "0";
 
     @Autowired
@@ -57,9 +63,113 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
     @Autowired
     private MaTeProjectMapper maTeProjectMapper;
 
+    private MaProjectMapper maProjectMapper;
+
     @Autowired
     private ProjectMapper projectMapper;
 
+    @Autowired
+    private TbFileService fileService;
+
+    @Autowired
+    private ContractRecordMapper contractRecordMapper;
+
+    /**
+     * 商户入驻申请注册
+     *
+     * @param req 申请参数
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void apply(MaTechnicianAppAddVo req) {
+        String phone = req.getTePhone();
+        //商户入住前置条件校验
+        getMaTechnician(req, phone);
+        MaTechnician maTechnician = new MaTechnician();
+        BeanUtils.copyProperties(req, maTechnician);
+        //技师类型默认为真实商户
+        maTechnician.setTechType(0);
+        maTechnicianMapper.insert(maTechnician);
+
+    }
+
+    /**
+     * 商户入住前置条件校验
+     *
+     * @param req
+     * @param phone
+     */
+    private void getMaTechnician(MaTechnicianAppAddVo req, String phone) {
+        // 1. 判断当前用户是否已入驻
+        MaTechnician userProfile = getMaTechnician(req);
+        if (userProfile != null) {
+            throw new RuntimeException("当前用户已入驻,请勿重复提交");
+        }
+
+        // 2. 判断手机号是否已存在
+        LambdaQueryWrapper<MaTechnician> queryPhoneWrapper = new LambdaQueryWrapper<>();
+        queryPhoneWrapper.eq(MaTechnician::getTePhone, phone);
+        queryPhoneWrapper.eq(MaTechnician::getIsDelete, 0);
+        MaTechnician maTechnicianPhone = maTechnicianMapper.selectOne(queryPhoneWrapper);
+        if (maTechnicianPhone != null) {
+            throw new RuntimeException("手机号已存在,请更换手机号");
+        }
+        //3、判断手机号是否已绑定其他用户
+        LambdaQueryWrapper<MaTechnician> queryTePhoneWrapper = new LambdaQueryWrapper<>();
+        queryTePhoneWrapper.eq(MaTechnician::getTePhone, phone);
+        queryTePhoneWrapper.eq(MaTechnician::getIsDelete, 0);
+        queryTePhoneWrapper.eq(MaTechnician::getAuditStatus, 2);
+        MaTechnician maTechnicianTePhone = maTechnicianMapper.selectOne(queryTePhoneWrapper);
+        if (maTechnicianPhone != null) {
+            throw new RuntimeException("手机号已被其他用户绑定,请更换手机号");
+        }
+
+    }
+
+    /**
+     * 判断当前用户是否已入驻
+     *
+     * @return
+     */
+    private MaTechnician getMaTechnician(MaTechnicianAppAddVo req) {
+        LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(MaTechnician::getTePhone, req.getTePhone());
+        queryWrapper.eq(MaTechnician::getIsDelete, 0);
+        queryWrapper.eq(MaTechnician::getAuditStatus, 2);
+        queryWrapper.eq(MaTechnician::getOpenService, req.getOpenService());
+        MaTechnician userProfile = maTechnicianMapper.selectOne(queryWrapper);
+        return userProfile;
+    }
+
+    /**
+     * 查询商户服务项目列表
+     *
+     * @param userId      商户id
+     * @param auditStatus 审核状态
+     * @return 技师列表
+     */
+    @Override
+    public List<MaProject> selectMaTechnicianListBy(String userId, String auditStatus) {
+
+        LambdaQueryWrapper<MaProject> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(MaProject::getMerchantId, userId);
+        queryWrapper.eq(MaProject::getAuditStatus, auditStatus);
+        return maProjectMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 查询服务分类项目列表
+     *
+     * @param typeId 技师类型
+     * @return 技师列表
+     */
+    @Override
+    public List<Project> selectTechnicianListBy(String typeId) {
+        LambdaQueryWrapper<Project> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(Project::getType, typeId);
+        return projectMapper.selectList(queryWrapper);
+    }
+
     /**
      * 查询技师
      *
@@ -148,6 +258,118 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         return rows;
     }
 
+    /**
+     * 后台编辑商户
+     *
+     * @param id        商户ID
+     * @param dto       编辑商户参数
+     * @param loginUser 当前登录用户
+     * @return 结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int updateMerchant(Long id, MaTechnicianMerchantAddDTO dto, LoginUser loginUser) {
+        if (id == null) {
+            throw new ServiceException("商户ID不能为空");
+        }
+        MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id.intValue());
+        if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
+            throw new ServiceException("商户不存在或已删除");
+        }
+
+        MerchantProjectSelection selection = checkMerchantAddParam(dto);
+        String userName = loginUser.getUser().getUserName();
+
+        MaTechnician maTechnician = new MaTechnician();
+        maTechnician.setId(id);
+        maTechnician.setTeName(dto.getTeName().trim());
+        maTechnician.setTeNickName(dto.getTeNickName().trim());
+        maTechnician.setTeSex(dto.getTeSex());
+        maTechnician.setTePhone(dto.getTePhone().trim());
+        maTechnician.setOpenService(joinIds(selection.getCategoryIds()));
+        maTechnician.setTeProject(joinProjectTitles(selection.getProjectIds(), selection.getProjectMap()));
+        maTechnician.setTechType(dto.getTechType());
+        maTechnician.setIsRecommend(normalizeSwitchValue(dto.getIsRecommend(), "是否推荐"));
+        maTechnician.setUpdateBy(userName);
+        maTechnician.setUpdateTime(DateUtils.getNowDate());
+
+        int rows = maTechnicianMapper.updateMerchantById(maTechnician);
+        if (rows <= 0) {
+            throw new ServiceException("编辑商户失败");
+        }
+        replaceProjectRelations(id, selection.getProjectIds());
+        return rows;
+    }
+
+    /**
+     * 后台上传商户合同文件
+     *
+     * @param id        商户ID
+     * @param file      合同文件
+     * @param loginUser 当前登录用户
+     * @return 上传结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult uploadMerchantContract(Integer id, MultipartFile file, LoginUser loginUser) {
+        if (id == null) {
+            throw new ServiceException("商户ID不能为空");
+        }
+        if (file == null || file.isEmpty()) {
+            throw new ServiceException("合同文件不能为空");
+        }
+        MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id);
+        if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
+            throw new ServiceException("商户不存在或已删除");
+        }
+
+        AjaxResult uploadResult = fileService.uploadFile(file);
+        if (uploadResult == null || uploadResult.isError()) {
+            String message = uploadResult == null ? "合同文件上传失败" : String.valueOf(uploadResult.get(AjaxResult.MSG_TAG));
+            throw new ServiceException(message);
+        }
+        Object url = uploadResult.get("url");
+        if (url == null || StringUtils.isBlank(String.valueOf(url))) {
+            throw new ServiceException("合同文件上传失败,未返回文件地址");
+        }
+
+        ContractRecord contractRecord = new ContractRecord();
+        contractRecord.setMerchantId(id);
+        contractRecord.setContractName(file.getOriginalFilename());
+        contractRecord.setFileUrl(String.valueOf(url));
+        contractRecord.setSignTime(DateUtils.getNowDate());
+        contractRecord.setSignerName(existsMerchant.getTeName());
+        contractRecord.setCreateTime(DateUtils.getNowDate());
+
+        int rows = contractRecordMapper.insert(contractRecord);
+        if (rows <= 0) {
+            throw new ServiceException("保存合同记录失败");
+        }
+        return uploadResult;
+    }
+
+    /**
+     * 全量替换商户与服务项目关联关系。
+     *
+     * @param technicianId 商户ID
+     * @param projectIds   服务项目ID集合
+     */
+    private void replaceProjectRelations(Long technicianId, Set<Long> projectIds) {
+        if (technicianId == null) {
+            throw new ServiceException("商户ID不能为空");
+        }
+        maTeProjectMapper.deleteByTechnicianId(technicianId);
+        for (Long projectId : projectIds) {
+            MaTeProject relation = new MaTeProject();
+            relation.setTeId(technicianId);
+            relation.setProjectId(projectId);
+            int rows = maTeProjectMapper.insert(relation);
+            if (rows <= 0) {
+                throw new ServiceException("编辑商户服务项目失败");
+            }
+        }
+    }
+
     /**
      * 后台查询商户列表
      *
@@ -305,14 +527,14 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         }
 
         List<Project> projects = projectMapper.selectList(new LambdaQueryWrapper<Project>()
-                .in(Project::getId, distinctProjectIds)
-                .eq(Project::getIsDelete, 0));
+                                                                  .in(Project::getId, distinctProjectIds)
+                                                                  .eq(Project::getIsDelete, 0));
         if (projects.size() != distinctProjectIds.size()) {
             throw new ServiceException("服务项目不存在或已删除");
         }
 
         Map<Long, Project> projectMap = projects.stream()
-                .collect(Collectors.toMap(project -> project.getId().longValue(), Function.identity(), (left, right) -> left));
+                                                .collect(Collectors.toMap(project -> project.getId().longValue(), Function.identity(), (left, right) -> left));
         Set<Integer> projectCategoryIds = new LinkedHashSet<>();
         for (Long projectId : distinctProjectIds) {
             Project project = projectMap.get(projectId);
@@ -352,16 +574,16 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
 
     private String joinIds(Set<Integer> ids) {
         return ids.stream()
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
+                       .map(String::valueOf)
+                       .collect(Collectors.joining(","));
     }
 
     private String joinProjectTitles(Set<Long> projectIds, Map<Long, Project> projectMap) {
         return projectIds.stream()
-                .map(projectMap::get)
-                .map(Project::getTitle)
-                .filter(StringUtils::isNotBlank)
-                .collect(Collectors.joining(","));
+                       .map(projectMap::get)
+                       .map(Project::getTitle)
+                       .filter(StringUtils::isNotBlank)
+                       .collect(Collectors.joining(","));
     }
 
     /**
@@ -411,4 +633,66 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
             return projectMap;
         }
     }
+
+    /**
+     * 获取未申请技能列表
+     *
+     * @param userId
+     * @param typeId
+     * @return
+     */
+    @Override
+    public List<Project> getNotApplyList(String userId, String typeId) {
+        LambdaQueryWrapper<MaProject> query = new LambdaQueryWrapper<>();
+        query.eq(MaProject::getMerchantId, userId);
+        query.eq(MaProject::getMerchantType, typeId);
+        List<MaProject> maProjectList = maProjectMapper.selectList(query);
+        // 获取已申请技能ID集合
+        List<String> projectIdList = maProjectList.stream().map(MaProject::getProjectId).collect(Collectors.toList());
+        LambdaQueryWrapper<Project> query1 = new LambdaQueryWrapper<>();
+        query1.eq(Project::getType, typeId);
+        query1.notIn(Project::getId, projectIdList);
+        return projectMapper.selectList(query1);
+    }
+
+    /**
+     * 申请开通新服务
+     *
+     * @param dto
+     * @return
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public int applyForService(MaProjectSaveDto dto) {
+        if (Objects.isNull(dto)) {
+            return 0;
+        }
+        if (dto.getProjectIdList().size() > 0) {
+            // 插入商户技能
+            extracted(dto);
+        } else {
+            return 0;
+        }
+        return 1;
+    }
+
+    private void extracted(MaProjectSaveDto dto) {
+        LambdaQueryWrapper<Project> query = new LambdaQueryWrapper<>();
+        query.in(Project::getId, dto.getProjectIdList());
+        List<Project> projectList = projectMapper.selectList(query);
+        for (Project project : projectList) {
+            MaProject maProject = new MaProject();
+            maProject.setProjectId(project.getId().toString());
+            maProject.setProjectName(project.getTitle());
+            maProject.setProjectDescribe(project.getDetail());
+            maProject.setProjectDuration(project.getStandardDuration());
+            maProject.setProjectOriginalPrice(project.getPrice());
+            maProject.setProjectMaxPrice(project.getPriceMax());
+            maProject.setProjectLowestPrice(project.getPriceMin());
+            maProject.setCreateBy(dto.getUserId());
+            maProject.setMerchantId(dto.getUserId());
+            maProject.setApplyTime((Data) new Date());
+            maProject.setMerchantPhone(dto.getMerchantPhone());
+            maProjectMapper.insert(maProject);
+        }
+    }
 }

+ 16 - 13
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TFareFreeRuleServiceImpl.java

@@ -6,12 +6,14 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.common.utils.SecurityUtils;
 import com.ylx.common.utils.StringUtils;
+import com.ylx.massage.domain.MaTechnician;
 import com.ylx.massage.domain.TFareFreeRule;
 import com.ylx.massage.domain.TJs;
 import com.ylx.massage.domain.TXiangmu;
 import com.ylx.massage.domain.vo.FareFreeRuleVo;
 import com.ylx.massage.domain.vo.TimeRangeVo;
 import com.ylx.massage.mapper.TFareFreeRuleMapper;
+import com.ylx.massage.service.IMaTechnicianService;
 import com.ylx.massage.service.TFareFreeRuleService;
 import com.ylx.massage.service.TJsService;
 import com.ylx.massage.service.TXiangmuService;
@@ -41,7 +43,7 @@ public class TFareFreeRuleServiceImpl extends ServiceImpl<TFareFreeRuleMapper, T
     private TXiangmuService xiangmuService;
 
     @Resource
-    private TJsService jsService;
+    private IMaTechnicianService jsService;
 
     /**
      * 新增免车费规则
@@ -125,11 +127,12 @@ public class TFareFreeRuleServiceImpl extends ServiceImpl<TFareFreeRuleMapper, T
         } else {
             // ========== 新增场景 ==========
             //根据openID获取技师的信息
-            TJs js = jsService.getOne(new LambdaQueryWrapper<TJs>().eq(TJs::getcOpenId, fareFreeRule.getOpenId()));
+//            TJs js = jsService.getOne(new LambdaQueryWrapper<TJs>().eq(TJs::getcOpenId, fareFreeRule.getOpenId()));
+            MaTechnician js = jsService.getOne(new LambdaQueryWrapper<MaTechnician>().eq(MaTechnician::getId, fareFreeRule.getTechId()));
             if (js == null) {
                 throw new ServiceException("技师不存在");
             }
-            fareFreeRule.setTechId(js.getId());
+            fareFreeRule.setTechId(js.getId().toString());
 
             // 2.5 检查是否存在启用的规则(同一技师、同一项目)
             if ("1".equals(fareFreeRule.getEnable())) {
@@ -154,18 +157,18 @@ public class TFareFreeRuleServiceImpl extends ServiceImpl<TFareFreeRuleMapper, T
      /**
      * 禁用免车费规则(H5 技师端)
      *
-     * @param id 主键ID
+     * @param
      * @return 是否成功
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean disableFareFreeRule(String openId,String enable) {
         //根据openId查询技师信息
-        TJs js = jsService.getOne(new LambdaQueryWrapper<TJs>().eq(TJs::getcOpenId, openId));
+        MaTechnician js = jsService.getOne(new LambdaQueryWrapper<MaTechnician>().eq(MaTechnician::getId, openId));
         if (js == null) {
             throw new ServiceException("技师不存在");
         }
-        String techId = js.getId();
+        String techId = js.getId().toString();
         LambdaQueryWrapper<TFareFreeRule> eq = new LambdaQueryWrapper<TFareFreeRule>().eq(TFareFreeRule::getTechId, techId).orderByDesc(TFareFreeRule::getCreateTime).last("limit 1");
         TFareFreeRule existingRule = this.getOne(eq);
         if (existingRule == null) {
@@ -180,17 +183,17 @@ public class TFareFreeRuleServiceImpl extends ServiceImpl<TFareFreeRuleMapper, T
     /**
      * 查询免车费规则列表
      *
-     * @param techId 技师ID
+     * @param openId 技师ID
      * @return List<FareFreeRuleVo> 免车费规则VO列表
      */
     @Override
     public FareFreeRuleVo listFareFreeRules(String openId) {
         //根据openId查询技师信息
-        TJs js = jsService.getOne(new LambdaQueryWrapper<TJs>().eq(TJs::getcOpenId, openId));
+        MaTechnician js = jsService.getOne(new LambdaQueryWrapper<MaTechnician>().eq(MaTechnician::getId, openId));
         if (js == null) {
             throw new ServiceException("技师不存在");
         }
-        String techId = js.getId();
+        String techId = js.getId().toString();
         // 1. 查询规则列表
         LambdaQueryWrapper<TFareFreeRule> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(TFareFreeRule::getTechId, techId).eq(TFareFreeRule::getEnable, 1)
@@ -268,10 +271,10 @@ public class TFareFreeRuleServiceImpl extends ServiceImpl<TFareFreeRuleMapper, T
      * @param fareFreeRule 免车费规则
      */
     private void validateFareFreeRule(TFareFreeRule fareFreeRule) {
-        // 校验openID
-        if(StringUtils.isBlank(fareFreeRule.getOpenId())){
-            throw new ServiceException("openID不能为空");
-        }
+//        // 校验openID
+//        if(StringUtils.isBlank(fareFreeRule.getOpenId())){
+//            throw new ServiceException("openID不能为空");
+//        }
         // 校验服务项目
         if (StringUtils.isBlank(fareFreeRule.getProjectId())) {
             throw new ServiceException("请至少选择一个服务项目");

+ 10 - 0
nightFragrance-massage/src/main/java/com/ylx/project/controller/ProjectController.java

@@ -6,6 +6,8 @@ import com.ylx.common.core.domain.R;
 import com.ylx.common.enums.BusinessType;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.project.domain.Project;
+import com.ylx.project.domain.bookMerchant.dto.BookMerchantDTO;
+import com.ylx.project.domain.bookMerchant.vo.BookProjectDetailVO;
 import com.ylx.project.domain.dto.ProjectAddDTO;
 import com.ylx.project.domain.dto.ProjectSearchDTO;
 import com.ylx.project.domain.dto.ProjectUpdateDTO;
@@ -138,4 +140,12 @@ public class ProjectController {
         return R.ok(list);
     }
 
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("客户端获取去预约项目详情")
+    @PostMapping("/bookDetail")
+    public R<BookProjectDetailVO> getBookingProjectDetail(BookMerchantDTO dto) {
+        BookProjectDetailVO vo = projectService.getBookingProjectDetail(dto);
+        return R.ok(vo);
+    }
+
 }

+ 26 - 0
nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/dto/BookMerchantDTO.java

@@ -0,0 +1,26 @@
+package com.ylx.project.domain.bookMerchant.dto;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+@ApiModel("项目新增DTO")
+public class BookMerchantDTO extends Page implements Serializable {
+    private static final long serialVersionUID = 3546839192282131252L;
+
+    @NotNull(message = "项目ID不能为空")
+    @ApiModelProperty("项目ID")
+    private Long projectId;
+
+    @ApiModelProperty("经度")
+    private double longitude;
+
+    @ApiModelProperty("纬度")
+    private double latitude;
+
+}

+ 42 - 0
nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/vo/BookMerchantVO.java

@@ -0,0 +1,42 @@
+package com.ylx.project.domain.bookMerchant.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@ApiModel("预约商户信息vo")
+public class BookMerchantVO implements Serializable {
+    private static final long serialVersionUID = 5070447071110617415L;
+
+    @ApiModelProperty("商户服务项目ID")
+    private String id;
+
+    @ApiModelProperty("项目ID")
+    private String projectId;
+
+    @ApiModelProperty("商户ID")
+    private String merchantId;
+
+    @ApiModelProperty("商户昵称")
+    private String merchantNickName;
+
+    @ApiModelProperty("商户头像")
+    private String merchantAvatar;
+
+    @ApiModelProperty("商户评分")
+    private Double merchantStar;
+
+    @ApiModelProperty("已服务人数")
+    private Integer servedCount;
+
+    @ApiModelProperty("距离")
+    private String distanceText;
+
+    @ApiModelProperty("当前售价")
+    private BigDecimal currentPrice;
+
+}

+ 21 - 0
nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/vo/BookProjectDetailVO.java

@@ -0,0 +1,21 @@
+package com.ylx.project.domain.bookMerchant.vo;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@ApiModel("项目基本信息vo")
+public class BookProjectDetailVO implements Serializable {
+    private static final long serialVersionUID = 8868692859281349616L;
+
+    @ApiModelProperty("项目基础信息")
+    private ProjectInfoVO projectInfo;
+
+    @ApiModelProperty("预约商户列表")
+    private Page<BookMerchantVO> merchantList;
+
+}

+ 36 - 0
nightFragrance-massage/src/main/java/com/ylx/project/domain/bookMerchant/vo/ProjectInfoVO.java

@@ -0,0 +1,36 @@
+package com.ylx.project.domain.bookMerchant.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@ApiModel("项目基础信息vo")
+public class ProjectInfoVO implements Serializable {
+    private static final long serialVersionUID = 7681704041763799139L;
+
+    @ApiModelProperty("项目ID")
+    private String id;
+
+    @ApiModelProperty("项目名称")
+    private String title;
+
+    @ApiModelProperty("标准时长")
+    private Integer standardDuration;
+
+    @ApiModelProperty("计量单位")
+    private String unitType;
+
+    @ApiModelProperty("封面图")
+    private String cover;
+
+    @ApiModelProperty("项目亮点")
+    private String highlight;
+
+    @ApiModelProperty("标价")
+    private BigDecimal price;
+
+}

+ 4 - 0
nightFragrance-massage/src/main/java/com/ylx/project/service/ProjectService.java

@@ -8,6 +8,8 @@ import com.ylx.massage.domain.dto.ServiceOptionDTO;
 import com.ylx.massage.domain.vo.ProductOptionVO;
 import com.ylx.massage.domain.vo.ProductServiceOptionVO;
 import com.ylx.project.domain.Project;
+import com.ylx.project.domain.bookMerchant.dto.BookMerchantDTO;
+import com.ylx.project.domain.bookMerchant.vo.BookProjectDetailVO;
 import com.ylx.project.domain.dto.ProjectAddDTO;
 import com.ylx.project.domain.dto.ProjectSearchDTO;
 import com.ylx.project.domain.dto.ProjectUpdateDTO;
@@ -34,4 +36,6 @@ public interface ProjectService extends IService<Project> {
     Page<ProductServiceOptionVO> selectServiceOptionsPage(Page page, ServiceOptionDTO dto);
 
     List<ProjectBaseVo> getProjectTabListByType(Integer type);
+
+    BookProjectDetailVO getBookingProjectDetail(BookMerchantDTO dto);
 }

+ 35 - 2
nightFragrance-massage/src/main/java/com/ylx/project/service/impl/ProjectServiceImpl.java

@@ -13,12 +13,16 @@ import com.ylx.common.core.domain.entity.SysDictData;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.common.utils.DateUtils;
 import com.ylx.common.utils.SecurityUtils;
-import com.ylx.giftCard.domain.vo.GiftCardVO;
 import com.ylx.massage.domain.dto.OptionDTO;
 import com.ylx.massage.domain.dto.ServiceOptionDTO;
 import com.ylx.massage.domain.vo.ProductOptionVO;
 import com.ylx.massage.domain.vo.ProductServiceOptionVO;
+import com.ylx.massage.service.IMaProjectService;
 import com.ylx.project.domain.Project;
+import com.ylx.project.domain.bookMerchant.dto.BookMerchantDTO;
+import com.ylx.project.domain.bookMerchant.vo.BookMerchantVO;
+import com.ylx.project.domain.bookMerchant.vo.BookProjectDetailVO;
+import com.ylx.project.domain.bookMerchant.vo.ProjectInfoVO;
 import com.ylx.project.domain.dto.ProjectAddDTO;
 import com.ylx.project.domain.dto.ProjectSearchDTO;
 import com.ylx.project.domain.dto.ProjectUpdateDTO;
@@ -38,7 +42,6 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -50,6 +53,8 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
     private ServiceCategoryService serviceCategoryService;
     @Resource
     private ISysDictDataService sysDictDataService;
+    @Resource
+    private IMaProjectService maProjectService;
 
     @Override
     public Page<ProjectPageVo> list(Page<Project> page, ProjectSearchDTO dto) {
@@ -186,6 +191,34 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
         return BeanUtil.copyToList(projects, ProjectBaseVo.class);
     }
 
+    @Override
+    public BookProjectDetailVO getBookingProjectDetail(BookMerchantDTO dto) {
+        BookProjectDetailVO vo = new BookProjectDetailVO();
+
+        Long projectId = dto.getProjectId();
+
+        // 1. 查询项目基础信息
+        Project project = this.baseMapper.selectById(projectId);
+        if (ObjectUtil.isNull(project)) {
+            throw new ServiceException("项目不存在");
+        }
+
+        if (ObjectUtil.equals(project.getStatus(), ProjectStatusEnum.OFF_SHELF.getCode())) {
+            throw new ServiceException("项目已下架");
+        }
+
+        ProjectInfoVO projectInfo = new ProjectInfoVO();
+        BeanUtil.copyProperties(project, projectInfo);
+        vo.setProjectInfo(projectInfo);
+
+        // 2. 查询商户列表
+//        Page<BookMerchantVO> merchantList = maProjectService.selectMerchantList(dto);
+
+
+//        vo.setMerchantList(merchantList);
+        return vo;
+    }
+
     private ProjectPageVo convertToVo(Project entity) {
         ProjectPageVo vo = new ProjectPageVo();
         // 属性拷贝(推荐)