This commit is contained in:
2025-09-18 15:29:52 +08:00
parent 335e21347b
commit e560e90970
26 changed files with 566 additions and 30 deletions

View File

@@ -0,0 +1,74 @@
package com.example.demo.customer.controller;
import com.example.demo.common.AppDefaultsProperties;
import com.example.demo.customer.dto.CustomerDtos;
import com.example.demo.customer.entity.Customer;
import com.example.demo.customer.service.CustomerService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/customers")
public class CustomerController {
private final CustomerService customerService;
private final AppDefaultsProperties defaults;
public CustomerController(CustomerService customerService, AppDefaultsProperties defaults) {
this.customerService = customerService;
this.defaults = defaults;
}
@GetMapping
public ResponseEntity<?> search(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "debtOnly", required = false, defaultValue = "false") boolean debtOnly,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "50") int size) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
return ResponseEntity.ok(customerService.search(sid, kw, debtOnly, Math.max(0, page-1), size));
}
@GetMapping("/{id}")
public ResponseEntity<?> detail(@PathVariable("id") Long id,
@RequestHeader(name = "X-Shop-Id", required = false) Long shopId) {
java.util.Optional<Customer> oc = customerService.findById(id);
if (oc.isEmpty()) return ResponseEntity.notFound().build();
Customer c = oc.get();
java.util.Map<String,Object> body = new java.util.HashMap<>();
body.put("id", c.getId());
body.put("name", c.getName());
body.put("contactName", c.getContactName());
body.put("mobile", c.getMobile());
body.put("phone", c.getPhone());
body.put("level", c.getLevel());
body.put("priceLevel", c.getPriceLevel());
body.put("remark", c.getRemark());
body.put("address", c.getAddress());
body.put("receivable", new java.math.BigDecimal("0")); // 详情页如需可扩展计算
return ResponseEntity.ok(body);
}
@PostMapping
public ResponseEntity<?> create(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@RequestBody CustomerDtos.CreateOrUpdateCustomerRequest req) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
Long id = customerService.create(sid, uid, req);
java.util.Map<String,Object> body = new java.util.HashMap<>(); body.put("id", id); return ResponseEntity.ok(body);
}
@PutMapping("/{id}")
public ResponseEntity<?> update(@PathVariable("id") Long id,
@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@RequestBody CustomerDtos.CreateOrUpdateCustomerRequest req) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
customerService.update(id, sid, uid, req);
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,36 @@
package com.example.demo.customer.dto;
import java.math.BigDecimal;
public class CustomerDtos {
public static class CustomerListItem {
public Long id;
public String name;
public String contactName;
public String mobile;
public String phone;
public String level;
public String priceLevel;
public String remark;
public BigDecimal receivable;
public CustomerListItem() {}
public CustomerListItem(Long id, String name, String contactName, String mobile, String phone, String level, String priceLevel, String remark, BigDecimal receivable) {
this.id = id; this.name = name; this.contactName = contactName; this.mobile = mobile; this.phone = phone; this.level = level; this.priceLevel = priceLevel; this.remark = remark; this.receivable = receivable;
}
}
public static class CreateOrUpdateCustomerRequest {
public String name;
public String level;
public String priceLevel;
public String contactName;
public String mobile;
public String phone;
public String address;
public java.math.BigDecimal arOpening;
public String remark;
}
}

View File

@@ -0,0 +1,93 @@
package com.example.demo.customer.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "shop_id", nullable = false)
private Long shopId;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "name", nullable = false, length = 120)
private String name;
@Column(name = "phone", length = 32)
private String phone;
@Column(name = "mobile", length = 32)
private String mobile;
@Column(name = "address", length = 255)
private String address;
@Column(name = "level", length = 32)
private String level;
@Column(name = "contact_name", length = 64)
private String contactName;
@Column(name = "price_level", nullable = false, length = 32)
private String priceLevel;
@Column(name = "status", nullable = false)
private Integer status;
@Column(name = "ar_opening", precision = 18, scale = 2, nullable = false)
private BigDecimal arOpening;
@Column(name = "remark", length = 255)
private String remark;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "deleted_at")
private LocalDateTime deletedAt;
public Long getId() { return id; }
public Long getShopId() { return shopId; }
public void setShopId(Long shopId) { this.shopId = shopId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getMobile() { return mobile; }
public void setMobile(String mobile) { this.mobile = mobile; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getLevel() { return level; }
public void setLevel(String level) { this.level = level; }
public String getContactName() { return contactName; }
public void setContactName(String contactName) { this.contactName = contactName; }
public String getPriceLevel() { return priceLevel; }
public void setPriceLevel(String priceLevel) { this.priceLevel = priceLevel; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public BigDecimal getArOpening() { return arOpening; }
public void setArOpening(BigDecimal arOpening) { this.arOpening = arOpening; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}

View File

@@ -0,0 +1,16 @@
package com.example.demo.customer.repo;
import com.example.demo.customer.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
@Query("SELECT c FROM Customer c WHERE c.shopId=:shopId AND c.deletedAt IS NULL AND ( :kw IS NULL OR c.name LIKE CONCAT('%',:kw,'%') OR c.mobile LIKE CONCAT('%',:kw,'%') OR c.phone LIKE CONCAT('%',:kw,'%')) ORDER BY c.id DESC")
List<Customer> search(@Param("shopId") Long shopId, @Param("kw") String kw, org.springframework.data.domain.Pageable pageable);
}

View File

@@ -0,0 +1,85 @@
package com.example.demo.customer.service;
import com.example.demo.customer.dto.CustomerDtos;
import com.example.demo.customer.entity.Customer;
import com.example.demo.customer.repo.CustomerRepository;
import org.springframework.data.domain.PageRequest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class CustomerService {
private final CustomerRepository customerRepository;
private final JdbcTemplate jdbcTemplate;
public CustomerService(CustomerRepository customerRepository, JdbcTemplate jdbcTemplate) {
this.customerRepository = customerRepository;
this.jdbcTemplate = jdbcTemplate;
}
public java.util.Map<String, Object> search(Long shopId, String kw, boolean debtOnly, int page, int size) {
List<Customer> list = customerRepository.search(shopId, kw, PageRequest.of(page, size));
List<CustomerDtos.CustomerListItem> items = list.stream().map(c -> new CustomerDtos.CustomerListItem(
c.getId(), c.getName(), c.getContactName(), c.getMobile(), c.getPhone(), c.getLevel(), c.getPriceLevel(), c.getRemark(), calcReceivable(shopId, c.getId(), c.getArOpening())
)).collect(Collectors.toList());
if (debtOnly) {
items = items.stream().filter(it -> it.receivable != null && it.receivable.compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
}
java.util.Map<String, Object> resp = new java.util.HashMap<>();
resp.put("list", items);
return resp;
}
public Optional<Customer> findById(Long id) {
return customerRepository.findById(id);
}
@Transactional
public Long create(Long shopId, Long userId, CustomerDtos.CreateOrUpdateCustomerRequest req) {
Customer c = new Customer();
c.setShopId(shopId); c.setUserId(userId);
c.setName(req.name); c.setLevel(req.level); c.setPriceLevel(nullToDefaultPriceLevel(req.priceLevel));
c.setContactName(req.contactName); c.setMobile(req.mobile); c.setPhone(req.phone); c.setAddress(req.address);
c.setStatus(1);
c.setArOpening(req.arOpening == null ? BigDecimal.ZERO : req.arOpening);
c.setRemark(req.remark);
java.time.LocalDateTime now = java.time.LocalDateTime.now();
c.setCreatedAt(now);
c.setUpdatedAt(now);
return customerRepository.save(c).getId();
}
@Transactional
public void update(Long id, Long shopId, Long userId, CustomerDtos.CreateOrUpdateCustomerRequest req) {
Customer c = customerRepository.findById(id).orElseThrow();
if (!c.getShopId().equals(shopId)) throw new IllegalArgumentException("跨店铺修改");
c.setName(req.name); c.setLevel(req.level); c.setPriceLevel(nullToDefaultPriceLevel(req.priceLevel));
c.setContactName(req.contactName); c.setMobile(req.mobile); c.setPhone(req.phone); c.setAddress(req.address);
if (req.arOpening != null) c.setArOpening(req.arOpening);
c.setRemark(req.remark);
c.setUpdatedAt(java.time.LocalDateTime.now());
customerRepository.save(c);
}
private String nullToDefaultPriceLevel(String v) { return (v == null || v.isBlank()) ? "retail" : v; }
private BigDecimal calcReceivable(Long shopId, Long customerId, BigDecimal opening) {
BigDecimal open = opening == null ? BigDecimal.ZERO : opening;
BigDecimal sale = n(jdbcTemplate.queryForObject("SELECT COALESCE(SUM(amount - paid_amount),0) FROM sales_orders WHERE shop_id=? AND customer_id=? AND status='approved'", BigDecimal.class, shopId, customerId));
BigDecimal saleRet = n(jdbcTemplate.queryForObject("SELECT COALESCE(SUM(amount - paid_amount),0) FROM sales_return_orders WHERE shop_id=? AND customer_id=? AND status='approved'", BigDecimal.class, shopId, customerId));
BigDecimal otherIn = n(jdbcTemplate.queryForObject("SELECT COALESCE(SUM(amount),0) FROM other_transactions WHERE shop_id=? AND counterparty_type='customer' AND counterparty_id=? AND `type`='income'", BigDecimal.class, shopId, customerId));
BigDecimal otherOut = n(jdbcTemplate.queryForObject("SELECT COALESCE(SUM(amount),0) FROM other_transactions WHERE shop_id=? AND counterparty_type='customer' AND counterparty_id=? AND `type`='expense'", BigDecimal.class, shopId, customerId));
return open.add(sale).subtract(saleRet).add(otherIn).subtract(otherOut).setScale(2, java.math.RoundingMode.HALF_UP);
}
private static BigDecimal n(BigDecimal v) { return v == null ? BigDecimal.ZERO : v; }
}