|
|
@@ -24,9 +24,11 @@ import org.apache.catalina.User;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
+import java.sql.Timestamp;
|
|
|
import java.time.*;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.time.format.DateTimeParseException;
|
|
|
@@ -65,6 +67,8 @@ public class ScheduleTask {
|
|
|
@Autowired
|
|
|
private CommissionTableMapper commissionTableMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private JdbcTemplate jdbcTemplate;
|
|
|
|
|
|
/*每天凌晨定时同步任务状态*/
|
|
|
@SneakyThrows
|
|
|
@@ -1874,7 +1878,310 @@ public class ScheduleTask {
|
|
|
return McR.success();
|
|
|
}
|
|
|
|
|
|
- /*todo:人员获取部门*/
|
|
|
+
|
|
|
+ /**
|
|
|
+ * todo: commission_detail 业务提成表
|
|
|
+ */
|
|
|
+
|
|
|
+ /*【说明】
|
|
|
+ * commissionDetailFull 不会自动执行。
|
|
|
+
|
|
|
+ 它只在以下情况手工调用:
|
|
|
+
|
|
|
+ - 首次初始化异常,需要重新全量导入
|
|
|
+ - 发现 commission_detail 数据缺失、错误或与 Teambition 不一致
|
|
|
+ - 调整了筛选条件、字段映射后,需要重建全量数据
|
|
|
+
|
|
|
+ 正常情况下,首次调用 /commissionDetail 后,后续只依赖每天 05:10 的增量同步。
|
|
|
+ * */
|
|
|
+ @SneakyThrows
|
|
|
+ @PostMapping("/commissionDetailFull")
|
|
|
+ public McR commissionDetailFull() {
|
|
|
+ final String projectId = "6878b323386fac7ab9dbe5e9";
|
|
|
+ final String baseCondition = "tagId != 68f5840f90efa8d7498bd5e9 " +
|
|
|
+ "AND tagId != 68f58407a89ee3bbe639f85c " +
|
|
|
+ "AND cf:691c29c1f8cbae320a1c8e75 != 无 " +
|
|
|
+ "AND cf:691c29c1f8cbae320a1c8e75 != 扩版 " +
|
|
|
+ "AND created >= 2026-01-01 ";
|
|
|
+
|
|
|
+ List<String> conditions = Arrays.asList(
|
|
|
+ baseCondition + "AND scenarioId = 6878b39a27ae5f3cac355fb9 " +
|
|
|
+ "AND tfsId = 6878b384d3c6329fa3728bc9",
|
|
|
+ baseCondition + "AND scenarioId = 6a7c2663e9cc163c31e5cd62 " +
|
|
|
+ "AND tfsId = 6a7c003127a2b19abbba302e"
|
|
|
+ );
|
|
|
+
|
|
|
+ Map<String, String> header = getCommonHeader();
|
|
|
+ Set<String> taskIds = new LinkedHashSet<>();
|
|
|
+ for (String condition : conditions) {
|
|
|
+ collectCommissionDetailTaskIds(projectId, condition, header, taskIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Object[]> rows = new ArrayList<>();
|
|
|
+ for (String taskId : taskIds) {
|
|
|
+ JSONObject task = queryCommissionDetailTask(taskId, header);
|
|
|
+ if (task == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONArray customFields = task.getJSONArray("customfields");
|
|
|
+ String designer = getCommissionDetailFieldValue(customFields, "6a8e4ce280d14a316ce57b0e");
|
|
|
+ String customerShortName = getCommissionDetailFieldValue(customFields, "687df3d4d7dc27d64d57201e");
|
|
|
+ String designFee = getCommissionDetailFieldValue(customFields, "6a798e303023b086a6a38f47");
|
|
|
+ String designCoefficient = getCommissionDetailFieldValue(customFields, "691aafe69819400cd5497bbf");
|
|
|
+ String designLevel = getCommissionDetailFieldValue(customFields, "691c29c1f8cbae320a1c8e75");
|
|
|
+ String businessPerson = getCommissionDetailFieldValue(customFields, "688191972ab6c4f412ea2bb9");
|
|
|
+ String businessConfirmed = getBusinessConfirmationStatus(taskId, header);
|
|
|
+
|
|
|
+ rows.add(new Object[]{
|
|
|
+ taskId,
|
|
|
+ task.getString("content"),
|
|
|
+ designer,
|
|
|
+ customerShortName,
|
|
|
+ designFee,
|
|
|
+ designCoefficient,
|
|
|
+ designLevel,
|
|
|
+ businessPerson,
|
|
|
+ businessConfirmed
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // API
|
|
|
+ jdbcTemplate.update("DELETE FROM commission_detail");
|
|
|
+ if (!rows.isEmpty()) {
|
|
|
+ jdbcTemplate.batchUpdate(
|
|
|
+ "INSERT INTO commission_detail " +
|
|
|
+ "(task_id, task_name, xm, khjc, jg, xs, dj, ywmc, ywsfqy) " +
|
|
|
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
|
+ rows
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("新增同步个数: {} ", rows.size());
|
|
|
+ return McR.success(rows.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Scheduled(cron = "0 10 5 * * ?")
|
|
|
+ @SneakyThrows
|
|
|
+ @PostMapping("/commissionDetail")
|
|
|
+ public McR commissionDetail() {
|
|
|
+ LocalDateTime lastSuccessAt = getCommissionDetailLastSuccessAt();
|
|
|
+ if (lastSuccessAt == null) {
|
|
|
+ McR response = commissionDetailFull();
|
|
|
+ saveCommissionDetailLastSuccessAt(LocalDateTime.now());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+
|
|
|
+ String updatedFrom = DateTimeFormatter.ISO_INSTANT.format(
|
|
|
+ lastSuccessAt.minusMinutes(5).atZone(ZoneId.of("Asia/Shanghai")).toInstant()
|
|
|
+ );
|
|
|
+ String condition = "updated >= " + updatedFrom + " AND (scenarioId = 6878b39a27ae5f3cac355fb9 " +
|
|
|
+ "OR scenarioId = 6a7c2663e9cc163c31e5cd62)";
|
|
|
+
|
|
|
+ Set<String> taskIds = new LinkedHashSet<>();
|
|
|
+ collectCommissionDetailTaskIds("6878b323386fac7ab9dbe5e9", condition, getCommonHeader(), taskIds);
|
|
|
+
|
|
|
+ int written = 0;
|
|
|
+ int removed = 0;
|
|
|
+ Map<String, String> header = getCommonHeader();
|
|
|
+ for (String taskId : taskIds) {
|
|
|
+ JSONObject task = queryCommissionDetailTask(taskId, header);
|
|
|
+ if (task == null || !isCommissionDetailEligible(task)) {
|
|
|
+ removed += jdbcTemplate.update("DELETE FROM commission_detail WHERE task_id = ?", taskId);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ upsertCommissionDetailRow(buildCommissionDetailRow(task, header));
|
|
|
+ written++;
|
|
|
+ }
|
|
|
+
|
|
|
+ saveCommissionDetailLastSuccessAt(LocalDateTime.now());
|
|
|
+ log.info("commission_detail incremental sync completed, written={}, removed={}", written, removed);
|
|
|
+ return McR.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ private LocalDateTime getCommissionDetailLastSuccessAt() {
|
|
|
+ List<Timestamp> timestamps = jdbcTemplate.query(
|
|
|
+ "SELECT last_success_at FROM commission_detail_sync_state WHERE sync_name = ?",
|
|
|
+ (resultSet, rowNum) -> resultSet.getTimestamp(1),
|
|
|
+ "commission_detail"
|
|
|
+ );
|
|
|
+ return timestamps.isEmpty() ? null : timestamps.get(0).toLocalDateTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveCommissionDetailLastSuccessAt(LocalDateTime successAt) {
|
|
|
+ int updated = jdbcTemplate.update(
|
|
|
+ "UPDATE commission_detail_sync_state SET last_success_at = ? WHERE sync_name = ?",
|
|
|
+ Timestamp.valueOf(successAt),
|
|
|
+ "commission_detail"
|
|
|
+ );
|
|
|
+ if (updated == 0) {
|
|
|
+ jdbcTemplate.update(
|
|
|
+ "INSERT INTO commission_detail_sync_state (sync_name, last_success_at) VALUES (?, ?)",
|
|
|
+ "commission_detail",
|
|
|
+ Timestamp.valueOf(successAt)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isCommissionDetailEligible(JSONObject task) {
|
|
|
+ String scenarioId = task.getString("sfcId");
|
|
|
+ String taskStatusId = task.getString("tfsId");
|
|
|
+ boolean designCompleted = "6878b39a27ae5f3cac355fb9".equals(scenarioId)
|
|
|
+ && "6878b384d3c6329fa3728bc9".equals(taskStatusId);
|
|
|
+ boolean seriesCompleted = "6a7c2663e9cc163c31e5cd62".equals(scenarioId)
|
|
|
+ && "6a7c003127a2b19abbba302e".equals(taskStatusId);
|
|
|
+ if (!designCompleted && !seriesCompleted) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ String created = task.getString("created");
|
|
|
+ if (StringUtils.isBlank(created) || created.compareTo("2026-01-01") < 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONArray tagIds = task.getJSONArray("tagIds");
|
|
|
+ if (tagIds != null && (tagIds.contains("68f5840f90efa8d7498bd5e9")
|
|
|
+ || tagIds.contains("68f58407a89ee3bbe639f85c"))) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ String designLevel = getCommissionDetailFieldValue(
|
|
|
+ task.getJSONArray("customfields"),
|
|
|
+ "691c29c1f8cbae320a1c8e75"
|
|
|
+ );
|
|
|
+ return !"\u65e0".equals(designLevel) && !"\u6269\u7248".equals(designLevel);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object[] buildCommissionDetailRow(JSONObject task, Map<String, String> header) {
|
|
|
+ JSONArray customFields = task.getJSONArray("customfields");
|
|
|
+ String taskId = task.getString("id");
|
|
|
+ return new Object[]{
|
|
|
+ taskId,
|
|
|
+ task.getString("content"),
|
|
|
+ getCommissionDetailFieldValue(customFields, "6a8e4ce280d14a316ce57b0e"),
|
|
|
+ getCommissionDetailFieldValue(customFields, "687df3d4d7dc27d64d57201e"),
|
|
|
+ getCommissionDetailFieldValue(customFields, "6a798e303023b086a6a38f47"),
|
|
|
+ getCommissionDetailFieldValue(customFields, "691aafe69819400cd5497bbf"),
|
|
|
+ getCommissionDetailFieldValue(customFields, "691c29c1f8cbae320a1c8e75"),
|
|
|
+ getCommissionDetailFieldValue(customFields, "688191972ab6c4f412ea2bb9"),
|
|
|
+ getBusinessConfirmationStatus(taskId, header)
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private void upsertCommissionDetailRow(Object[] row) {
|
|
|
+ int updated = jdbcTemplate.update(
|
|
|
+ "UPDATE commission_detail SET task_name = ?, xm = ?, khjc = ?, jg = ?, xs = ?, " +
|
|
|
+ "dj = ?, ywmc = ?, ywsfqy = ? WHERE task_id = ?",
|
|
|
+ row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[0]
|
|
|
+ );
|
|
|
+ if (updated == 0) {
|
|
|
+ jdbcTemplate.update(
|
|
|
+ "INSERT INTO commission_detail " +
|
|
|
+ "(task_id, task_name, xm, khjc, jg, xs, dj, ywmc, ywsfqy) " +
|
|
|
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
|
+ row
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void collectCommissionDetailTaskIds(String projectId, String condition,
|
|
|
+ Map<String, String> header, Set<String> taskIds) throws Exception {
|
|
|
+ Map<String, Object> countParam = new HashMap<>();
|
|
|
+ countParam.put("q", condition);
|
|
|
+ String countResponse = UtilHttp.doGet(
|
|
|
+ "https://open.teambition.com/api/v3/project/" + projectId + "/task/count",
|
|
|
+ header,
|
|
|
+ countParam
|
|
|
+ );
|
|
|
+ Matcher matcher = Pattern.compile("\\\"result\\\":(\\d+)").matcher(countResponse);
|
|
|
+ int total = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
|
|
|
+ String pageToken = "";
|
|
|
+
|
|
|
+ for (int page = 0; page < (total + 99) / 100; page++) {
|
|
|
+ Map<String, Object> queryParam = new HashMap<>();
|
|
|
+ queryParam.put("pageSize", 100);
|
|
|
+ queryParam.put("q", condition);
|
|
|
+ if (StringUtils.isNotBlank(pageToken)) {
|
|
|
+ queryParam.put("pageToken", pageToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ String queryResponse = UtilHttp.doGet(
|
|
|
+ "https://open.teambition.com/api/v3/project/" + projectId + "/task/query",
|
|
|
+ header,
|
|
|
+ queryParam
|
|
|
+ );
|
|
|
+ taskIds.addAll(extractIdsFromJson(queryResponse));
|
|
|
+ JsonNode nextPageToken = new ObjectMapper().readTree(queryResponse).get("nextPageToken");
|
|
|
+ pageToken = nextPageToken == null ? "" : nextPageToken.asText();
|
|
|
+ if (StringUtils.isBlank(pageToken)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject queryCommissionDetailTask(String taskId, Map<String, String> header) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> param = new HashMap<>();
|
|
|
+ param.put("taskId", taskId);
|
|
|
+ String response = UtilHttp.doGet("https://open.teambition.com/api/v3/task/query", header, param);
|
|
|
+ JSONArray result = JSON.parseObject(response).getJSONArray("result");
|
|
|
+ return result == null || result.isEmpty() ? null : result.getJSONObject(0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("taskId={}", taskId, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getCommissionDetailFieldValue(JSONArray customFields, String fieldId) {
|
|
|
+ if (customFields == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ List<String> values = new ArrayList<>();
|
|
|
+ for (int i = 0; i < customFields.size(); i++) {
|
|
|
+ JSONObject field = customFields.getJSONObject(i);
|
|
|
+ if (!fieldId.equals(field.getString("cfId"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ JSONArray valueArray = field.getJSONArray("value");
|
|
|
+ if (valueArray == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (int j = 0; j < valueArray.size(); j++) {
|
|
|
+ String value = valueArray.getJSONObject(j).getString("title");
|
|
|
+ if (StringUtils.isNotBlank(value)) {
|
|
|
+ values.add(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return String.join(",", values);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getBusinessConfirmationStatus(String taskId, Map<String, String> header) {
|
|
|
+ try {
|
|
|
+ String response = UtilHttp.doGet(
|
|
|
+ "https://open.teambition.com/api/v3/task/" + taskId + "/node/list",
|
|
|
+ header,
|
|
|
+ new HashMap<>()
|
|
|
+ );
|
|
|
+ JSONArray nodes = JSON.parseObject(response).getJSONArray("result");
|
|
|
+ if (nodes == null) {
|
|
|
+ return "\u672a\u786e\u8ba4";
|
|
|
+ }
|
|
|
+ for (int i = 0; i < nodes.size(); i++) {
|
|
|
+ JSONObject node = nodes.getJSONObject(i);
|
|
|
+ String nodeTfsId = node.getString("tfsId");
|
|
|
+ if ("6a4b18e857406a1184ebad21".equals(nodeTfsId)
|
|
|
+ || "6a7c003127a2b19abbba302d".equals(nodeTfsId)) {
|
|
|
+ return "finish".equalsIgnoreCase(node.getString("status")) ? "\u5df2\u786e\u8ba4" : "\u672a\u786e\u8ba4";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("taskId={}", taskId, e);
|
|
|
+ }
|
|
|
+ return "\u672a\u786e\u8ba4";
|
|
|
+ }
|
|
|
+
|
|
|
@SneakyThrows
|
|
|
@PostMapping("/UserGetDeprt")
|
|
|
String UserGetDeprt(@RequestParam String User){
|