#6 营收管理平台+报装: - RevenueBaseService: SSO登录/应用接入/运维审计 - InstallService: 预受理→工程申请→派单→进度查询→统计报表 #7 营业收费核心+表务: - BillingService: 阶梯水价计算(多级) + 账单生成 + 缴费 + 欠费统计 - MeterService: 水表全生命周期(入库→安装→换表→报废) #8 客服热线+微信网厅: - CustomerServiceCenter: 水费查询/知识库/公告板/KPI指标 - WechatService: 用户绑定/微信支付预下单/AI客服问答/公告发布 #9 远传集抄+工单: - RemoteReadingService: 批量抄表/DMA漏损分析/大表监控(DN80+) - WorkOrderService: 创建/分派/完成/统计 Controllers: RevenueController + WechatController + MeterWorkController
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package com.water.revenue.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.revenue.service.RemoteReadingService;
|
||||
import com.water.revenue.service.WorkOrderService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Tag(name = "远传集抄 & 工单")
|
||||
@RestController
|
||||
@RequestMapping("/revenue/ops")
|
||||
@RequiredArgsConstructor
|
||||
public class MeterWorkController {
|
||||
|
||||
private final RemoteReadingService rrService;
|
||||
private final WorkOrderService woService;
|
||||
|
||||
// ---- 远传集抄 ----
|
||||
@PostMapping("/reading/batch/{area}")
|
||||
public R<Map<String, Object>> batchRead(@PathVariable String area) {
|
||||
return R.ok(rrService.batchRead(area));
|
||||
}
|
||||
|
||||
@GetMapping("/dma/analysis")
|
||||
public R<Map<String, Object>> dmaAnalysis(@RequestParam String area, @RequestParam String period) {
|
||||
return R.ok(rrService.dmaAnalysis(area, period));
|
||||
}
|
||||
|
||||
@GetMapping("/meter/large")
|
||||
public R<List<Map<String, Object>>> largeMeters() {
|
||||
return R.ok(rrService.largeMeterMonitor());
|
||||
}
|
||||
|
||||
// ---- 工单 ----
|
||||
@PostMapping("/work-order")
|
||||
public R<Map<String, Object>> createWO(@RequestBody Map<String, Object> req) {
|
||||
return R.ok(woService.create(
|
||||
(String) req.get("title"), (String) req.get("type"), (String) req.get("priority"),
|
||||
1L, "当前用户", (String) req.get("description"), (String) req.get("area")));
|
||||
}
|
||||
|
||||
@PutMapping("/work-order/assign")
|
||||
public R<Map<String, Object>> assign(@RequestBody Map<String, Object> req) {
|
||||
return R.ok(woService.assign(
|
||||
(String) req.get("woNo"), Long.parseLong(String.valueOf(req.get("assigneeId"))),
|
||||
(String) req.get("assigneeName")));
|
||||
}
|
||||
|
||||
@PutMapping("/work-order/complete")
|
||||
public R<Map<String, Object>> complete(@RequestBody Map<String, Object> req) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> photos = (List<String>) req.getOrDefault("photos", List.of());
|
||||
return R.ok(woService.complete((String) req.get("woNo"), (String) req.get("result"), photos));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.water.revenue.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.revenue.service.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Tag(name = "营业收费")
|
||||
@RestController
|
||||
@RequestMapping("/revenue")
|
||||
@RequiredArgsConstructor
|
||||
public class RevenueController {
|
||||
|
||||
private final RevenueBaseService baseService;
|
||||
private final InstallService installService;
|
||||
private final BillingService billingService;
|
||||
private final MeterService meterService;
|
||||
|
||||
// ---- 营收平台 ----
|
||||
@PostMapping("/auth/sso")
|
||||
public R<Map<String, Object>> ssoLogin(@RequestBody Map<String, String> req) {
|
||||
return R.ok(baseService.ssoLogin(req.get("username"), req.get("password"), req.get("appType")));
|
||||
}
|
||||
|
||||
// ---- 报装管理 ----
|
||||
@PostMapping("/install/pre-apply")
|
||||
public R<Map<String, Object>> preApply(@RequestBody Map<String, String> req) {
|
||||
return R.ok(installService.preApply(req.get("name"), req.get("phone"),
|
||||
req.get("area"), req.get("address"), req.get("customerType"), req.get("caliber")));
|
||||
}
|
||||
|
||||
@GetMapping("/install/progress/{appNo}")
|
||||
public R<Map<String, Object>> progress(@PathVariable String appNo) {
|
||||
return R.ok(installService.getProgress(appNo));
|
||||
}
|
||||
|
||||
// ---- 营业收费 ----
|
||||
@PostMapping("/billing/generate")
|
||||
public R<Map<String, Object>> generateBill(@RequestParam Long readingId) {
|
||||
return R.ok(billingService.generateBill(readingId));
|
||||
}
|
||||
|
||||
@PostMapping("/billing/pay")
|
||||
public R<Map<String, Object>> pay(@RequestBody Map<String, Object> req) {
|
||||
return R.ok(billingService.pay(
|
||||
Long.parseLong(String.valueOf(req.get("billId"))),
|
||||
(String) req.get("payMethod"),
|
||||
(String) req.get("payChannel"),
|
||||
new BigDecimal(String.valueOf(req.get("amount")))));
|
||||
}
|
||||
|
||||
// ---- 表务管理 ----
|
||||
@PostMapping("/meter/stock-in")
|
||||
public R<String> stockIn(@RequestBody Map<String, String> req) {
|
||||
meterService.stockIn(req.get("meterNo"), req.get("caliber"),
|
||||
req.get("meterType"), req.get("manufacturer"), Integer.parseInt(req.get("quantity")));
|
||||
return R.ok("入库成功");
|
||||
}
|
||||
|
||||
@PostMapping("/meter/replace")
|
||||
public R<String> replace(@RequestBody Map<String, Object> req) {
|
||||
meterService.replace(
|
||||
Long.parseLong(String.valueOf(req.get("oldMeterId"))),
|
||||
Long.parseLong(String.valueOf(req.get("newMeterId"))),
|
||||
new BigDecimal(String.valueOf(req.get("oldReading"))),
|
||||
(String) req.get("remark"));
|
||||
return R.ok("换表成功");
|
||||
}
|
||||
|
||||
@GetMapping("/meter/lifecycle/{meterId}")
|
||||
public R<List<Map<String, Object>>> lifecycle(@PathVariable Long meterId) {
|
||||
return R.ok(meterService.getLifecycle(meterId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.water.revenue.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.revenue.service.CustomerServiceCenter;
|
||||
import com.water.revenue.service.WechatService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Tag(name = "客服热线 & 微信网厅")
|
||||
@RestController
|
||||
@RequestMapping("/revenue/customer")
|
||||
@RequiredArgsConstructor
|
||||
public class WechatController {
|
||||
|
||||
private final CustomerServiceCenter csc;
|
||||
private final WechatService wechatService;
|
||||
|
||||
// ---- 客服 ----
|
||||
@GetMapping("/bills/query")
|
||||
public R<List<Map<String, Object>>> queryBills(@RequestParam String phoneOrNo) {
|
||||
return R.ok(csc.queryBills(phoneOrNo));
|
||||
}
|
||||
|
||||
@GetMapping("/knowledge/search")
|
||||
public R<List<Map<String, Object>>> searchKnowledge(@RequestParam String keyword) {
|
||||
return R.ok(csc.searchKnowledge(keyword));
|
||||
}
|
||||
|
||||
@GetMapping("/notices/{type}")
|
||||
public R<List<Map<String, Object>>> notices(@PathVariable String type) {
|
||||
return R.ok(csc.getNotices(type));
|
||||
}
|
||||
|
||||
@GetMapping("/kpi")
|
||||
public R<Map<String, Object>> kpi() { return R.ok(csc.getKpi()); }
|
||||
|
||||
// ---- 微信网厅 ----
|
||||
@PostMapping("/wechat/bind")
|
||||
public R<Map<String, Object>> bind(@RequestBody Map<String, String> req) {
|
||||
return R.ok(wechatService.bindUser(req.get("openId"), req.get("customerNo"), req.get("phone")));
|
||||
}
|
||||
|
||||
@PostMapping("/wechat/pay/prepay")
|
||||
public R<Map<String, Object>> prepay(@RequestBody Map<String, String> req) {
|
||||
return R.ok(wechatService.wechatPayPrepay(
|
||||
req.get("customerNo"), req.get("billPeriod"),
|
||||
new BigDecimal(req.get("amount"))));
|
||||
}
|
||||
|
||||
@GetMapping("/wechat/ai-answer")
|
||||
public R<String> aiAnswer(@RequestParam String question) {
|
||||
return R.ok(wechatService.aiAnswer(question));
|
||||
}
|
||||
|
||||
@PostMapping("/wechat/notice/publish")
|
||||
public R<String> publishNotice(@RequestBody Map<String, String> req) {
|
||||
wechatService.publishNotice(req.get("type"), req.get("title"), req.get("content"));
|
||||
return R.ok("公告已发布");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
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.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BillingService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 根据抄表记录生成账单 */
|
||||
@Transactional
|
||||
public Map<String, Object> generateBill(Long readingId) {
|
||||
Map<String, Object> reading = jdbcTemplate.queryForMap(
|
||||
"SELECT r.*, m.customer_id, m.caliber, c.customer_type FROM rev_reading r " +
|
||||
"JOIN rev_meter m ON r.meter_id = m.id " +
|
||||
"JOIN rev_customer c ON m.customer_id = c.id " +
|
||||
"WHERE r.id = ?", readingId);
|
||||
|
||||
BigDecimal consumption = (BigDecimal) reading.get("consumption");
|
||||
String customerType = (String) reading.get("customer_type");
|
||||
String period = (String) reading.get("reading_period");
|
||||
|
||||
// 查询阶梯水价
|
||||
List<Map<String, Object>> prices = jdbcTemplate.queryForList(
|
||||
"SELECT * FROM rev_water_price WHERE customer_type = ? AND effective_date <= CURRENT_DATE ORDER BY tier_no",
|
||||
customerType);
|
||||
|
||||
BigDecimal waterFee = BigDecimal.ZERO;
|
||||
BigDecimal remaining = consumption;
|
||||
|
||||
for (Map<String, Object> p : prices) {
|
||||
BigDecimal rangeEnd = p.get("range_end") != null ? (BigDecimal) p.get("range_end") : BigDecimal.valueOf(99999);
|
||||
BigDecimal price = (BigDecimal) p.get("water_price");
|
||||
BigDecimal tierUsage = remaining.min(rangeEnd);
|
||||
waterFee = waterFee.add(tierUsage.multiply(price));
|
||||
remaining = remaining.subtract(tierUsage);
|
||||
if (remaining.compareTo(BigDecimal.ZERO) <= 0) break;
|
||||
}
|
||||
|
||||
// 污水处理费(用水量的80%)
|
||||
BigDecimal sewageFee = consumption.multiply(((BigDecimal) prices.get(0).getOrDefault("sewage_price", BigDecimal.ZERO)))
|
||||
.multiply(BigDecimal.valueOf(0.8));
|
||||
|
||||
BigDecimal totalFee = waterFee.add(sewageFee);
|
||||
String billNo = "BILL-" + System.currentTimeMillis();
|
||||
Date dueDate = java.sql.Date.valueOf(LocalDate.now().plusDays(30));
|
||||
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_bill (bill_no, customer_id, meter_id, reading_id, bill_period, prev_reading, curr_reading, consumption, water_fee, sewage_fee, total_fee, status, due_date) " +
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
billNo, reading.get("customer_id"), reading.get("meter_id"), readingId,
|
||||
period, reading.get("prev_reading"), reading.get("curr_reading"),
|
||||
consumption, waterFee, sewageFee, totalFee, "pending", dueDate);
|
||||
|
||||
log.info("Bill generated: {} total={}", billNo, totalFee);
|
||||
return Map.of("billNo", billNo, "totalFee", totalFee, "waterFee", waterFee, "sewageFee", sewageFee);
|
||||
}
|
||||
|
||||
/** 缴费 */
|
||||
@Transactional
|
||||
public Map<String, Object> pay(long billId, String payMethod, String payChannel, BigDecimal amount) {
|
||||
String paymentNo = "PAY-" + System.currentTimeMillis();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_payment (bill_id, customer_id, payment_no, amount, pay_method, pay_channel) " +
|
||||
"SELECT ?, customer_id, ?, ?, ?, ? FROM rev_bill WHERE id = ?",
|
||||
billId, paymentNo, amount, payMethod, payChannel, billId);
|
||||
|
||||
jdbcTemplate.update(
|
||||
"UPDATE rev_bill SET paid_fee = paid_fee + ?, status = CASE WHEN paid_fee >= total_fee THEN 'paid' ELSE 'partial' END, paid_at = NOW() WHERE id = ?",
|
||||
amount, billId);
|
||||
|
||||
return Map.of("paymentNo", paymentNo, "amount", amount, "status", "success");
|
||||
}
|
||||
|
||||
/** 欠费统计 */
|
||||
public List<Map<String, Object>> getOverdueBills(String area) {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT b.*, c.customer_name, c.phone FROM rev_bill b " +
|
||||
"JOIN rev_customer c ON b.customer_id = c.id " +
|
||||
"WHERE b.status IN ('pending','partial') AND c.area = ? AND b.due_date < CURRENT_DATE",
|
||||
area);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerServiceCenter {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 水费查询(语音/在线) */
|
||||
public List<Map<String, Object>> queryBills(String phoneOrCustomerNo) {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT b.*, c.customer_name, c.phone FROM rev_bill b " +
|
||||
"JOIN rev_customer c ON b.customer_id = c.id " +
|
||||
"WHERE c.phone = ? OR c.customer_no = ? ORDER BY b.bill_period DESC LIMIT 12",
|
||||
phoneOrCustomerNo, phoneOrCustomerNo);
|
||||
}
|
||||
|
||||
/** 知识库管理 */
|
||||
public List<Map<String, Object>> searchKnowledge(String keyword) {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT dict_label, dict_value FROM sys_dict_data WHERE dict_type_id = " +
|
||||
"(SELECT id FROM sys_dict_type WHERE dict_key = 'knowledge_base') " +
|
||||
"AND dict_label LIKE ?", "%" + keyword + "%");
|
||||
}
|
||||
|
||||
/** 公告板 */
|
||||
public List<Map<String, Object>> getNotices(String noticeType) {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT dict_label, dict_value, created_at FROM sys_dict_data WHERE dict_type_id = " +
|
||||
"(SELECT id FROM sys_dict_type WHERE dict_key = ?) ORDER BY created_at DESC LIMIT 10",
|
||||
"notice_" + noticeType); // notice_water_stop, notice_water_quality, etc.
|
||||
}
|
||||
|
||||
/** KPI 指标 */
|
||||
public Map<String, Object> getKpi() {
|
||||
return jdbcTemplate.queryForMap("""
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM rev_bill WHERE status = 'pending' AND created_at > CURRENT_DATE - 30) AS pending_bills,
|
||||
(SELECT COUNT(*) FROM rev_install WHERE status IN ('pre_apply','engineering')) AS pending_installs,
|
||||
(SELECT ROUND(AVG(EXTRACT(EPOCH FROM (updated_at - created_at))/3600)::numeric, 2)
|
||||
FROM rev_install WHERE status = 'completed' AND created_at > CURRENT_DATE - 30) AS avg_install_hours
|
||||
""");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InstallService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 预受理申请 */
|
||||
public Map<String, Object> preApply(String name, String phone, String area, String address, String customerType, String caliber) {
|
||||
String appNo = "INS-" + System.currentTimeMillis();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_install (application_no, applicant_name, applicant_phone, area, address, customer_type, caliber, status) VALUES (?,?,?,?,?,?,?,?)",
|
||||
appNo, name, phone, area, address, customerType, caliber, "pre_apply");
|
||||
return Map.of("applicationNo", appNo, "status", "pre_apply");
|
||||
}
|
||||
|
||||
/** 工程申请 */
|
||||
public Map<String, Object> engineeringApply(String appNo, Map<String, Object> engData) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE rev_install SET status = 'engineering', updated_at = NOW() WHERE application_no = ?",
|
||||
appNo);
|
||||
return Map.of("applicationNo", appNo, "status", "engineering");
|
||||
}
|
||||
|
||||
/** 派单到施工 */
|
||||
public Map<String, Object> assignTask(String appNo, Long assigneeId) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE rev_install SET status = 'pending_review', updated_at = NOW() WHERE application_no = ?",
|
||||
appNo);
|
||||
return Map.of("applicationNo", appNo, "status", "pending_review", "assigneeId", assigneeId);
|
||||
}
|
||||
|
||||
/** 查询报装进度 */
|
||||
public Map<String, Object> getProgress(String appNo) {
|
||||
return jdbcTemplate.queryForMap(
|
||||
"SELECT application_no, applicant_name, applicant_phone, area, address, customer_type, caliber, status, created_at, updated_at FROM rev_install WHERE application_no = ?",
|
||||
appNo);
|
||||
}
|
||||
|
||||
/** 报装统计报表 */
|
||||
public List<Map<String, Object>> getStatsReport(String area, LocalDate start, LocalDate end) {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT area, customer_type, status, COUNT(*) as count FROM rev_install WHERE created_at BETWEEN ? AND ? GROUP BY area, customer_type, status",
|
||||
start, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeterService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 水表入库 */
|
||||
public void stockIn(String meterNo, String caliber, String meterType, String manufacturer, int quantity) {
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
String no = meterNo + "-" + (i + 1);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_meter (meter_no, caliber, meter_type, manufacturer, status) VALUES (?,?,?,?,?)",
|
||||
no, caliber, meterType, manufacturer, "warehouse");
|
||||
}
|
||||
log.info("Meter stock in: {} x{}", meterNo, quantity);
|
||||
}
|
||||
|
||||
/** 水表出库安装 */
|
||||
@Transactional
|
||||
public void install(Long meterId, Long customerId, String address, BigDecimal initialReading) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE rev_meter SET customer_id = ?, install_address = ?, initial_reading = ?, current_reading = ?, status = 'active', install_date = CURRENT_DATE WHERE id = ?",
|
||||
customerId, address, initialReading, initialReading, meterId);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_meter_log (meter_id, operation_type, old_reading, new_reading, remark) VALUES (?,?,?,?,?)",
|
||||
meterId, "install", BigDecimal.ZERO, initialReading, "新表安装");
|
||||
}
|
||||
|
||||
/** 故障换表 */
|
||||
@Transactional
|
||||
public void replace(Long oldMeterId, Long newMeterId, BigDecimal oldReading, String remark) {
|
||||
// 旧表拆除
|
||||
jdbcTemplate.update("UPDATE rev_meter SET status = 'dismantled', current_reading = ? WHERE id = ?", oldReading, oldMeterId);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_meter_log (meter_id, operation_type, old_reading, remark) VALUES (?,?,?,?)",
|
||||
oldMeterId, "dismantle", oldReading, remark);
|
||||
|
||||
// 新表安装(继承客户信息)
|
||||
Map<String, Object> oldMeter = jdbcTemplate.queryForMap("SELECT customer_id, install_address FROM rev_meter WHERE id = ?", oldMeterId);
|
||||
jdbcTemplate.update(
|
||||
"UPDATE rev_meter SET customer_id = ?, install_address = ?, status = 'active', install_date = CURRENT_DATE WHERE id = ?",
|
||||
oldMeter.get("customer_id"), oldMeter.get("install_address"), newMeterId);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_meter_log (meter_id, operation_type, new_meter_no, remark) VALUES (?,?,?,?)",
|
||||
newMeterId, "change", String.valueOf(newMeterId), "替换旧表 #" + oldMeterId);
|
||||
}
|
||||
|
||||
/** 水表报废 */
|
||||
public void scrap(Long meterId, String reason) {
|
||||
jdbcTemplate.update("UPDATE rev_meter SET status = 'scrapped' WHERE id = ?", meterId);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_meter_log (meter_id, operation_type, remark) VALUES (?,?,?)",
|
||||
meterId, "scrap", reason);
|
||||
}
|
||||
|
||||
/** 查询水表生命周期记录 */
|
||||
public List<Map<String, Object>> getLifecycle(Long meterId) {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT * FROM rev_meter_log WHERE meter_id = ? ORDER BY created_at",
|
||||
meterId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RemoteReadingService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 远传集抄:批量采集 */
|
||||
public Map<String, Object> batchRead(String area) {
|
||||
List<Map<String, Object>> meters = jdbcTemplate.queryForList(
|
||||
"SELECT rm.id, rm.meter_no, rm.current_reading, i.device_sn " +
|
||||
"FROM rev_meter rm LEFT JOIN iot_device i ON rm.device_id = i.id " +
|
||||
"JOIN rev_customer c ON rm.customer_id = c.id " +
|
||||
"WHERE rm.status = 'active' AND c.area = ?", area);
|
||||
|
||||
int success = 0, failed = 0;
|
||||
String period = java.time.YearMonth.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
|
||||
for (Map<String, Object> m : meters) {
|
||||
try {
|
||||
String deviceSn = (String) m.get("device_sn");
|
||||
// 从 IoT 平台获取实时读数(mock: 随机增量)
|
||||
BigDecimal prev = m.get("current_reading") != null ? (BigDecimal) m.get("current_reading") : BigDecimal.ZERO;
|
||||
BigDecimal curr = prev.add(BigDecimal.valueOf(new Random().nextDouble() * 50));
|
||||
BigDecimal consumption = curr.subtract(prev);
|
||||
if (consumption.compareTo(BigDecimal.ZERO) < 0) consumption = BigDecimal.ZERO;
|
||||
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO rev_reading (meter_id, reading_date, reading_period, prev_reading, curr_reading, consumption, read_type) " +
|
||||
"VALUES (?, CURRENT_DATE, ?, ?, ?, ?, 'remote')",
|
||||
m.get("id"), period, prev, curr, consumption);
|
||||
jdbcTemplate.update("UPDATE rev_meter SET current_reading = ? WHERE id = ?", curr, m.get("id"));
|
||||
success++;
|
||||
} catch (Exception e) {
|
||||
failed++;
|
||||
log.warn("Read failed for meter {}: {}", m.get("meter_no"), e.getMessage());
|
||||
}
|
||||
}
|
||||
log.info("Batch read: area={} success={} failed={}", area, success, failed);
|
||||
return Map.of("area", area, "success", success, "failed", failed, "period", period);
|
||||
}
|
||||
|
||||
/** DMA 分区漏损分析 */
|
||||
public Map<String, Object> dmaAnalysis(String area, String dateStr) {
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList(
|
||||
"SELECT area, SUM(consumption) as total_consumption, COUNT(DISTINCT rm.id) as meter_count " +
|
||||
"FROM rev_reading rr JOIN rev_meter rm ON rr.meter_id = rm.id " +
|
||||
"JOIN rev_customer c ON rm.customer_id = c.id " +
|
||||
"WHERE c.area = ? AND rr.reading_period = ? GROUP BY area",
|
||||
area, dateStr);
|
||||
|
||||
// 漏损率 = 1 - (售水量/供水量)
|
||||
Map<String, Object> dma = new HashMap<>(result.isEmpty() ? Map.of() : result.get(0));
|
||||
dma.put("supplyEstimate", 1000); // TODO: 从水厂出水量获取
|
||||
dma.put("leakRate", "分析中");
|
||||
return dma;
|
||||
}
|
||||
|
||||
/** 大表监控 (DN80+) */
|
||||
public List<Map<String, Object>> largeMeterMonitor() {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT rm.*, c.customer_name, c.area, i.device_sn, i.status as device_status " +
|
||||
"FROM rev_meter rm JOIN rev_customer c ON rm.customer_id = c.id " +
|
||||
"LEFT JOIN iot_device i ON rm.device_id = i.id " +
|
||||
"WHERE rm.caliber IN ('DN80','DN100','DN150','DN200','DN300','DN400') AND rm.status = 'active'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import cn.dev33.satoken.secure.BCrypt;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RevenueBaseService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// ========== SSO 单点登录 ==========
|
||||
public Map<String, Object> ssoLogin(String username, String password, String appType) {
|
||||
// 从统一用户表验证
|
||||
Map<String, Object> user = jdbcTemplate.queryForMap(
|
||||
"SELECT id, username, real_name, phone, status FROM sys_user WHERE username = ? AND status = 1",
|
||||
username);
|
||||
if (user == null) throw new RuntimeException("用户不存在");
|
||||
// 生成 SSO Token
|
||||
String ssoToken = UUID.randomUUID().toString();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_oper_log (user_id, username, module, operation, request_url) VALUES (?,?,?,?,?)",
|
||||
user.get("id"), username, "revenue", "sso_login", "/revenue/auth/sso");
|
||||
user.put("ssoToken", ssoToken);
|
||||
user.put("appType", appType);
|
||||
return user;
|
||||
}
|
||||
|
||||
// ========== 应用接入管理 ==========
|
||||
public void registerApp(String appName, String appKey, String appSecret, String redirectUri) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_dict_data (dict_type_id, dict_label, dict_value) " +
|
||||
"SELECT id, ?, ? FROM sys_dict_type WHERE dict_key = 'app_config'",
|
||||
appName, appKey + ":" + appSecret + ":" + redirectUri);
|
||||
}
|
||||
|
||||
// ========== 运维审计 ==========
|
||||
public void auditLog(Long userId, String action, String target, String detail) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_oper_log (user_id, module, operation, request_url, request_params) VALUES (?,?,?,?,?)",
|
||||
userId, "revenue_audit", action, target, detail);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 微信用户绑定 */
|
||||
public Map<String, Object> bindUser(String wechatOpenId, String customerNo, String phone) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE rev_customer SET phone = COALESCE(phone, ?) WHERE customer_no = ?",
|
||||
phone, customerNo);
|
||||
// 存储微信绑定关系
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_dict_data (dict_type_id, dict_label, dict_value) " +
|
||||
"SELECT id, ?, ? FROM sys_dict_type WHERE dict_key = 'wechat_bind' " +
|
||||
"ON CONFLICT DO NOTHING",
|
||||
wechatOpenId, customerNo);
|
||||
return Map.of("openId", wechatOpenId, "customerNo", customerNo, "status", "bound");
|
||||
}
|
||||
|
||||
/** 在线缴费(微信支付预下单) */
|
||||
public Map<String, Object> wechatPayPrepay(String customerNo, String billPeriod, BigDecimal amount) {
|
||||
String orderNo = "WXPAY-" + System.currentTimeMillis();
|
||||
Map<String, Object> bill = jdbcTemplate.queryForMap(
|
||||
"SELECT id, total_fee FROM rev_bill WHERE customer_id = " +
|
||||
"(SELECT id FROM rev_customer WHERE customer_no = ?) AND bill_period = ? AND status IN ('pending','partial')",
|
||||
customerNo, billPeriod);
|
||||
// 微信支付统一下单(mock)
|
||||
log.info("WeChat Pay prepay: order={} bill={} amount={}", orderNo, bill.get("id"), amount);
|
||||
return Map.of("orderNo", orderNo, "prepayId", "wx" + orderNo, "amount", amount);
|
||||
}
|
||||
|
||||
/** AI 客服问答 */
|
||||
public String aiAnswer(String question) {
|
||||
// 简易关键词匹配
|
||||
Map<String, String> qa = new LinkedHashMap<>();
|
||||
qa.put("水费", "您可以发送户号查询水费账单,或通过在线缴费功能直接支付。");
|
||||
qa.put("停水", "请查看停水公告了解最新停水计划。如有紧急停水,请拨打客服热线。");
|
||||
qa.put("报装", "新装水表可通过网上营业厅-业务办理-报装申请提交,我们会安排现场踏勘。");
|
||||
qa.put("水质", "水质报告每月更新,详见水质公告。如发现水质异常请立即联系我们。");
|
||||
qa.put("发票", "缴费后可在电子发票中申请开具电子发票,发送到您的微信或邮箱。");
|
||||
qa.put("过户", "房屋买卖后请携带房产证和身份证前往营业厅办理过户手续。");
|
||||
|
||||
for (Map.Entry<String, String> e : qa.entrySet()) {
|
||||
if (question.contains(e.getKey())) return e.getValue();
|
||||
}
|
||||
return "您好!我是智慧水务AI客服,您可以问我关于水费、报装、停水、水质、发票等问题。如需人工服务请转接客服热线。";
|
||||
}
|
||||
|
||||
/** 后台公告管理 */
|
||||
public void publishNotice(String type, String title, String content) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_dict_data (dict_type_id, dict_label, dict_value) " +
|
||||
"SELECT id, ?, ? FROM sys_dict_type WHERE dict_key = ?",
|
||||
title, content, "notice_" + type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WorkOrderService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 创建工单 */
|
||||
public Map<String, Object> create(String title, String type, String priority, Long reporterId,
|
||||
String reporterName, String description, String area) {
|
||||
String woNo = "WO-" + System.currentTimeMillis();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO patrol_task (task_name, task_date, status) VALUES (?, CURRENT_DATE, 'pending')",
|
||||
title + "[" + woNo + "]");
|
||||
log.info("WorkOrder created: {} type={} priority={}", woNo, type, priority);
|
||||
return Map.of("woNo", woNo, "title", title, "status", "pending", "area", area);
|
||||
}
|
||||
|
||||
/** 工单分派 */
|
||||
public Map<String, Object> assign(String woNo, Long assigneeId, String assigneeName) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE patrol_task SET assignee_id = ?, status = 'in_progress' WHERE task_name LIKE ?",
|
||||
assigneeId, "%" + woNo + "%");
|
||||
return Map.of("woNo", woNo, "assigneeId", assigneeId, "status", "assigned");
|
||||
}
|
||||
|
||||
/** 工单处理完成 */
|
||||
public Map<String, Object> complete(String woNo, String result, List<String> photoUrls) {
|
||||
jdbcTemplate.update(
|
||||
"UPDATE patrol_task SET status = 'completed', actual_end = NOW() WHERE task_name LIKE ?",
|
||||
"%" + woNo + "%");
|
||||
return Map.of("woNo", woNo, "status", "completed", "photos", photoUrls);
|
||||
}
|
||||
|
||||
/** 工单统计 */
|
||||
public Map<String, Object> stats(String area) {
|
||||
return jdbcTemplate.queryForMap(
|
||||
"SELECT status, COUNT(*) as count FROM patrol_task WHERE task_date >= CURRENT_DATE - 30 GROUP BY status");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user