diff --git a/wm-revenue/src/main/java/com/water/revenue/controller/MeterWorkController.java b/wm-revenue/src/main/java/com/water/revenue/controller/MeterWorkController.java new file mode 100644 index 00000000..a3f791b1 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/controller/MeterWorkController.java @@ -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> batchRead(@PathVariable String area) { + return R.ok(rrService.batchRead(area)); + } + + @GetMapping("/dma/analysis") + public R> dmaAnalysis(@RequestParam String area, @RequestParam String period) { + return R.ok(rrService.dmaAnalysis(area, period)); + } + + @GetMapping("/meter/large") + public R>> largeMeters() { + return R.ok(rrService.largeMeterMonitor()); + } + + // ---- 工单 ---- + @PostMapping("/work-order") + public R> createWO(@RequestBody Map 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> assign(@RequestBody Map 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> complete(@RequestBody Map req) { + @SuppressWarnings("unchecked") + List photos = (List) req.getOrDefault("photos", List.of()); + return R.ok(woService.complete((String) req.get("woNo"), (String) req.get("result"), photos)); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/controller/RevenueController.java b/wm-revenue/src/main/java/com/water/revenue/controller/RevenueController.java new file mode 100644 index 00000000..f1ee58d2 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/controller/RevenueController.java @@ -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> ssoLogin(@RequestBody Map req) { + return R.ok(baseService.ssoLogin(req.get("username"), req.get("password"), req.get("appType"))); + } + + // ---- 报装管理 ---- + @PostMapping("/install/pre-apply") + public R> preApply(@RequestBody Map 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> progress(@PathVariable String appNo) { + return R.ok(installService.getProgress(appNo)); + } + + // ---- 营业收费 ---- + @PostMapping("/billing/generate") + public R> generateBill(@RequestParam Long readingId) { + return R.ok(billingService.generateBill(readingId)); + } + + @PostMapping("/billing/pay") + public R> pay(@RequestBody Map 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 stockIn(@RequestBody Map 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 replace(@RequestBody Map 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>> lifecycle(@PathVariable Long meterId) { + return R.ok(meterService.getLifecycle(meterId)); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/controller/WechatController.java b/wm-revenue/src/main/java/com/water/revenue/controller/WechatController.java new file mode 100644 index 00000000..a2d0b0e3 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/controller/WechatController.java @@ -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>> queryBills(@RequestParam String phoneOrNo) { + return R.ok(csc.queryBills(phoneOrNo)); + } + + @GetMapping("/knowledge/search") + public R>> searchKnowledge(@RequestParam String keyword) { + return R.ok(csc.searchKnowledge(keyword)); + } + + @GetMapping("/notices/{type}") + public R>> notices(@PathVariable String type) { + return R.ok(csc.getNotices(type)); + } + + @GetMapping("/kpi") + public R> kpi() { return R.ok(csc.getKpi()); } + + // ---- 微信网厅 ---- + @PostMapping("/wechat/bind") + public R> bind(@RequestBody Map req) { + return R.ok(wechatService.bindUser(req.get("openId"), req.get("customerNo"), req.get("phone"))); + } + + @PostMapping("/wechat/pay/prepay") + public R> prepay(@RequestBody Map req) { + return R.ok(wechatService.wechatPayPrepay( + req.get("customerNo"), req.get("billPeriod"), + new BigDecimal(req.get("amount")))); + } + + @GetMapping("/wechat/ai-answer") + public R aiAnswer(@RequestParam String question) { + return R.ok(wechatService.aiAnswer(question)); + } + + @PostMapping("/wechat/notice/publish") + public R publishNotice(@RequestBody Map req) { + wechatService.publishNotice(req.get("type"), req.get("title"), req.get("content")); + return R.ok("公告已发布"); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/BillingService.java b/wm-revenue/src/main/java/com/water/revenue/service/BillingService.java new file mode 100644 index 00000000..514ad65e --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/BillingService.java @@ -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 generateBill(Long readingId) { + Map 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> 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 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 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> 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); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/CustomerServiceCenter.java b/wm-revenue/src/main/java/com/water/revenue/service/CustomerServiceCenter.java new file mode 100644 index 00000000..031b9101 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/CustomerServiceCenter.java @@ -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> 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> 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> 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 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 + """); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/InstallService.java b/wm-revenue/src/main/java/com/water/revenue/service/InstallService.java new file mode 100644 index 00000000..fedc7d36 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/InstallService.java @@ -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 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 engineeringApply(String appNo, Map 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 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 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> 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); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/MeterService.java b/wm-revenue/src/main/java/com/water/revenue/service/MeterService.java new file mode 100644 index 00000000..53f6ebf4 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/MeterService.java @@ -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 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> getLifecycle(Long meterId) { + return jdbcTemplate.queryForList( + "SELECT * FROM rev_meter_log WHERE meter_id = ? ORDER BY created_at", + meterId); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/RemoteReadingService.java b/wm-revenue/src/main/java/com/water/revenue/service/RemoteReadingService.java new file mode 100644 index 00000000..6113705e --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/RemoteReadingService.java @@ -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 batchRead(String area) { + List> 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 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 dmaAnalysis(String area, String dateStr) { + List> 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 dma = new HashMap<>(result.isEmpty() ? Map.of() : result.get(0)); + dma.put("supplyEstimate", 1000); // TODO: 从水厂出水量获取 + dma.put("leakRate", "分析中"); + return dma; + } + + /** 大表监控 (DN80+) */ + public List> 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'"); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/RevenueBaseService.java b/wm-revenue/src/main/java/com/water/revenue/service/RevenueBaseService.java new file mode 100644 index 00000000..39b5c4c2 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/RevenueBaseService.java @@ -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 ssoLogin(String username, String password, String appType) { + // 从统一用户表验证 + Map 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); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/WechatService.java b/wm-revenue/src/main/java/com/water/revenue/service/WechatService.java new file mode 100644 index 00000000..e0e48f04 --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/WechatService.java @@ -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 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 wechatPayPrepay(String customerNo, String billPeriod, BigDecimal amount) { + String orderNo = "WXPAY-" + System.currentTimeMillis(); + Map 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 qa = new LinkedHashMap<>(); + qa.put("水费", "您可以发送户号查询水费账单,或通过在线缴费功能直接支付。"); + qa.put("停水", "请查看停水公告了解最新停水计划。如有紧急停水,请拨打客服热线。"); + qa.put("报装", "新装水表可通过网上营业厅-业务办理-报装申请提交,我们会安排现场踏勘。"); + qa.put("水质", "水质报告每月更新,详见水质公告。如发现水质异常请立即联系我们。"); + qa.put("发票", "缴费后可在电子发票中申请开具电子发票,发送到您的微信或邮箱。"); + qa.put("过户", "房屋买卖后请携带房产证和身份证前往营业厅办理过户手续。"); + + for (Map.Entry 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); + } +} diff --git a/wm-revenue/src/main/java/com/water/revenue/service/WorkOrderService.java b/wm-revenue/src/main/java/com/water/revenue/service/WorkOrderService.java new file mode 100644 index 00000000..9aa6672c --- /dev/null +++ b/wm-revenue/src/main/java/com/water/revenue/service/WorkOrderService.java @@ -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 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 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 complete(String woNo, String result, List 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 stats(String area) { + return jdbcTemplate.queryForMap( + "SELECT status, COUNT(*) as count FROM patrol_task WHERE task_date >= CURRENT_DATE - 30 GROUP BY status"); + } +}