Pārlūkot izejas kodu

月卡类型管理

loemkie 5 dienas atpakaļ
vecāks
revīzija
d9b22619ca
22 mainītis faili ar 1752 papildinājumiem un 0 dzēšanām
  1. 155 0
      src/main/java/com/qmrb/system/controller/ParkingCardController.java
  2. 150 0
      src/main/java/com/qmrb/system/controller/ParkingCardTypeController.java
  3. 79 0
      src/main/java/com/qmrb/system/converter/ParkingCardConverter.java
  4. 78 0
      src/main/java/com/qmrb/system/converter/ParkingCardTypeConverter.java
  5. 17 0
      src/main/java/com/qmrb/system/mapper/ParkingCardMapper.java
  6. 17 0
      src/main/java/com/qmrb/system/mapper/ParkingCardTypeMapper.java
  7. 17 0
      src/main/java/com/qmrb/system/mapper/QuotaUsageMapper.java
  8. 80 0
      src/main/java/com/qmrb/system/pojo/entity/ParkingCard.java
  9. 51 0
      src/main/java/com/qmrb/system/pojo/entity/ParkingCardType.java
  10. 38 0
      src/main/java/com/qmrb/system/pojo/entity/QuotaUsage.java
  11. 64 0
      src/main/java/com/qmrb/system/pojo/form/ParkingCardForm.java
  12. 40 0
      src/main/java/com/qmrb/system/pojo/form/ParkingCardTypeForm.java
  13. 53 0
      src/main/java/com/qmrb/system/pojo/query/ParkingCardQuery.java
  14. 29 0
      src/main/java/com/qmrb/system/pojo/query/ParkingCardTypeQuery.java
  15. 47 0
      src/main/java/com/qmrb/system/pojo/vo/ParkingCardTypeVO.java
  16. 71 0
      src/main/java/com/qmrb/system/pojo/vo/ParkingCardVO.java
  17. 89 0
      src/main/java/com/qmrb/system/service/IParkingCardService.java
  18. 71 0
      src/main/java/com/qmrb/system/service/IParkingCardTypeService.java
  19. 56 0
      src/main/java/com/qmrb/system/service/IQuotaUsageService.java
  20. 275 0
      src/main/java/com/qmrb/system/service/impl/ParkingCardServiceImpl.java
  21. 178 0
      src/main/java/com/qmrb/system/service/impl/ParkingCardTypeServiceImpl.java
  22. 97 0
      src/main/java/com/qmrb/system/service/impl/QuotaUsageServiceImpl.java

+ 155 - 0
src/main/java/com/qmrb/system/controller/ParkingCardController.java

@@ -0,0 +1,155 @@
+package com.qmrb.system.controller;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springdoc.core.annotations.ParameterObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmrb.system.common.result.PageResult;
+import com.qmrb.system.common.result.Result;
+import com.qmrb.system.common.result.ResultCode;
+import com.qmrb.system.converter.ParkingCardConverter;
+import com.qmrb.system.framework.resubmit.Resubmit;
+import com.qmrb.system.pojo.entity.ParkingCard;
+import com.qmrb.system.pojo.form.ParkingCardForm;
+import com.qmrb.system.pojo.query.ParkingCardQuery;
+import com.qmrb.system.pojo.vo.ParkingCardVO;
+import com.qmrb.system.service.IParkingCardService;
+
+import cn.hutool.core.util.StrUtil;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+
+/**
+ * <p>
+ * 月卡购买记录 前端控制器
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Tag(name = "月卡购买记录接口")
+@RestController
+@RequestMapping("/api/v1/parking-cards")
+@CrossOrigin
+public class ParkingCardController {
+
+    @Autowired
+    private IParkingCardService parkingCardService;
+
+    @Autowired
+    private ParkingCardConverter parkingCardConverter;
+
+    @Operation(summary = "月卡购买记录分页列表", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/page")
+    public PageResult<ParkingCardVO> getPageList(@ParameterObject ParkingCardQuery queryParams) {
+        Page<ParkingCardVO> result = parkingCardService.getPage(queryParams);
+        return PageResult.success(result);
+    }
+
+    @Operation(summary = "新增月卡记录", security = {@SecurityRequirement(name = "Authorization")})
+    @PostMapping
+    @Resubmit
+    public Result<ParkingCardForm> addParkingCard(@RequestBody @Valid ParkingCardForm form) {
+        ParkingCardForm result = parkingCardService.saveForm(form);
+        return Result.success(result);
+    }
+
+    @Operation(summary = "修改月卡记录", security = {@SecurityRequirement(name = "Authorization")})
+    @PutMapping(value = "/{id}")
+    public Result<?> updateParkingCard(
+            @Parameter(description = "月卡记录ID") @PathVariable Long id,
+            @RequestBody @Validated ParkingCardForm form) {
+        boolean result = parkingCardService.updateForm(id, form);
+        return Result.judge(result);
+    }
+
+    @Operation(summary = "月卡记录表单数据", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/{id}/form")
+    public Result<ParkingCardForm> getFormData(
+            @Parameter(description = "月卡记录ID") @PathVariable Long id) {
+        ParkingCardForm formData = parkingCardService.getFormData(id);
+        return Result.success(formData);
+    }
+
+    @Operation(summary = "获取月卡记录详情", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/{id}")
+    public Result<ParkingCardVO> getParkingCardDetail(
+            @Parameter(description = "月卡记录ID") @PathVariable Long id) {
+        ParkingCard entity = parkingCardService.getById(id);
+        if (entity == null) {
+            return Result.failed("月卡记录不存在");
+        }
+        
+        ParkingCardVO vo = parkingCardConverter.entity2VO(entity);
+        return Result.success(vo);
+    }
+
+    @Operation(summary = "更新月卡状态", security = {@SecurityRequirement(name = "Authorization")})
+    @PutMapping("/{id}/status")
+    public Result<?> updateStatus(
+            @Parameter(description = "月卡记录ID") @PathVariable Long id,
+            @Parameter(description = "状态") @RequestParam String status) {
+        boolean result = parkingCardService.updateStatus(id, status);
+        return Result.judge(result);
+    }
+
+    @Operation(summary = "删除月卡记录", security = {@SecurityRequirement(name = "Authorization")})
+    @DeleteMapping("/{ids}")
+    public Result<?> deleteParkingCards(
+            @Parameter(description = "月卡记录ID,多个以英文逗号(,)分割") @PathVariable String ids) {
+        if(StrUtil.isBlank(ids)) {
+            return Result.failed(ResultCode.PARAM_ERROR, "删除的月卡记录数据为空");
+        }
+        // 逻辑删除
+        List<Long> idList = Arrays.asList(ids.split(",")).stream()
+                .map(idStr -> Long.parseLong(idStr)).collect(Collectors.toList());
+        boolean result = parkingCardService.removeByIds(idList);
+        return Result.judge(result);
+    }
+
+    @Operation(summary = "计算月卡期限", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/calculate-period")
+    public Result<LocalDate[]> calculatePeriod(
+            @Parameter(description = "购买月数") @RequestParam Integer months) {
+        LocalDate[] period = parkingCardService.calculatePeriod(months);
+        return Result.success(period);
+    }
+
+    @Operation(summary = "计算月卡费用", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/calculate-fee")
+    public Result<BigDecimal> calculateFee(
+            @Parameter(description = "月卡类型ID") @RequestParam Long typeId,
+            @Parameter(description = "购买月数") @RequestParam Integer months) {
+        BigDecimal fee = parkingCardService.calculatePayment(typeId, months);
+        return Result.success(fee);
+    }
+
+    @Operation(summary = "检查月卡类型配额", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/check-quota")
+    public Result<Boolean> checkQuota(
+            @Parameter(description = "月卡类型ID") @RequestParam Long typeId) {
+        boolean available = parkingCardService.checkQuotaAvailability(typeId);
+        return Result.success(available);
+    }
+} 

+ 150 - 0
src/main/java/com/qmrb/system/controller/ParkingCardTypeController.java

@@ -0,0 +1,150 @@
+package com.qmrb.system.controller;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springdoc.core.annotations.ParameterObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmrb.system.common.result.PageResult;
+import com.qmrb.system.common.result.Result;
+import com.qmrb.system.common.result.ResultCode;
+import com.qmrb.system.converter.ParkingCardTypeConverter;
+import com.qmrb.system.framework.resubmit.Resubmit;
+import com.qmrb.system.pojo.entity.ParkingCard;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import com.qmrb.system.pojo.form.ParkingCardTypeForm;
+import com.qmrb.system.pojo.query.ParkingCardTypeQuery;
+import com.qmrb.system.pojo.vo.ParkingCardTypeVO;
+import com.qmrb.system.service.IParkingCardService;
+import com.qmrb.system.service.IParkingCardTypeService;
+import com.qmrb.system.service.IQuotaUsageService;
+
+import cn.hutool.core.util.StrUtil;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+
+/**
+ * <p>
+ * 月卡类型 前端控制器
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Tag(name = "月卡类型接口")
+@RestController
+@RequestMapping("/api/v1/parking-card-types")
+@CrossOrigin
+public class ParkingCardTypeController {
+
+    @Autowired
+    private IParkingCardTypeService parkingCardTypeService;
+    
+    @Autowired
+    private ParkingCardTypeConverter converter;
+    
+    @Autowired
+    private IQuotaUsageService quotaUsageService;
+    
+    @Autowired
+    private IParkingCardService parkingCardService;
+
+    @Operation(summary = "月卡类型分页列表", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/page")
+    public PageResult<ParkingCardTypeVO> getPageList(@ParameterObject ParkingCardTypeQuery queryParams) {
+        Page<ParkingCardTypeVO> result = parkingCardTypeService.getPage(queryParams);
+        return PageResult.success(result);
+    }
+
+    @Operation(summary = "新增月卡类型", security = {@SecurityRequirement(name = "Authorization")})
+    @PostMapping
+    @Resubmit
+    public Result<ParkingCardTypeForm> addParkingCardType(@RequestBody @Valid ParkingCardTypeForm form) {
+        ParkingCardTypeForm result = parkingCardTypeService.saveForm(form);
+        return Result.success(result);
+    }
+
+    @Operation(summary = "修改月卡类型", security = {@SecurityRequirement(name = "Authorization")})
+    @PutMapping(value = "/{id}")
+    public Result<?> updateParkingCardType(
+            @Parameter(description = "月卡类型ID") @PathVariable Long id,
+            @RequestBody @Validated ParkingCardTypeForm form) {
+        boolean result = parkingCardTypeService.updateForm(id, form);
+        return Result.judge(result);
+    }
+
+    @Operation(summary = "月卡类型表单数据", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/{id}/form")
+    public Result<ParkingCardTypeForm> getFormData(
+            @Parameter(description = "月卡类型ID") @PathVariable Long id) {
+        ParkingCardTypeForm formData = parkingCardTypeService.getFormData(id);
+        return Result.success(formData);
+    }
+
+    @Operation(summary = "获取月卡类型详情", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/{id}")
+    public Result<ParkingCardTypeVO> getParkingCardTypeDetail(
+            @Parameter(description = "月卡类型ID") @PathVariable Long id) {
+        ParkingCardType entity = parkingCardTypeService.getById(id);
+        if (entity == null) {
+            return Result.failed("月卡类型不存在");
+        }
+        
+        ParkingCardTypeVO vo = converter.entity2VO(entity);
+        // 填充已使用配额和剩余配额信息
+        Integer usedQuota = quotaUsageService.getUsedQuota(id);
+        vo.setUsedQuota(usedQuota);
+        vo.setRemainingQuota(vo.getTotalQuota() - usedQuota);
+        
+        return Result.success(vo);
+    }
+
+    @Operation(summary = "删除月卡类型", security = {@SecurityRequirement(name = "Authorization")})
+    @DeleteMapping("/{ids}")
+    public Result<?> deleteParkingCardTypes(
+            @Parameter(description = "月卡类型ID,多个以英文逗号(,)分割") @PathVariable String ids) {
+        if(StrUtil.isBlank(ids)) {
+            return Result.failed(ResultCode.PARAM_ERROR, "删除的月卡类型数据为空");
+        }
+        
+        List<Long> idList = Arrays.asList(ids.split(",")).stream()
+                .map(idStr -> Long.parseLong(idStr)).collect(Collectors.toList());
+        
+        // 检查是否有关联的月卡记录
+        for (Long id : idList) {
+            boolean hasRelatedCards = parkingCardService.count(new LambdaQueryWrapper<ParkingCard>()
+                    .eq(ParkingCard::getTypeId, id)) > 0;
+            if (hasRelatedCards) {
+                return Result.failed("ID为" + id + "的月卡类型下存在月卡记录,无法删除");
+            }
+        }
+        
+        boolean result = parkingCardTypeService.removeByIds(idList);
+        return Result.judge(result);
+    }
+
+    @Operation(summary = "获取所有可用的月卡类型", security = {@SecurityRequirement(name = "Authorization")})
+    @GetMapping("/available")
+    public Result<List<ParkingCardTypeVO>> listAllAvailable() {
+        List<ParkingCardTypeVO> list = parkingCardTypeService.listAllAvailable();
+        return Result.success(list);
+    }
+} 

+ 79 - 0
src/main/java/com/qmrb/system/converter/ParkingCardConverter.java

@@ -0,0 +1,79 @@
+package com.qmrb.system.converter;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmrb.system.pojo.entity.ParkingCard;
+import com.qmrb.system.pojo.form.ParkingCardForm;
+import com.qmrb.system.pojo.vo.ParkingCardVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 月卡购买记录表 转换器
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Mapper(componentModel = "spring")
+public interface ParkingCardConverter {
+
+    /**
+     * 实体转换为VO
+     *
+     * @param entity 实体
+     * @return VO
+     */
+    @Mapping(target = "statusDesc", expression = "java(\"active\".equals(entity.getStatus()) ? \"有效\" : \"过期\")")
+    ParkingCardVO entity2VO(ParkingCard entity);
+
+    /**
+     * 实体列表转换为VO列表
+     *
+     * @param entities 实体列表
+     * @return VO列表
+     */
+    List<ParkingCardVO> entity2VOList(List<ParkingCard> entities);
+
+    /**
+     * 实体分页转换为VO分页
+     *
+     * @param entityPage 实体分页
+     * @return VO分页
+     */
+    default Page<ParkingCardVO> entity2Page(Page<ParkingCard> entityPage) {
+        Page<ParkingCardVO> voPage = new Page<>();
+        voPage.setCurrent(entityPage.getCurrent());
+        voPage.setSize(entityPage.getSize());
+        voPage.setTotal(entityPage.getTotal());
+        voPage.setRecords(entity2VOList(entityPage.getRecords()));
+        return voPage;
+    }
+
+    /**
+     * 表单转换为实体
+     *
+     * @param form 表单
+     * @return 实体
+     */
+    ParkingCard form2Entity(ParkingCardForm form);
+
+    /**
+     * 表单更新实体
+     *
+     * @param form   表单
+     * @param entity 实体
+     */
+    void updateEntityFromForm(ParkingCardForm form, @MappingTarget ParkingCard entity);
+
+    /**
+     * 实体转换为表单
+     *
+     * @param entity 实体
+     * @return 表单
+     */
+    ParkingCardForm entity2Form(ParkingCard entity);
+} 

+ 78 - 0
src/main/java/com/qmrb/system/converter/ParkingCardTypeConverter.java

@@ -0,0 +1,78 @@
+package com.qmrb.system.converter;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import com.qmrb.system.pojo.form.ParkingCardTypeForm;
+import com.qmrb.system.pojo.vo.ParkingCardTypeVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 月卡类型表 转换器
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Mapper(componentModel = "spring")
+public interface ParkingCardTypeConverter {
+
+    /**
+     * 实体转换为VO
+     *
+     * @param entity 实体
+     * @return VO
+     */
+    ParkingCardTypeVO entity2VO(ParkingCardType entity);
+
+    /**
+     * 实体列表转换为VO列表
+     *
+     * @param entities 实体列表
+     * @return VO列表
+     */
+    List<ParkingCardTypeVO> entity2VOList(List<ParkingCardType> entities);
+
+    /**
+     * 实体分页转换为VO分页
+     *
+     * @param entityPage 实体分页
+     * @return VO分页
+     */
+    default Page<ParkingCardTypeVO> entity2Page(Page<ParkingCardType> entityPage) {
+        Page<ParkingCardTypeVO> voPage = new Page<>();
+        voPage.setCurrent(entityPage.getCurrent());
+        voPage.setSize(entityPage.getSize());
+        voPage.setTotal(entityPage.getTotal());
+        voPage.setRecords(entity2VOList(entityPage.getRecords()));
+        return voPage;
+    }
+
+    /**
+     * 表单转换为实体
+     *
+     * @param form 表单
+     * @return 实体
+     */
+    ParkingCardType form2Entity(ParkingCardTypeForm form);
+
+    /**
+     * 表单更新实体
+     *
+     * @param form   表单
+     * @param entity 实体
+     */
+    void updateEntityFromForm(ParkingCardTypeForm form, @MappingTarget ParkingCardType entity);
+
+    /**
+     * 实体转换为表单
+     *
+     * @param entity 实体
+     * @return 表单
+     */
+    ParkingCardTypeForm entity2Form(ParkingCardType entity);
+} 

+ 17 - 0
src/main/java/com/qmrb/system/mapper/ParkingCardMapper.java

@@ -0,0 +1,17 @@
+package com.qmrb.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmrb.system.pojo.entity.ParkingCard;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 月卡购买记录表 Mapper 接口
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Mapper
+public interface ParkingCardMapper extends BaseMapper<ParkingCard> {
+} 

+ 17 - 0
src/main/java/com/qmrb/system/mapper/ParkingCardTypeMapper.java

@@ -0,0 +1,17 @@
+package com.qmrb.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 月卡类型表 Mapper 接口
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Mapper
+public interface ParkingCardTypeMapper extends BaseMapper<ParkingCardType> {
+} 

+ 17 - 0
src/main/java/com/qmrb/system/mapper/QuotaUsageMapper.java

@@ -0,0 +1,17 @@
+package com.qmrb.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmrb.system.pojo.entity.QuotaUsage;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 配额使用统计表 Mapper 接口
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Mapper
+public interface QuotaUsageMapper extends BaseMapper<QuotaUsage> {
+} 

+ 80 - 0
src/main/java/com/qmrb/system/pojo/entity/ParkingCard.java

@@ -0,0 +1,80 @@
+package com.qmrb.system.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.qmrb.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 月卡购买记录表
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("parking_card")
+@Schema(description = "月卡购买记录实体")
+public class ParkingCard extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "用户ID")
+    @TableField("user_id")
+    private Long userId;
+
+    @Schema(description = "月卡类型ID")
+    @TableField("type_id")
+    private Long typeId;
+
+    @Schema(description = "购买的月数(1-12)")
+    @TableField("purchased_months")
+    private Integer purchasedMonths;
+
+    @Schema(description = "总费用")
+    @TableField("total_fee")
+    private BigDecimal totalFee;
+
+    @Schema(description = "有效期开始日期")
+    @TableField("start_time")
+    private LocalDate startTime;
+
+    @Schema(description = "有效期结束日期")
+    @TableField("end_time")
+    private LocalDate endTime;
+
+    @Schema(description = "状态")
+    @TableField("status")
+    private String status;
+
+    @Schema(description = "车牌号")
+    @TableField("plate_number")
+    private String plateNumber;
+
+    @Schema(description = "联系人姓名")
+    @TableField("name")
+    private String name;
+
+    @Schema(description = "手机号")
+    @TableField("phone")
+    private String phone;
+
+    @Schema(description = "创建时间")
+    @TableField("created_at")
+    private LocalDateTime createdAt;
+
+    @Schema(description = "更新时间")
+    @TableField("updated_at")
+    private LocalDateTime updatedAt;
+} 

+ 51 - 0
src/main/java/com/qmrb/system/pojo/entity/ParkingCardType.java

@@ -0,0 +1,51 @@
+package com.qmrb.system.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.qmrb.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 月卡类型表
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("parking_card_type")
+@Schema(description = "月卡类型实体")
+public class ParkingCardType extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "月卡类型名称")
+    @TableField("type_name")
+    private String typeName;
+
+    @Schema(description = "月单价")
+    @TableField("price_per_month")
+    private BigDecimal pricePerMonth;
+
+    @Schema(description = "总配额")
+    @TableField("total_quota")
+    private Integer totalQuota;
+
+    @Schema(description = "创建时间")
+    @TableField("created_at")
+    private LocalDateTime createdAt;
+
+    @Schema(description = "更新时间")
+    @TableField("updated_at")
+    private LocalDateTime updatedAt;
+} 

+ 38 - 0
src/main/java/com/qmrb/system/pojo/entity/QuotaUsage.java

@@ -0,0 +1,38 @@
+package com.qmrb.system.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.qmrb.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 配额使用统计表
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("quota_usage")
+@Schema(description = "配额使用统计实体")
+public class QuotaUsage extends BaseEntity{
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "月卡类型ID")
+    @TableId(value = "type_id", type = IdType.INPUT)
+    private Long typeId;
+
+    @Schema(description = "已使用配额")
+    @TableField("used_quota")
+    private Integer usedQuota;
+
+    @Schema(description = "更新时间")
+    @TableField("updated_at")
+    private LocalDateTime updatedAt;
+} 

+ 64 - 0
src/main/java/com/qmrb/system/pojo/form/ParkingCardForm.java

@@ -0,0 +1,64 @@
+package com.qmrb.system.pojo.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.*;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * <p>
+ * 月卡购买记录表 表单对象
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@Schema(description = "月卡购买记录表单对象")
+public class ParkingCardForm {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "用户ID", required = true)
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    @Schema(description = "月卡类型ID", required = true)
+    @NotNull(message = "月卡类型ID不能为空")
+    private Long typeId;
+
+    @Schema(description = "购买的月数(1-12)", required = true)
+    @NotNull(message = "购买月数不能为空")
+    @Min(value = 1, message = "购买月数最少为1个月")
+    @Max(value = 12, message = "购买月数最多为12个月")
+    private Integer purchasedMonths;
+
+    @Schema(description = "总费用")
+    private BigDecimal totalFee;
+
+    @Schema(description = "有效期开始日期")
+    private LocalDate startTime;
+
+    @Schema(description = "有效期结束日期")
+    private LocalDate endTime;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "车牌号", required = true)
+    @NotBlank(message = "车牌号不能为空")
+    @Pattern(regexp = "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{4,5}[A-Z0-9挂学警港澳]$", message = "车牌号格式不正确")
+    private String plateNumber;
+
+    @Schema(description = "联系人姓名", required = true)
+    @NotBlank(message = "联系人姓名不能为空")
+    private String name;
+
+    @Schema(description = "手机号", required = true)
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+    private String phone;
+} 

+ 40 - 0
src/main/java/com/qmrb/system/pojo/form/ParkingCardTypeForm.java

@@ -0,0 +1,40 @@
+package com.qmrb.system.pojo.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * <p>
+ * 月卡类型表 表单对象
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@Schema(description = "月卡类型表单对象")
+public class ParkingCardTypeForm {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "月卡类型名称", required = true)
+    @NotBlank(message = "月卡类型名称不能为空")
+    private String typeName;
+
+    @Schema(description = "月单价", required = true)
+    @NotNull(message = "月单价不能为空")
+    @DecimalMin(value = "0.01", message = "月单价必须大于0")
+    private BigDecimal pricePerMonth;
+
+    @Schema(description = "总配额", required = true)
+    @NotNull(message = "总配额不能为空")
+    @Min(value = 1, message = "总配额必须大于0")
+    private Integer totalQuota;
+} 

+ 53 - 0
src/main/java/com/qmrb/system/pojo/query/ParkingCardQuery.java

@@ -0,0 +1,53 @@
+package com.qmrb.system.pojo.query;
+
+import com.qmrb.system.common.base.BasePageQuery;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 月卡购买记录表 查询对象
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "月卡购买记录查询对象")
+public class ParkingCardQuery extends BasePageQuery {
+
+    @Schema(description = "月卡类型ID")
+    private Long typeId;
+
+    @Schema(description = "车牌号")
+    private String plateNumber;
+
+    @Schema(description = "联系人姓名")
+    private String name;
+
+    @Schema(description = "手机号")
+    private String phone;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "有效期开始日期起始")
+    private String startTimeBegin;
+
+    @Schema(description = "有效期开始日期结束")
+    private String startTimeEnd;
+
+    @Schema(description = "有效期结束日期起始")
+    private String endTimeBegin;
+
+    @Schema(description = "有效期结束日期结束")
+    private String endTimeEnd;
+
+    @Schema(description = "购买时间起始")
+    private String createdAtBegin;
+
+    @Schema(description = "购买时间结束")
+    private String createdAtEnd;
+} 

+ 29 - 0
src/main/java/com/qmrb/system/pojo/query/ParkingCardTypeQuery.java

@@ -0,0 +1,29 @@
+package com.qmrb.system.pojo.query;
+
+import com.qmrb.system.common.base.BasePageQuery;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 月卡类型表 查询对象
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "月卡类型查询对象")
+public class ParkingCardTypeQuery extends BasePageQuery {
+
+    @Schema(description = "月卡类型名称")
+    private String typeName;
+
+    @Schema(description = "最小月单价")
+    private String minPrice;
+
+    @Schema(description = "最大月单价")
+    private String maxPrice;
+} 

+ 47 - 0
src/main/java/com/qmrb/system/pojo/vo/ParkingCardTypeVO.java

@@ -0,0 +1,47 @@
+package com.qmrb.system.pojo.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 月卡类型表 视图对象
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@Schema(description = "月卡类型视图对象")
+public class ParkingCardTypeVO {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "月卡类型名称")
+    private String typeName;
+
+    @Schema(description = "月单价")
+    private BigDecimal pricePerMonth;
+
+    @Schema(description = "总配额")
+    private Integer totalQuota;
+
+    @Schema(description = "已使用配额")
+    private Integer usedQuota;
+
+    @Schema(description = "剩余配额")
+    private Integer remainingQuota;
+
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createdAt;
+
+    @Schema(description = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updatedAt;
+} 

+ 71 - 0
src/main/java/com/qmrb/system/pojo/vo/ParkingCardVO.java

@@ -0,0 +1,71 @@
+package com.qmrb.system.pojo.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 月卡购买记录表 视图对象
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Data
+@Schema(description = "月卡购买记录视图对象")
+public class ParkingCardVO {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "用户ID")
+    private Long userId;
+
+    @Schema(description = "月卡类型ID")
+    private Long typeId;
+
+    @Schema(description = "月卡类型名称")
+    private String typeName;
+
+    @Schema(description = "购买的月数(1-12)")
+    private Integer purchasedMonths;
+
+    @Schema(description = "总费用")
+    private BigDecimal totalFee;
+
+    @Schema(description = "有效期开始日期")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate startTime;
+
+    @Schema(description = "有效期结束日期")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate endTime;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "状态描述")
+    private String statusDesc;
+
+    @Schema(description = "车牌号")
+    private String plateNumber;
+
+    @Schema(description = "联系人姓名")
+    private String name;
+
+    @Schema(description = "手机号")
+    private String phone;
+
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createdAt;
+
+    @Schema(description = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updatedAt;
+} 

+ 89 - 0
src/main/java/com/qmrb/system/service/IParkingCardService.java

@@ -0,0 +1,89 @@
+package com.qmrb.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmrb.system.pojo.entity.ParkingCard;
+import com.qmrb.system.pojo.form.ParkingCardForm;
+import com.qmrb.system.pojo.query.ParkingCardQuery;
+import com.qmrb.system.pojo.vo.ParkingCardVO;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * <p>
+ * 月卡购买记录表 服务类
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+public interface IParkingCardService extends IService<ParkingCard> {
+
+    /**
+     * 分页查询
+     *
+     * @param queryParams 查询参数
+     * @return 分页结果
+     */
+    Page<ParkingCardVO> getPage(ParkingCardQuery queryParams);
+
+    /**
+     * 新增月卡记录
+     *
+     * @param form 表单数据
+     * @return 创建结果
+     */
+    ParkingCardForm saveForm(ParkingCardForm form);
+
+    /**
+     * 修改月卡记录
+     *
+     * @param id 月卡记录ID
+     * @param form 表单数据
+     * @return 是否成功
+     */
+    boolean updateForm(Long id, ParkingCardForm form);
+
+    /**
+     * 获取月卡表单数据
+     *
+     * @param id 月卡记录ID
+     * @return 表单数据
+     */
+    ParkingCardForm getFormData(Long id);
+
+    /**
+     * 计算月卡的开始日期和结束日期
+     *
+     * @param months 购买月数
+     * @return 开始和结束日期组成的数组,第一个元素为开始日期,第二个元素为结束日期
+     */
+    LocalDate[] calculatePeriod(Integer months);
+
+    /**
+     * 计算购买月卡需要支付的费用
+     *
+     * @param typeId 月卡类型ID
+     * @param months 购买月数
+     * @return 计算后的总费用
+     */
+    BigDecimal calculatePayment(Long typeId, Integer months);
+
+    /**
+     * 检查月卡类型的可用配额
+     *
+     * @param typeId 月卡类型ID
+     * @return 是否有可用配额
+     */
+    boolean checkQuotaAvailability(Long typeId);
+
+    /**
+     * 更新月卡状态
+     *
+     * @param id 月卡ID
+     * @param status 状态
+     * @return 是否更新成功
+     */
+    boolean updateStatus(Long id, String status);
+} 

+ 71 - 0
src/main/java/com/qmrb/system/service/IParkingCardTypeService.java

@@ -0,0 +1,71 @@
+package com.qmrb.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import com.qmrb.system.pojo.form.ParkingCardTypeForm;
+import com.qmrb.system.pojo.query.ParkingCardTypeQuery;
+import com.qmrb.system.pojo.vo.ParkingCardTypeVO;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * <p>
+ * 月卡类型表 服务类
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+public interface IParkingCardTypeService extends IService<ParkingCardType> {
+
+    /**
+     * 分页查询
+     *
+     * @param queryParams 查询参数
+     * @return 分页结果
+     */
+    Page<ParkingCardTypeVO> getPage(ParkingCardTypeQuery queryParams);
+
+    /**
+     * 新增月卡类型
+     *
+     * @param form 表单数据
+     * @return 创建结果
+     */
+    ParkingCardTypeForm saveForm(ParkingCardTypeForm form);
+
+    /**
+     * 修改月卡类型
+     *
+     * @param id 月卡类型ID
+     * @param form 表单数据
+     * @return 是否成功
+     */
+    boolean updateForm(Long id, ParkingCardTypeForm form);
+
+    /**
+     * 获取月卡类型表单数据
+     *
+     * @param id 月卡类型ID
+     * @return 表单数据
+     */
+    ParkingCardTypeForm getFormData(Long id);
+
+    /**
+     * 获取所有可用的月卡类型
+     *
+     * @return 月卡类型列表
+     */
+    List<ParkingCardTypeVO> listAllAvailable();
+
+    /**
+     * 根据月卡类型ID和购买月数计算费用
+     *
+     * @param typeId 月卡类型ID
+     * @param months 购买月数
+     * @return 计算后的总费用
+     */
+    BigDecimal calculateFee(Long typeId, Integer months);
+} 

+ 56 - 0
src/main/java/com/qmrb/system/service/IQuotaUsageService.java

@@ -0,0 +1,56 @@
+package com.qmrb.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmrb.system.pojo.entity.QuotaUsage;
+
+/**
+ * <p>
+ * 配额使用统计表 服务类
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+public interface IQuotaUsageService extends IService<QuotaUsage> {
+
+    /**
+     * 增加类型的已使用配额
+     *
+     * @param typeId 月卡类型ID
+     * @return 是否成功
+     */
+    boolean incrementUsedQuota(Long typeId);
+
+    /**
+     * 减少类型的已使用配额
+     *
+     * @param typeId 月卡类型ID
+     * @return 是否成功
+     */
+    boolean decrementUsedQuota(Long typeId);
+
+    /**
+     * 检查类型的配额是否可用
+     *
+     * @param typeId 月卡类型ID
+     * @return 是否有可用配额
+     */
+    boolean checkQuotaAvailability(Long typeId);
+
+    /**
+     * 获取类型的已使用配额
+     *
+     * @param typeId 月卡类型ID
+     * @return 已使用配额
+     */
+    Integer getUsedQuota(Long typeId);
+
+    /**
+     * 初始化类型的配额
+     *
+     * @param typeId 月卡类型ID
+     * @param initialUsedQuota 初始已使用配额
+     * @return 是否成功
+     */
+    boolean initQuota(Long typeId, Integer initialUsedQuota);
+} 

+ 275 - 0
src/main/java/com/qmrb/system/service/impl/ParkingCardServiceImpl.java

@@ -0,0 +1,275 @@
+package com.qmrb.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmrb.system.converter.ParkingCardConverter;
+import com.qmrb.system.mapper.ParkingCardMapper;
+import com.qmrb.system.pojo.entity.ParkingCard;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import com.qmrb.system.pojo.form.ParkingCardForm;
+import com.qmrb.system.pojo.query.ParkingCardQuery;
+import com.qmrb.system.pojo.vo.ParkingCardVO;
+import com.qmrb.system.service.IParkingCardService;
+import com.qmrb.system.service.IParkingCardTypeService;
+import com.qmrb.system.service.IQuotaUsageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.TemporalAdjusters;
+
+/**
+ * <p>
+ * 月卡购买记录表 服务实现类
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Service
+public class ParkingCardServiceImpl extends ServiceImpl<ParkingCardMapper, ParkingCard> implements IParkingCardService {
+
+    @Autowired
+    private ParkingCardConverter converter;
+
+    @Autowired
+    private IParkingCardTypeService parkingCardTypeService;
+
+    @Autowired
+    private IQuotaUsageService quotaUsageService;
+
+    @Override
+    public Page<ParkingCardVO> getPage(ParkingCardQuery queryParams) {
+        // 查询参数
+        int pageNum = queryParams.getPageNum();
+        int pageSize = queryParams.getPageSize();
+
+        LambdaQueryWrapper<ParkingCard> queryWrapper = new LambdaQueryWrapper<>();
+        
+        // 月卡类型ID查询
+        if (queryParams.getTypeId() != null) {
+            queryWrapper.eq(ParkingCard::getTypeId, queryParams.getTypeId());
+        }
+        
+        // 车牌号模糊查询
+        if (StrUtil.isNotBlank(queryParams.getPlateNumber())) {
+            queryWrapper.like(ParkingCard::getPlateNumber, queryParams.getPlateNumber());
+        }
+        
+        // 联系人姓名模糊查询
+        if (StrUtil.isNotBlank(queryParams.getName())) {
+            queryWrapper.like(ParkingCard::getName, queryParams.getName());
+        }
+        
+        // 手机号模糊查询
+        if (StrUtil.isNotBlank(queryParams.getPhone())) {
+            queryWrapper.like(ParkingCard::getPhone, queryParams.getPhone());
+        }
+        
+        // 状态查询
+        if (StrUtil.isNotBlank(queryParams.getStatus())) {
+            queryWrapper.eq(ParkingCard::getStatus, queryParams.getStatus());
+        }
+        
+        // 时间范围查询
+        if (StrUtil.isNotBlank(queryParams.getStartTimeBegin())) {
+            queryWrapper.ge(ParkingCard::getStartTime, queryParams.getStartTimeBegin());
+        }
+        if (StrUtil.isNotBlank(queryParams.getStartTimeEnd())) {
+            queryWrapper.le(ParkingCard::getStartTime, queryParams.getStartTimeEnd());
+        }
+        if (StrUtil.isNotBlank(queryParams.getEndTimeBegin())) {
+            queryWrapper.ge(ParkingCard::getEndTime, queryParams.getEndTimeBegin());
+        }
+        if (StrUtil.isNotBlank(queryParams.getEndTimeEnd())) {
+            queryWrapper.le(ParkingCard::getEndTime, queryParams.getEndTimeEnd());
+        }
+        if (StrUtil.isNotBlank(queryParams.getCreatedAtBegin())) {
+            queryWrapper.ge(ParkingCard::getCreatedAt, queryParams.getCreatedAtBegin());
+        }
+        if (StrUtil.isNotBlank(queryParams.getCreatedAtEnd())) {
+            queryWrapper.le(ParkingCard::getCreatedAt, queryParams.getCreatedAtEnd());
+        }
+        
+        // 默认按创建时间降序排序
+        queryWrapper.orderByDesc(ParkingCard::getCreatedAt);
+
+        // 查询数据
+        Page<ParkingCard> entityPage = this.page(
+                new Page<>(pageNum, pageSize),
+                queryWrapper
+        );
+
+        // 实体转换
+        Page<ParkingCardVO> pageResult = converter.entity2Page(entityPage);
+        
+        // 填充月卡类型名称
+        for (ParkingCardVO vo : pageResult.getRecords()) {
+            if (vo.getTypeId() != null) {
+                ParkingCardType cardType = parkingCardTypeService.getById(vo.getTypeId());
+                if (cardType != null) {
+                    vo.setTypeName(cardType.getTypeName());
+                }
+            }
+        }
+        
+        return pageResult;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ParkingCardForm saveForm(ParkingCardForm form) {
+        // 校验月卡类型是否存在
+        ParkingCardType cardType = parkingCardTypeService.getById(form.getTypeId());
+        Assert.notNull(cardType, "月卡类型不存在");
+        
+        // 校验购买月数是否合法
+        Assert.isTrue(form.getPurchasedMonths() >= 1 && form.getPurchasedMonths() <= 12, "购买月数必须在1-12月之间");
+        
+        // 检查配额是否可用
+        boolean quotaAvailable = quotaUsageService.checkQuotaAvailability(form.getTypeId());
+        Assert.isTrue(quotaAvailable, "该月卡类型已无可用配额");
+        
+        // 计算费用
+        BigDecimal totalFee = calculatePayment(form.getTypeId(), form.getPurchasedMonths());
+        form.setTotalFee(totalFee);
+        
+        // 计算有效期
+        LocalDate[] period = calculatePeriod(form.getPurchasedMonths());
+        form.setStartTime(period[0]);
+        form.setEndTime(period[1]);
+        
+        // 设置状态为有效
+        form.setStatus("active");
+        
+        // 实体对象转换 form->entity
+        ParkingCard entity = converter.form2Entity(form);
+        
+        // 设置创建和更新时间
+        LocalDateTime now = LocalDateTime.now();
+        entity.setCreatedAt(now);
+        entity.setUpdatedAt(now);
+        
+        // 持久化
+        this.save(entity);
+        
+        // 增加已使用配额
+        quotaUsageService.incrementUsedQuota(form.getTypeId());
+        
+        ParkingCardForm result = converter.entity2Form(entity);
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateForm(Long id, ParkingCardForm form) {
+        // 获取原记录
+        ParkingCard entity = this.getById(id);
+        Assert.notNull(entity, "月卡记录不存在");
+        
+        // 如果更改了月卡类型,需要处理配额变更
+        if (!entity.getTypeId().equals(form.getTypeId())) {
+            // 检查新类型的配额是否可用
+            boolean quotaAvailable = quotaUsageService.checkQuotaAvailability(form.getTypeId());
+            Assert.isTrue(quotaAvailable, "新月卡类型已无可用配额");
+            
+            // 减少原类型的已使用配额
+            quotaUsageService.decrementUsedQuota(entity.getTypeId());
+            
+            // 增加新类型的已使用配额
+            quotaUsageService.incrementUsedQuota(form.getTypeId());
+        }
+        
+        // 如果更改了购买月数,需要重新计算费用和有效期
+        if (!entity.getPurchasedMonths().equals(form.getPurchasedMonths())) {
+            // 计算费用
+            BigDecimal totalFee = calculatePayment(form.getTypeId(), form.getPurchasedMonths());
+            form.setTotalFee(totalFee);
+            
+            // 计算有效期
+            LocalDate[] period = calculatePeriod(form.getPurchasedMonths());
+            form.setStartTime(period[0]);
+            form.setEndTime(period[1]);
+        }
+        
+        // 更新实体
+        converter.updateEntityFromForm(form, entity);
+        entity.setUpdatedAt(LocalDateTime.now());
+        
+        // 保存更新
+        boolean result = this.updateById(entity);
+        return result;
+    }
+
+    @Override
+    public ParkingCardForm getFormData(Long id) {
+        // 获取entity
+        ParkingCard entity = this.getById(id);
+        Assert.notNull(entity, "月卡记录不存在");
+
+        // 实体转换
+        ParkingCardForm form = converter.entity2Form(entity);
+        return form;
+    }
+
+    @Override
+    public LocalDate[] calculatePeriod(Integer months) {
+        Assert.isTrue(months >= 1 && months <= 12, "购买月数必须在1-12月之间");
+        
+        LocalDate today = LocalDate.now();
+        LocalDate startDate = today;
+        
+        // 计算结束日期:当前日期加上指定月数,然后减去1天
+        LocalDate endDate = today.plusMonths(months).minusDays(1);
+        
+        return new LocalDate[] { startDate, endDate };
+    }
+
+    @Override
+    public BigDecimal calculatePayment(Long typeId, Integer months) {
+        return parkingCardTypeService.calculateFee(typeId, months);
+    }
+
+    @Override
+    public boolean checkQuotaAvailability(Long typeId) {
+        return quotaUsageService.checkQuotaAvailability(typeId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateStatus(Long id, String status) {
+        ParkingCard entity = this.getById(id);
+        Assert.notNull(entity, "月卡记录不存在");
+        
+        // 如果状态没变,直接返回成功
+        if (entity.getStatus().equals(status)) {
+            return true;
+        }
+        
+        // 更新状态
+        entity.setStatus(status);
+        entity.setUpdatedAt(LocalDateTime.now());
+        
+        // 如果从有效变为过期,减少已使用配额
+        if ("active".equals(entity.getStatus()) && "expired".equals(status)) {
+            quotaUsageService.decrementUsedQuota(entity.getTypeId());
+        }
+        // 如果从过期变为有效,增加已使用配额
+        else if ("expired".equals(entity.getStatus()) && "active".equals(status)) {
+            // 检查配额是否可用
+            boolean quotaAvailable = quotaUsageService.checkQuotaAvailability(entity.getTypeId());
+            Assert.isTrue(quotaAvailable, "该月卡类型已无可用配额");
+            
+            quotaUsageService.incrementUsedQuota(entity.getTypeId());
+        }
+        
+        return this.updateById(entity);
+    }
+} 

+ 178 - 0
src/main/java/com/qmrb/system/service/impl/ParkingCardTypeServiceImpl.java

@@ -0,0 +1,178 @@
+package com.qmrb.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmrb.system.converter.ParkingCardTypeConverter;
+import com.qmrb.system.mapper.ParkingCardTypeMapper;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import com.qmrb.system.pojo.form.ParkingCardTypeForm;
+import com.qmrb.system.pojo.query.ParkingCardTypeQuery;
+import com.qmrb.system.pojo.vo.ParkingCardTypeVO;
+import com.qmrb.system.service.IParkingCardTypeService;
+import com.qmrb.system.service.IQuotaUsageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 月卡类型表 服务实现类
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Service
+public class ParkingCardTypeServiceImpl extends ServiceImpl<ParkingCardTypeMapper, ParkingCardType> implements IParkingCardTypeService {
+
+    @Autowired
+    private ParkingCardTypeConverter converter;
+
+    @Autowired
+    private IQuotaUsageService quotaUsageService;
+
+    @Override
+    public Page<ParkingCardTypeVO> getPage(ParkingCardTypeQuery queryParams) {
+        // 查询参数
+        int pageNum = queryParams.getPageNum();
+        int pageSize = queryParams.getPageSize();
+
+        LambdaQueryWrapper<ParkingCardType> queryWrapper = new LambdaQueryWrapper<>();
+        // 根据类型名称模糊查询
+        if (StrUtil.isNotBlank(queryParams.getTypeName())) {
+            queryWrapper.like(ParkingCardType::getTypeName, queryParams.getTypeName());
+        }
+        // 价格范围查询
+        if (StrUtil.isNotBlank(queryParams.getMinPrice())) {
+            queryWrapper.ge(ParkingCardType::getPricePerMonth, queryParams.getMinPrice());
+        }
+        if (StrUtil.isNotBlank(queryParams.getMaxPrice())) {
+            queryWrapper.le(ParkingCardType::getPricePerMonth, queryParams.getMaxPrice());
+        }
+        // 按创建时间降序排序
+        queryWrapper.orderByDesc(ParkingCardType::getCreatedAt);
+
+        // 查询数据
+        Page<ParkingCardType> entityPage = this.page(
+                new Page<>(pageNum, pageSize),
+                queryWrapper
+        );
+
+        // 实体转换
+        Page<ParkingCardTypeVO> pageResult = converter.entity2Page(entityPage);
+        
+        // 填充已使用配额和剩余配额信息
+        for (ParkingCardTypeVO vo : pageResult.getRecords()) {
+            Integer usedQuota = quotaUsageService.getUsedQuota(vo.getId());
+            vo.setUsedQuota(usedQuota);
+            vo.setRemainingQuota(vo.getTotalQuota() - usedQuota);
+        }
+        
+        return pageResult;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ParkingCardTypeForm saveForm(ParkingCardTypeForm form) {
+        // 检查类型名称是否已存在
+        Long count = this.count(new LambdaQueryWrapper<ParkingCardType>()
+                .eq(ParkingCardType::getTypeName, form.getTypeName()));
+        Assert.isTrue(count == 0, "月卡类型名称已存在");
+
+        // 实体对象转换 form->entity
+        ParkingCardType entity = converter.form2Entity(form);
+        
+        // 设置创建和更新时间
+        LocalDateTime now = LocalDateTime.now();
+        entity.setCreatedAt(now);
+        entity.setUpdatedAt(now);
+        
+        // 持久化
+        this.save(entity);
+        
+        // 初始化配额使用记录
+        quotaUsageService.initQuota(entity.getId(), 0);
+        
+        ParkingCardTypeForm result = converter.entity2Form(entity);
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateForm(Long id, ParkingCardTypeForm form) {
+        // 检查类型名称是否已被其他记录使用
+        Long count = this.count(new LambdaQueryWrapper<ParkingCardType>()
+                .eq(ParkingCardType::getTypeName, form.getTypeName())
+                .ne(ParkingCardType::getId, id));
+        Assert.isTrue(count == 0, "月卡类型名称已存在");
+
+        // 获取原记录
+        ParkingCardType entity = this.getById(id);
+        Assert.notNull(entity, "月卡类型不存在");
+        
+        // 检查配额变更的合法性
+        Integer usedQuota = quotaUsageService.getUsedQuota(id);
+        Assert.isTrue(form.getTotalQuota() >= usedQuota, "新配额不能小于已使用配额: " + usedQuota);
+
+        // 更新实体
+        converter.updateEntityFromForm(form, entity);
+        entity.setUpdatedAt(LocalDateTime.now());
+        
+        // 保存更新
+        boolean result = this.updateById(entity);
+        return result;
+    }
+
+    @Override
+    public ParkingCardTypeForm getFormData(Long id) {
+        // 获取entity
+        ParkingCardType entity = this.getById(id);
+        Assert.notNull(entity, "月卡类型不存在");
+
+        // 实体转换
+        ParkingCardTypeForm form = converter.entity2Form(entity);
+        return form;
+    }
+
+    @Override
+    public List<ParkingCardTypeVO> listAllAvailable() {
+        // 查询所有月卡类型
+        List<ParkingCardType> entities = this.list();
+        
+        // 转换为VO
+        List<ParkingCardTypeVO> voList = converter.entity2VOList(entities);
+        
+        // 填充已使用配额和剩余配额信息
+        for (ParkingCardTypeVO vo : voList) {
+            Integer usedQuota = quotaUsageService.getUsedQuota(vo.getId());
+            vo.setUsedQuota(usedQuota);
+            vo.setRemainingQuota(vo.getTotalQuota() - usedQuota);
+        }
+        
+        // 只返回还有可用配额的类型
+        return voList.stream()
+                .filter(vo -> vo.getRemainingQuota() > 0)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public BigDecimal calculateFee(Long typeId, Integer months) {
+        Assert.isTrue(months >= 1 && months <= 12, "购买月数必须在1-12月之间");
+        
+        // 获取月卡类型
+        ParkingCardType cardType = this.getById(typeId);
+        Assert.notNull(cardType, "月卡类型不存在");
+        
+        // 计算费用 = 月单价 × 购买月数
+        return cardType.getPricePerMonth().multiply(new BigDecimal(months));
+    }
+} 

+ 97 - 0
src/main/java/com/qmrb/system/service/impl/QuotaUsageServiceImpl.java

@@ -0,0 +1,97 @@
+package com.qmrb.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmrb.system.mapper.QuotaUsageMapper;
+import com.qmrb.system.pojo.entity.ParkingCardType;
+import com.qmrb.system.pojo.entity.QuotaUsage;
+import com.qmrb.system.service.IParkingCardTypeService;
+import com.qmrb.system.service.IQuotaUsageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 配额使用统计表 服务实现类
+ * </p>
+ *
+ * @author assistant
+ * @since 2025-04-12
+ */
+@Service
+public class QuotaUsageServiceImpl extends ServiceImpl<QuotaUsageMapper, QuotaUsage> implements IQuotaUsageService {
+
+    @Autowired
+    private IParkingCardTypeService parkingCardTypeService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean incrementUsedQuota(Long typeId) {
+        QuotaUsage quotaUsage = this.getById(typeId);
+        if (quotaUsage == null) {
+            // 如果不存在则初始化
+            quotaUsage = new QuotaUsage();
+            quotaUsage.setTypeId(typeId);
+            quotaUsage.setUsedQuota(1);
+            quotaUsage.setUpdatedAt(LocalDateTime.now());
+            return this.save(quotaUsage);
+        } else {
+            // 如果已存在则增加
+            quotaUsage.setUsedQuota(quotaUsage.getUsedQuota() + 1);
+            quotaUsage.setUpdatedAt(LocalDateTime.now());
+            return this.updateById(quotaUsage);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean decrementUsedQuota(Long typeId) {
+        QuotaUsage quotaUsage = this.getById(typeId);
+        if (quotaUsage == null || quotaUsage.getUsedQuota() <= 0) {
+            return false;
+        } else {
+            quotaUsage.setUsedQuota(quotaUsage.getUsedQuota() - 1);
+            quotaUsage.setUpdatedAt(LocalDateTime.now());
+            return this.updateById(quotaUsage);
+        }
+    }
+
+    @Override
+    public boolean checkQuotaAvailability(Long typeId) {
+        // 获取月卡类型信息
+        ParkingCardType cardType = parkingCardTypeService.getById(typeId);
+        if (cardType == null) {
+            return false;
+        }
+
+        // 获取已使用配额
+        Integer usedQuota = getUsedQuota(typeId);
+
+        // 检查是否还有可用配额
+        return usedQuota < cardType.getTotalQuota();
+    }
+
+    @Override
+    public Integer getUsedQuota(Long typeId) {
+        QuotaUsage quotaUsage = this.getById(typeId);
+        return quotaUsage == null ? 0 : quotaUsage.getUsedQuota();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean initQuota(Long typeId, Integer initialUsedQuota) {
+        QuotaUsage quotaUsage = new QuotaUsage();
+        quotaUsage.setTypeId(typeId);
+        quotaUsage.setUsedQuota(initialUsedQuota);
+        quotaUsage.setUpdatedAt(LocalDateTime.now());
+        
+        // 先删除已有记录
+        this.remove(new LambdaQueryWrapper<QuotaUsage>().eq(QuotaUsage::getTypeId, typeId));
+        
+        // 保存新记录
+        return this.save(quotaUsage);
+    }
+}