- 数据库:新增team_score_pool自动发放字段,新建score_grant_record表 - 后端:新增ScoreAutoGrantService定时任务服务 - 后端:新增ScoreGrantRecord实体和Mapper - 后端:DepartmentController新增发放记录和手动发放接口 - 后端:TeamScorePoolService新增自动发放配置更新方法 - 前端:部门编辑弹窗新增积分自动发放配置 - 前端:积分池弹窗新增发放记录展示和手动发放按钮 - 前端:department.ts新增相关API - 前端:config.ts TeamScorePool类型新增自动发放字段
12 KiB
12 KiB
需求部门积分池增强方案
一、功能概述
为需求部门(包括普通需求部门和一级需求部门)的积分池增加以下功能:
- 定时自动发放积分 - 支持按周/月自动发放固定积分值
- 过期未使用清零 - 周期结束时未使用的积分自动清零
- 积分发放记录 - 记录每次自动发放的历史
二、数据库设计
2.1 修改 team_score_pool 表
-- 新增字段
ALTER TABLE team_score_pool ADD COLUMN auto_grant_enabled TINYINT DEFAULT 0 COMMENT '是否启用自动发放:0-否,1-是';
ALTER TABLE team_score_pool ADD COLUMN auto_grant_period VARCHAR(20) DEFAULT 'WEEKLY' COMMENT '自动发放周期:WEEKLY-每周,MONTHLY-每月';
ALTER TABLE team_score_pool ADD COLUMN auto_grant_amount INT DEFAULT 0 COMMENT '每次自动发放积分数量';
ALTER TABLE team_score_pool ADD COLUMN auto_grant_day INT DEFAULT 1 COMMENT '发放日期:周发放为1-7(周一到周日),月发放为1-28';
ALTER TABLE team_score_pool ADD COLUMN expire_unused TINYINT DEFAULT 0 COMMENT '过期未使用是否清零:0-否,1-是';
ALTER TABLE team_score_pool ADD COLUMN last_grant_time DATETIME COMMENT '上次自动发放时间';
ALTER TABLE team_score_pool ADD COLUMN next_grant_time DATETIME COMMENT '下次自动发放时间';
2.2 新增积分发放记录表
CREATE TABLE IF NOT EXISTS score_grant_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
dept_id BIGINT NOT NULL COMMENT '部门ID',
grant_type VARCHAR(20) NOT NULL COMMENT '发放类型:AUTO-自动发放,MANUAL-手动充值',
grant_amount INT NOT NULL COMMENT '发放积分数量',
expired_amount INT DEFAULT 0 COMMENT '过期清零积分数量',
balance_before INT COMMENT '发放前余额',
balance_after INT COMMENT '发放后余额',
grant_period VARCHAR(20) COMMENT '发放周期:WEEKLY/MONTHLY',
period_start DATE COMMENT '周期开始日期',
period_end DATE COMMENT '周期结束日期',
remark VARCHAR(500) COMMENT '备注',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_dept_id (dept_id),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分发放记录表';
三、后端开发
3.1 实体类修改
TeamScorePool.java 新增字段:
private Integer autoGrantEnabled; // 是否启用自动发放
private String autoGrantPeriod; // 自动发放周期:WEEKLY/MONTHLY
private Integer autoGrantAmount; // 每次自动发放积分数量
private Integer autoGrantDay; // 发放日期
private Integer expireUnused; // 过期未使用是否清零
private LocalDateTime lastGrantTime; // 上次自动发放时间
private LocalDateTime nextGrantTime; // 下次自动发放时间
3.2 新增实体类
ScoreGrantRecord.java
@Data
@TableName("score_grant_record")
public class ScoreGrantRecord {
@TableId(type = IdType.AUTO)
private Long id;
private Long deptId;
private String grantType; // AUTO/MANUAL
private Integer grantAmount;
private Integer expiredAmount;
private Integer balanceBefore;
private Integer balanceAfter;
private String grantPeriod;
private LocalDate periodStart;
private LocalDate periodEnd;
private String remark;
private LocalDateTime createTime;
}
3.3 定时任务服务
ScoreAutoGrantService.java
@Service
@Slf4j
public class ScoreAutoGrantService {
@Autowired
private TeamScorePoolMapper teamScorePoolMapper;
@Autowired
private ScoreGrantRecordMapper scoreGrantRecordMapper;
/**
* 定时任务:每天凌晨1点检查并执行自动发放
*/
@Scheduled(cron = "0 0 1 * * ?")
@Transactional
public void autoGrantScores() {
LocalDateTime now = LocalDateTime.now();
int dayOfWeek = now.getDayOfWeek().getValue(); // 1-7
int dayOfMonth = now.getDayOfMonth();
// 查询需要自动发放的积分池
LambdaQueryWrapper<TeamScorePool> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(TeamScorePool::getAutoGrantEnabled, 1);
List<TeamScorePool> pools = teamScorePoolMapper.selectList(wrapper);
for (TeamScorePool pool : pools) {
try {
processAutoGrant(pool, now, dayOfWeek, dayOfMonth);
} catch (Exception e) {
log.error("自动发放积分失败: deptId={}", pool.getDeptId(), e);
}
}
}
private void processAutoGrant(TeamScorePool pool, LocalDateTime now,
int dayOfWeek, int dayOfMonth) {
String period = pool.getAutoGrantPeriod();
int grantDay = pool.getAutoGrantDay();
boolean shouldGrant = false;
if ("WEEKLY".equals(period) && dayOfWeek == grantDay) {
shouldGrant = true;
} else if ("MONTHLY".equals(period) && dayOfMonth == grantDay) {
shouldGrant = true;
}
if (!shouldGrant) return;
// 检查是否已经发放过
if (pool.getLastGrantTime() != null) {
if ("WEEKLY".equals(period)) {
// 本周是否已发放
LocalDateTime weekStart = now.with(DayOfWeek.MONDAY).withHour(0).withMinute(0).withSecond(0);
if (pool.getLastGrantTime().isAfter(weekStart)) {
return; // 本周已发放
}
} else if ("MONTHLY".equals(period)) {
// 本月是否已发放
if (pool.getLastGrantTime().getMonthValue() == now.getMonthValue() &&
pool.getLastGrantTime().getYear() == now.getYear()) {
return; // 本月已发放
}
}
}
// 执行发放
int balanceBefore = pool.getBalance();
int expiredAmount = 0;
// 如果启用过期清零,先清零旧积分
if (pool.getExpireUnused() == 1 && balanceBefore > 0) {
expiredAmount = balanceBefore;
pool.setBalance(0);
}
// 发放新积分
int grantAmount = pool.getAutoGrantAmount();
pool.setBalance(pool.getBalance() + grantAmount);
pool.setTotalRecharge(pool.getTotalRecharge() + grantAmount);
pool.setLastGrantTime(now);
pool.setNextGrantTime(calculateNextGrantTime(now, period, grantDay));
teamScorePoolMapper.updateById(pool);
// 记录发放历史
ScoreGrantRecord record = new ScoreGrantRecord();
record.setDeptId(pool.getDeptId());
record.setGrantType("AUTO");
record.setGrantAmount(grantAmount);
record.setExpiredAmount(expiredAmount);
record.setBalanceBefore(balanceBefore);
record.setBalanceAfter(pool.getBalance());
record.setGrantPeriod(period);
record.setPeriodStart(calculatePeriodStart(now, period));
record.setPeriodEnd(calculatePeriodEnd(now, period));
record.setRemark(String.format("自动发放%s积分,过期清零%s积分",
grantAmount, expiredAmount));
record.setCreateTime(now);
scoreGrantRecordMapper.insert(record);
log.info("自动发放积分成功: deptId={}, amount={}, expired={}",
pool.getDeptId(), grantAmount, expiredAmount);
}
private LocalDateTime calculateNextGrantTime(LocalDateTime now, String period, int grantDay) {
if ("WEEKLY".equals(period)) {
return now.plusWeeks(1).with(DayOfWeek.of(grantDay)).withHour(1).withMinute(0);
} else {
return now.plusMonths(1).withDayOfMonth(grantDay).withHour(1).withMinute(0);
}
}
}
四、前端开发
4.1 部门编辑弹窗增加配置
在部门编辑弹窗中,当部门类型为 demand 或 primary_demand 时,显示积分自动发放配置:
<!-- 积分自动发放配置(仅需求部门和一级需求部门) -->
<a-divider v-if="isDemandDept">积分自动发放配置</a-divider>
<template v-if="isDemandDept">
<a-form-item label="启用自动发放">
<a-switch v-model:checked="formData.department.autoGrantEnabled" />
</a-form-item>
<template v-if="formData.department.autoGrantEnabled">
<a-form-item label="发放周期">
<a-radio-group v-model:value="formData.department.autoGrantPeriod">
<a-radio value="WEEKLY">每周发放</a-radio>
<a-radio value="MONTHLY">每月发放</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="发放日期">
<a-select v-model:value="formData.department.autoGrantDay">
<template v-if="formData.department.autoGrantPeriod === 'WEEKLY'">
<a-select-option :value="1">周一</a-select-option>
<a-select-option :value="2">周二</a-select-option>
<a-select-option :value="3">周三</a-select-option>
<a-select-option :value="4">周四</a-select-option>
<a-select-option :value="5">周五</a-select-option>
<a-select-option :value="6">周六</a-select-option>
<a-select-option :value="7">周日</a-select-option>
</template>
<template v-else>
<a-select-option v-for="d in 28" :key="d" :value="d">{{ d }}号</a-select-option>
</template>
</a-select>
</a-form-item>
<a-form-item label="发放积分数量">
<a-input-number
v-model:value="formData.department.autoGrantAmount"
:min="0"
:max="100000"
placeholder="每次发放的积分数量"
style="width: 200px;"
/>
</a-form-item>
<a-form-item label="过期清零">
<a-switch v-model:checked="formData.department.expireUnused" />
<span style="margin-left: 8px; color: #888;">
开启后,新积分发放时会清零上期未使用的积分
</span>
</a-form-item>
</template>
</template>
4.2 积分池弹窗显示发放记录
在积分池管理弹窗中增加"发放记录"tab:
<a-tabs>
<a-tab-pane key="overview" tab="积分概览">
<!-- 现有的积分概览内容 -->
</a-tab-pane>
<a-tab-pane key="grant-records" tab="发放记录">
<a-table
:columns="grantRecordColumns"
:data-source="grantRecords"
:pagination="{ pageSize: 10 }"
size="small"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'grantType'">
<a-tag :color="record.grantType === 'AUTO' ? 'blue' : 'green'">
{{ record.grantType === 'AUTO' ? '自动发放' : '手动充值' }}
</a-tag>
</template>
<template v-else-if="column.key === 'expiredAmount'">
<span v-if="record.expiredAmount > 0" style="color: #ff4d4f;">
-{{ record.expiredAmount }}
</span>
<span v-else>--</span>
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
五、使用场景示例
场景1:每周发放固定积分,过期清零
配置:
- 自动发放:开启
- 发放周期:每周
- 发放日期:周一
- 发放积分:500分
- 过期清零:开启
效果:
- 每周一凌晨自动发放500积分
- 上周未使用的积分自动清零
- 部门每周有固定500积分预算发起任务
场景2:每月发放积分,累积使用
配置:
- 自动发放:开启
- 发放周期:每月
- 发放日期:1号
- 发放积分:2000分
- 过期清零:关闭
效果:
- 每月1号自动发放2000积分
- 未使用的积分累积到下月
- 部门可以灵活使用积分
六、开发工时估算
| 阶段 | 内容 | 工时 |
|---|---|---|
| 数据库 | 表结构修改和新建 | 0.5天 |
| 后端 | 实体类、Service、定时任务 | 1天 |
| 后端 | API接口开发 | 0.5天 |
| 前端 | 部门编辑弹窗修改 | 0.5天 |
| 前端 | 积分池弹窗增强 | 0.5天 |
| 测试 | 功能测试和联调 | 0.5天 |
| 合计 | 3.5天 |
七、注意事项
- 定时任务需要确保服务器时间准确
- 过期清零会影响部门的任务发起能力,需要提前通知
- 建议在发放前一天发送提醒通知
- 需要考虑服务重启时定时任务的补偿机制
是否需要我开始实现这个功能?