Ver código fonte

Add CRM modules: Report, PricingRule, Ticket, Satisfaction, ChatLog with entities, mappers, services, controllers and SQL tables

docker 2 meses atrás
pai
commit
9b8e17e728
31 arquivos alterados com 1230 adições e 0 exclusões
  1. 124 0
      backend/sql/init.sql
  2. 56 0
      backend/src/main/java/com/oms/controller/ChatLogController.java
  3. 50 0
      backend/src/main/java/com/oms/controller/PricingRuleController.java
  4. 56 0
      backend/src/main/java/com/oms/controller/ReportController.java
  5. 48 0
      backend/src/main/java/com/oms/controller/ReportDataController.java
  6. 55 0
      backend/src/main/java/com/oms/controller/SatisfactionController.java
  7. 64 0
      backend/src/main/java/com/oms/controller/TicketController.java
  8. 20 0
      backend/src/main/java/com/oms/dto/ChatLogDTO.java
  9. 19 0
      backend/src/main/java/com/oms/dto/PricingRuleDTO.java
  10. 13 0
      backend/src/main/java/com/oms/dto/ReportDTO.java
  11. 19 0
      backend/src/main/java/com/oms/dto/ReportDataDTO.java
  12. 16 0
      backend/src/main/java/com/oms/dto/SatisfactionDTO.java
  13. 16 0
      backend/src/main/java/com/oms/dto/TicketDTO.java
  14. 28 0
      backend/src/main/java/com/oms/entity/ChatLog.java
  15. 31 0
      backend/src/main/java/com/oms/entity/PricingRule.java
  16. 26 0
      backend/src/main/java/com/oms/entity/Report.java
  17. 26 0
      backend/src/main/java/com/oms/entity/ReportData.java
  18. 29 0
      backend/src/main/java/com/oms/entity/Satisfaction.java
  19. 32 0
      backend/src/main/java/com/oms/entity/Ticket.java
  20. 9 0
      backend/src/main/java/com/oms/mapper/ChatLogMapper.java
  21. 9 0
      backend/src/main/java/com/oms/mapper/PricingRuleMapper.java
  22. 9 0
      backend/src/main/java/com/oms/mapper/ReportDataMapper.java
  23. 9 0
      backend/src/main/java/com/oms/mapper/ReportMapper.java
  24. 9 0
      backend/src/main/java/com/oms/mapper/SatisfactionMapper.java
  25. 9 0
      backend/src/main/java/com/oms/mapper/TicketMapper.java
  26. 68 0
      backend/src/main/java/com/oms/service/ChatLogService.java
  27. 89 0
      backend/src/main/java/com/oms/service/PricingRuleService.java
  28. 63 0
      backend/src/main/java/com/oms/service/ReportDataService.java
  29. 68 0
      backend/src/main/java/com/oms/service/ReportService.java
  30. 75 0
      backend/src/main/java/com/oms/service/SatisfactionService.java
  31. 85 0
      backend/src/main/java/com/oms/service/TicketService.java

+ 124 - 0
backend/sql/init.sql

@@ -1256,6 +1256,130 @@ CREATE TABLE inventory_turnover (
     PRIMARY KEY (id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存周转表';
 
+-- =============================================
+-- 17. CRM扩展 (crm_report, crm_report_data, crm_pricing_rule, crm_ticket, crm_satisfaction, crm_chat_log)
+-- =============================================
+
+CREATE TABLE crm_report (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    report_no VARCHAR(64) NOT NULL COMMENT '报表编号',
+    report_name VARCHAR(256) NOT NULL COMMENT '报表名称',
+    report_type VARCHAR(64) COMMENT '报表类型',
+    period_type VARCHAR(32) COMMENT '周期类型',
+    start_date DATETIME COMMENT '开始日期',
+    end_date DATETIME COMMENT '结束日期',
+    status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '状态',
+    created_by VARCHAR(64) COMMENT '创建人',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_report_no (report_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报表表';
+
+CREATE TABLE crm_report_data (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    report_id BIGINT NOT NULL COMMENT '报表ID',
+    data_key VARCHAR(128) NOT NULL COMMENT '数据键',
+    data_value TEXT COMMENT '数据值',
+    data_type VARCHAR(32) COMMENT '数据类型',
+    category VARCHAR(64) COMMENT '分类',
+    sub_category VARCHAR(64) COMMENT '子分类',
+    numeric_value DECIMAL(16,4) COMMENT '数值',
+    string_value VARCHAR(512) COMMENT '字符串值',
+    report_date DATETIME COMMENT '报表日期',
+    dimensions VARCHAR(512) COMMENT '维度JSON',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id),
+    KEY idx_report_id (report_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报表数据表';
+
+CREATE TABLE crm_pricing_rule (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    rule_no VARCHAR(64) NOT NULL COMMENT '规则编号',
+    rule_name VARCHAR(256) NOT NULL COMMENT '规则名称',
+    rule_type VARCHAR(32) COMMENT '规则类型',
+    product_sku_id VARCHAR(64) COMMENT '商品SKU ID',
+    channel_id VARCHAR(64) COMMENT '渠道ID',
+    base_price DECIMAL(12,2) COMMENT '原价',
+    discount_rate DECIMAL(5,2) COMMENT '折扣率%',
+    fixed_price DECIMAL(12,2) COMMENT '固定价格',
+    priority INT NOT NULL DEFAULT 0 COMMENT '优先级',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    start_date DATETIME COMMENT '开始日期',
+    end_date DATETIME COMMENT '结束日期',
+    created_by VARCHAR(64) COMMENT '创建人',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_rule_no (rule_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='价格规则表';
+
+CREATE TABLE crm_ticket (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    ticket_no VARCHAR(64) NOT NULL COMMENT '工单编号',
+    title VARCHAR(512) NOT NULL COMMENT '工单标题',
+    content TEXT COMMENT '工单内容',
+    ticket_type VARCHAR(32) COMMENT '工单类型',
+    priority VARCHAR(32) COMMENT '优先级',
+    status VARCHAR(32) NOT NULL DEFAULT 'OPEN' COMMENT '状态',
+    customer_id BIGINT COMMENT '客户ID',
+    customer_name VARCHAR(128) COMMENT '客户姓名',
+    customer_email VARCHAR(256) COMMENT '客户邮箱',
+    assigned_to BIGINT COMMENT '分配给',
+    assigned_name VARCHAR(128) COMMENT '处理人姓名',
+    assigned_at DATETIME COMMENT '分配时间',
+    resolved_at DATETIME COMMENT '解决时间',
+    resolution TEXT COMMENT '解决方案',
+    created_by VARCHAR(64) COMMENT '创建人',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_ticket_no (ticket_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工单表';
+
+CREATE TABLE crm_satisfaction (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    satisfaction_no VARCHAR(64) NOT NULL COMMENT '满意度编号',
+    order_id BIGINT COMMENT '订单ID',
+    order_no VARCHAR(64) COMMENT '订单编号',
+    customer_id BIGINT COMMENT '客户ID',
+    customer_name VARCHAR(128) COMMENT '客户姓名',
+    score INT NOT NULL COMMENT '评分',
+    rating VARCHAR(32) COMMENT '评级',
+    feedback TEXT COMMENT '反馈内容',
+    response TEXT COMMENT '回复内容',
+    response_by VARCHAR(64) COMMENT '回复人',
+    responded_at DATETIME COMMENT '回复时间',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_satisfaction_no (satisfaction_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='满意度表';
+
+CREATE TABLE crm_chat_log (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    session_no VARCHAR(64) COMMENT '会话编号',
+    session_id VARCHAR(128) COMMENT '会话ID',
+    customer_id BIGINT COMMENT '客户ID',
+    customer_name VARCHAR(128) COMMENT '客户姓名',
+    channel VARCHAR(64) COMMENT '渠道',
+    message_type VARCHAR(32) COMMENT '消息类型',
+    message_content TEXT COMMENT '消息内容',
+    direction VARCHAR(16) COMMENT '方向 IN/OUT',
+    sender_type VARCHAR(32) COMMENT '发送者类型',
+    sender_id BIGINT COMMENT '发送者ID',
+    sender_name VARCHAR(128) COMMENT '发送者姓名',
+    agent_name VARCHAR(128) COMMENT '客服姓名',
+    status VARCHAR(32) COMMENT '状态',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id),
+    KEY idx_session_id (session_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='聊天记录表';
+
 -- =============================================
 -- 初始化数据
 -- =============================================

+ 56 - 0
backend/src/main/java/com/oms/controller/ChatLogController.java

@@ -0,0 +1,56 @@
+package com.oms.controller;
+
+import com.oms.dto.ChatLogDTO;
+import com.oms.entity.ChatLog;
+import com.oms.service.ChatLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/crm/chat-logs")
+public class ChatLogController {
+
+    @Autowired
+    private ChatLogService chatLogService;
+
+    @GetMapping
+    public ResponseEntity<Map<String, List<?>>> getChatLogs(
+            @RequestParam(required = false) String sessionId,
+            @RequestParam(required = false) String status) {
+        List<ChatLog> items = chatLogService.listChatLogs(sessionId, status);
+        return ResponseEntity.ok(Map.of("items", items));
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<ChatLog> getChatLog(@PathVariable Long id) {
+        ChatLog log = chatLogService.getChatLog(id);
+        return log != null ? ResponseEntity.ok(log) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<ChatLog> createChatLog(@RequestBody ChatLogDTO dto) {
+        ChatLog log = chatLogService.createChatLog(dto);
+        return ResponseEntity.ok(log);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<ChatLog> updateChatLog(@PathVariable Long id, @RequestBody ChatLogDTO dto) {
+        ChatLog log = chatLogService.updateChatLog(id, dto);
+        return log != null ? ResponseEntity.ok(log) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping("/{id}/mark")
+    public ResponseEntity<Map<String, Object>> markChatLog(@PathVariable Long id, @RequestBody Map<String, String> body) {
+        ChatLog log = chatLogService.markChatLog(id, body.get("tag"));
+        return ResponseEntity.ok(Map.of("success", log != null));
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteChatLog(@PathVariable Long id) {
+        chatLogService.deleteChatLog(id);
+        return ResponseEntity.ok().build();
+    }
+}

+ 50 - 0
backend/src/main/java/com/oms/controller/PricingRuleController.java

@@ -0,0 +1,50 @@
+package com.oms.controller;
+
+import com.oms.dto.PricingRuleDTO;
+import com.oms.entity.PricingRule;
+import com.oms.service.PricingRuleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/pricing-rules")
+public class PricingRuleController {
+
+    @Autowired
+    private PricingRuleService pricingRuleService;
+
+    @GetMapping
+    public ResponseEntity<Map<String, List<?>>> getPricingRules(
+            @RequestParam(required = false) String ruleType,
+            @RequestParam(required = false) String status) {
+        List<PricingRule> items = pricingRuleService.listRules(ruleType, status);
+        return ResponseEntity.ok(Map.of("items", items));
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<PricingRule> getPricingRule(@PathVariable Long id) {
+        PricingRule rule = pricingRuleService.getRule(id);
+        return rule != null ? ResponseEntity.ok(rule) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<PricingRule> createPricingRule(@RequestBody PricingRuleDTO dto) {
+        PricingRule rule = pricingRuleService.createRule(dto);
+        return ResponseEntity.ok(rule);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<PricingRule> updatePricingRule(@PathVariable Long id, @RequestBody PricingRuleDTO dto) {
+        PricingRule rule = pricingRuleService.updateRule(id, dto);
+        return rule != null ? ResponseEntity.ok(rule) : ResponseEntity.notFound().build();
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deletePricingRule(@PathVariable Long id) {
+        pricingRuleService.deleteRule(id);
+        return ResponseEntity.ok().build();
+    }
+}

+ 56 - 0
backend/src/main/java/com/oms/controller/ReportController.java

@@ -0,0 +1,56 @@
+package com.oms.controller;
+
+import com.oms.dto.ReportDTO;
+import com.oms.entity.Report;
+import com.oms.service.ReportService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/reports")
+public class ReportController {
+
+    @Autowired
+    private ReportService reportService;
+
+    @GetMapping
+    public ResponseEntity<Map<String, List<?>>> getReports(
+            @RequestParam(required = false) String reportType,
+            @RequestParam(required = false) String status) {
+        List<Report> items = reportService.listReports(reportType, status);
+        return ResponseEntity.ok(Map.of("items", items));
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Report> getReport(@PathVariable Long id) {
+        Report report = reportService.getReport(id);
+        return report != null ? ResponseEntity.ok(report) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<Report> createReport(@RequestBody ReportDTO dto) {
+        Report report = reportService.createReport(dto);
+        return ResponseEntity.ok(report);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<Report> updateReport(@PathVariable Long id, @RequestBody ReportDTO dto) {
+        Report report = reportService.updateReport(id, dto);
+        return report != null ? ResponseEntity.ok(report) : ResponseEntity.notFound().build();
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteReport(@PathVariable Long id) {
+        reportService.deleteReport(id);
+        return ResponseEntity.ok().build();
+    }
+
+    @PostMapping("/{id}/generate")
+    public ResponseEntity<Report> generateReport(@PathVariable Long id) {
+        Report report = reportService.generateReport(id);
+        return report != null ? ResponseEntity.ok(report) : ResponseEntity.notFound().build();
+    }
+}

+ 48 - 0
backend/src/main/java/com/oms/controller/ReportDataController.java

@@ -0,0 +1,48 @@
+package com.oms.controller;
+
+import com.oms.dto.ReportDataDTO;
+import com.oms.entity.ReportData;
+import com.oms.service.ReportDataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/report-data")
+public class ReportDataController {
+
+    @Autowired
+    private ReportDataService reportDataService;
+
+    @GetMapping
+    public ResponseEntity<Map<String, List<?>>> getReportData(@RequestParam Long reportId) {
+        List<ReportData> items = reportDataService.listByReportId(reportId);
+        return ResponseEntity.ok(Map.of("items", items));
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<ReportData> getReportDataById(@PathVariable Long id) {
+        ReportData data = reportDataService.getReportData(id);
+        return data != null ? ResponseEntity.ok(data) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<ReportData> createReportData(@RequestBody ReportDataDTO dto) {
+        ReportData data = reportDataService.createReportData(dto);
+        return ResponseEntity.ok(data);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<ReportData> updateReportData(@PathVariable Long id, @RequestBody ReportDataDTO dto) {
+        ReportData data = reportDataService.updateReportData(id, dto);
+        return data != null ? ResponseEntity.ok(data) : ResponseEntity.notFound().build();
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteReportData(@PathVariable Long id) {
+        reportDataService.deleteReportData(id);
+        return ResponseEntity.ok().build();
+    }
+}

+ 55 - 0
backend/src/main/java/com/oms/controller/SatisfactionController.java

@@ -0,0 +1,55 @@
+package com.oms.controller;
+
+import com.oms.dto.SatisfactionDTO;
+import com.oms.entity.Satisfaction;
+import com.oms.service.SatisfactionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/crm/satisfactions")
+public class SatisfactionController {
+
+    @Autowired
+    private SatisfactionService satisfactionService;
+
+    @GetMapping
+    public ResponseEntity<Map<String, List<?>>> getSatisfactions(
+            @RequestParam(required = false) String rating) {
+        List<Satisfaction> items = satisfactionService.listSatisfactions(rating);
+        return ResponseEntity.ok(Map.of("items", items));
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Satisfaction> getSatisfaction(@PathVariable Long id) {
+        Satisfaction sat = satisfactionService.getSatisfaction(id);
+        return sat != null ? ResponseEntity.ok(sat) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<Satisfaction> createSatisfaction(@RequestBody SatisfactionDTO dto) {
+        Satisfaction sat = satisfactionService.createSatisfaction(dto);
+        return ResponseEntity.ok(sat);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<Satisfaction> updateSatisfaction(@PathVariable Long id, @RequestBody SatisfactionDTO dto) {
+        Satisfaction sat = satisfactionService.updateSatisfaction(id, dto);
+        return sat != null ? ResponseEntity.ok(sat) : ResponseEntity.notFound().build();
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteSatisfaction(@PathVariable Long id) {
+        satisfactionService.deleteSatisfaction(id);
+        return ResponseEntity.ok().build();
+    }
+
+    @GetMapping("/average-score")
+    public ResponseEntity<Map<String, Double>> getAverageScore() {
+        Double avg = satisfactionService.getAverageScore();
+        return ResponseEntity.ok(Map.of("averageScore", avg));
+    }
+}

+ 64 - 0
backend/src/main/java/com/oms/controller/TicketController.java

@@ -0,0 +1,64 @@
+package com.oms.controller;
+
+import com.oms.dto.TicketDTO;
+import com.oms.entity.Ticket;
+import com.oms.service.TicketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/crm/tickets")
+public class TicketController {
+
+    @Autowired
+    private TicketService ticketService;
+
+    @GetMapping
+    public ResponseEntity<Map<String, List<?>>> getTickets(
+            @RequestParam(required = false) String status,
+            @RequestParam(required = false) String priority) {
+        List<Ticket> items = ticketService.listTickets(status, priority);
+        return ResponseEntity.ok(Map.of("items", items));
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Ticket> getTicket(@PathVariable Long id) {
+        Ticket ticket = ticketService.getTicket(id);
+        return ticket != null ? ResponseEntity.ok(ticket) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<Ticket> createTicket(@RequestBody TicketDTO dto) {
+        Ticket ticket = ticketService.createTicket(dto);
+        return ResponseEntity.ok(ticket);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<Ticket> updateTicket(@PathVariable Long id, @RequestBody TicketDTO dto) {
+        Ticket ticket = ticketService.updateTicket(id, dto);
+        return ticket != null ? ResponseEntity.ok(ticket) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping("/{id}/assign")
+    public ResponseEntity<Ticket> assignTicket(@PathVariable Long id, @RequestBody Map<String, Object> body) {
+        Long assignedTo = Long.valueOf(body.get("assignedTo").toString());
+        String assignedName = body.get("assignedName").toString();
+        Ticket ticket = ticketService.assignTicket(id, assignedTo, assignedName);
+        return ticket != null ? ResponseEntity.ok(ticket) : ResponseEntity.notFound().build();
+    }
+
+    @PostMapping("/{id}/resolve")
+    public ResponseEntity<Ticket> resolveTicket(@PathVariable Long id, @RequestBody Map<String, String> body) {
+        Ticket ticket = ticketService.resolveTicket(id, body.get("resolution"));
+        return ticket != null ? ResponseEntity.ok(ticket) : ResponseEntity.notFound().build();
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteTicket(@PathVariable Long id) {
+        ticketService.deleteTicket(id);
+        return ResponseEntity.ok().build();
+    }
+}

+ 20 - 0
backend/src/main/java/com/oms/dto/ChatLogDTO.java

@@ -0,0 +1,20 @@
+package com.oms.dto;
+
+import lombok.Data;
+
+@Data
+public class ChatLogDTO {
+    private String sessionNo;
+    private String sessionId;
+    private Long customerId;
+    private String customerName;
+    private String channel;
+    private String messageType;
+    private String messageContent;
+    private String direction;
+    private String senderType;
+    private Long senderId;
+    private String senderName;
+    private String agentName;
+    private String status;
+}

+ 19 - 0
backend/src/main/java/com/oms/dto/PricingRuleDTO.java

@@ -0,0 +1,19 @@
+package com.oms.dto;
+
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class PricingRuleDTO {
+    private String ruleName;
+    private String ruleType;
+    private String productSkuId;
+    private String channelId;
+    private BigDecimal basePrice;
+    private BigDecimal discountRate;
+    private BigDecimal fixedPrice;
+    private String priority;
+    private LocalDateTime startDate;
+    private LocalDateTime endDate;
+}

+ 13 - 0
backend/src/main/java/com/oms/dto/ReportDTO.java

@@ -0,0 +1,13 @@
+package com.oms.dto;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class ReportDTO {
+    private String reportName;
+    private String reportType;
+    private String periodType;
+    private LocalDateTime startDate;
+    private LocalDateTime endDate;
+}

+ 19 - 0
backend/src/main/java/com/oms/dto/ReportDataDTO.java

@@ -0,0 +1,19 @@
+package com.oms.dto;
+
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class ReportDataDTO {
+    private Long reportId;
+    private String dataKey;
+    private String dataValue;
+    private String dataType;
+    private String category;
+    private String subCategory;
+    private BigDecimal numericValue;
+    private String stringValue;
+    private LocalDateTime reportDate;
+    private String dimensions;
+}

+ 16 - 0
backend/src/main/java/com/oms/dto/SatisfactionDTO.java

@@ -0,0 +1,16 @@
+package com.oms.dto;
+
+import lombok.Data;
+
+@Data
+public class SatisfactionDTO {
+    private Long orderId;
+    private String orderNo;
+    private Long customerId;
+    private String customerName;
+    private Integer score;
+    private String feedback;
+    private String response;
+    private String responseBy;
+    private String updatedBy;
+}

+ 16 - 0
backend/src/main/java/com/oms/dto/TicketDTO.java

@@ -0,0 +1,16 @@
+package com.oms.dto;
+
+import lombok.Data;
+
+@Data
+public class TicketDTO {
+    private String title;
+    private String content;
+    private String ticketType;
+    private String priority;
+    private Long customerId;
+    private String customerName;
+    private String customerEmail;
+    private String createdBy;
+    private String updatedBy;
+}

+ 28 - 0
backend/src/main/java/com/oms/entity/ChatLog.java

@@ -0,0 +1,28 @@
+package com.oms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("crm_chat_log")
+public class ChatLog {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String sessionNo;
+    private String sessionId;
+    private Long customerId;
+    private String customerName;
+    private String channel;
+    private String messageType;
+    private String messageContent;
+    private String direction;
+    private String senderType;
+    private Long senderId;
+    private String senderName;
+    private String agentName;
+    private String status;
+    private LocalDateTime createdAt;
+}

+ 31 - 0
backend/src/main/java/com/oms/entity/PricingRule.java

@@ -0,0 +1,31 @@
+package com.oms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("crm_pricing_rule")
+public class PricingRule {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String ruleNo;
+    private String ruleName;
+    private String ruleType;
+    private String productSkuId;
+    private String channelId;
+    private BigDecimal basePrice;
+    private BigDecimal discountRate;
+    private BigDecimal fixedPrice;
+    private String priority;
+    private String status;
+    private LocalDateTime startDate;
+    private LocalDateTime endDate;
+    private String createdBy;
+    private LocalDateTime createdAt;
+    private String updatedBy;
+    private LocalDateTime updatedAt;
+}

+ 26 - 0
backend/src/main/java/com/oms/entity/Report.java

@@ -0,0 +1,26 @@
+package com.oms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("crm_report")
+public class Report {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String reportNo;
+    private String reportName;
+    private String reportType;
+    private String periodType;
+    private LocalDateTime startDate;
+    private LocalDateTime endDate;
+    private String status;
+    private String createdBy;
+    private LocalDateTime createdAt;
+    private String updatedBy;
+    private LocalDateTime updatedAt;
+}

+ 26 - 0
backend/src/main/java/com/oms/entity/ReportData.java

@@ -0,0 +1,26 @@
+package com.oms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("crm_report_data")
+public class ReportData {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long reportId;
+    private String dataKey;
+    private String dataValue;
+    private String dataType;
+    private String category;
+    private String subCategory;
+    private BigDecimal numericValue;
+    private String stringValue;
+    private LocalDateTime reportDate;
+    private String dimensions;
+    private LocalDateTime createdAt;
+}

+ 29 - 0
backend/src/main/java/com/oms/entity/Satisfaction.java

@@ -0,0 +1,29 @@
+package com.oms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("crm_satisfaction")
+public class Satisfaction {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String satisfactionNo;
+    private Long orderId;
+    private String orderNo;
+    private Long customerId;
+    private String customerName;
+    private Integer score;
+    private String rating;
+    private String feedback;
+    private String response;
+    private String responseBy;
+    private LocalDateTime respondedAt;
+    private LocalDateTime createdAt;
+    private String updatedBy;
+    private LocalDateTime updatedAt;
+}

+ 32 - 0
backend/src/main/java/com/oms/entity/Ticket.java

@@ -0,0 +1,32 @@
+package com.oms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("crm_ticket")
+public class Ticket {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String ticketNo;
+    private String title;
+    private String content;
+    private String ticketType;
+    private String priority;
+    private String status;
+    private Long customerId;
+    private String customerName;
+    private String customerEmail;
+    private Long assignedTo;
+    private String assignedName;
+    private LocalDateTime assignedAt;
+    private LocalDateTime resolvedAt;
+    private String resolution;
+    private String createdBy;
+    private LocalDateTime createdAt;
+    private String updatedBy;
+    private LocalDateTime updatedAt;
+}

+ 9 - 0
backend/src/main/java/com/oms/mapper/ChatLogMapper.java

@@ -0,0 +1,9 @@
+package com.oms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oms.entity.ChatLog;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ChatLogMapper extends BaseMapper<ChatLog> {
+}

+ 9 - 0
backend/src/main/java/com/oms/mapper/PricingRuleMapper.java

@@ -0,0 +1,9 @@
+package com.oms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oms.entity.PricingRule;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PricingRuleMapper extends BaseMapper<PricingRule> {
+}

+ 9 - 0
backend/src/main/java/com/oms/mapper/ReportDataMapper.java

@@ -0,0 +1,9 @@
+package com.oms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oms.entity.ReportData;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportDataMapper extends BaseMapper<ReportData> {
+}

+ 9 - 0
backend/src/main/java/com/oms/mapper/ReportMapper.java

@@ -0,0 +1,9 @@
+package com.oms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oms.entity.Report;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReportMapper extends BaseMapper<Report> {
+}

+ 9 - 0
backend/src/main/java/com/oms/mapper/SatisfactionMapper.java

@@ -0,0 +1,9 @@
+package com.oms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oms.entity.Satisfaction;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SatisfactionMapper extends BaseMapper<Satisfaction> {
+}

+ 9 - 0
backend/src/main/java/com/oms/mapper/TicketMapper.java

@@ -0,0 +1,9 @@
+package com.oms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oms.entity.Ticket;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TicketMapper extends BaseMapper<Ticket> {
+}

+ 68 - 0
backend/src/main/java/com/oms/service/ChatLogService.java

@@ -0,0 +1,68 @@
+package com.oms.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.oms.entity.ChatLog;
+import com.oms.mapper.ChatLogMapper;
+import com.oms.dto.ChatLogDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class ChatLogService {
+    private final ChatLogMapper mapper;
+
+    public List<ChatLog> listChatLogs(String sessionId, String status) {
+        LambdaQueryWrapper<ChatLog> wrapper = new LambdaQueryWrapper<>();
+        if (sessionId != null) wrapper.eq(ChatLog::getSessionId, sessionId);
+        if (status != null) wrapper.eq(ChatLog::getStatus, status);
+        wrapper.orderByDesc(ChatLog::getCreatedAt);
+        return mapper.selectList(wrapper);
+    }
+
+    public ChatLog getChatLog(Long id) {
+        return mapper.selectById(id);
+    }
+
+    public ChatLog createChatLog(ChatLogDTO dto) {
+        ChatLog log = new ChatLog();
+        log.setSessionNo(dto.getSessionNo());
+        log.setSessionId(dto.getSessionId());
+        log.setCustomerId(dto.getCustomerId());
+        log.setCustomerName(dto.getCustomerName());
+        log.setChannel(dto.getChannel());
+        log.setMessageType(dto.getMessageType());
+        log.setMessageContent(dto.getMessageContent());
+        log.setDirection(dto.getDirection());
+        log.setSenderType(dto.getSenderType());
+        log.setSenderId(dto.getSenderId());
+        log.setSenderName(dto.getSenderName());
+        log.setAgentName(dto.getAgentName());
+        log.setStatus(dto.getStatus());
+        log.setCreatedAt(LocalDateTime.now());
+        mapper.insert(log);
+        return log;
+    }
+
+    public ChatLog updateChatLog(Long id, ChatLogDTO dto) {
+        ChatLog log = mapper.selectById(id);
+        if (log == null) return null;
+        log.setStatus(dto.getStatus());
+        mapper.updateById(log);
+        return log;
+    }
+
+    public void deleteChatLog(Long id) {
+        mapper.deleteById(id);
+    }
+
+    public ChatLog markChatLog(Long id, String tag) {
+        ChatLog log = mapper.selectById(id);
+        if (log == null) return null;
+        log.setStatus(tag);
+        mapper.updateById(log);
+        return log;
+    }
+}

+ 89 - 0
backend/src/main/java/com/oms/service/PricingRuleService.java

@@ -0,0 +1,89 @@
+package com.oms.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.oms.entity.PricingRule;
+import com.oms.mapper.PricingRuleMapper;
+import com.oms.dto.PricingRuleDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class PricingRuleService {
+    private final PricingRuleMapper mapper;
+
+    public List<PricingRule> listRules(String ruleType, String status) {
+        LambdaQueryWrapper<PricingRule> wrapper = new LambdaQueryWrapper<>();
+        if (ruleType != null) wrapper.eq(PricingRule::getRuleType, ruleType);
+        if (status != null) wrapper.eq(PricingRule::getStatus, status);
+        wrapper.orderByDesc(PricingRule::getCreatedAt);
+        return mapper.selectList(wrapper);
+    }
+
+    public PricingRule getRule(Long id) {
+        return mapper.selectById(id);
+    }
+
+    public PricingRule createRule(PricingRuleDTO dto) {
+        PricingRule rule = new PricingRule();
+        rule.setRuleNo("PR" + System.currentTimeMillis());
+        rule.setRuleName(dto.getRuleName());
+        rule.setRuleType(dto.getRuleType());
+        rule.setProductSkuId(dto.getProductSkuId());
+        rule.setChannelId(dto.getChannelId());
+        rule.setBasePrice(dto.getBasePrice());
+        rule.setDiscountRate(dto.getDiscountRate());
+        rule.setFixedPrice(dto.getFixedPrice());
+        rule.setPriority(dto.getPriority());
+        rule.setStatus("ACTIVE");
+        rule.setStartDate(dto.getStartDate());
+        rule.setEndDate(dto.getEndDate());
+        rule.setCreatedAt(LocalDateTime.now());
+        mapper.insert(rule);
+        return rule;
+    }
+
+    public PricingRule updateRule(Long id, PricingRuleDTO dto) {
+        PricingRule rule = mapper.selectById(id);
+        if (rule == null) return null;
+        rule.setRuleName(dto.getRuleName());
+        rule.setRuleType(dto.getRuleType());
+        rule.setProductSkuId(dto.getProductSkuId());
+        rule.setChannelId(dto.getChannelId());
+        rule.setBasePrice(dto.getBasePrice());
+        rule.setDiscountRate(dto.getDiscountRate());
+        rule.setFixedPrice(dto.getFixedPrice());
+        rule.setPriority(dto.getPriority());
+        rule.setStartDate(dto.getStartDate());
+        rule.setEndDate(dto.getEndDate());
+        rule.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(rule);
+        return rule;
+    }
+
+    public void deleteRule(Long id) {
+        mapper.deleteById(id);
+    }
+
+    public BigDecimal calculatePrice(Long skuId, String channelId, BigDecimal basePrice) {
+        LambdaQueryWrapper<PricingRule> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(PricingRule::getProductSkuId, skuId.toString())
+               .eq(PricingRule::getChannelId, channelId)
+               .eq(PricingRule::getStatus, "ACTIVE");
+        PricingRule rule = mapper.selectOne(wrapper);
+        
+        if (rule == null) return basePrice;
+        
+        if (rule.getFixedPrice() != null) {
+            return rule.getFixedPrice();
+        }
+        if (rule.getDiscountRate() != null) {
+            return basePrice.multiply(rule.getDiscountRate()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+        }
+        return basePrice;
+    }
+}

+ 63 - 0
backend/src/main/java/com/oms/service/ReportDataService.java

@@ -0,0 +1,63 @@
+package com.oms.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.oms.entity.ReportData;
+import com.oms.mapper.ReportDataMapper;
+import com.oms.dto.ReportDataDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class ReportDataService {
+    private final ReportDataMapper mapper;
+
+    public List<ReportData> listByReportId(Long reportId) {
+        LambdaQueryWrapper<ReportData> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ReportData::getReportId, reportId);
+        return mapper.selectList(wrapper);
+    }
+
+    public ReportData getReportData(Long id) {
+        return mapper.selectById(id);
+    }
+
+    public ReportData createReportData(ReportDataDTO dto) {
+        ReportData data = new ReportData();
+        data.setReportId(dto.getReportId());
+        data.setDataKey(dto.getDataKey());
+        data.setDataValue(dto.getDataValue());
+        data.setDataType(dto.getDataType());
+        data.setCategory(dto.getCategory());
+        data.setSubCategory(dto.getSubCategory());
+        data.setNumericValue(dto.getNumericValue());
+        data.setStringValue(dto.getStringValue());
+        data.setReportDate(dto.getReportDate());
+        data.setDimensions(dto.getDimensions());
+        data.setCreatedAt(LocalDateTime.now());
+        mapper.insert(data);
+        return data;
+    }
+
+    public ReportData updateReportData(Long id, ReportDataDTO dto) {
+        ReportData data = mapper.selectById(id);
+        if (data == null) return null;
+        data.setDataKey(dto.getDataKey());
+        data.setDataValue(dto.getDataValue());
+        data.setDataType(dto.getDataType());
+        data.setCategory(dto.getCategory());
+        data.setSubCategory(dto.getSubCategory());
+        data.setNumericValue(dto.getNumericValue());
+        data.setStringValue(dto.getStringValue());
+        data.setReportDate(dto.getReportDate());
+        data.setDimensions(dto.getDimensions());
+        mapper.updateById(data);
+        return data;
+    }
+
+    public void deleteReportData(Long id) {
+        mapper.deleteById(id);
+    }
+}

+ 68 - 0
backend/src/main/java/com/oms/service/ReportService.java

@@ -0,0 +1,68 @@
+package com.oms.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.oms.entity.Report;
+import com.oms.mapper.ReportMapper;
+import com.oms.dto.ReportDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class ReportService {
+    private final ReportMapper mapper;
+
+    public List<Report> listReports(String reportType, String status) {
+        LambdaQueryWrapper<Report> wrapper = new LambdaQueryWrapper<>();
+        if (reportType != null) wrapper.eq(Report::getReportType, reportType);
+        if (status != null) wrapper.eq(Report::getStatus, status);
+        wrapper.orderByDesc(Report::getCreatedAt);
+        return mapper.selectList(wrapper);
+    }
+
+    public Report getReport(Long id) {
+        return mapper.selectById(id);
+    }
+
+    public Report createReport(ReportDTO dto) {
+        Report report = new Report();
+        report.setReportNo("RPT" + System.currentTimeMillis());
+        report.setReportName(dto.getReportName());
+        report.setReportType(dto.getReportType());
+        report.setPeriodType(dto.getPeriodType());
+        report.setStartDate(dto.getStartDate());
+        report.setEndDate(dto.getEndDate());
+        report.setStatus("DRAFT");
+        report.setCreatedAt(LocalDateTime.now());
+        mapper.insert(report);
+        return report;
+    }
+
+    public Report updateReport(Long id, ReportDTO dto) {
+        Report report = mapper.selectById(id);
+        if (report == null) return null;
+        report.setReportName(dto.getReportName());
+        report.setReportType(dto.getReportType());
+        report.setPeriodType(dto.getPeriodType());
+        report.setStartDate(dto.getStartDate());
+        report.setEndDate(dto.getEndDate());
+        report.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(report);
+        return report;
+    }
+
+    public void deleteReport(Long id) {
+        mapper.deleteById(id);
+    }
+
+    public Report generateReport(Long id) {
+        Report report = mapper.selectById(id);
+        if (report == null) return null;
+        report.setStatus("GENERATED");
+        report.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(report);
+        return report;
+    }
+}

+ 75 - 0
backend/src/main/java/com/oms/service/SatisfactionService.java

@@ -0,0 +1,75 @@
+package com.oms.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.oms.entity.Satisfaction;
+import com.oms.mapper.SatisfactionMapper;
+import com.oms.dto.SatisfactionDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class SatisfactionService {
+    private final SatisfactionMapper mapper;
+
+    public List<Satisfaction> listSatisfactions(String rating) {
+        LambdaQueryWrapper<Satisfaction> wrapper = new LambdaQueryWrapper<>();
+        if (rating != null) wrapper.eq(Satisfaction::getRating, rating);
+        wrapper.orderByDesc(Satisfaction::getCreatedAt);
+        return mapper.selectList(wrapper);
+    }
+
+    public Satisfaction getSatisfaction(Long id) {
+        return mapper.selectById(id);
+    }
+
+    public Satisfaction createSatisfaction(SatisfactionDTO dto) {
+        Satisfaction sat = new Satisfaction();
+        sat.setSatisfactionNo("SAT" + System.currentTimeMillis());
+        sat.setOrderId(dto.getOrderId());
+        sat.setOrderNo(dto.getOrderNo());
+        sat.setCustomerId(dto.getCustomerId());
+        sat.setCustomerName(dto.getCustomerName());
+        sat.setScore(dto.getScore());
+        sat.setRating(calculateRating(dto.getScore()));
+        sat.setFeedback(dto.getFeedback());
+        sat.setCreatedAt(LocalDateTime.now());
+        mapper.insert(sat);
+        return sat;
+    }
+
+    public Satisfaction updateSatisfaction(Long id, SatisfactionDTO dto) {
+        Satisfaction sat = mapper.selectById(id);
+        if (sat == null) return null;
+        if (dto.getResponse() != null) {
+            sat.setResponse(dto.getResponse());
+            sat.setResponseBy(dto.getResponseBy());
+            sat.setRespondedAt(LocalDateTime.now());
+        }
+        sat.setUpdatedBy(dto.getUpdatedBy());
+        sat.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(sat);
+        return sat;
+    }
+
+    public void deleteSatisfaction(Long id) {
+        mapper.deleteById(id);
+    }
+
+    private String calculateRating(Integer score) {
+        if (score == null) return "UNKNOWN";
+        if (score >= 5) return "EXCELLENT";
+        if (score >= 4) return "GOOD";
+        if (score >= 3) return "AVERAGE";
+        if (score >= 2) return "POOR";
+        return "VERY_POOR";
+    }
+
+    public Double getAverageScore() {
+        List<Satisfaction> list = mapper.selectList(null);
+        if (list.isEmpty()) return 0.0;
+        return list.stream().mapToInt(Satisfaction::getScore).average().orElse(0.0);
+    }
+}

+ 85 - 0
backend/src/main/java/com/oms/service/TicketService.java

@@ -0,0 +1,85 @@
+package com.oms.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.oms.entity.Ticket;
+import com.oms.mapper.TicketMapper;
+import com.oms.dto.TicketDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class TicketService {
+    private final TicketMapper mapper;
+
+    public List<Ticket> listTickets(String status, String priority) {
+        LambdaQueryWrapper<Ticket> wrapper = new LambdaQueryWrapper<>();
+        if (status != null) wrapper.eq(Ticket::getStatus, status);
+        if (priority != null) wrapper.eq(Ticket::getPriority, priority);
+        wrapper.orderByDesc(Ticket::getCreatedAt);
+        return mapper.selectList(wrapper);
+    }
+
+    public Ticket getTicket(Long id) {
+        return mapper.selectById(id);
+    }
+
+    public Ticket createTicket(TicketDTO dto) {
+        Ticket ticket = new Ticket();
+        ticket.setTicketNo("TKT" + System.currentTimeMillis());
+        ticket.setTitle(dto.getTitle());
+        ticket.setContent(dto.getContent());
+        ticket.setTicketType(dto.getTicketType());
+        ticket.setPriority(dto.getPriority());
+        ticket.setStatus("OPEN");
+        ticket.setCustomerId(dto.getCustomerId());
+        ticket.setCustomerName(dto.getCustomerName());
+        ticket.setCustomerEmail(dto.getCustomerEmail());
+        ticket.setCreatedBy(dto.getCreatedBy());
+        ticket.setCreatedAt(LocalDateTime.now());
+        mapper.insert(ticket);
+        return ticket;
+    }
+
+    public Ticket updateTicket(Long id, TicketDTO dto) {
+        Ticket ticket = mapper.selectById(id);
+        if (ticket == null) return null;
+        ticket.setTitle(dto.getTitle());
+        ticket.setContent(dto.getContent());
+        ticket.setTicketType(dto.getTicketType());
+        ticket.setPriority(dto.getPriority());
+        ticket.setUpdatedBy(dto.getUpdatedBy());
+        ticket.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(ticket);
+        return ticket;
+    }
+
+    public Ticket assignTicket(Long id, Long assignedTo, String assignedName) {
+        Ticket ticket = mapper.selectById(id);
+        if (ticket == null) return null;
+        ticket.setAssignedTo(assignedTo);
+        ticket.setAssignedName(assignedName);
+        ticket.setAssignedAt(LocalDateTime.now());
+        ticket.setStatus("ASSIGNED");
+        ticket.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(ticket);
+        return ticket;
+    }
+
+    public Ticket resolveTicket(Long id, String resolution) {
+        Ticket ticket = mapper.selectById(id);
+        if (ticket == null) return null;
+        ticket.setResolution(resolution);
+        ticket.setResolvedAt(LocalDateTime.now());
+        ticket.setStatus("RESOLVED");
+        ticket.setUpdatedAt(LocalDateTime.now());
+        mapper.updateById(ticket);
+        return ticket;
+    }
+
+    public void deleteTicket(Long id) {
+        mapper.deleteById(id);
+    }
+}