分销
This commit is contained in:
1 parent
694789e0f8
commit
a6d3eb943f
31 files changed
+2573
-1
No files matched your search
+32
@@ -0,0 +1,32 @@
|
||||
package com.bsd.pjcloud.supermarket.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Schema(description = "酒店分销层级规则")
|
||||
public class SmHotelFxLevelRuleDTO {
|
||||
|
||||
@Schema(description = "层级 1~5")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "岗位名")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "占奖金池比例,如 0.40")
|
||||
private BigDecimal shareOfPool;
|
||||
|
||||
@Schema(description = "是否可晋升")
|
||||
private Boolean canPromote;
|
||||
|
||||
@Schema(description = "升到本级所需累计 promote 分,如四级升三级则为三级门槛 50000")
|
||||
private BigDecimal promoteMin;
|
||||
|
||||
@Schema(description = "保级门槛(二级:窗口内区域内新酒店锁定基数,默认 40000)")
|
||||
private BigDecimal demoteMin;
|
||||
|
||||
@Schema(description = "保级窗口天数(二级默认 90)")
|
||||
private Integer demoteWindowDays;
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_agent")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店分销员")
|
||||
public class SmHotelFxAgentEntity extends Model<SmHotelFxAgentEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "系统用户 ID")
|
||||
private Long userId;
|
||||
|
||||
private String username;
|
||||
|
||||
private String realName;
|
||||
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "层级 1~5")
|
||||
private Integer levelNo;
|
||||
|
||||
@Schema(description = "上级分销员")
|
||||
private Long parentId;
|
||||
|
||||
private String inviteCode;
|
||||
|
||||
private String cityAdcode;
|
||||
|
||||
private String regionJson;
|
||||
|
||||
@Schema(description = "ACTIVE/DISABLED/EXIT")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "晋升累计分")
|
||||
private BigDecimal promoteScore;
|
||||
|
||||
@Schema(description = "二级保级窗口累计")
|
||||
private BigDecimal demoteWindowScore;
|
||||
|
||||
private LocalDateTime demoteWindowStart;
|
||||
|
||||
private String remark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "上级姓名")
|
||||
private String parentName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "绑定酒店数")
|
||||
private Integer hotelCount;
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
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;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_commission")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店分销佣金明细")
|
||||
public class SmHotelFxCommissionEntity extends Model<SmHotelFxCommissionEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
private Long orderId;
|
||||
|
||||
private Long hotelId;
|
||||
|
||||
private Long agentId;
|
||||
|
||||
private Long ownerAgentId;
|
||||
|
||||
@Schema(description = "本条对应层级份额 1~5")
|
||||
private Integer chainLevel;
|
||||
|
||||
@Schema(description = "结算时分销员层级快照")
|
||||
private Integer agentLevel;
|
||||
|
||||
private BigDecimal costAmount;
|
||||
|
||||
private BigDecimal poolAmount;
|
||||
|
||||
private BigDecimal shareRate;
|
||||
|
||||
private BigDecimal commissionAmount;
|
||||
|
||||
private BigDecimal promoteBaseAmount;
|
||||
|
||||
@Schema(description = "ESTIMATED/LOCKED/CANCELLED")
|
||||
private String status;
|
||||
|
||||
private LocalDate checkOutDate;
|
||||
|
||||
private LocalDateTime lockTime;
|
||||
|
||||
@Schema(description = "提现单 ID")
|
||||
private Long withdrawId;
|
||||
|
||||
@Schema(description = "配置快照 JSON")
|
||||
private String configSnapshot;
|
||||
|
||||
private String remark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String agentName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String hotelName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String channelNo;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_config")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店分销佣金配置")
|
||||
public class SmHotelFxConfigEntity extends Model<SmHotelFxConfigEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "1开 0关")
|
||||
private String enabled;
|
||||
|
||||
@Schema(description = "奖金池成本系数,默认 0.06")
|
||||
private BigDecimal poolCostRate;
|
||||
|
||||
@Schema(description = "奖金池销售分佣池比例,默认 0.30")
|
||||
private BigDecimal poolSalesRate;
|
||||
|
||||
@Schema(description = "晋升累计成本系数,默认 0.06")
|
||||
private BigDecimal promoteBaseRate;
|
||||
|
||||
@Schema(description = "层级规则 JSON")
|
||||
private String levelRulesJson;
|
||||
|
||||
private String remark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_exit")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店分销退出")
|
||||
public class SmHotelFxExitEntity extends Model<SmHotelFxExitEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
private Long agentId;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "PENDING/DONE/REJECTED/CANCELLED")
|
||||
private String status;
|
||||
|
||||
private Long successorAgentId;
|
||||
|
||||
private Integer hotelCount;
|
||||
|
||||
private String auditRemark;
|
||||
|
||||
private String auditBy;
|
||||
|
||||
private LocalDateTime auditTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String agentName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String successorName;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_hotel_bind")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店与分销员绑定")
|
||||
public class SmHotelFxHotelBindEntity extends Model<SmHotelFxHotelBindEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
private Long hotelId;
|
||||
|
||||
private Long agentId;
|
||||
|
||||
@Schema(description = "BOUND/UNBOUND")
|
||||
private String bindStatus;
|
||||
|
||||
private String auditRemark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String hotelName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String hotelCode;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String agentName;
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_l1_apply")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店分销一级申请")
|
||||
public class SmHotelFxL1ApplyEntity extends Model<SmHotelFxL1ApplyEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
private Long agentId;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String cityAdcode;
|
||||
|
||||
private String cityName;
|
||||
|
||||
private BigDecimal promoteScore;
|
||||
|
||||
@Schema(description = "PENDING/APPROVED/REJECTED/CANCELLED")
|
||||
private String status;
|
||||
|
||||
private String applyRemark;
|
||||
|
||||
private String auditRemark;
|
||||
|
||||
private String auditBy;
|
||||
|
||||
private LocalDateTime auditTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String agentName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private Integer agentLevel;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.bsd.pjcloud.supermarket.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.bsd.pjcloud.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("canteen_hotel_fx_withdraw")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "酒店分销提现")
|
||||
public class SmHotelFxWithdrawEntity extends Model<SmHotelFxWithdrawEntity> {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long tenantId;
|
||||
|
||||
private Long agentId;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private Integer commissionCount;
|
||||
|
||||
private String accountName;
|
||||
|
||||
private String accountNo;
|
||||
|
||||
private String bankName;
|
||||
|
||||
@Schema(description = "PENDING/APPROVED/PAID/REJECTED")
|
||||
private String status;
|
||||
|
||||
private String applyRemark;
|
||||
|
||||
private String auditRemark;
|
||||
|
||||
private String auditBy;
|
||||
|
||||
private LocalDateTime auditTime;
|
||||
|
||||
private LocalDateTime payTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String agentName;
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
package com.bsd.pjcloud.supermarket.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxCommissionEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxConfigEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxExitEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxHotelBindEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxL1ApplyEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxWithdrawEntity;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxCommissionService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxConfigService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxExitService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxL1ApplyService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxWithdrawService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 酒店分销:配置 / 分销员 / 佣金 / 一级申请 / 提现 / 退出。
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/smHotelFx")
|
||||
@Tag(name = "酒店分销")
|
||||
public class SmHotelFxController {
|
||||
|
||||
private final SmHotelFxConfigService configService;
|
||||
|
||||
private final SmHotelFxAgentService agentService;
|
||||
|
||||
private final SmHotelFxCommissionService commissionService;
|
||||
|
||||
private final SmHotelFxL1ApplyService l1ApplyService;
|
||||
|
||||
private final SmHotelFxWithdrawService withdrawService;
|
||||
|
||||
private final SmHotelFxExitService exitService;
|
||||
|
||||
@Operation(summary = "分销配置")
|
||||
@GetMapping("/config")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFx_view')")
|
||||
public R<SmHotelFxConfigEntity> getConfig() {
|
||||
return R.ok(configService.getOrCreateCurrent());
|
||||
}
|
||||
|
||||
@SysLog("保存酒店分销配置")
|
||||
@Operation(summary = "保存分销配置(仅影响新单)")
|
||||
@PutMapping("/config")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFx_edit')")
|
||||
public R<SmHotelFxConfigEntity> saveConfig(@RequestBody SmHotelFxConfigEntity body) {
|
||||
return configService.saveCurrent(body);
|
||||
}
|
||||
|
||||
@Operation(summary = "分销员分页")
|
||||
@GetMapping("/agent/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxAgent_view')")
|
||||
public R<Page<SmHotelFxAgentEntity>> agentPage(Page<SmHotelFxAgentEntity> page, SmHotelFxAgentEntity query) {
|
||||
return R.ok(agentService.pageAgents(page, query));
|
||||
}
|
||||
|
||||
@SysLog("保存酒店分销员")
|
||||
@Operation(summary = "新增/编辑分销员")
|
||||
@PostMapping("/agent")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxAgent_edit')")
|
||||
public R<SmHotelFxAgentEntity> saveAgent(@RequestBody SmHotelFxAgentEntity body) {
|
||||
return agentService.saveAgent(body);
|
||||
}
|
||||
|
||||
@SysLog("划调酒店分销区域")
|
||||
@Operation(summary = "划调二级负责区域(adcode 数组 JSON)")
|
||||
@PutMapping("/agent/region")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxAgent_edit')")
|
||||
public R<SmHotelFxAgentEntity> saveRegion(@RequestParam Long agentId,
|
||||
@RequestParam(required = false) String regionJson,
|
||||
@RequestParam(required = false) String cityAdcode) {
|
||||
return agentService.saveRegion(agentId, regionJson, cityAdcode);
|
||||
}
|
||||
|
||||
@SysLog("手动执行二级保级考核")
|
||||
@Operation(summary = "手动扫描二级到期降级/滚动窗口")
|
||||
@PostMapping("/agent/demoteDue")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFx_edit')")
|
||||
public R<Integer> demoteDue() {
|
||||
return R.ok(agentService.processDueDemotions());
|
||||
}
|
||||
|
||||
@SysLog("绑定酒店分销员")
|
||||
@Operation(summary = "酒店绑定分销员")
|
||||
@PostMapping("/bind")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxAgent_edit')")
|
||||
public R<Boolean> bind(@RequestParam Long hotelId, @RequestParam Long agentId,
|
||||
@RequestParam(required = false) String remark) {
|
||||
return agentService.bindHotel(hotelId, agentId, remark);
|
||||
}
|
||||
|
||||
@SysLog("解绑酒店分销员")
|
||||
@Operation(summary = "解绑酒店")
|
||||
@PostMapping("/unbind")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxAgent_edit')")
|
||||
public R<Boolean> unbind(@RequestParam Long hotelId) {
|
||||
return agentService.unbindHotel(hotelId);
|
||||
}
|
||||
|
||||
@Operation(summary = "酒店绑定分页")
|
||||
@GetMapping("/bind/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxAgent_view')")
|
||||
public R<Page<SmHotelFxHotelBindEntity>> bindPage(Page<SmHotelFxHotelBindEntity> page,
|
||||
@RequestParam(required = false) Long agentId, @RequestParam(required = false) Long hotelId) {
|
||||
return R.ok(agentService.pageBinds(page, agentId, hotelId));
|
||||
}
|
||||
|
||||
@Operation(summary = "佣金明细分页(管理端)")
|
||||
@GetMapping("/commission/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxCommission_view')")
|
||||
public R<Page<SmHotelFxCommissionEntity>> commissionPage(Page<SmHotelFxCommissionEntity> page,
|
||||
SmHotelFxCommissionEntity query) {
|
||||
return R.ok(commissionService.pageAdmin(page, query));
|
||||
}
|
||||
|
||||
@SysLog("手动锁定到期分销佣金")
|
||||
@Operation(summary = "手动执行退房T+1锁定")
|
||||
@PostMapping("/commission/lockDue")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFx_edit')")
|
||||
public R<Integer> lockDue() {
|
||||
return R.ok(commissionService.lockDueCommissions());
|
||||
}
|
||||
|
||||
@Operation(summary = "一级申请分页")
|
||||
@GetMapping("/l1/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxL1_view')")
|
||||
public R<Page<SmHotelFxL1ApplyEntity>> l1Page(Page<SmHotelFxL1ApplyEntity> page, SmHotelFxL1ApplyEntity query) {
|
||||
return R.ok(l1ApplyService.pageAdmin(page, query));
|
||||
}
|
||||
|
||||
@SysLog("审核一级申请")
|
||||
@Operation(summary = "审核一级申请")
|
||||
@PostMapping("/l1/audit")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxL1_audit')")
|
||||
public R<Boolean> l1Audit(@RequestParam Long id, @RequestParam boolean approved,
|
||||
@RequestParam(required = false) String auditRemark) {
|
||||
return l1ApplyService.audit(id, approved, auditRemark);
|
||||
}
|
||||
|
||||
@Operation(summary = "提现分页(管理端)")
|
||||
@GetMapping("/withdraw/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxWithdraw_view')")
|
||||
public R<Page<SmHotelFxWithdrawEntity>> withdrawPage(Page<SmHotelFxWithdrawEntity> page,
|
||||
SmHotelFxWithdrawEntity query) {
|
||||
return R.ok(withdrawService.pageAdmin(page, query));
|
||||
}
|
||||
|
||||
@SysLog("审核分销提现")
|
||||
@Operation(summary = "审核提现")
|
||||
@PostMapping("/withdraw/audit")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxWithdraw_audit')")
|
||||
public R<Boolean> withdrawAudit(@RequestParam Long id, @RequestParam boolean approved,
|
||||
@RequestParam(required = false) String auditRemark) {
|
||||
return withdrawService.audit(id, approved, auditRemark);
|
||||
}
|
||||
|
||||
@SysLog("分销提现打款")
|
||||
@Operation(summary = "标记提现已打款")
|
||||
@PostMapping("/withdraw/paid")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxWithdraw_audit')")
|
||||
public R<Boolean> withdrawPaid(@RequestParam Long id, @RequestParam(required = false) String remark) {
|
||||
return withdrawService.markPaid(id, remark);
|
||||
}
|
||||
|
||||
@Operation(summary = "退出申请分页")
|
||||
@GetMapping("/exit/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxExit_view')")
|
||||
public R<Page<SmHotelFxExitEntity>> exitPage(Page<SmHotelFxExitEntity> page, SmHotelFxExitEntity query) {
|
||||
return R.ok(exitService.pageAdmin(page, query));
|
||||
}
|
||||
|
||||
@SysLog("审核分销退出")
|
||||
@Operation(summary = "审核退出(通过须传 successorAgentId)")
|
||||
@PostMapping("/exit/audit")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelFxExit_audit')")
|
||||
public R<Boolean> exitAudit(@RequestParam Long id, @RequestParam boolean approved,
|
||||
@RequestParam(required = false) Long successorAgentId,
|
||||
@RequestParam(required = false) String auditRemark) {
|
||||
return exitService.audit(id, approved, successorAgentId, auditRemark);
|
||||
}
|
||||
|
||||
@Operation(summary = "我的佣金汇总(App)")
|
||||
@GetMapping("/app/mySummary")
|
||||
public R<Map<String, Object>> mySummary() {
|
||||
return commissionService.mySummary();
|
||||
}
|
||||
|
||||
@Operation(summary = "我的佣金明细(App)")
|
||||
@GetMapping("/app/myPage")
|
||||
public R<Page<SmHotelFxCommissionEntity>> myPage(Page<SmHotelFxCommissionEntity> page,
|
||||
@RequestParam(required = false) String status) {
|
||||
return R.ok(commissionService.pageMine(page, status));
|
||||
}
|
||||
|
||||
@Operation(summary = "申请一级(App)")
|
||||
@PostMapping("/app/l1/apply")
|
||||
public R<SmHotelFxL1ApplyEntity> appL1Apply(@RequestParam String cityAdcode,
|
||||
@RequestParam(required = false) String cityName, @RequestParam(required = false) String remark) {
|
||||
return l1ApplyService.applyMine(cityAdcode, cityName, remark);
|
||||
}
|
||||
|
||||
@Operation(summary = "可提现汇总(App)")
|
||||
@GetMapping("/app/withdraw/summary")
|
||||
public R<Map<String, Object>> appWithdrawSummary() {
|
||||
return withdrawService.withdrawableMine();
|
||||
}
|
||||
|
||||
@Operation(summary = "申请提现(App)")
|
||||
@PostMapping("/app/withdraw/apply")
|
||||
public R<SmHotelFxWithdrawEntity> appWithdrawApply(@RequestParam String accountName,
|
||||
@RequestParam String accountNo, @RequestParam(required = false) String bankName,
|
||||
@RequestParam(required = false) String remark) {
|
||||
return withdrawService.applyMine(accountName, accountNo, bankName, remark);
|
||||
}
|
||||
|
||||
@Operation(summary = "我的提现单(App)")
|
||||
@GetMapping("/app/withdraw/page")
|
||||
public R<Page<SmHotelFxWithdrawEntity>> appWithdrawPage(Page<SmHotelFxWithdrawEntity> page,
|
||||
@RequestParam(required = false) String status) {
|
||||
return R.ok(withdrawService.pageMine(page, status));
|
||||
}
|
||||
|
||||
@Operation(summary = "申请退出(App)")
|
||||
@PostMapping("/app/exit/apply")
|
||||
public R<SmHotelFxExitEntity> appExitApply(@RequestParam(required = false) String reason) {
|
||||
return exitService.applyMine(reason);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.bsd.pjcloud.supermarket.job;
|
||||
|
||||
import com.bsd.pjcloud.common.data.tenant.TenantBroker;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxCommissionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 退房 T+1 锁定预计佣金:默认每天 01:15。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxCommissionLockSchedule {
|
||||
|
||||
private final SmHotelFxCommissionService commissionService;
|
||||
|
||||
@Value("${sm-hotel.fx.lock-schedule-enabled:true}")
|
||||
private boolean scheduleEnabled;
|
||||
|
||||
@Value("${sm-hotel.fx.tenant-id:1}")
|
||||
private Long tenantId;
|
||||
|
||||
@Scheduled(cron = "${sm-hotel.fx.lock-cron:0 15 1 * * ?}")
|
||||
public void lockDue() {
|
||||
if (!scheduleEnabled) {
|
||||
return;
|
||||
}
|
||||
Long tid = tenantId == null ? 1L : tenantId;
|
||||
try {
|
||||
int n = TenantBroker.applyAs(tid, t -> commissionService.lockDueCommissions());
|
||||
if (n > 0) {
|
||||
log.info("|酒店分销|T+1锁定|tenant={}|count={}|", tid, n);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("|酒店分销|T+1锁定异常|", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.bsd.pjcloud.supermarket.job;
|
||||
|
||||
import com.bsd.pjcloud.common.data.tenant.TenantBroker;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 二级区域保级考核:窗口到期未达 demoteMin 则降三级;达标则滚动新窗口。默认每天 02:15。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxDemoteSchedule {
|
||||
|
||||
private final SmHotelFxAgentService agentService;
|
||||
|
||||
@Value("${sm-hotel.fx.demote-schedule-enabled:true}")
|
||||
private boolean scheduleEnabled;
|
||||
|
||||
@Value("${sm-hotel.fx.tenant-id:1}")
|
||||
private Long tenantId;
|
||||
|
||||
@Scheduled(cron = "${sm-hotel.fx.demote-cron:0 15 2 * * ?}")
|
||||
public void processDemote() {
|
||||
if (!scheduleEnabled) {
|
||||
return;
|
||||
}
|
||||
Long tid = tenantId == null ? 1L : tenantId;
|
||||
try {
|
||||
int n = TenantBroker.applyAs(tid, t -> agentService.processDueDemotions());
|
||||
if (n > 0) {
|
||||
log.info("|酒店分销|二级降级|tenant={}|count={}|", tid, n);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("|酒店分销|二级降级任务异常|", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxAgentMapper extends PjcloudBaseMapper<SmHotelFxAgentEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxCommissionEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxCommissionMapper extends PjcloudBaseMapper<SmHotelFxCommissionEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxConfigEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxConfigMapper extends PjcloudBaseMapper<SmHotelFxConfigEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxExitEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxExitMapper extends PjcloudBaseMapper<SmHotelFxExitEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxHotelBindEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxHotelBindMapper extends PjcloudBaseMapper<SmHotelFxHotelBindEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxL1ApplyEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxL1ApplyMapper extends PjcloudBaseMapper<SmHotelFxL1ApplyEntity> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.supermarket.mapper;
|
||||
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxWithdrawEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelFxWithdrawMapper extends PjcloudBaseMapper<SmHotelFxWithdrawEntity> {
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxHotelBindEntity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public interface SmHotelFxAgentService extends IService<SmHotelFxAgentEntity> {
|
||||
|
||||
Page<SmHotelFxAgentEntity> pageAgents(Page<SmHotelFxAgentEntity> page, SmHotelFxAgentEntity query);
|
||||
|
||||
R<SmHotelFxAgentEntity> saveAgent(SmHotelFxAgentEntity body);
|
||||
|
||||
/** 划调二级负责区域(district adcode 列表 JSON) */
|
||||
R<SmHotelFxAgentEntity> saveRegion(Long agentId, String regionJson, String cityAdcode);
|
||||
|
||||
R<Boolean> bindHotel(Long hotelId, Long agentId, String remark);
|
||||
|
||||
R<Boolean> unbindHotel(Long hotelId);
|
||||
|
||||
Page<SmHotelFxHotelBindEntity> pageBinds(Page<SmHotelFxHotelBindEntity> page, Long agentId, Long hotelId);
|
||||
|
||||
SmHotelFxAgentEntity getByUserId(Long userId);
|
||||
|
||||
SmHotelFxAgentEntity getBoundAgentByHotelId(Long hotelId);
|
||||
|
||||
/** 锁定佣金后:累加终身晋升分,并尝试 4→3 / 3→2 自动晋升 */
|
||||
void onPromoteBaseLocked(Long ownerAgentId, BigDecimal promoteBaseAmount);
|
||||
|
||||
/**
|
||||
* 锁定佣金后:若开发员为二级,且酒店在其区域内、绑定时间≥窗口起算,则累加保级窗口分。
|
||||
*/
|
||||
void onDemoteWindowEligible(Long ownerAgentId, Long hotelId, BigDecimal promoteBaseAmount);
|
||||
|
||||
/** 扫描到期二级:窗口内未达 demoteMin 则降为三级 */
|
||||
int processDueDemotions();
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxCommissionEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelOrderEntity;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 酒店分销佣金:下单记预计、退房 T+1 锁定、取消作废;金额按配置快照留存。
|
||||
*/
|
||||
public interface SmHotelFxCommissionService extends IService<SmHotelFxCommissionEntity> {
|
||||
|
||||
/** 订单创建/上游成功后生成预计佣金(幂等) */
|
||||
void createEstimatedForOrder(SmHotelOrderEntity order);
|
||||
|
||||
/** 订单取消:预计佣金作废 */
|
||||
void cancelForOrder(Long orderId);
|
||||
|
||||
/** 退房日次日锁定预计佣金 */
|
||||
int lockDueCommissions();
|
||||
|
||||
Page<SmHotelFxCommissionEntity> pageAdmin(Page<SmHotelFxCommissionEntity> page, SmHotelFxCommissionEntity query);
|
||||
|
||||
/** App:当前登录用户作为分销员的佣金汇总 + 分页 */
|
||||
R<Map<String, Object>> mySummary();
|
||||
|
||||
Page<SmHotelFxCommissionEntity> pageMine(Page<SmHotelFxCommissionEntity> page, String status);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelFxLevelRuleDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxConfigEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SmHotelFxConfigService extends IService<SmHotelFxConfigEntity> {
|
||||
|
||||
SmHotelFxConfigEntity getOrCreateCurrent();
|
||||
|
||||
R<SmHotelFxConfigEntity> saveCurrent(SmHotelFxConfigEntity body);
|
||||
|
||||
List<SmHotelFxLevelRuleDTO> parseLevelRules(SmHotelFxConfigEntity cfg);
|
||||
|
||||
boolean isEnabled(SmHotelFxConfigEntity cfg);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxExitEntity;
|
||||
|
||||
public interface SmHotelFxExitService extends IService<SmHotelFxExitEntity> {
|
||||
|
||||
Page<SmHotelFxExitEntity> pageAdmin(Page<SmHotelFxExitEntity> page, SmHotelFxExitEntity query);
|
||||
|
||||
R<SmHotelFxExitEntity> applyMine(String reason);
|
||||
|
||||
/**
|
||||
* 审核退出:通过时必须指定 successorAgentId,重分配酒店绑定、下级挂靠,并作废预计佣金。
|
||||
*/
|
||||
R<Boolean> audit(Long id, boolean approved, Long successorAgentId, String auditRemark);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxL1ApplyEntity;
|
||||
|
||||
public interface SmHotelFxL1ApplyService extends IService<SmHotelFxL1ApplyEntity> {
|
||||
|
||||
Page<SmHotelFxL1ApplyEntity> pageAdmin(Page<SmHotelFxL1ApplyEntity> page, SmHotelFxL1ApplyEntity query);
|
||||
|
||||
/** App:二级达标后申请城市合伙人 */
|
||||
R<SmHotelFxL1ApplyEntity> applyMine(String cityAdcode, String cityName, String remark);
|
||||
|
||||
R<Boolean> audit(Long id, boolean approved, String auditRemark);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxWithdrawEntity;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface SmHotelFxWithdrawService extends IService<SmHotelFxWithdrawEntity> {
|
||||
|
||||
Page<SmHotelFxWithdrawEntity> pageAdmin(Page<SmHotelFxWithdrawEntity> page, SmHotelFxWithdrawEntity query);
|
||||
|
||||
Page<SmHotelFxWithdrawEntity> pageMine(Page<SmHotelFxWithdrawEntity> page, String status);
|
||||
|
||||
/** 可提现:已锁定且未挂提现单的佣金合计 */
|
||||
R<Map<String, Object>> withdrawableMine();
|
||||
|
||||
R<SmHotelFxWithdrawEntity> applyMine(String accountName, String accountNo, String bankName, String remark);
|
||||
|
||||
R<Boolean> audit(Long id, boolean approved, String auditRemark);
|
||||
|
||||
/** 已审通过后标记打款完成 */
|
||||
R<Boolean> markPaid(Long id, String remark);
|
||||
}
|
||||
+489
@@ -0,0 +1,489 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelFxLevelRuleDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxHotelBindEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxAgentMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxHotelBindMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxAgentServiceImpl extends ServiceImpl<SmHotelFxAgentMapper, SmHotelFxAgentEntity>
|
||||
implements SmHotelFxAgentService {
|
||||
|
||||
private final SmHotelFxHotelBindMapper bindMapper;
|
||||
|
||||
private final SmHotelMapper hotelMapper;
|
||||
|
||||
private final SmHotelFxConfigService configService;
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxAgentEntity> pageAgents(Page<SmHotelFxAgentEntity> page, SmHotelFxAgentEntity query) {
|
||||
Page<SmHotelFxAgentEntity> result = page(page, Wrappers.<SmHotelFxAgentEntity>lambdaQuery()
|
||||
.eq(query.getLevelNo() != null, SmHotelFxAgentEntity::getLevelNo, query.getLevelNo())
|
||||
.eq(StrUtil.isNotBlank(query.getStatus()), SmHotelFxAgentEntity::getStatus, query.getStatus())
|
||||
.like(StrUtil.isNotBlank(query.getRealName()), SmHotelFxAgentEntity::getRealName, query.getRealName())
|
||||
.like(StrUtil.isNotBlank(query.getMobile()), SmHotelFxAgentEntity::getMobile, query.getMobile())
|
||||
.like(StrUtil.isNotBlank(query.getUsername()), SmHotelFxAgentEntity::getUsername, query.getUsername())
|
||||
.orderByAsc(SmHotelFxAgentEntity::getLevelNo)
|
||||
.orderByDesc(SmHotelFxAgentEntity::getId));
|
||||
fillExtras(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fillExtras(List<SmHotelFxAgentEntity> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> parentIds = list.stream().map(SmHotelFxAgentEntity::getParentId).filter(Objects::nonNull).distinct()
|
||||
.toList();
|
||||
Map<Long, String> parentName = Map.of();
|
||||
if (!parentIds.isEmpty()) {
|
||||
parentName = listByIds(parentIds).stream()
|
||||
.collect(Collectors.toMap(SmHotelFxAgentEntity::getId,
|
||||
a -> StrUtil.blankToDefault(a.getRealName(), a.getUsername()), (a, b) -> a));
|
||||
}
|
||||
List<Long> ids = list.stream().map(SmHotelFxAgentEntity::getId).toList();
|
||||
List<SmHotelFxHotelBindEntity> binds = bindMapper.selectList(Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.in(SmHotelFxHotelBindEntity::getAgentId, ids)
|
||||
.eq(SmHotelFxHotelBindEntity::getBindStatus, "BOUND"));
|
||||
Map<Long, Long> cnt = binds.stream()
|
||||
.collect(Collectors.groupingBy(SmHotelFxHotelBindEntity::getAgentId, Collectors.counting()));
|
||||
for (SmHotelFxAgentEntity a : list) {
|
||||
a.setParentName(a.getParentId() == null ? null : parentName.get(a.getParentId()));
|
||||
a.setHotelCount(cnt.getOrDefault(a.getId(), 0L).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<SmHotelFxAgentEntity> saveAgent(SmHotelFxAgentEntity body) {
|
||||
if (body == null || body.getUserId() == null) {
|
||||
return R.failed("请选择系统用户");
|
||||
}
|
||||
int level = body.getLevelNo() == null ? 4 : body.getLevelNo();
|
||||
if (level < 1 || level > 5) {
|
||||
return R.failed("层级须在 1~5");
|
||||
}
|
||||
body.setLevelNo(level);
|
||||
if (StrUtil.isBlank(body.getStatus())) {
|
||||
body.setStatus("ACTIVE");
|
||||
}
|
||||
if (body.getId() == null) {
|
||||
Long exists = count(Wrappers.<SmHotelFxAgentEntity>lambdaQuery()
|
||||
.eq(SmHotelFxAgentEntity::getUserId, body.getUserId()));
|
||||
if (exists != null && exists > 0) {
|
||||
return R.failed("该用户已是分销员");
|
||||
}
|
||||
if (StrUtil.isBlank(body.getInviteCode())) {
|
||||
body.setInviteCode("FX" + IdUtil.getSnowflakeNextIdStr().substring(10));
|
||||
}
|
||||
if (body.getPromoteScore() == null) {
|
||||
body.setPromoteScore(BigDecimal.ZERO);
|
||||
}
|
||||
if (body.getDemoteWindowScore() == null) {
|
||||
body.setDemoteWindowScore(BigDecimal.ZERO);
|
||||
}
|
||||
if (body.getParentId() != null && (body.getLevelNo() == null || body.getLevelNo() == 4)) {
|
||||
SmHotelFxAgentEntity parent = getById(body.getParentId());
|
||||
if (parent != null && Integer.valueOf(4).equals(parent.getLevelNo())) {
|
||||
body.setLevelNo(5);
|
||||
}
|
||||
}
|
||||
save(body);
|
||||
if (Integer.valueOf(2).equals(body.getLevelNo())) {
|
||||
initLevel2DemoteWindow(body);
|
||||
updateById(body);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SmHotelFxAgentEntity old = getById(body.getId());
|
||||
if (old == null) {
|
||||
return R.failed("分销员不存在");
|
||||
}
|
||||
Integer prevLevel = old.getLevelNo();
|
||||
old.setRealName(body.getRealName());
|
||||
old.setMobile(body.getMobile());
|
||||
old.setUsername(body.getUsername());
|
||||
old.setLevelNo(body.getLevelNo());
|
||||
old.setParentId(body.getParentId());
|
||||
old.setCityAdcode(body.getCityAdcode());
|
||||
old.setRegionJson(body.getRegionJson());
|
||||
old.setStatus(body.getStatus());
|
||||
old.setRemark(body.getRemark());
|
||||
// 人工调到二级:开启保级窗口;从二级调走:清窗口
|
||||
if (Integer.valueOf(2).equals(old.getLevelNo()) && !Integer.valueOf(2).equals(prevLevel)) {
|
||||
initLevel2DemoteWindow(old);
|
||||
}
|
||||
else if (!Integer.valueOf(2).equals(old.getLevelNo()) && Integer.valueOf(2).equals(prevLevel)) {
|
||||
clearDemoteWindow(old);
|
||||
}
|
||||
updateById(old);
|
||||
body = old;
|
||||
}
|
||||
return R.ok(body);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<SmHotelFxAgentEntity> saveRegion(Long agentId, String regionJson, String cityAdcode) {
|
||||
if (agentId == null) {
|
||||
return R.failed("分销员为空");
|
||||
}
|
||||
SmHotelFxAgentEntity agent = getById(agentId);
|
||||
if (agent == null) {
|
||||
return R.failed("分销员不存在");
|
||||
}
|
||||
if (StrUtil.isNotBlank(regionJson)) {
|
||||
try {
|
||||
List<?> parsed = JSONUtil.parseArray(regionJson);
|
||||
if (parsed == null) {
|
||||
return R.failed("区域 JSON 无效,需为 adcode 数组");
|
||||
}
|
||||
agent.setRegionJson(JSONUtil.toJsonStr(parsed));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return R.failed("区域 JSON 无效,需为 adcode 数组,如 [\"330402\",\"330411\"]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
agent.setRegionJson(null);
|
||||
}
|
||||
if (cityAdcode != null) {
|
||||
agent.setCityAdcode(StrUtil.blankToDefault(cityAdcode, null));
|
||||
}
|
||||
updateById(agent);
|
||||
return R.ok(agent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> bindHotel(Long hotelId, Long agentId, String remark) {
|
||||
if (hotelId == null || agentId == null) {
|
||||
return R.failed("酒店与分销员不能为空");
|
||||
}
|
||||
SmHotelEntity hotel = hotelMapper.selectById(hotelId);
|
||||
if (hotel == null) {
|
||||
return R.failed("酒店不存在");
|
||||
}
|
||||
SmHotelFxAgentEntity agent = getById(agentId);
|
||||
if (agent == null || !"ACTIVE".equals(agent.getStatus())) {
|
||||
return R.failed("分销员不可用");
|
||||
}
|
||||
SmHotelFxHotelBindEntity exist = bindMapper.selectOne(Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.eq(SmHotelFxHotelBindEntity::getHotelId, hotelId)
|
||||
.last("limit 1"));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (exist == null) {
|
||||
SmHotelFxHotelBindEntity row = new SmHotelFxHotelBindEntity();
|
||||
row.setHotelId(hotelId);
|
||||
row.setAgentId(agentId);
|
||||
row.setBindStatus("BOUND");
|
||||
row.setAuditRemark(remark);
|
||||
row.setCreateTime(now);
|
||||
bindMapper.insert(row);
|
||||
}
|
||||
else {
|
||||
boolean rebound = !"BOUND".equals(exist.getBindStatus())
|
||||
|| !Objects.equals(exist.getAgentId(), agentId);
|
||||
exist.setAgentId(agentId);
|
||||
exist.setBindStatus("BOUND");
|
||||
exist.setAuditRemark(remark);
|
||||
// 解绑后再绑 / 换绑:视为「新审核酒店」,保级窗口可计入
|
||||
if (rebound) {
|
||||
exist.setCreateTime(now);
|
||||
}
|
||||
bindMapper.updateById(exist);
|
||||
}
|
||||
return R.ok(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> unbindHotel(Long hotelId) {
|
||||
SmHotelFxHotelBindEntity exist = bindMapper.selectOne(Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.eq(SmHotelFxHotelBindEntity::getHotelId, hotelId)
|
||||
.last("limit 1"));
|
||||
if (exist == null) {
|
||||
return R.ok(Boolean.TRUE);
|
||||
}
|
||||
exist.setBindStatus("UNBOUND");
|
||||
bindMapper.updateById(exist);
|
||||
return R.ok(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxHotelBindEntity> pageBinds(Page<SmHotelFxHotelBindEntity> page, Long agentId, Long hotelId) {
|
||||
Page<SmHotelFxHotelBindEntity> result = bindMapper.selectPage(page,
|
||||
Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.eq(agentId != null, SmHotelFxHotelBindEntity::getAgentId, agentId)
|
||||
.eq(hotelId != null, SmHotelFxHotelBindEntity::getHotelId, hotelId)
|
||||
.eq(SmHotelFxHotelBindEntity::getBindStatus, "BOUND")
|
||||
.orderByDesc(SmHotelFxHotelBindEntity::getId));
|
||||
for (SmHotelFxHotelBindEntity b : result.getRecords()) {
|
||||
SmHotelEntity h = hotelMapper.selectById(b.getHotelId());
|
||||
if (h != null) {
|
||||
b.setHotelName(h.getHotelName());
|
||||
b.setHotelCode(h.getHotelCode());
|
||||
}
|
||||
SmHotelFxAgentEntity a = getById(b.getAgentId());
|
||||
if (a != null) {
|
||||
b.setAgentName(StrUtil.blankToDefault(a.getRealName(), a.getUsername()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmHotelFxAgentEntity getByUserId(Long userId) {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
}
|
||||
return getOne(Wrappers.<SmHotelFxAgentEntity>lambdaQuery()
|
||||
.eq(SmHotelFxAgentEntity::getUserId, userId)
|
||||
.eq(SmHotelFxAgentEntity::getStatus, "ACTIVE")
|
||||
.last("limit 1"), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmHotelFxAgentEntity getBoundAgentByHotelId(Long hotelId) {
|
||||
if (hotelId == null) {
|
||||
return null;
|
||||
}
|
||||
SmHotelFxHotelBindEntity bind = bindMapper.selectOne(Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.eq(SmHotelFxHotelBindEntity::getHotelId, hotelId)
|
||||
.eq(SmHotelFxHotelBindEntity::getBindStatus, "BOUND")
|
||||
.last("limit 1"));
|
||||
if (bind == null) {
|
||||
return null;
|
||||
}
|
||||
SmHotelFxAgentEntity agent = getById(bind.getAgentId());
|
||||
if (agent == null || !"ACTIVE".equals(agent.getStatus())) {
|
||||
return null;
|
||||
}
|
||||
return agent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void onPromoteBaseLocked(Long ownerAgentId, BigDecimal promoteBaseAmount) {
|
||||
if (ownerAgentId == null || promoteBaseAmount == null
|
||||
|| promoteBaseAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
SmHotelFxAgentEntity agent = getById(ownerAgentId);
|
||||
if (agent == null || !"ACTIVE".equals(agent.getStatus())) {
|
||||
return;
|
||||
}
|
||||
BigDecimal score = agent.getPromoteScore() == null ? BigDecimal.ZERO : agent.getPromoteScore();
|
||||
agent.setPromoteScore(score.add(promoteBaseAmount));
|
||||
updateById(agent);
|
||||
tryAutoPromote(agent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void onDemoteWindowEligible(Long ownerAgentId, Long hotelId, BigDecimal promoteBaseAmount) {
|
||||
if (ownerAgentId == null || hotelId == null || promoteBaseAmount == null
|
||||
|| promoteBaseAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
SmHotelFxAgentEntity agent = getById(ownerAgentId);
|
||||
if (agent == null || !"ACTIVE".equals(agent.getStatus()) || !Integer.valueOf(2).equals(agent.getLevelNo())) {
|
||||
return;
|
||||
}
|
||||
if (agent.getDemoteWindowStart() == null) {
|
||||
initLevel2DemoteWindow(agent);
|
||||
updateById(agent);
|
||||
}
|
||||
if (!isHotelInAgentRegion(agent, hotelId)) {
|
||||
return;
|
||||
}
|
||||
if (!isNewHotelForDemoteWindow(agent, hotelId)) {
|
||||
return;
|
||||
}
|
||||
BigDecimal win = agent.getDemoteWindowScore() == null ? BigDecimal.ZERO : agent.getDemoteWindowScore();
|
||||
agent.setDemoteWindowScore(win.add(promoteBaseAmount));
|
||||
updateById(agent);
|
||||
log.info("|酒店分销|保级窗口累加|agentId={}|hotelId={}|add={}|total={}|", ownerAgentId, hotelId,
|
||||
promoteBaseAmount, agent.getDemoteWindowScore());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int processDueDemotions() {
|
||||
SmHotelFxLevelRuleDTO rule2 = findLevelRule(2);
|
||||
int windowDays = rule2 != null && rule2.getDemoteWindowDays() != null ? rule2.getDemoteWindowDays() : 90;
|
||||
BigDecimal demoteMin = rule2 != null && rule2.getDemoteMin() != null ? rule2.getDemoteMin()
|
||||
: new BigDecimal("40000");
|
||||
LocalDateTime deadline = LocalDateTime.now().minusDays(windowDays);
|
||||
List<SmHotelFxAgentEntity> due = list(Wrappers.<SmHotelFxAgentEntity>lambdaQuery()
|
||||
.eq(SmHotelFxAgentEntity::getLevelNo, 2)
|
||||
.eq(SmHotelFxAgentEntity::getStatus, "ACTIVE")
|
||||
.isNotNull(SmHotelFxAgentEntity::getDemoteWindowStart)
|
||||
.le(SmHotelFxAgentEntity::getDemoteWindowStart, deadline)
|
||||
.last("limit 500"));
|
||||
if (CollUtil.isEmpty(due)) {
|
||||
return 0;
|
||||
}
|
||||
int n = 0;
|
||||
for (SmHotelFxAgentEntity agent : due) {
|
||||
BigDecimal win = agent.getDemoteWindowScore() == null ? BigDecimal.ZERO : agent.getDemoteWindowScore();
|
||||
if (win.compareTo(demoteMin) >= 0) {
|
||||
// 达标:滚动新窗口继续考核
|
||||
initLevel2DemoteWindow(agent);
|
||||
updateById(agent);
|
||||
log.info("|酒店分销|二级保级通过|agentId={}|score={}|min={}|", agent.getId(), win, demoteMin);
|
||||
continue;
|
||||
}
|
||||
demoteLevel2To3(agent);
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/** 四级→三级、三级→二级自动升;二级→一级三期人工申请 */
|
||||
private void tryAutoPromote(SmHotelFxAgentEntity agent) {
|
||||
if (agent == null || agent.getLevelNo() == null) {
|
||||
return;
|
||||
}
|
||||
int lv = agent.getLevelNo();
|
||||
if (lv <= 2 || lv == 5) {
|
||||
return;
|
||||
}
|
||||
List<SmHotelFxLevelRuleDTO> rules = configService.parseLevelRules(configService.getOrCreateCurrent());
|
||||
int target = lv - 1;
|
||||
SmHotelFxLevelRuleDTO targetRule = rules.stream().filter(r -> Objects.equals(r.getLevel(), target)).findFirst()
|
||||
.orElse(null);
|
||||
if (targetRule == null || targetRule.getPromoteMin() == null) {
|
||||
return;
|
||||
}
|
||||
if (Boolean.FALSE.equals(targetRule.getCanPromote()) && target == 1) {
|
||||
return;
|
||||
}
|
||||
BigDecimal score = agent.getPromoteScore() == null ? BigDecimal.ZERO : agent.getPromoteScore();
|
||||
if (score.compareTo(targetRule.getPromoteMin()) < 0) {
|
||||
return;
|
||||
}
|
||||
agent.setLevelNo(target);
|
||||
if (target == 2) {
|
||||
initLevel2DemoteWindow(agent);
|
||||
}
|
||||
updateById(agent);
|
||||
log.info("|酒店分销|自动晋升|agentId={}|{}→{}|score={}|need={}|", agent.getId(), lv, target, score,
|
||||
targetRule.getPromoteMin());
|
||||
// 连续达标可连升(如一次锁定后从 4 直接够 2 的门槛)
|
||||
if (target > 2) {
|
||||
tryAutoPromote(agent);
|
||||
}
|
||||
}
|
||||
|
||||
private void demoteLevel2To3(SmHotelFxAgentEntity agent) {
|
||||
BigDecimal win = agent.getDemoteWindowScore() == null ? BigDecimal.ZERO : agent.getDemoteWindowScore();
|
||||
agent.setLevelNo(3);
|
||||
clearDemoteWindow(agent);
|
||||
// 佣金明细/历史锁定业绩保留;晋升分回落到「三级门槛」,避免立刻因终身分再升回二级
|
||||
SmHotelFxLevelRuleDTO rule3 = findLevelRule(3);
|
||||
BigDecimal floor = rule3 != null && rule3.getPromoteMin() != null ? rule3.getPromoteMin()
|
||||
: new BigDecimal("50000");
|
||||
agent.setPromoteScore(floor);
|
||||
String tip = "二级保级未达标降三级(窗口分=" + win.toPlainString() + ")";
|
||||
agent.setRemark(StrUtil.maxLength(StrUtil.blankToDefault(agent.getRemark(), "") + ";" + tip, 255));
|
||||
updateById(agent);
|
||||
log.info("|酒店分销|二级降级|agentId={}|windowScore={}|promoteScore→{}|", agent.getId(), win, floor);
|
||||
}
|
||||
|
||||
private void initLevel2DemoteWindow(SmHotelFxAgentEntity agent) {
|
||||
agent.setDemoteWindowStart(LocalDateTime.now());
|
||||
agent.setDemoteWindowScore(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
private void clearDemoteWindow(SmHotelFxAgentEntity agent) {
|
||||
agent.setDemoteWindowStart(null);
|
||||
agent.setDemoteWindowScore(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
/** 酒店区县 adcode 是否在分销员 regionJson 内;未配置区域则不计保级分 */
|
||||
private boolean isHotelInAgentRegion(SmHotelFxAgentEntity agent, Long hotelId) {
|
||||
Set<String> codes = parseRegionAdcodes(agent.getRegionJson());
|
||||
if (codes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
SmHotelEntity hotel = hotelMapper.selectById(hotelId);
|
||||
if (hotel == null || hotel.getDistrictAdcode() == null) {
|
||||
return false;
|
||||
}
|
||||
return codes.contains(String.valueOf(hotel.getDistrictAdcode()));
|
||||
}
|
||||
|
||||
/** 升二级之后新绑定/再审的酒店才计入保级窗口 */
|
||||
private boolean isNewHotelForDemoteWindow(SmHotelFxAgentEntity agent, Long hotelId) {
|
||||
if (agent.getDemoteWindowStart() == null) {
|
||||
return false;
|
||||
}
|
||||
SmHotelFxHotelBindEntity bind = bindMapper.selectOne(Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.eq(SmHotelFxHotelBindEntity::getHotelId, hotelId)
|
||||
.eq(SmHotelFxHotelBindEntity::getAgentId, agent.getId())
|
||||
.eq(SmHotelFxHotelBindEntity::getBindStatus, "BOUND")
|
||||
.last("limit 1"));
|
||||
if (bind == null || bind.getCreateTime() == null) {
|
||||
return false;
|
||||
}
|
||||
return !bind.getCreateTime().isBefore(agent.getDemoteWindowStart());
|
||||
}
|
||||
|
||||
private Set<String> parseRegionAdcodes(String regionJson) {
|
||||
Set<String> set = new HashSet<>();
|
||||
if (StrUtil.isBlank(regionJson)) {
|
||||
return set;
|
||||
}
|
||||
try {
|
||||
List<Object> arr = new ArrayList<>(JSONUtil.parseArray(regionJson));
|
||||
for (Object o : arr) {
|
||||
if (o == null) {
|
||||
continue;
|
||||
}
|
||||
String s = String.valueOf(o).trim();
|
||||
if (StrUtil.isNotBlank(s)) {
|
||||
set.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
// ignore bad json
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private SmHotelFxLevelRuleDTO findLevelRule(int level) {
|
||||
List<SmHotelFxLevelRuleDTO> rules = configService.parseLevelRules(configService.getOrCreateCurrent());
|
||||
return rules.stream().filter(r -> Objects.equals(r.getLevel(), level)).findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelFxLevelRuleDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxCommissionEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxConfigEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelOrderEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxCommissionMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelOrderMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxCommissionService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxCommissionServiceImpl extends ServiceImpl<SmHotelFxCommissionMapper, SmHotelFxCommissionEntity>
|
||||
implements SmHotelFxCommissionService {
|
||||
|
||||
private static final String ST_EST = "ESTIMATED";
|
||||
|
||||
private static final String ST_LOCK = "LOCKED";
|
||||
|
||||
private static final String ST_CANCEL = "CANCELLED";
|
||||
|
||||
private final SmHotelFxConfigService configService;
|
||||
|
||||
private final SmHotelFxAgentService agentService;
|
||||
|
||||
private final SmHotelMapper hotelMapper;
|
||||
|
||||
private final SmHotelOrderMapper orderMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createEstimatedForOrder(SmHotelOrderEntity order) {
|
||||
if (order == null || order.getId() == null || order.getHotelId() == null) {
|
||||
return;
|
||||
}
|
||||
SmHotelFxConfigEntity cfg = configService.getOrCreateCurrent();
|
||||
if (!configService.isEnabled(cfg)) {
|
||||
return;
|
||||
}
|
||||
long exists = count(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getOrderId, order.getId())
|
||||
.ne(SmHotelFxCommissionEntity::getStatus, ST_CANCEL));
|
||||
if (exists > 0) {
|
||||
return;
|
||||
}
|
||||
SmHotelFxAgentEntity owner = agentService.getBoundAgentByHotelId(order.getHotelId());
|
||||
if (owner == null) {
|
||||
return;
|
||||
}
|
||||
BigDecimal cost = order.getCostAmount();
|
||||
if (cost == null || cost.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.info("|酒店分销|跳过无成本订单|orderId={}|", order.getId());
|
||||
return;
|
||||
}
|
||||
BigDecimal poolCostRate = nvl(cfg.getPoolCostRate(), "0.06");
|
||||
BigDecimal poolSalesRate = nvl(cfg.getPoolSalesRate(), "0.30");
|
||||
BigDecimal promoteBaseRate = nvl(cfg.getPromoteBaseRate(), "0.06");
|
||||
BigDecimal pool = cost.multiply(poolCostRate).multiply(poolSalesRate).setScale(4, RoundingMode.HALF_UP);
|
||||
BigDecimal promoteBase = cost.multiply(promoteBaseRate).setScale(4, RoundingMode.HALF_UP);
|
||||
|
||||
List<SmHotelFxLevelRuleDTO> rules = configService.parseLevelRules(cfg);
|
||||
Map<Integer, SmHotelFxLevelRuleDTO> ruleByLevel = rules.stream()
|
||||
.filter(r -> r.getLevel() != null)
|
||||
.collect(Collectors.toMap(SmHotelFxLevelRuleDTO::getLevel, r -> r, (a, b) -> a));
|
||||
|
||||
// 开发员向上链路:每人只拿「自身当前层级」对应份额(缺层则该份额不发)
|
||||
List<SmHotelFxAgentEntity> chain = buildUplineChain(owner);
|
||||
Map<String, Object> snapshot = buildSnapshot(cfg, rules, cost, pool, promoteBase, owner.getId());
|
||||
|
||||
List<SmHotelFxCommissionEntity> rows = new ArrayList<>();
|
||||
for (SmHotelFxAgentEntity agent : chain) {
|
||||
int lv = agent.getLevelNo() == null ? 0 : agent.getLevelNo();
|
||||
SmHotelFxLevelRuleDTO rule = ruleByLevel.get(lv);
|
||||
if (rule == null || rule.getShareOfPool() == null
|
||||
|| rule.getShareOfPool().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal amount = pool.multiply(rule.getShareOfPool()).setScale(4, RoundingMode.HALF_UP);
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
SmHotelFxCommissionEntity row = new SmHotelFxCommissionEntity();
|
||||
row.setOrderId(order.getId());
|
||||
row.setHotelId(order.getHotelId());
|
||||
row.setAgentId(agent.getId());
|
||||
row.setOwnerAgentId(owner.getId());
|
||||
row.setChainLevel(lv);
|
||||
row.setAgentLevel(lv);
|
||||
row.setCostAmount(cost);
|
||||
row.setPoolAmount(pool);
|
||||
row.setShareRate(rule.getShareOfPool());
|
||||
row.setCommissionAmount(amount);
|
||||
row.setPromoteBaseAmount(promoteBase);
|
||||
row.setStatus(ST_EST);
|
||||
row.setCheckOutDate(order.getCheckOutDate());
|
||||
row.setConfigSnapshot(JSONUtil.toJsonStr(snapshot));
|
||||
row.setRemark("预计佣金");
|
||||
rows.add(row);
|
||||
}
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
saveBatch(rows);
|
||||
log.info("|酒店分销|已记预计佣金|orderId={}|hotelId={}|lines={}|pool={}|", order.getId(), order.getHotelId(),
|
||||
rows.size(), pool);
|
||||
}
|
||||
|
||||
/** 从开发员沿 parent 向上,同层级只保留最先遇到的人 */
|
||||
private List<SmHotelFxAgentEntity> buildUplineChain(SmHotelFxAgentEntity owner) {
|
||||
List<SmHotelFxAgentEntity> chain = new ArrayList<>();
|
||||
Map<Integer, Boolean> seenLevel = new HashMap<>();
|
||||
SmHotelFxAgentEntity cur = owner;
|
||||
int guard = 0;
|
||||
while (cur != null && guard++ < 16) {
|
||||
Integer lv = cur.getLevelNo();
|
||||
if (lv != null && !Boolean.TRUE.equals(seenLevel.get(lv))) {
|
||||
seenLevel.put(lv, true);
|
||||
chain.add(cur);
|
||||
}
|
||||
if (cur.getParentId() == null) {
|
||||
break;
|
||||
}
|
||||
cur = agentService.getById(cur.getParentId());
|
||||
if (cur == null || !"ACTIVE".equals(cur.getStatus())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildSnapshot(SmHotelFxConfigEntity cfg, List<SmHotelFxLevelRuleDTO> rules,
|
||||
BigDecimal cost, BigDecimal pool, BigDecimal promoteBase, Long ownerAgentId) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("poolCostRate", cfg.getPoolCostRate());
|
||||
m.put("poolSalesRate", cfg.getPoolSalesRate());
|
||||
m.put("promoteBaseRate", cfg.getPromoteBaseRate());
|
||||
m.put("levelRules", rules);
|
||||
m.put("costAmount", cost);
|
||||
m.put("poolAmount", pool);
|
||||
m.put("promoteBaseAmount", promoteBase);
|
||||
m.put("ownerAgentId", ownerAgentId);
|
||||
m.put("formula", "pool = cost * poolCostRate * poolSalesRate; commission = pool * shareOfPool");
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelForOrder(Long orderId) {
|
||||
if (orderId == null) {
|
||||
return;
|
||||
}
|
||||
List<SmHotelFxCommissionEntity> list = list(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getOrderId, orderId)
|
||||
.eq(SmHotelFxCommissionEntity::getStatus, ST_EST));
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (SmHotelFxCommissionEntity row : list) {
|
||||
row.setStatus(ST_CANCEL);
|
||||
row.setRemark("订单取消作废");
|
||||
row.setUpdateTime(now);
|
||||
}
|
||||
updateBatchById(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int lockDueCommissions() {
|
||||
// 退房日次日 00:00 起可锁:check_out_date < today
|
||||
LocalDate today = LocalDate.now();
|
||||
List<SmHotelFxCommissionEntity> due = list(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getStatus, ST_EST)
|
||||
.isNotNull(SmHotelFxCommissionEntity::getCheckOutDate)
|
||||
.lt(SmHotelFxCommissionEntity::getCheckOutDate, today)
|
||||
.last("limit 2000"));
|
||||
if (CollUtil.isEmpty(due)) {
|
||||
return 0;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// owner 晋升分按人合并;保级窗口按「每笔×酒店」逐条判断区域/新酒店
|
||||
Map<Long, BigDecimal> promoteAdd = new HashMap<>();
|
||||
List<SmHotelFxCommissionEntity> ownerRows = new ArrayList<>();
|
||||
for (SmHotelFxCommissionEntity row : due) {
|
||||
row.setStatus(ST_LOCK);
|
||||
row.setLockTime(now);
|
||||
row.setRemark("退房T+1锁定");
|
||||
if (Objects.equals(row.getAgentId(), row.getOwnerAgentId()) && row.getPromoteBaseAmount() != null) {
|
||||
promoteAdd.merge(row.getOwnerAgentId(), row.getPromoteBaseAmount(), BigDecimal::add);
|
||||
ownerRows.add(row);
|
||||
}
|
||||
}
|
||||
updateBatchById(due);
|
||||
for (SmHotelFxCommissionEntity row : ownerRows) {
|
||||
agentService.onDemoteWindowEligible(row.getOwnerAgentId(), row.getHotelId(), row.getPromoteBaseAmount());
|
||||
}
|
||||
for (Map.Entry<Long, BigDecimal> e : promoteAdd.entrySet()) {
|
||||
agentService.onPromoteBaseLocked(e.getKey(), e.getValue());
|
||||
}
|
||||
return due.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxCommissionEntity> pageAdmin(Page<SmHotelFxCommissionEntity> page,
|
||||
SmHotelFxCommissionEntity query) {
|
||||
Page<SmHotelFxCommissionEntity> result = page(page, Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(query.getAgentId() != null, SmHotelFxCommissionEntity::getAgentId, query.getAgentId())
|
||||
.eq(query.getHotelId() != null, SmHotelFxCommissionEntity::getHotelId, query.getHotelId())
|
||||
.eq(query.getOrderId() != null, SmHotelFxCommissionEntity::getOrderId, query.getOrderId())
|
||||
.eq(StrUtil.isNotBlank(query.getStatus()), SmHotelFxCommissionEntity::getStatus, query.getStatus())
|
||||
.orderByDesc(SmHotelFxCommissionEntity::getId));
|
||||
fillDisplay(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Map<String, Object>> mySummary() {
|
||||
SmHotelFxAgentEntity me = currentAgent();
|
||||
if (me == null) {
|
||||
return R.failed("当前账号不是分销员");
|
||||
}
|
||||
List<SmHotelFxCommissionEntity> all = list(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getAgentId, me.getId())
|
||||
.ne(SmHotelFxCommissionEntity::getStatus, ST_CANCEL));
|
||||
BigDecimal estimated = BigDecimal.ZERO;
|
||||
BigDecimal locked = BigDecimal.ZERO;
|
||||
BigDecimal withdrawn = BigDecimal.ZERO;
|
||||
BigDecimal withdrawable = BigDecimal.ZERO;
|
||||
for (SmHotelFxCommissionEntity c : all) {
|
||||
BigDecimal amt = c.getCommissionAmount() == null ? BigDecimal.ZERO : c.getCommissionAmount();
|
||||
if (ST_EST.equals(c.getStatus())) {
|
||||
estimated = estimated.add(amt);
|
||||
}
|
||||
else if (ST_LOCK.equals(c.getStatus())) {
|
||||
locked = locked.add(amt);
|
||||
if (c.getWithdrawId() == null) {
|
||||
withdrawable = withdrawable.add(amt);
|
||||
}
|
||||
}
|
||||
else if ("WITHDRAWN".equals(c.getStatus())) {
|
||||
withdrawn = withdrawn.add(amt);
|
||||
}
|
||||
}
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("agentId", me.getId());
|
||||
data.put("levelNo", me.getLevelNo());
|
||||
data.put("realName", me.getRealName());
|
||||
data.put("promoteScore", me.getPromoteScore());
|
||||
data.put("cityAdcode", me.getCityAdcode());
|
||||
data.put("demoteWindowScore", me.getDemoteWindowScore());
|
||||
data.put("demoteWindowStart", me.getDemoteWindowStart());
|
||||
data.put("regionJson", me.getRegionJson());
|
||||
data.put("estimatedAmount", estimated);
|
||||
data.put("lockedAmount", locked);
|
||||
data.put("withdrawableAmount", withdrawable);
|
||||
data.put("withdrawnAmount", withdrawn);
|
||||
data.put("totalAmount", estimated.add(locked));
|
||||
List<SmHotelFxLevelRuleDTO> rules = configService.parseLevelRules(configService.getOrCreateCurrent());
|
||||
if (Integer.valueOf(2).equals(me.getLevelNo())) {
|
||||
SmHotelFxLevelRuleDTO r2 = rules.stream().filter(r -> Objects.equals(r.getLevel(), 2)).findFirst()
|
||||
.orElse(null);
|
||||
data.put("demoteMin", r2 != null ? r2.getDemoteMin() : new BigDecimal("40000"));
|
||||
data.put("demoteWindowDays", r2 != null ? r2.getDemoteWindowDays() : 90);
|
||||
SmHotelFxLevelRuleDTO r1 = rules.stream().filter(r -> Objects.equals(r.getLevel(), 1)).findFirst()
|
||||
.orElse(null);
|
||||
BigDecimal l1Need = r1 != null && r1.getPromoteMin() != null ? r1.getPromoteMin() : new BigDecimal("500000");
|
||||
data.put("l1PromoteMin", l1Need);
|
||||
BigDecimal score = me.getPromoteScore() == null ? BigDecimal.ZERO : me.getPromoteScore();
|
||||
data.put("canApplyL1", score.compareTo(l1Need) >= 0);
|
||||
}
|
||||
else {
|
||||
data.put("canApplyL1", false);
|
||||
}
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxCommissionEntity> pageMine(Page<SmHotelFxCommissionEntity> page, String status) {
|
||||
SmHotelFxAgentEntity me = currentAgent();
|
||||
if (me == null) {
|
||||
return page;
|
||||
}
|
||||
Page<SmHotelFxCommissionEntity> result = page(page, Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getAgentId, me.getId())
|
||||
.eq(StrUtil.isNotBlank(status), SmHotelFxCommissionEntity::getStatus, status)
|
||||
.ne(SmHotelFxCommissionEntity::getStatus, ST_CANCEL)
|
||||
.orderByDesc(SmHotelFxCommissionEntity::getId));
|
||||
fillDisplay(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
private SmHotelFxAgentEntity currentAgent() {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null || user.getId() == null) {
|
||||
return null;
|
||||
}
|
||||
return agentService.getByUserId(user.getId());
|
||||
}
|
||||
|
||||
private void fillDisplay(List<SmHotelFxCommissionEntity> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (SmHotelFxCommissionEntity c : list) {
|
||||
if (c.getHotelId() != null) {
|
||||
SmHotelEntity h = hotelMapper.selectById(c.getHotelId());
|
||||
if (h != null) {
|
||||
c.setHotelName(h.getHotelName());
|
||||
}
|
||||
}
|
||||
if (c.getAgentId() != null) {
|
||||
SmHotelFxAgentEntity a = agentService.getById(c.getAgentId());
|
||||
if (a != null) {
|
||||
c.setAgentName(StrUtil.blankToDefault(a.getRealName(), a.getUsername()));
|
||||
}
|
||||
}
|
||||
if (c.getOrderId() != null) {
|
||||
SmHotelOrderEntity o = orderMapper.selectById(c.getOrderId());
|
||||
if (o != null) {
|
||||
c.setChannelNo(o.getChannelNo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BigDecimal nvl(BigDecimal v, String def) {
|
||||
return v == null ? new BigDecimal(def) : v;
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelFxLevelRuleDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxConfigEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxConfigMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SmHotelFxConfigServiceImpl extends ServiceImpl<SmHotelFxConfigMapper, SmHotelFxConfigEntity>
|
||||
implements SmHotelFxConfigService {
|
||||
|
||||
private static final String DEFAULT_RULES = "["
|
||||
+ "{\"level\":1,\"name\":\"城市合伙人\",\"shareOfPool\":0.40,\"canPromote\":false,\"promoteMin\":500000,\"demoteMin\":null,\"demoteWindowDays\":null},"
|
||||
+ "{\"level\":2,\"name\":\"区域负责人\",\"shareOfPool\":0.30,\"canPromote\":true,\"promoteMin\":200000,\"demoteMin\":40000,\"demoteWindowDays\":90},"
|
||||
+ "{\"level\":3,\"name\":\"团队骨干\",\"shareOfPool\":0.20,\"canPromote\":true,\"promoteMin\":50000,\"demoteMin\":null,\"demoteWindowDays\":null},"
|
||||
+ "{\"level\":4,\"name\":\"开发业务员\",\"shareOfPool\":0.10,\"canPromote\":true,\"promoteMin\":null,\"demoteMin\":null,\"demoteWindowDays\":null},"
|
||||
+ "{\"level\":5,\"name\":\"被邀请人\",\"shareOfPool\":0.00,\"canPromote\":false,\"promoteMin\":null,\"demoteMin\":null,\"demoteWindowDays\":null}"
|
||||
+ "]";
|
||||
|
||||
@Override
|
||||
public SmHotelFxConfigEntity getOrCreateCurrent() {
|
||||
SmHotelFxConfigEntity one = getOne(Wrappers.<SmHotelFxConfigEntity>lambdaQuery().last("limit 1"), false);
|
||||
if (one != null) {
|
||||
return one;
|
||||
}
|
||||
SmHotelFxConfigEntity created = new SmHotelFxConfigEntity();
|
||||
created.setEnabled("1");
|
||||
created.setPoolCostRate(new BigDecimal("0.06"));
|
||||
created.setPoolSalesRate(new BigDecimal("0.30"));
|
||||
created.setPromoteBaseRate(new BigDecimal("0.06"));
|
||||
created.setLevelRulesJson(DEFAULT_RULES);
|
||||
created.setRemark("系统自动初始化");
|
||||
save(created);
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<SmHotelFxConfigEntity> saveCurrent(SmHotelFxConfigEntity body) {
|
||||
if (body == null) {
|
||||
return R.failed("参数为空");
|
||||
}
|
||||
SmHotelFxConfigEntity cur = getOrCreateCurrent();
|
||||
if (body.getEnabled() != null) {
|
||||
cur.setEnabled(body.getEnabled());
|
||||
}
|
||||
if (body.getPoolCostRate() != null) {
|
||||
cur.setPoolCostRate(body.getPoolCostRate());
|
||||
}
|
||||
if (body.getPoolSalesRate() != null) {
|
||||
cur.setPoolSalesRate(body.getPoolSalesRate());
|
||||
}
|
||||
if (body.getPromoteBaseRate() != null) {
|
||||
cur.setPromoteBaseRate(body.getPromoteBaseRate());
|
||||
}
|
||||
if (body.getLevelRulesJson() != null) {
|
||||
List<SmHotelFxLevelRuleDTO> rules = parseLevelRules(body);
|
||||
if (rules.isEmpty()) {
|
||||
return R.failed("层级规则 JSON 无效");
|
||||
}
|
||||
cur.setLevelRulesJson(JSONUtil.toJsonStr(rules));
|
||||
}
|
||||
if (body.getRemark() != null) {
|
||||
cur.setRemark(body.getRemark());
|
||||
}
|
||||
updateById(cur);
|
||||
return R.ok(cur);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmHotelFxLevelRuleDTO> parseLevelRules(SmHotelFxConfigEntity cfg) {
|
||||
String json = cfg == null ? null : cfg.getLevelRulesJson();
|
||||
if (StrUtil.isBlank(json)) {
|
||||
json = DEFAULT_RULES;
|
||||
}
|
||||
try {
|
||||
List<SmHotelFxLevelRuleDTO> list = JSONUtil.toList(json, SmHotelFxLevelRuleDTO.class);
|
||||
if (list == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
list.sort(Comparator.comparing(r -> r.getLevel() == null ? 99 : r.getLevel()));
|
||||
return list;
|
||||
}
|
||||
catch (Exception e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(SmHotelFxConfigEntity cfg) {
|
||||
return cfg != null && "1".equals(cfg.getEnabled());
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxCommissionEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxExitEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxHotelBindEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxCommissionMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxExitMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxHotelBindMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxExitService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxExitServiceImpl extends ServiceImpl<SmHotelFxExitMapper, SmHotelFxExitEntity>
|
||||
implements SmHotelFxExitService {
|
||||
|
||||
private final SmHotelFxAgentService agentService;
|
||||
|
||||
private final SmHotelFxHotelBindMapper bindMapper;
|
||||
|
||||
private final SmHotelFxCommissionMapper commissionMapper;
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxExitEntity> pageAdmin(Page<SmHotelFxExitEntity> page, SmHotelFxExitEntity query) {
|
||||
Page<SmHotelFxExitEntity> result = page(page, Wrappers.<SmHotelFxExitEntity>lambdaQuery()
|
||||
.eq(query.getAgentId() != null, SmHotelFxExitEntity::getAgentId, query.getAgentId())
|
||||
.eq(StrUtil.isNotBlank(query.getStatus()), SmHotelFxExitEntity::getStatus, query.getStatus())
|
||||
.orderByDesc(SmHotelFxExitEntity::getId));
|
||||
fillNames(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<SmHotelFxExitEntity> applyMine(String reason) {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null || user.getId() == null) {
|
||||
return R.failed("未登录");
|
||||
}
|
||||
SmHotelFxAgentEntity me = agentService.getByUserId(user.getId());
|
||||
if (me == null || !"ACTIVE".equals(me.getStatus())) {
|
||||
return R.failed("当前不是在职分销员");
|
||||
}
|
||||
long pending = count(Wrappers.<SmHotelFxExitEntity>lambdaQuery()
|
||||
.eq(SmHotelFxExitEntity::getAgentId, me.getId())
|
||||
.eq(SmHotelFxExitEntity::getStatus, "PENDING"));
|
||||
if (pending > 0) {
|
||||
return R.failed("已有待审退出申请");
|
||||
}
|
||||
SmHotelFxExitEntity row = new SmHotelFxExitEntity();
|
||||
row.setAgentId(me.getId());
|
||||
row.setUserId(me.getUserId());
|
||||
row.setReason(StrUtil.blankToDefault(reason, "主动申请退出"));
|
||||
row.setStatus("PENDING");
|
||||
save(row);
|
||||
return R.ok(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> audit(Long id, boolean approved, Long successorAgentId, String auditRemark) {
|
||||
SmHotelFxExitEntity row = getById(id);
|
||||
if (row == null) {
|
||||
return R.failed("退出单不存在");
|
||||
}
|
||||
if (!"PENDING".equals(row.getStatus())) {
|
||||
return R.failed("已处理");
|
||||
}
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
row.setAuditBy(user != null ? user.getUsername() : null);
|
||||
row.setAuditTime(LocalDateTime.now());
|
||||
row.setAuditRemark(auditRemark);
|
||||
if (!approved) {
|
||||
row.setStatus("REJECTED");
|
||||
updateById(row);
|
||||
return R.ok(Boolean.TRUE, "已驳回退出申请");
|
||||
}
|
||||
if (successorAgentId == null) {
|
||||
return R.failed("通过退出须指定酒店接收分销员");
|
||||
}
|
||||
if (successorAgentId.equals(row.getAgentId())) {
|
||||
return R.failed("接收人不能是本人");
|
||||
}
|
||||
SmHotelFxAgentEntity successor = agentService.getById(successorAgentId);
|
||||
if (successor == null || !"ACTIVE".equals(successor.getStatus())) {
|
||||
return R.failed("接收分销员不可用");
|
||||
}
|
||||
SmHotelFxAgentEntity agent = agentService.getById(row.getAgentId());
|
||||
if (agent == null) {
|
||||
return R.failed("退出分销员不存在");
|
||||
}
|
||||
|
||||
// 1) 酒店绑定重分配
|
||||
List<SmHotelFxHotelBindEntity> binds = bindMapper.selectList(Wrappers.<SmHotelFxHotelBindEntity>lambdaQuery()
|
||||
.eq(SmHotelFxHotelBindEntity::getAgentId, agent.getId())
|
||||
.eq(SmHotelFxHotelBindEntity::getBindStatus, "BOUND"));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (SmHotelFxHotelBindEntity b : binds) {
|
||||
b.setAgentId(successorAgentId);
|
||||
b.setCreateTime(now);
|
||||
b.setAuditRemark(StrUtil.maxLength("退出重分配自 agent=" + agent.getId(), 255));
|
||||
bindMapper.updateById(b);
|
||||
}
|
||||
|
||||
// 2) 下级挂靠到原上级(或接收人上级链之外:挂到退出者的 parent)
|
||||
Long newParent = agent.getParentId();
|
||||
List<SmHotelFxAgentEntity> children = agentService.list(Wrappers.<SmHotelFxAgentEntity>lambdaQuery()
|
||||
.eq(SmHotelFxAgentEntity::getParentId, agent.getId())
|
||||
.eq(SmHotelFxAgentEntity::getStatus, "ACTIVE"));
|
||||
for (SmHotelFxAgentEntity child : children) {
|
||||
child.setParentId(newParent);
|
||||
agentService.updateById(child);
|
||||
}
|
||||
|
||||
// 3) 作废本人预计佣金(已锁定可继续提现)
|
||||
List<SmHotelFxCommissionEntity> est = commissionMapper.selectList(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getAgentId, agent.getId())
|
||||
.eq(SmHotelFxCommissionEntity::getStatus, "ESTIMATED"));
|
||||
for (SmHotelFxCommissionEntity c : est) {
|
||||
c.setStatus("CANCELLED");
|
||||
c.setRemark("分销员退出作废预计佣金");
|
||||
commissionMapper.updateById(c);
|
||||
}
|
||||
|
||||
agent.setStatus("EXIT");
|
||||
agent.setRemark(StrUtil.maxLength(StrUtil.blankToDefault(agent.getRemark(), "") + ";已退出", 255));
|
||||
agentService.updateById(agent);
|
||||
|
||||
row.setSuccessorAgentId(successorAgentId);
|
||||
row.setHotelCount(binds.size());
|
||||
row.setStatus("DONE");
|
||||
updateById(row);
|
||||
log.info("|酒店分销|退出完成|agentId={}|successor={}|hotels={}|children={}|", agent.getId(), successorAgentId,
|
||||
binds.size(), children.size());
|
||||
return R.ok(Boolean.TRUE, "已退出并重分配 " + binds.size() + " 家酒店");
|
||||
}
|
||||
|
||||
private void fillNames(List<SmHotelFxExitEntity> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (SmHotelFxExitEntity row : list) {
|
||||
SmHotelFxAgentEntity a = agentService.getById(row.getAgentId());
|
||||
if (a != null) {
|
||||
row.setAgentName(StrUtil.blankToDefault(a.getRealName(), a.getUsername()));
|
||||
}
|
||||
if (row.getSuccessorAgentId() != null) {
|
||||
SmHotelFxAgentEntity s = agentService.getById(row.getSuccessorAgentId());
|
||||
if (s != null) {
|
||||
row.setSuccessorName(StrUtil.blankToDefault(s.getRealName(), s.getUsername()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelFxLevelRuleDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxL1ApplyEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxL1ApplyMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxConfigService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxL1ApplyService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxL1ApplyServiceImpl extends ServiceImpl<SmHotelFxL1ApplyMapper, SmHotelFxL1ApplyEntity>
|
||||
implements SmHotelFxL1ApplyService {
|
||||
|
||||
private final SmHotelFxAgentService agentService;
|
||||
|
||||
private final SmHotelFxConfigService configService;
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxL1ApplyEntity> pageAdmin(Page<SmHotelFxL1ApplyEntity> page, SmHotelFxL1ApplyEntity query) {
|
||||
Page<SmHotelFxL1ApplyEntity> result = page(page, Wrappers.<SmHotelFxL1ApplyEntity>lambdaQuery()
|
||||
.eq(query.getAgentId() != null, SmHotelFxL1ApplyEntity::getAgentId, query.getAgentId())
|
||||
.eq(StrUtil.isNotBlank(query.getStatus()), SmHotelFxL1ApplyEntity::getStatus, query.getStatus())
|
||||
.eq(StrUtil.isNotBlank(query.getCityAdcode()), SmHotelFxL1ApplyEntity::getCityAdcode, query.getCityAdcode())
|
||||
.orderByDesc(SmHotelFxL1ApplyEntity::getId));
|
||||
fillAgent(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<SmHotelFxL1ApplyEntity> applyMine(String cityAdcode, String cityName, String remark) {
|
||||
SmHotelFxAgentEntity me = requireActiveAgent();
|
||||
if (me == null) {
|
||||
return R.failed("当前账号不是在职分销员");
|
||||
}
|
||||
if (!Integer.valueOf(2).equals(me.getLevelNo())) {
|
||||
return R.failed("仅二级可申请城市合伙人");
|
||||
}
|
||||
if (StrUtil.isBlank(cityAdcode)) {
|
||||
return R.failed("请填写申请城市 adcode");
|
||||
}
|
||||
BigDecimal need = level1PromoteMin();
|
||||
BigDecimal score = me.getPromoteScore() == null ? BigDecimal.ZERO : me.getPromoteScore();
|
||||
if (score.compareTo(need) < 0) {
|
||||
return R.failed("晋升累计未达门槛(需≥" + need.toPlainString() + ")");
|
||||
}
|
||||
long pending = count(Wrappers.<SmHotelFxL1ApplyEntity>lambdaQuery()
|
||||
.eq(SmHotelFxL1ApplyEntity::getAgentId, me.getId())
|
||||
.eq(SmHotelFxL1ApplyEntity::getStatus, "PENDING"));
|
||||
if (pending > 0) {
|
||||
return R.failed("已有待审核申请,请勿重复提交");
|
||||
}
|
||||
SmHotelFxL1ApplyEntity row = new SmHotelFxL1ApplyEntity();
|
||||
row.setAgentId(me.getId());
|
||||
row.setUserId(me.getUserId());
|
||||
row.setCityAdcode(cityAdcode.trim());
|
||||
row.setCityName(cityName);
|
||||
row.setPromoteScore(score);
|
||||
row.setStatus("PENDING");
|
||||
row.setApplyRemark(remark);
|
||||
save(row);
|
||||
return R.ok(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> audit(Long id, boolean approved, String auditRemark) {
|
||||
SmHotelFxL1ApplyEntity row = getById(id);
|
||||
if (row == null) {
|
||||
return R.failed("申请不存在");
|
||||
}
|
||||
if (!"PENDING".equals(row.getStatus())) {
|
||||
return R.failed("申请已处理");
|
||||
}
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
row.setAuditBy(user != null ? user.getUsername() : null);
|
||||
row.setAuditTime(LocalDateTime.now());
|
||||
row.setAuditRemark(auditRemark);
|
||||
if (!approved) {
|
||||
row.setStatus("REJECTED");
|
||||
updateById(row);
|
||||
return R.ok(Boolean.TRUE, "已驳回");
|
||||
}
|
||||
SmHotelFxAgentEntity agent = agentService.getById(row.getAgentId());
|
||||
if (agent == null || !"ACTIVE".equals(agent.getStatus())) {
|
||||
return R.failed("申请人状态不可用");
|
||||
}
|
||||
if (!Integer.valueOf(2).equals(agent.getLevelNo())) {
|
||||
return R.failed("申请人当前已非二级,无法任命");
|
||||
}
|
||||
// 一城一人:原一级降为二级并开保级窗口
|
||||
List<SmHotelFxAgentEntity> cityL1 = agentService.list(Wrappers.<SmHotelFxAgentEntity>lambdaQuery()
|
||||
.eq(SmHotelFxAgentEntity::getLevelNo, 1)
|
||||
.eq(SmHotelFxAgentEntity::getStatus, "ACTIVE")
|
||||
.eq(SmHotelFxAgentEntity::getCityAdcode, row.getCityAdcode())
|
||||
.ne(SmHotelFxAgentEntity::getId, agent.getId()));
|
||||
for (SmHotelFxAgentEntity old : cityL1) {
|
||||
old.setLevelNo(2);
|
||||
old.setDemoteWindowStart(LocalDateTime.now());
|
||||
old.setDemoteWindowScore(BigDecimal.ZERO);
|
||||
old.setRemark(StrUtil.maxLength(
|
||||
StrUtil.blankToDefault(old.getRemark(), "") + ";被新城市合伙人替换降二级", 255));
|
||||
agentService.updateById(old);
|
||||
log.info("|酒店分销|一级替换降二级|oldAgentId={}|city={}|", old.getId(), row.getCityAdcode());
|
||||
}
|
||||
agent.setLevelNo(1);
|
||||
agent.setCityAdcode(row.getCityAdcode());
|
||||
agent.setDemoteWindowStart(null);
|
||||
agent.setDemoteWindowScore(BigDecimal.ZERO);
|
||||
agentService.updateById(agent);
|
||||
row.setStatus("APPROVED");
|
||||
updateById(row);
|
||||
log.info("|酒店分销|任命一级|agentId={}|city={}|", agent.getId(), row.getCityAdcode());
|
||||
return R.ok(Boolean.TRUE, "已任命为城市合伙人");
|
||||
}
|
||||
|
||||
private BigDecimal level1PromoteMin() {
|
||||
List<SmHotelFxLevelRuleDTO> rules = configService.parseLevelRules(configService.getOrCreateCurrent());
|
||||
SmHotelFxLevelRuleDTO r1 = rules.stream().filter(r -> Objects.equals(r.getLevel(), 1)).findFirst().orElse(null);
|
||||
return r1 != null && r1.getPromoteMin() != null ? r1.getPromoteMin() : new BigDecimal("500000");
|
||||
}
|
||||
|
||||
private SmHotelFxAgentEntity requireActiveAgent() {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null || user.getId() == null) {
|
||||
return null;
|
||||
}
|
||||
return agentService.getByUserId(user.getId());
|
||||
}
|
||||
|
||||
private void fillAgent(List<SmHotelFxL1ApplyEntity> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (SmHotelFxL1ApplyEntity row : list) {
|
||||
SmHotelFxAgentEntity a = agentService.getById(row.getAgentId());
|
||||
if (a != null) {
|
||||
row.setAgentName(StrUtil.blankToDefault(a.getRealName(), a.getUsername()));
|
||||
row.setAgentLevel(a.getLevelNo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxAgentEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxCommissionEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelFxWithdrawEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxCommissionMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelFxWithdrawMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxAgentService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelFxWithdrawService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelFxWithdrawServiceImpl extends ServiceImpl<SmHotelFxWithdrawMapper, SmHotelFxWithdrawEntity>
|
||||
implements SmHotelFxWithdrawService {
|
||||
|
||||
private static final String ST_LOCK = "LOCKED";
|
||||
|
||||
private static final String ST_WITHDRAWN = "WITHDRAWN";
|
||||
|
||||
private final SmHotelFxAgentService agentService;
|
||||
|
||||
private final SmHotelFxCommissionMapper commissionMapper;
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxWithdrawEntity> pageAdmin(Page<SmHotelFxWithdrawEntity> page, SmHotelFxWithdrawEntity query) {
|
||||
Page<SmHotelFxWithdrawEntity> result = page(page, Wrappers.<SmHotelFxWithdrawEntity>lambdaQuery()
|
||||
.eq(query.getAgentId() != null, SmHotelFxWithdrawEntity::getAgentId, query.getAgentId())
|
||||
.eq(StrUtil.isNotBlank(query.getStatus()), SmHotelFxWithdrawEntity::getStatus, query.getStatus())
|
||||
.orderByDesc(SmHotelFxWithdrawEntity::getId));
|
||||
fillAgent(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SmHotelFxWithdrawEntity> pageMine(Page<SmHotelFxWithdrawEntity> page, String status) {
|
||||
SmHotelFxAgentEntity me = currentAgent();
|
||||
if (me == null) {
|
||||
return page;
|
||||
}
|
||||
return page(page, Wrappers.<SmHotelFxWithdrawEntity>lambdaQuery()
|
||||
.eq(SmHotelFxWithdrawEntity::getAgentId, me.getId())
|
||||
.eq(StrUtil.isNotBlank(status), SmHotelFxWithdrawEntity::getStatus, status)
|
||||
.orderByDesc(SmHotelFxWithdrawEntity::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Map<String, Object>> withdrawableMine() {
|
||||
SmHotelFxAgentEntity me = currentAgent();
|
||||
if (me == null) {
|
||||
return R.failed("当前账号不是分销员");
|
||||
}
|
||||
List<SmHotelFxCommissionEntity> list = listWithdrawable(me.getId());
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
for (SmHotelFxCommissionEntity c : list) {
|
||||
amount = amount.add(c.getCommissionAmount() == null ? BigDecimal.ZERO : c.getCommissionAmount());
|
||||
}
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("agentId", me.getId());
|
||||
data.put("withdrawableAmount", amount);
|
||||
data.put("commissionCount", list.size());
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<SmHotelFxWithdrawEntity> applyMine(String accountName, String accountNo, String bankName, String remark) {
|
||||
SmHotelFxAgentEntity me = currentAgent();
|
||||
if (me == null || !"ACTIVE".equals(me.getStatus())) {
|
||||
return R.failed("当前账号不是在职分销员");
|
||||
}
|
||||
if (StrUtil.isBlank(accountName) || StrUtil.isBlank(accountNo)) {
|
||||
return R.failed("请填写收款户名与账号");
|
||||
}
|
||||
List<SmHotelFxCommissionEntity> list = listWithdrawable(me.getId());
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return R.failed("暂无可提现锁定佣金");
|
||||
}
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
for (SmHotelFxCommissionEntity c : list) {
|
||||
amount = amount.add(c.getCommissionAmount() == null ? BigDecimal.ZERO : c.getCommissionAmount());
|
||||
}
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return R.failed("可提现金额为 0");
|
||||
}
|
||||
SmHotelFxWithdrawEntity wd = new SmHotelFxWithdrawEntity();
|
||||
wd.setAgentId(me.getId());
|
||||
wd.setUserId(me.getUserId());
|
||||
wd.setAmount(amount);
|
||||
wd.setCommissionCount(list.size());
|
||||
wd.setAccountName(accountName.trim());
|
||||
wd.setAccountNo(accountNo.trim());
|
||||
wd.setBankName(bankName);
|
||||
wd.setStatus("PENDING");
|
||||
wd.setApplyRemark(remark);
|
||||
save(wd);
|
||||
for (SmHotelFxCommissionEntity c : list) {
|
||||
c.setWithdrawId(wd.getId());
|
||||
commissionMapper.updateById(c);
|
||||
}
|
||||
log.info("|酒店分销|提现申请|agentId={}|amount={}|lines={}|wdId={}|", me.getId(), amount, list.size(),
|
||||
wd.getId());
|
||||
return R.ok(wd);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> audit(Long id, boolean approved, String auditRemark) {
|
||||
SmHotelFxWithdrawEntity wd = getById(id);
|
||||
if (wd == null) {
|
||||
return R.failed("提现单不存在");
|
||||
}
|
||||
if (!"PENDING".equals(wd.getStatus())) {
|
||||
return R.failed("仅待审提现可审核");
|
||||
}
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
wd.setAuditBy(user != null ? user.getUsername() : null);
|
||||
wd.setAuditTime(LocalDateTime.now());
|
||||
wd.setAuditRemark(auditRemark);
|
||||
if (!approved) {
|
||||
wd.setStatus("REJECTED");
|
||||
updateById(wd);
|
||||
releaseCommissions(wd.getId());
|
||||
return R.ok(Boolean.TRUE, "已驳回,佣金已释放可再提");
|
||||
}
|
||||
wd.setStatus("APPROVED");
|
||||
updateById(wd);
|
||||
return R.ok(Boolean.TRUE, "已通过,待打款");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> markPaid(Long id, String remark) {
|
||||
SmHotelFxWithdrawEntity wd = getById(id);
|
||||
if (wd == null) {
|
||||
return R.failed("提现单不存在");
|
||||
}
|
||||
if (!"APPROVED".equals(wd.getStatus()) && !"PENDING".equals(wd.getStatus())) {
|
||||
return R.failed("仅待审/已审通过的提现可标记打款");
|
||||
}
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if ("PENDING".equals(wd.getStatus())) {
|
||||
wd.setAuditBy(user != null ? user.getUsername() : null);
|
||||
wd.setAuditTime(LocalDateTime.now());
|
||||
}
|
||||
wd.setStatus("PAID");
|
||||
wd.setPayTime(LocalDateTime.now());
|
||||
if (StrUtil.isNotBlank(remark)) {
|
||||
wd.setAuditRemark(StrUtil.blankToDefault(wd.getAuditRemark(), "") + ";" + remark);
|
||||
}
|
||||
updateById(wd);
|
||||
List<SmHotelFxCommissionEntity> list = commissionMapper.selectList(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getWithdrawId, id)
|
||||
.eq(SmHotelFxCommissionEntity::getStatus, ST_LOCK));
|
||||
for (SmHotelFxCommissionEntity c : list) {
|
||||
c.setStatus(ST_WITHDRAWN);
|
||||
c.setRemark("已打款提现");
|
||||
commissionMapper.updateById(c);
|
||||
}
|
||||
return R.ok(Boolean.TRUE, "已标记打款");
|
||||
}
|
||||
|
||||
private List<SmHotelFxCommissionEntity> listWithdrawable(Long agentId) {
|
||||
return commissionMapper.selectList(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getAgentId, agentId)
|
||||
.eq(SmHotelFxCommissionEntity::getStatus, ST_LOCK)
|
||||
.isNull(SmHotelFxCommissionEntity::getWithdrawId));
|
||||
}
|
||||
|
||||
private void releaseCommissions(Long withdrawId) {
|
||||
List<SmHotelFxCommissionEntity> list = commissionMapper.selectList(Wrappers.<SmHotelFxCommissionEntity>lambdaQuery()
|
||||
.eq(SmHotelFxCommissionEntity::getWithdrawId, withdrawId));
|
||||
for (SmHotelFxCommissionEntity c : list) {
|
||||
c.setWithdrawId(null);
|
||||
commissionMapper.updateById(c);
|
||||
}
|
||||
}
|
||||
|
||||
private SmHotelFxAgentEntity currentAgent() {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null || user.getId() == null) {
|
||||
return null;
|
||||
}
|
||||
return agentService.getByUserId(user.getId());
|
||||
}
|
||||
|
||||
private void fillAgent(List<SmHotelFxWithdrawEntity> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (SmHotelFxWithdrawEntity row : list) {
|
||||
SmHotelFxAgentEntity a = agentService.getById(row.getAgentId());
|
||||
if (a != null) {
|
||||
row.setAgentName(StrUtil.blankToDefault(a.getRealName(), a.getUsername()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,19 @@ spring:
|
||||
# price-roll-day-offset: 90
|
||||
# # 滚价按本地酒店 id 游标每批数量(防 OOM)
|
||||
# price-roll-batch-size: 100
|
||||
# # 全量房价房量:每天最多次数 / 最小间隔小时(「拉取全部启用详情」受此限制)
|
||||
# # 全量房价房量:每天最多次数 / 最小间隔小时(全量详情 + 渠道范围详情共用)
|
||||
# full-price-sync-max-per-day: 3
|
||||
# full-price-sync-min-interval-hours: 6
|
||||
# # 上游渠道「全量详情」定时(默认关;Cron 改后需重启;开关可库表热更)
|
||||
# detail-pull-daily-enabled: false
|
||||
# detail-pull-daily-cron: "0 0 4 * * ?"
|
||||
# # 全部下游渠道范围合集「全量详情」定时(只拉范围内上游酒店,跳过本地;默认关)
|
||||
# scope-detail-pull-daily-enabled: false
|
||||
# scope-detail-pull-daily-cron: "0 0 5 * * ?"
|
||||
# # 近窗详情:刷渠道范围合集未来 N 天(默认 15;可改 7);不占全量 6h/每日次数;默认开、每 2 小时
|
||||
# near-detail-pull-enabled: true
|
||||
# near-detail-pull-days: 15
|
||||
# near-detail-pull-cron: "0 0 */2 * * ?"
|
||||
# # 全量拉详情:每家间隔毫秒(并行时主要靠 price-api-qps,默认 0)
|
||||
# detail-pull-interval-ms: 0
|
||||
# # 全量拉详情酒店并发(吃满上游约 20 QPS;建议 4~8;多实例请调低)
|
||||
|
||||
Reference in new issue
Block a user