package com.talentcard.server.business.portal;
|
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.ArrayList;
|
import java.util.Locale;
|
import java.util.Map;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.TypeReference;
|
import com.talentcard.server.business.BusinessIdGenerator;
|
import com.talentcard.server.business.DatabaseRows;
|
import com.talentcard.server.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class FuelMerchantPortalService {
|
private final JdbcTemplate jdbc;
|
|
public FuelMerchantPortalService(JdbcTemplate jdbc) {
|
this.jdbc = jdbc;
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long merchantId, Long talentCardId) {
|
Map<String, Object> merchant = fuelMerchant(merchantId);
|
Map<String, Object> benefit = one("""
|
select id, talent_levels, discount_text, oil_types, single_limit_text, annual_limit_text,
|
available_time, stock, valid_end, usage_instructions, rule_text, status
|
from transport_fuel_benefit
|
where merchant_id = ? and deleted = 0
|
order by id asc limit 1
|
""", merchantId);
|
String cardLevel = talentCardId == null ? "" : talentCardLevel(talentCardId);
|
String sourceAreaType = talentCardId == null ? "" : talentSourceAreaType(talentCardId);
|
Map<String, Object> right = matchingRight(parseRights(merchant.get("merchantRightsConfig")), cardLevel, sourceAreaType);
|
boolean available = number(merchant.get("status")) == 1 && number(benefit.get("status")) == 1;
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("merchantId", merchant.get("id"));
|
result.put("merchantName", merchant.get("merchantName"));
|
result.put("merchantType", "fuel");
|
result.put("coverUrl", merchant.get("coverUrl"));
|
result.put("address", merchant.get("address"));
|
result.put("contactMobile", merchant.get("contactMobile"));
|
result.put("latitude", merchant.get("latitude"));
|
result.put("longitude", merchant.get("longitude"));
|
result.put("available", available);
|
result.put("serviceLabel", text(merchant.get("serviceLabel"), "请咨询商户"));
|
result.put("discountText", text(benefit.get("discountText"), "请咨询商户"));
|
result.put("rightsTag", text(right == null ? null : right.get("tag"), ""));
|
result.put("applicableCards", text(right == null ? null : right.get("tag"), text(benefit.get("talentLevels"), "请咨询商户")));
|
result.put("groupBuyingEnabled", false);
|
String configuredDetail = text(right == null ? null : right.get("detail"), "");
|
result.put("usageInstructions", text(configuredDetail, text(benefit.get("usageInstructions"), "请咨询商户")));
|
result.put("rightsText", text(configuredDetail, text(benefit.get("ruleText"), "请咨询商户")));
|
result.put("benefit", benefitView(benefit));
|
result.put("heroImages", query("""
|
select id, image_url, sort_no
|
from transport_fuel_hero_image
|
where merchant_id = ? and deleted = 0 and enabled = 1
|
order by sort_no asc, id asc
|
""", merchantId));
|
result.put("applicableStores", query("""
|
select id, store_name, address, latitude, longitude, sort_no,
|
case when latitude is not null and longitude is not null and latitude <> 0 and longitude <> 0 then 1 else 0 end as can_navigate
|
from transport_fuel_store
|
where merchant_id = ? and deleted = 0 and enabled = 1
|
order by sort_no asc, id asc
|
""", merchantId));
|
return result;
|
}
|
|
@Transactional
|
public Map<String, Object> registerMobile(Long merchantId, FuelMobileRegistrationRequest request, String idempotencyKey) {
|
if (idempotencyKey == null || idempotencyKey.isBlank() || idempotencyKey.length() > 128) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "提交凭证不能为空");
|
}
|
List<Map<String, Object>> existing = query("""
|
select id, registration_no, merchant_id, talent_id, mobile, status, created_at
|
from transport_fuel_mobile_registration
|
where idempotency_key = ? and deleted = 0
|
""", idempotencyKey);
|
if (!existing.isEmpty()) {
|
Map<String, Object> registration = existing.get(0);
|
ensureAdminApplication(
|
text(registration.get("registrationNo"), ""),
|
longNumber(registration.get("merchantId")),
|
longNumber(registration.get("talentId")),
|
text(registration.get("mobile"), request.mobile()),
|
idempotencyKey,
|
registration.get("createdAt")
|
);
|
return registrationView(registration);
|
}
|
|
Map<String, Object> merchant = fuelMerchant(merchantId);
|
if (number(merchant.get("status")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "加油站当前不可用");
|
Map<String, Object> benefit = one("""
|
select id, status from transport_fuel_benefit
|
where id = ? and merchant_id = ? and deleted = 0
|
""", request.benefitId(), merchantId);
|
if (number(benefit.get("status")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "加油权益当前不可用");
|
Integer talentCount = jdbc.queryForObject("select count(*) from talent_user where id = ? and deleted = 0", Integer.class, request.talentId());
|
if (talentCount == null || talentCount == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "人才用户不存在");
|
|
String registrationNo = BusinessIdGenerator.next("FR");
|
jdbc.update("""
|
insert into transport_fuel_mobile_registration(registration_no, merchant_id, benefit_id, talent_id, mobile, status, idempotency_key)
|
values (?, ?, ?, ?, ?, 'SUBMITTED', ?)
|
""", registrationNo, merchantId, request.benefitId(), request.talentId(), request.mobile(), idempotencyKey);
|
ensureAdminApplication(registrationNo, merchantId, request.talentId(), request.mobile(), idempotencyKey, null);
|
return registrationView(one("""
|
select id, registration_no, status
|
from transport_fuel_mobile_registration
|
where idempotency_key = ? and deleted = 0
|
""", idempotencyKey));
|
}
|
|
private void ensureAdminApplication(String registrationNo, Long merchantId, Long talentId, String mobile,
|
String idempotencyKey, Object submittedAt) {
|
Integer existing = jdbc.queryForObject("""
|
select count(*) from service_application
|
where deleted = 0 and (application_no = ? or idempotency_key = ?)
|
""", Integer.class, registrationNo, idempotencyKey);
|
if (existing != null && existing > 0) return;
|
|
Map<String, Object> card = activeCard(talentId);
|
Map<String, Object> merchant = fuelMerchant(merchantId);
|
Integer previous = jdbc.queryForObject("""
|
select count(*) from transport_fuel_mobile_registration
|
where talent_id = ? and registration_no <> ? and deleted = 0
|
""", Integer.class, talentId, registrationNo);
|
String submissionType = previous != null && previous > 0 ? "修改" : "新增";
|
Object effectiveSubmittedAt = submittedAt == null ? java.time.LocalDateTime.now() : submittedAt;
|
|
jdbc.update("""
|
insert into service_application(
|
application_no, application_type, talent_id, card_id, merchant_id,
|
talent_name, talent_mobile, talent_level, card_no, merchant_name, contact_mobile,
|
applicant_name, applicant_mobile, alipay_mobile, submission_type,
|
submitted_at, status, idempotency_key, deleted
|
) values (?, 'FUEL_SERVICE', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '待处理', ?, 0)
|
""",
|
registrationNo,
|
talentId,
|
card.get("cardId"),
|
merchantId,
|
card.get("talentName"),
|
card.get("talentMobile"),
|
card.get("talentLevel"),
|
card.get("cardNo"),
|
merchant.get("merchantName"),
|
merchant.get("contactMobile"),
|
card.get("talentName"),
|
mobile,
|
mobile,
|
submissionType,
|
effectiveSubmittedAt,
|
idempotencyKey
|
);
|
Long applicationId = jdbc.queryForObject(
|
"select id from service_application where idempotency_key = ? and deleted = 0",
|
Long.class,
|
idempotencyKey
|
);
|
jdbc.update("""
|
insert into service_application_operation_log(
|
application_id, application_no, operation_type, after_status,
|
operator_type, operator_id, operator_name, remark, created_at
|
) values (?, ?, 'SUBMIT', '待处理', 'TALENT', ?, ?, '提交加油服务登记', ?)
|
""", applicationId, registrationNo, talentId, card.get("talentName"), effectiveSubmittedAt);
|
}
|
|
private Map<String, Object> activeCard(Long talentId) {
|
List<Map<String, Object>> cards = query("""
|
select c.id as card_id, c.card_no, c.card_level as talent_level,
|
u.name as talent_name, u.mobile as talent_mobile
|
from talent_card c
|
join talent_user u on u.id = c.talent_id and u.deleted = 0 and u.status = 1
|
where c.talent_id = ? and c.deleted = 0 and c.status = 1
|
and (c.valid_start is null or c.valid_start <= current_date)
|
and (c.valid_end is null or c.valid_end >= current_date)
|
and (c.expire_time is null or c.expire_time > now())
|
order by case when c.card_type = 'PERSONAL' then 0 else 1 end, c.id desc
|
limit 1
|
""", talentId);
|
if (cards.isEmpty()) throw new BusinessException(ErrorCode.CONFLICT, "未找到有效人才卡");
|
return cards.get(0);
|
}
|
|
private Map<String, Object> fuelMerchant(Long merchantId) {
|
return one("""
|
select id, merchant_name, merchant_type_code as merchant_type, contact_mobile, address, cover_url, service_label,
|
merchant_rights_config, status, latitude, longitude
|
from merchant
|
where id = ? and merchant_type_code = 'fuel' and deleted = 0
|
""", merchantId);
|
}
|
|
private Map<String, Object> matchingRight(List<Map<String, Object>> rights, String cardLevel, String sourceAreaType) {
|
if (rights.isEmpty() || cardLevel.isBlank()) return null;
|
for (String candidate : rightCandidates(cardLevel, sourceAreaType)) {
|
for (Map<String, Object> right : rights) {
|
if (booleanValue(right.get("enabled")) && candidate.equals(text(right.get("level"), ""))) return right;
|
}
|
}
|
String level = normalizedCardLevel(cardLevel);
|
for (Map<String, Object> right : rights) {
|
if (!booleanValue(right.get("enabled"))) continue;
|
if (!level.equals(normalizedCardLevel(text(right.get("level"), "")))) continue;
|
if (!"EXCELLENT".equals(level) || !areaSpecificExcellentLevel(text(right.get("level"), ""))) return right;
|
}
|
return null;
|
}
|
|
private List<String> rightCandidates(String cardLevel, String sourceAreaType) {
|
String level = normalizedCardLevel(cardLevel);
|
if (!"EXCELLENT".equals(level)) return List.of(cardLevel);
|
String area = text(sourceAreaType, "").toUpperCase(Locale.ROOT);
|
if (area.contains("LOCAL") || area.contains("本地")) return List.of("卓越人才卡(本地)", "卓越人才卡");
|
if (area.contains("OUTSIDE") || area.contains("外地")) return List.of("卓越人才卡(外地)", "卓越人才卡");
|
return List.of("卓越人才卡");
|
}
|
|
private boolean areaSpecificExcellentLevel(String level) {
|
String value = text(level, "").toUpperCase(Locale.ROOT);
|
return value.contains("LOCAL") || value.contains("OUTSIDE") || value.contains("本地") || value.contains("外地");
|
}
|
|
private List<Map<String, Object>> parseRights(Object value) {
|
if (value instanceof List<?> list) {
|
List<Map<String, Object>> result = new ArrayList<>();
|
for (Object item : list) if (item instanceof Map<?, ?> map) result.add(copyMap(map));
|
return result;
|
}
|
String raw = text(value, "");
|
if (raw.isBlank()) return List.of();
|
try {
|
List<Map<String, Object>> parsed = JSON.parseObject(raw, new TypeReference<List<Map<String, Object>>>() {});
|
return parsed == null ? List.of() : parsed.stream().map(this::copyMap).toList();
|
} catch (Exception ignored) {
|
return List.of();
|
}
|
}
|
|
private Map<String, Object> copyMap(Map<?, ?> source) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
for (Map.Entry<?, ?> entry : source.entrySet()) result.put(String.valueOf(entry.getKey()), entry.getValue());
|
return result;
|
}
|
|
private String talentCardLevel(Long talentCardId) {
|
List<Map<String, Object>> cards = query("select card_level from talent_card where id = ? and deleted = 0 limit 1", talentCardId);
|
return cards.isEmpty() ? "" : text(cards.get(0).get("cardLevel"), "");
|
}
|
|
private String talentSourceAreaType(Long talentCardId) {
|
List<Map<String, Object>> cards = query("""
|
select u.source_area_type
|
from talent_card c join talent_user u on u.id = c.talent_id and u.deleted = 0
|
where c.id = ? and c.deleted = 0
|
limit 1
|
""", talentCardId);
|
return cards.isEmpty() ? "" : text(cards.get(0).get("sourceAreaType"), "");
|
}
|
|
private String normalizedCardLevel(String value) {
|
String raw = text(value, "");
|
String upper = raw.toUpperCase(Locale.ROOT);
|
if (upper.contains("EXCELLENT") || raw.contains("卓越")) return "EXCELLENT";
|
if (upper.contains("OUTSTANDING") || raw.contains("杰出")) return "OUTSTANDING";
|
if (upper.contains("ELITE") || raw.contains("菁英") || raw.contains("精英")) return "ELITE";
|
return upper;
|
}
|
|
private boolean booleanValue(Object value) {
|
if (value instanceof Boolean bool) return bool;
|
if (value instanceof Number number) return number.intValue() != 0;
|
String text = text(value, "").toLowerCase(Locale.ROOT);
|
return "true".equals(text) || "1".equals(text) || "yes".equals(text);
|
}
|
|
private Map<String, Object> benefitView(Map<String, Object> benefit) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("benefitId", benefit.get("id"));
|
result.put("oilTypes", text(benefit.get("oilTypes"), "请咨询商户"));
|
result.put("discount", text(benefit.get("discountText"), "请咨询商户"));
|
result.put("singleLimit", text(benefit.get("singleLimitText"), "请咨询商户"));
|
result.put("annualLimit", text(benefit.get("annualLimitText"), "请咨询商户"));
|
result.put("availableTime", text(benefit.get("availableTime"), "请咨询商户"));
|
result.put("stock", benefit.get("stock"));
|
result.put("validEnd", benefit.get("validEnd"));
|
return result;
|
}
|
|
private Map<String, Object> registrationView(Map<String, Object> row) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("registrationId", row.get("id"));
|
result.put("registrationNo", row.get("registrationNo"));
|
result.put("status", row.get("status"));
|
return result;
|
}
|
|
private List<Map<String, Object>> query(String sql, Object... args) {
|
return jdbc.queryForList(sql, args).stream().map(DatabaseRows::normalize).toList();
|
}
|
|
private Map<String, Object> one(String sql, Object... args) {
|
List<Map<String, Object>> rows = query(sql, args);
|
if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "加油站权益配置不存在");
|
return rows.get(0);
|
}
|
|
private int number(Object value) {
|
return value instanceof Number number ? number.intValue() : 0;
|
}
|
|
private Long longNumber(Object value) {
|
return value instanceof Number number ? number.longValue() : null;
|
}
|
|
private String text(Object value, String fallback) {
|
if (value == null) return fallback;
|
String text = String.valueOf(value).trim();
|
return text.isEmpty() ? fallback : text;
|
}
|
}
|