From d8ad6cde2018f57dcf28f629baf31ba57730c743 Mon Sep 17 00:00:00 2001 From: danaisuiyuan Date: Sat, 2 May 2026 06:13:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(integral-external):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=AF=84=E5=8D=96=E5=A4=96=E9=83=A8=E5=85=8D=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E4=B8=89=E4=BB=B6=E5=A5=97=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-adminend/src/api/integralExternal.js | 56 ++ .../src/router/modules/integralExternal.js | 18 + backend-adminend/src/utils/requestNoAuth.js | 5 + .../views/dashboard/components/gridMenu.vue | 21 + .../integral-external/grab-user/index.vue | 297 +++++++ .../integral-external/team-report/index.vue | 421 ++++++++++ .../integral-external/wa-order/index.vue | 747 ++++++++++++++++++ .../ExternalIntegralController.java | 110 +++ .../request/ExternalGrabUserRequest.java | 33 + .../request/TeamDailyReportRequest.java | 37 + .../response/ExternalGrabUserResponse.java | 115 +++ .../common/response/TeamDailyMemberRow.java | 71 ++ .../TeamDailyMultiReportResponse.java | 59 ++ .../response/TeamDailyReportResponse.java | 62 ++ .../common/response/TeamDailySummary.java | 50 ++ .../zbkj/common/response/WaOrderResponse.java | 6 + backend/crmeb-service/pom.xml | 7 + .../dao/consignment/ExternalGrabUserDao.java | 44 ++ .../dao/consignment/TeamReportDao.java | 43 + .../service/ExternalGrabUserService.java | 26 + .../service/TeamReportExternalService.java | 36 + .../impl/ExternalGrabUserServiceImpl.java | 80 ++ .../impl/TeamReportExternalServiceImpl.java | 385 +++++++++ .../service/impl/WaOrderAdminServiceImpl.java | 73 +- .../zbkj/service/util/GrabUserFormatter.java | 48 ++ .../zbkj/service/util/TeamReportFormula.java | 78 ++ .../consignment/ExternalGrabUserMapper.xml | 93 +++ .../mapper/consignment/TeamReportMapper.xml | 85 ++ .../service/util/GrabUserFormatterTest.java | 63 ++ .../service/util/TeamReportFormulaTest.java | 155 ++++ docs/今日抢单用户列表_开发说明文档.docx | Bin 0 -> 22840 bytes docs/团队每日对账日报_开发说明文档.docx | Bin 0 -> 24684 bytes docs/寄卖外部免认证三件功能_开发计划.docx | Bin 0 -> 24985 bytes docs/寄卖订单管理_开发说明文档.docx | Bin 0 -> 24914 bytes restart-backend.command | 59 ++ 35 files changed, 3369 insertions(+), 14 deletions(-) create mode 100644 backend-adminend/src/views/integral-external/grab-user/index.vue create mode 100644 backend-adminend/src/views/integral-external/team-report/index.vue create mode 100644 backend-adminend/src/views/integral-external/wa-order/index.vue create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/request/ExternalGrabUserRequest.java create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/request/TeamDailyReportRequest.java create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/response/ExternalGrabUserResponse.java create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMemberRow.java create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMultiReportResponse.java create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyReportResponse.java create mode 100644 backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailySummary.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/ExternalGrabUserDao.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/TeamReportDao.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/service/ExternalGrabUserService.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/service/TeamReportExternalService.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/ExternalGrabUserServiceImpl.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/TeamReportExternalServiceImpl.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/util/GrabUserFormatter.java create mode 100644 backend/crmeb-service/src/main/java/com/zbkj/service/util/TeamReportFormula.java create mode 100644 backend/crmeb-service/src/main/resources/mapper/consignment/ExternalGrabUserMapper.xml create mode 100644 backend/crmeb-service/src/main/resources/mapper/consignment/TeamReportMapper.xml create mode 100644 backend/crmeb-service/src/test/java/com/zbkj/service/util/GrabUserFormatterTest.java create mode 100644 backend/crmeb-service/src/test/java/com/zbkj/service/util/TeamReportFormulaTest.java create mode 100644 docs/今日抢单用户列表_开发说明文档.docx create mode 100644 docs/团队每日对账日报_开发说明文档.docx create mode 100644 docs/寄卖外部免认证三件功能_开发计划.docx create mode 100644 docs/寄卖订单管理_开发说明文档.docx create mode 100755 restart-backend.command diff --git a/backend-adminend/src/api/integralExternal.js b/backend-adminend/src/api/integralExternal.js index b0561ae..effc8f3 100644 --- a/backend-adminend/src/api/integralExternal.js +++ b/backend-adminend/src/api/integralExternal.js @@ -39,3 +39,59 @@ export function getExternalIntegralLog(data) { data: body, }); } + +/** + * 寄卖订单 列表(免认证) + */ +export function getExternalWaOrderList(params) { + return requestNoAuth({ + url: 'external/integral/wa-order/list', + method: 'get', + params, + }); +} + +/** + * 寄卖订单 详情(免认证) + */ +export function getExternalWaOrderInfo(id) { + return requestNoAuth({ + url: 'external/integral/wa-order/info', + method: 'get', + params: { id }, + }); +} + +/** + * 团队每日对账报表(免认证) + */ +export function getExternalTeamDailyReport(params) { + return requestNoAuth({ + url: 'external/integral/team-report/daily', + method: 'get', + params, + }); +} + +/** + * 团队每日对账报表 - Excel 导出(免认证) + */ +export function exportExternalTeamDailyReport(params) { + return requestNoAuth({ + url: 'external/integral/team-report/daily/export', + method: 'get', + params, + responseType: 'blob', + }); +} + +/** + * 今日抢单用户列表(免认证) + */ +export function getExternalGrabUserList(params) { + return requestNoAuth({ + url: 'external/integral/grab-user/list', + method: 'get', + params, + }); +} diff --git a/backend-adminend/src/router/modules/integralExternal.js b/backend-adminend/src/router/modules/integralExternal.js index 7ba5f1d..6371445 100644 --- a/backend-adminend/src/router/modules/integralExternal.js +++ b/backend-adminend/src/router/modules/integralExternal.js @@ -24,6 +24,24 @@ const integralExternalRouter = { name: 'IntegralExternalUserDetail', meta: { title: '用户积分明细' }, }, + { + path: 'wa-order', + component: () => import('@/views/integral-external/wa-order/index'), + name: 'IntegralExternalWaOrder', + meta: { title: '寄卖订单' }, + }, + { + path: 'team-report', + component: () => import('@/views/integral-external/team-report/index'), + name: 'IntegralExternalTeamReport', + meta: { title: '团队日报' }, + }, + { + path: 'grab-user', + component: () => import('@/views/integral-external/grab-user/index'), + name: 'IntegralExternalGrabUser', + meta: { title: '今日抢单用户' }, + }, ], }; diff --git a/backend-adminend/src/utils/requestNoAuth.js b/backend-adminend/src/utils/requestNoAuth.js index 5e03ffb..de53dca 100644 --- a/backend-adminend/src/utils/requestNoAuth.js +++ b/backend-adminend/src/utils/requestNoAuth.js @@ -28,6 +28,11 @@ service.interceptors.request.use( // 响应拦截器 — 不拦截 401 跳转 service.interceptors.response.use( (response) => { + // Blob / arraybuffer 等二进制响应直接透传,不做业务码拆包 + const responseType = response.config && response.config.responseType; + if (responseType === 'blob' || responseType === 'arraybuffer') { + return response.data; + } const res = response.data; if (res.code !== 0 && res.code !== 200) { Message({ diff --git a/backend-adminend/src/views/dashboard/components/gridMenu.vue b/backend-adminend/src/views/dashboard/components/gridMenu.vue index 1a80c95..10916a6 100644 --- a/backend-adminend/src/views/dashboard/components/gridMenu.vue +++ b/backend-adminend/src/views/dashboard/components/gridMenu.vue @@ -121,6 +121,27 @@ export default { url: '/integral-external/user/integral-detail', alwaysShow: true, }, + { + bgColor: '#F56C6C', + icon: 'icondingdanguanli', + title: '寄卖订单', + url: '/integral-external/wa-order', + alwaysShow: true, + }, + { + bgColor: '#67C23A', + icon: 'iconfenxiaoguanli', + title: '团队日报', + url: '/integral-external/team-report', + alwaysShow: true, + }, + { + bgColor: '#E6A23C', + icon: 'iconhuiyuanguanli', + title: '今日抢单用户', + url: '/integral-external/grab-user', + alwaysShow: true, + }, ], statisticData: [ { title: '待发货订单', num: 0, path: '/order/index', perms: ['admin:order:list'] }, diff --git a/backend-adminend/src/views/integral-external/grab-user/index.vue b/backend-adminend/src/views/integral-external/grab-user/index.vue new file mode 100644 index 0000000..b6814fd --- /dev/null +++ b/backend-adminend/src/views/integral-external/grab-user/index.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/backend-adminend/src/views/integral-external/team-report/index.vue b/backend-adminend/src/views/integral-external/team-report/index.vue new file mode 100644 index 0000000..689504b --- /dev/null +++ b/backend-adminend/src/views/integral-external/team-report/index.vue @@ -0,0 +1,421 @@ + + + + + diff --git a/backend-adminend/src/views/integral-external/wa-order/index.vue b/backend-adminend/src/views/integral-external/wa-order/index.vue new file mode 100644 index 0000000..e5be50c --- /dev/null +++ b/backend-adminend/src/views/integral-external/wa-order/index.vue @@ -0,0 +1,747 @@ + + + + + diff --git a/backend/crmeb-admin/src/main/java/com/zbkj/admin/controller/ExternalIntegralController.java b/backend/crmeb-admin/src/main/java/com/zbkj/admin/controller/ExternalIntegralController.java index 8f3b57f..6b945ef 100644 --- a/backend/crmeb-admin/src/main/java/com/zbkj/admin/controller/ExternalIntegralController.java +++ b/backend/crmeb-admin/src/main/java/com/zbkj/admin/controller/ExternalIntegralController.java @@ -3,20 +3,35 @@ package com.zbkj.admin.controller; import cn.hutool.core.collection.CollUtil; import com.zbkj.common.page.CommonPage; import com.zbkj.common.request.*; +import com.zbkj.common.response.ExternalGrabUserResponse; import com.zbkj.common.response.StoreOrderDetailResponse; +import com.zbkj.common.response.TeamDailyMultiReportResponse; +import com.zbkj.common.response.TeamDailyReportResponse; import com.zbkj.common.response.UserIntegralRecordResponse; import com.zbkj.common.response.UserResponse; +import com.zbkj.common.response.WaOrderResponse; import com.zbkj.common.result.CommonResult; +import com.zbkj.service.service.ExternalGrabUserService; import com.zbkj.service.service.StoreOrderService; +import com.zbkj.service.service.TeamReportExternalService; import com.zbkj.service.service.UserIntegralRecordService; import com.zbkj.service.service.UserService; +import com.zbkj.service.service.WaOrderAdminService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + /** * 积分模块外部免认证接口 Controller * 供管理后台外部页面(/integral-external/*)调用,跳过登录验证。 @@ -40,6 +55,15 @@ public class ExternalIntegralController { @Autowired private UserService userService; + @Autowired + private WaOrderAdminService waOrderAdminService; + + @Autowired + private TeamReportExternalService teamReportExternalService; + + @Autowired + private ExternalGrabUserService externalGrabUserService; + /** * 积分明细分页列表(免认证) * 复用 UserIntegralRecordService.findAdminList,与 /admin/user/integral/list 逻辑完全一致。 @@ -93,4 +117,90 @@ public class ExternalIntegralController { } return CommonResult.success(restPage); } + + // ======================================================================== + // 寄卖订单管理(wa_order) + // 复用 WaOrderAdminService(仅读路径),不使用 @PreAuthorize。 + // ======================================================================== + + /** + * 寄卖订单分页列表(免认证) + */ + @ApiOperation(value = "寄卖订单分页列表(免认证)") + @GetMapping(value = "/wa-order/list") + public CommonResult> waOrderList( + @ModelAttribute @Validated WaOrderSearchRequest request, + @Validated PageParamRequest pageParamRequest) { + CommonPage restPage = + CommonPage.restPage(waOrderAdminService.getAdminList(request, pageParamRequest)); + return CommonResult.success(restPage); + } + + /** + * 寄卖订单详情(免认证) + */ + @ApiOperation(value = "寄卖订单详情(免认证)") + @GetMapping(value = "/wa-order/info") + public CommonResult waOrderInfo(@RequestParam Integer id) { + return CommonResult.success(waOrderAdminService.getDetailById(id)); + } + + // ======================================================================== + // 团队每日对账日报 + // ======================================================================== + + /** + * 团队每日对账日报(免认证)。 + * - leaderId 非空:返回该团队对账(teams 仅 1 项) + * - leaderId 为空:按团队长分组返回所有团队对账 + 跨团队总计 + */ + @ApiOperation(value = "团队每日对账(免认证)") + @GetMapping(value = "/team-report/daily") + public CommonResult teamDailyReport( + @ModelAttribute @Validated TeamDailyReportRequest request) { + return CommonResult.success(teamReportExternalService.getMultiDailyReport(request)); + } + + /** + * 团队每日对账日报 - Excel 导出(免认证;仅支持单团队,必须传 leaderId) + */ + @ApiOperation(value = "团队每日对账 Excel 导出(免认证)") + @GetMapping(value = "/team-report/daily/export") + public ResponseEntity teamDailyReportExport( + @ModelAttribute @Validated TeamDailyReportRequest request) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TeamDailyReportResponse data = teamReportExternalService.exportDailyReport(request, out); + + String fileName = String.format("团队%s_%s.xlsx", + data.getTeamCode() == null ? "" : data.getTeamCode(), + data.getDate()); + String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + .replaceAll("\\+", "%20"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")); + headers.add(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + encoded + "\"; filename*=UTF-8''" + encoded); + + return new ResponseEntity<>(out.toByteArray(), headers, org.springframework.http.HttpStatus.OK); + } + + // ======================================================================== + // 今日抢单用户列表 + // ======================================================================== + + /** + * 今日抢单用户列表(免认证) + * 过滤口径:今日购买总金额 > 0;已在 SQL 层落实。 + */ + @ApiOperation(value = "今日抢单用户分页列表(免认证)") + @GetMapping(value = "/grab-user/list") + public CommonResult> grabUserList( + @ModelAttribute @Validated ExternalGrabUserRequest request, + @Validated PageParamRequest pageParamRequest) { + CommonPage restPage = + CommonPage.restPage(externalGrabUserService.list(request, pageParamRequest)); + return CommonResult.success(restPage); + } } diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/request/ExternalGrabUserRequest.java b/backend/crmeb-common/src/main/java/com/zbkj/common/request/ExternalGrabUserRequest.java new file mode 100644 index 0000000..60e5dec --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/request/ExternalGrabUserRequest.java @@ -0,0 +1,33 @@ +package com.zbkj.common.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 今日抢单用户列表 查询请求(外部免认证) + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "ExternalGrabUserRequest 对象", description = "今日抢单用户列表查询请求") +public class ExternalGrabUserRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "用户ID(精确)") + private Integer uid; + + @ApiModelProperty(value = "联系方式(模糊匹配 mobile / username)") + private String mobile; + + @ApiModelProperty(value = "上级ID(精确)") + private Integer pid; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/request/TeamDailyReportRequest.java b/backend/crmeb-common/src/main/java/com/zbkj/common/request/TeamDailyReportRequest.java new file mode 100644 index 0000000..26f9d65 --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/request/TeamDailyReportRequest.java @@ -0,0 +1,37 @@ +package com.zbkj.common.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 团队每日对账日报 查询请求(外部免认证) + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "TeamDailyReportRequest 对象", description = "团队每日对账查询请求") +public class TeamDailyReportRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "团队长 ID(wa_users.id);为空时按团队长分组返回所有团队") + private Integer leaderId; + + @ApiModelProperty(value = "查询日期 D(yyyy-MM-dd),缺省为昨天") + private String date; + + @ApiModelProperty(value = "是否包含禁用成员,默认 false") + private Boolean includeDisabled = Boolean.FALSE; + + @ApiModelProperty(value = "限定成员 ID 列表(前端勾选过滤)") + private List memberIds; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/response/ExternalGrabUserResponse.java b/backend/crmeb-common/src/main/java/com/zbkj/common/response/ExternalGrabUserResponse.java new file mode 100644 index 0000000..8e2079e --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/response/ExternalGrabUserResponse.java @@ -0,0 +1,115 @@ +package com.zbkj.common.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 今日抢单用户列表 响应对象(外部免认证) + * 字段保留 3 位小数(与上传参考图一致),由 Service 层格式化为字符串。 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "ExternalGrabUserResponse 对象", description = "今日抢单用户列表响应") +public class ExternalGrabUserResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "用户ID") + private Integer id; + + @ApiModelProperty(value = "账号 / 用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "手机号 / 联系方式") + private String mobile; + + @ApiModelProperty(value = "合同 URL(为空表示未上传)") + private String contract; + + @ApiModelProperty(value = "上级ID") + private Integer pid; + + @ApiModelProperty(value = "最高可抢单数") + private Integer maxOrder; + + @ApiModelProperty(value = "用户等级(数值)") + private Integer level; + + @ApiModelProperty(value = "用户等级文案") + private String levelName; + + @ApiModelProperty(value = "余额(保留 3 位小数)") + private String money; + + @ApiModelProperty(value = "优惠券(保留 3 位小数)") + private String coupon; + + @ApiModelProperty(value = "个人奖金(保留 3 位小数)") + private String selfBonus; + + @ApiModelProperty(value = "推广奖金(保留 3 位小数)") + private String shareBonus; + + @ApiModelProperty(value = "状态:0=禁用,1=正常") + private Integer status; + + @ApiModelProperty(value = "状态文案") + private String statusStr; + + @ApiModelProperty(value = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date updatedAt; + + @ApiModelProperty(value = "今日购买总金额(保留 3 位小数)") + private String todayBuyAmount; + + @ApiModelProperty(value = "今日卖出总金额(保留 3 位小数)") + private String todaySellAmount; + + @ApiModelProperty(value = "今日买单数") + private Integer todayBuyCnt; + + @ApiModelProperty(value = "昨日卖单数") + private Integer prevSellCnt; + + /** 内部使用:SQL 聚合后由 Service 二次处理为格式化字符串;不输出到 JSON */ + @JsonIgnore + @ApiModelProperty(hidden = true) + private BigDecimal todayBuyAmountRaw; + + @JsonIgnore + @ApiModelProperty(hidden = true) + private BigDecimal todaySellAmountRaw; + + @JsonIgnore + @ApiModelProperty(hidden = true) + private BigDecimal moneyRaw; + + @JsonIgnore + @ApiModelProperty(hidden = true) + private BigDecimal couponRaw; + + @JsonIgnore + @ApiModelProperty(hidden = true) + private BigDecimal selfBonusRaw; + + @JsonIgnore + @ApiModelProperty(hidden = true) + private BigDecimal shareBonusRaw; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMemberRow.java b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMemberRow.java new file mode 100644 index 0000000..7e7b13e --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMemberRow.java @@ -0,0 +1,71 @@ +package com.zbkj.common.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 团队每日对账日报 - 单成员行 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "TeamDailyMemberRow 对象", description = "团队每日对账 - 单成员行") +public class TeamDailyMemberRow implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "成员用户 ID") + private Integer userId; + + @ApiModelProperty(value = "成员昵称") + private String nickname; + + @ApiModelProperty(value = "团队代号") + private String teamCode; + + @ApiModelProperty(value = "成员状态:1=启用,0=禁用") + private Integer status; + + /** 内部使用:所属团队长 ID(即 wa_users.pid);不输出到 JSON */ + @JsonIgnore + @ApiModelProperty(hidden = true) + private Integer leaderId; + + @ApiModelProperty(value = "D-1 买单合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal prevBuy; + + @ApiModelProperty(value = "D 卖单合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal todaySell; + + @ApiModelProperty(value = "D 买单合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal todayBuy; + + @ApiModelProperty(value = "服务费 = D买单 × service_rate") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal serviceFee; + + @ApiModelProperty(value = "E 积分 = D买单 × e_score_rate") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal eScore; + + @ApiModelProperty(value = "实际收付 = D卖单 − D买单 − 服务费 − E积分") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal actual; + + @ApiModelProperty(value = "备注(前端会话级,非持久化)") + private String remark; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMultiReportResponse.java b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMultiReportResponse.java new file mode 100644 index 0000000..20ef2d2 --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyMultiReportResponse.java @@ -0,0 +1,59 @@ +package com.zbkj.common.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 团队每日对账日报 — 多团队聚合响应(外部免认证)。 + * + * 当前端不传 leaderId 时返回此结构,按团队长分组列出全部团队的报表, + * 并附带跨团队的总计。当 leaderId 传入时 teams 仅含 1 项。 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "TeamDailyMultiReportResponse 对象", description = "多团队每日对账响应") +public class TeamDailyMultiReportResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("查询日期 D(yyyy-MM-dd)") + private String date; + + @ApiModelProperty("D-1 日期(yyyy-MM-dd)") + private String previousDate; + + @ApiModelProperty("服务费率") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal serviceRate; + + @ApiModelProperty("E 积分率") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal eScoreRate; + + @ApiModelProperty("团队数") + private Integer teamCount; + + @ApiModelProperty("成员合计(跨团队)") + private Integer totalMemberCount; + + @ApiModelProperty("各团队报表(按 leaderId 分组)") + private List teams; + + @ApiModelProperty("跨团队总计") + private TeamDailySummary grandSummary; + + @ApiModelProperty("警告信息(如非法 memberIds)") + private List warnings; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyReportResponse.java b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyReportResponse.java new file mode 100644 index 0000000..472f5af --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailyReportResponse.java @@ -0,0 +1,62 @@ +package com.zbkj.common.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 团队每日对账日报 响应(外部免认证) + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "TeamDailyReportResponse 对象", description = "团队每日对账日报响应") +public class TeamDailyReportResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("团队长 ID") + private Integer leaderId; + + @ApiModelProperty("团队长昵称") + private String leaderNickname; + + @ApiModelProperty("团队代号") + private String teamCode; + + @ApiModelProperty("成员人数") + private Integer memberCount; + + @ApiModelProperty("查询日期 D(yyyy-MM-dd)") + private String date; + + @ApiModelProperty("D-1 日期(yyyy-MM-dd)") + private String previousDate; + + @ApiModelProperty("服务费率") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal serviceRate; + + @ApiModelProperty("E 积分率") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal eScoreRate; + + @ApiModelProperty("成员行") + private List rows; + + @ApiModelProperty("小计") + private TeamDailySummary summary; + + @ApiModelProperty("警告信息(如非法 memberIds)") + private List warnings; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailySummary.java b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailySummary.java new file mode 100644 index 0000000..f31662f --- /dev/null +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/response/TeamDailySummary.java @@ -0,0 +1,50 @@ +package com.zbkj.common.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 团队每日对账日报 - 小计 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "TeamDailySummary 对象", description = "团队每日对账 - 小计") +public class TeamDailySummary implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("D-1 买单合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal prevBuy = BigDecimal.ZERO; + + @ApiModelProperty("D 卖单合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal todaySell = BigDecimal.ZERO; + + @ApiModelProperty("D 买单合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal todayBuy = BigDecimal.ZERO; + + @ApiModelProperty("服务费合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal serviceFee = BigDecimal.ZERO; + + @ApiModelProperty("E 积分合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal eScore = BigDecimal.ZERO; + + @ApiModelProperty("实际收付合计") + @JsonFormat(shape = JsonFormat.Shape.STRING) + private BigDecimal actual = BigDecimal.ZERO; +} diff --git a/backend/crmeb-common/src/main/java/com/zbkj/common/response/WaOrderResponse.java b/backend/crmeb-common/src/main/java/com/zbkj/common/response/WaOrderResponse.java index d3d6fca..92106f3 100644 --- a/backend/crmeb-common/src/main/java/com/zbkj/common/response/WaOrderResponse.java +++ b/backend/crmeb-common/src/main/java/com/zbkj/common/response/WaOrderResponse.java @@ -93,6 +93,12 @@ public class WaOrderResponse implements Serializable { @ApiModelProperty(value = "寄售商品ID") private Integer merchandiseId; + @ApiModelProperty(value = "寄售商品标题") + private String merchandiseTitle; + + @ApiModelProperty(value = "寄售商品图片") + private String merchandiseImage; + @ApiModelProperty(value = "确认收货时间") private Date confirmTime; diff --git a/backend/crmeb-service/pom.xml b/backend/crmeb-service/pom.xml index 5daec8e..9a6c2dd 100644 --- a/backend/crmeb-service/pom.xml +++ b/backend/crmeb-service/pom.xml @@ -22,6 +22,13 @@ crmeb-common ${crmeb-common} + + + junit + junit + 4.12 + test + com.jayway.jsonpath json-path diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/ExternalGrabUserDao.java b/backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/ExternalGrabUserDao.java new file mode 100644 index 0000000..fab1752 --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/ExternalGrabUserDao.java @@ -0,0 +1,44 @@ +package com.zbkj.service.dao.consignment; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zbkj.common.model.consignment.WaUsers; +import com.zbkj.common.response.ExternalGrabUserResponse; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +/** + * 今日抢单用户 DAO(外部免认证) + * 通过自定义 SQL 一次性聚合: + * - 今日买单合计 / 笔数(INNER JOIN,HAVING SUM>0) + * - 今日卖单合计 + * - 昨日卖单笔数 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Mapper +public interface ExternalGrabUserDao extends BaseMapper { + + /** + * 列表查询。 + * + * @param todayStart 今天 00:00:00 + * @param todayEnd 今天 23:59:59 + * @param yesterdayStart 昨天 00:00:00 + * @param yesterdayEnd 昨天 23:59:59 + * @param uid 用户 ID(精确,可空) + * @param mobile 模糊匹配 mobile / username(可空) + * @param pid 上级 ID(精确,可空) + */ + List selectGrabUserList( + @Param("todayStart") Date todayStart, + @Param("todayEnd") Date todayEnd, + @Param("yesterdayStart") Date yesterdayStart, + @Param("yesterdayEnd") Date yesterdayEnd, + @Param("uid") Integer uid, + @Param("mobile") String mobile, + @Param("pid") Integer pid); +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/TeamReportDao.java b/backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/TeamReportDao.java new file mode 100644 index 0000000..787702a --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/dao/consignment/TeamReportDao.java @@ -0,0 +1,43 @@ +package com.zbkj.service.dao.consignment; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zbkj.common.model.consignment.WaUsers; +import com.zbkj.common.response.TeamDailyMemberRow; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.Date; +import java.util.List; + +/** + * 团队每日对账日报 DAO(外部免认证) + * 通过自定义 SQL 一次性聚合每个直推下级的:D-1 买单 / D 卖单 / D 买单。 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Mapper +public interface TeamReportDao extends BaseMapper { + + /** + * 查询团队成员每日对账原始数据。 + * + * @param leaderId 团队长 ID;为 null 时返回所有有 pid 的成员,由 Service 按 leader_id 分组 + */ + List selectTeamMemberAggregates( + @Param("leaderId") Integer leaderId, + @Param("dStart") Date dStart, + @Param("dEnd") Date dEnd, + @Param("prevStart") Date prevStart, + @Param("prevEnd") Date prevEnd, + @Param("includeDisabled") Boolean includeDisabled, + @Param("memberIds") List memberIds); + + /** + * 读取 wa_setting 中某 key 的字符串值(不存在时返回 null)。 + * KV 表无独立 Model,使用注解直接查询。 + */ + @Select("SELECT `value` FROM wa_setting WHERE `name` = #{name} LIMIT 1") + String selectSettingValue(@Param("name") String name); +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/service/ExternalGrabUserService.java b/backend/crmeb-service/src/main/java/com/zbkj/service/service/ExternalGrabUserService.java new file mode 100644 index 0000000..36a22ba --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/service/ExternalGrabUserService.java @@ -0,0 +1,26 @@ +package com.zbkj.service.service; + +import com.github.pagehelper.PageInfo; +import com.zbkj.common.request.ExternalGrabUserRequest; +import com.zbkj.common.request.PageParamRequest; +import com.zbkj.common.response.ExternalGrabUserResponse; + +/** + * 今日抢单用户列表 Service(外部免认证) + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +public interface ExternalGrabUserService { + + /** + * 分页查询当日已发生买单且金额 > 0 的用户列表。 + * 排序:今日购买总金额 DESC,相同时按 id DESC。 + * + * @param request 搜索条件 + * @param pageParamRequest 分页参数 + * @return PageInfo<ExternalGrabUserResponse> + */ + PageInfo list(ExternalGrabUserRequest request, + PageParamRequest pageParamRequest); +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/service/TeamReportExternalService.java b/backend/crmeb-service/src/main/java/com/zbkj/service/service/TeamReportExternalService.java new file mode 100644 index 0000000..efd0707 --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/service/TeamReportExternalService.java @@ -0,0 +1,36 @@ +package com.zbkj.service.service; + +import com.zbkj.common.request.TeamDailyReportRequest; +import com.zbkj.common.response.TeamDailyMultiReportResponse; +import com.zbkj.common.response.TeamDailyReportResponse; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * 团队每日对账日报 Service(外部免认证) + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +public interface TeamReportExternalService { + + /** + * 查询某团队某日对账数据(要求 leaderId 非空)。 + */ + TeamDailyReportResponse getDailyReport(TeamDailyReportRequest request); + + /** + * 查询多团队对账数据: + * - leaderId 为空:按团队长分组返回所有团队 + * - leaderId 非空:teams 仅含该团队 + * + * @return 多团队报表(含 grandSummary 与每个团队的子报表) + */ + TeamDailyMultiReportResponse getMultiDailyReport(TeamDailyReportRequest request); + + /** + * Excel 导出(仅支持单团队,要求 leaderId 非空)。 + */ + TeamDailyReportResponse exportDailyReport(TeamDailyReportRequest request, OutputStream out) throws IOException; +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/ExternalGrabUserServiceImpl.java b/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/ExternalGrabUserServiceImpl.java new file mode 100644 index 0000000..c00c02f --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/ExternalGrabUserServiceImpl.java @@ -0,0 +1,80 @@ +package com.zbkj.service.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.zbkj.common.request.ExternalGrabUserRequest; +import com.zbkj.common.request.PageParamRequest; +import com.zbkj.common.response.ExternalGrabUserResponse; +import com.zbkj.service.dao.consignment.ExternalGrabUserDao; +import com.zbkj.service.service.ExternalGrabUserService; +import com.zbkj.service.util.GrabUserFormatter; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; + +/** + * 今日抢单用户列表 Service 实现(外部免认证) + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Service +public class ExternalGrabUserServiceImpl implements ExternalGrabUserService { + + private static final ZoneId ZONE_CN = ZoneId.of("Asia/Shanghai"); + + @Resource + private ExternalGrabUserDao externalGrabUserDao; + + @Override + public PageInfo list(ExternalGrabUserRequest request, + PageParamRequest pageParamRequest) { + if (request == null) { + request = new ExternalGrabUserRequest(); + } + + // 时间窗(业务时区 CST) + LocalDate today = LocalDate.now(ZONE_CN); + Date todayStart = toDate(today.atStartOfDay()); + Date todayEnd = toDate(today.atTime(LocalTime.MAX)); + LocalDate yesterday = today.minusDays(1); + Date yesterdayStart = toDate(yesterday.atStartOfDay()); + Date yesterdayEnd = toDate(yesterday.atTime(LocalTime.MAX)); + + // 分页(PageHelper 在执行下一条 SQL 时生效) + int page = pageParamRequest != null && pageParamRequest.getPage() > 0 ? pageParamRequest.getPage() : 1; + int limit = pageParamRequest != null && pageParamRequest.getLimit() > 0 ? Math.min(pageParamRequest.getLimit(), 100) : 15; + PageHelper.startPage(page, limit); + + List rows = externalGrabUserDao.selectGrabUserList( + todayStart, todayEnd, yesterdayStart, yesterdayEnd, + request.getUid(), + StrUtil.trimToNull(request.getMobile()), + request.getPid()); + + // 后处理:金额格式化、状态/等级文案 + for (ExternalGrabUserResponse row : rows) { + row.setMoney(GrabUserFormatter.formatAmount(row.getMoneyRaw())); + row.setCoupon(GrabUserFormatter.formatAmount(row.getCouponRaw())); + row.setSelfBonus(GrabUserFormatter.formatAmount(row.getSelfBonusRaw())); + row.setShareBonus(GrabUserFormatter.formatAmount(row.getShareBonusRaw())); + row.setTodayBuyAmount(GrabUserFormatter.formatAmount(row.getTodayBuyAmountRaw())); + row.setTodaySellAmount(GrabUserFormatter.formatAmount(row.getTodaySellAmountRaw())); + row.setStatusStr(GrabUserFormatter.mapStatus(row.getStatus())); + row.setLevelName(GrabUserFormatter.mapLevelName(row.getLevel())); + } + + return new PageInfo<>(rows); + } + + private static Date toDate(LocalDateTime dt) { + return Date.from(dt.atZone(ZONE_CN).toInstant()); + } +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/TeamReportExternalServiceImpl.java b/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/TeamReportExternalServiceImpl.java new file mode 100644 index 0000000..7aa4492 --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/TeamReportExternalServiceImpl.java @@ -0,0 +1,385 @@ +package com.zbkj.service.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.zbkj.common.exception.CrmebException; +import com.zbkj.common.model.consignment.WaUsers; +import com.zbkj.common.request.TeamDailyReportRequest; +import com.zbkj.common.response.TeamDailyMemberRow; +import com.zbkj.common.response.TeamDailyMultiReportResponse; +import com.zbkj.common.response.TeamDailyReportResponse; +import com.zbkj.common.response.TeamDailySummary; +import com.zbkj.service.dao.consignment.TeamReportDao; +import com.zbkj.service.dao.consignment.WaUsersDao; +import com.zbkj.service.service.TeamReportExternalService; +import com.zbkj.service.util.TeamReportFormula; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 团队每日对账日报 Service 实现(外部免认证) + * + * 关键设计: + * 1) 费率从 wa_setting 读取(service_rate / e_score_rate),缺省 0.02 / 0.005; + * 2) BigDecimal 全程 HALF_UP 保留 2 位; + * 3) 小计的服务费 / E积分 / 实际收付 由"成员级别已舍入数值"再求和,避免逐行二次舍入; + * 4) 时区:业务时区 Asia/Shanghai。 + * +---------------------------------------------------------------------- + * | CRMEB [ CRMEB赋能开发者,助力企业发展 ] + * +---------------------------------------------------------------------- + */ +@Service +public class TeamReportExternalServiceImpl implements TeamReportExternalService { + + private static final ZoneId ZONE_CN = ZoneId.of("Asia/Shanghai"); + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final BigDecimal DEFAULT_SERVICE_RATE = new BigDecimal("0.02"); + private static final BigDecimal DEFAULT_E_SCORE_RATE = new BigDecimal("0.005"); + private static final int SCALE = 2; + + @Resource + private TeamReportDao teamReportDao; + + @Resource + private WaUsersDao waUsersDao; + + @Override + public TeamDailyReportResponse getDailyReport(TeamDailyReportRequest request) { + if (request == null || request.getLeaderId() == null) { + throw new CrmebException("团队长 ID 不能为空"); + } + return assembleSingleTeam(request); + } + + @Override + public TeamDailyMultiReportResponse getMultiDailyReport(TeamDailyReportRequest request) { + if (request == null) request = new TeamDailyReportRequest(); + + Ctx ctx = prepare(request); + + // 拉取原始聚合(leaderId 为空时一次拿所有团队成员) + List rows = teamReportDao.selectTeamMemberAggregates( + request.getLeaderId(), ctx.dStart, ctx.dEnd, ctx.pStart, ctx.pEnd, + request.getIncludeDisabled(), + (request.getMemberIds() != null && !request.getMemberIds().isEmpty()) + ? request.getMemberIds() : null); + if (rows == null) rows = new ArrayList<>(); + + // 公式计算 + for (TeamDailyMemberRow r : rows) { + TeamReportFormula.applyFormula(r, ctx.serviceRate, ctx.eScoreRate); + r.setRemark(""); + } + + // 按 leaderId 分组(保持稳定顺序) + Map> groups = new LinkedHashMap<>(); + Set leaderIds = new HashSet<>(); + for (TeamDailyMemberRow r : rows) { + Integer lid = r.getLeaderId(); + if (lid == null || lid <= 0) continue; + groups.computeIfAbsent(lid, k -> new ArrayList<>()).add(r); + leaderIds.add(lid); + } + + // 批量拉取团队长信息 + Map leaderMap = new HashMap<>(); + if (!leaderIds.isEmpty()) { + for (WaUsers u : waUsersDao.selectBatchIds(leaderIds)) { + leaderMap.put(u.getId(), u); + } + } + + // 组装每个团队子报表 + List teams = new ArrayList<>(groups.size()); + TeamDailySummary grand = new TeamDailySummary(); + int totalMembers = 0; + for (Map.Entry> e : groups.entrySet()) { + Integer lid = e.getKey(); + List teamRows = e.getValue(); + WaUsers leader = leaderMap.get(lid); + String leaderNickname = leader != null ? leader.getNickname() : null; + String teamCode = leader != null && StrUtil.isNotBlank(leader.getInvite()) + ? leader.getInvite() + : String.valueOf(lid); + + TeamDailySummary teamSummary = TeamReportFormula.aggregateSummary(teamRows); + + teams.add(new TeamDailyReportResponse() + .setLeaderId(lid) + .setLeaderNickname(leaderNickname) + .setTeamCode(teamCode) + .setMemberCount(teamRows.size()) + .setDate(ctx.d.format(DATE_FMT)) + .setPreviousDate(ctx.prev.format(DATE_FMT)) + .setServiceRate(ctx.serviceRate) + .setEScoreRate(ctx.eScoreRate) + .setRows(teamRows) + .setSummary(teamSummary)); + + // 累加 grand summary + grand.setPrevBuy(grand.getPrevBuy().add(teamSummary.getPrevBuy())); + grand.setTodaySell(grand.getTodaySell().add(teamSummary.getTodaySell())); + grand.setTodayBuy(grand.getTodayBuy().add(teamSummary.getTodayBuy())); + grand.setServiceFee(grand.getServiceFee().add(teamSummary.getServiceFee())); + grand.setEScore(grand.getEScore().add(teamSummary.getEScore())); + grand.setActual(grand.getActual().add(teamSummary.getActual())); + totalMembers += teamRows.size(); + } + + return new TeamDailyMultiReportResponse() + .setDate(ctx.d.format(DATE_FMT)) + .setPreviousDate(ctx.prev.format(DATE_FMT)) + .setServiceRate(ctx.serviceRate) + .setEScoreRate(ctx.eScoreRate) + .setTeamCount(teams.size()) + .setTotalMemberCount(totalMembers) + .setTeams(teams) + .setGrandSummary(grand); + } + + @Override + public TeamDailyReportResponse exportDailyReport(TeamDailyReportRequest request, OutputStream out) throws IOException { + if (request == null || request.getLeaderId() == null) { + throw new CrmebException("Excel 导出仅支持单团队,必须指定团队长 ID"); + } + TeamDailyReportResponse data = assembleSingleTeam(request); + writeExcel(data, out); + return data; + } + + // ------------------------------------------------------------------ + // 主流程:单团队装配 + // ------------------------------------------------------------------ + private TeamDailyReportResponse assembleSingleTeam(TeamDailyReportRequest request) { + // 校验团队长存在 + WaUsers leader = waUsersDao.selectById(request.getLeaderId()); + if (leader == null) { + throw new CrmebException("团队长不存在"); + } + + Ctx ctx = prepare(request); + + // 拉取原始聚合 + List rows = teamReportDao.selectTeamMemberAggregates( + request.getLeaderId(), ctx.dStart, ctx.dEnd, ctx.pStart, ctx.pEnd, + request.getIncludeDisabled(), + (request.getMemberIds() != null && !request.getMemberIds().isEmpty()) + ? request.getMemberIds() : null); + if (rows == null) rows = new ArrayList<>(); + + for (TeamDailyMemberRow r : rows) { + TeamReportFormula.applyFormula(r, ctx.serviceRate, ctx.eScoreRate); + r.setRemark(""); + } + TeamDailySummary summary = TeamReportFormula.aggregateSummary(rows); + + String teamCode = StrUtil.isNotBlank(leader.getInvite()) + ? leader.getInvite() + : String.valueOf(leader.getId()); + + return new TeamDailyReportResponse() + .setLeaderId(leader.getId()) + .setLeaderNickname(leader.getNickname()) + .setTeamCode(teamCode) + .setMemberCount(rows.size()) + .setDate(ctx.d.format(DATE_FMT)) + .setPreviousDate(ctx.prev.format(DATE_FMT)) + .setServiceRate(ctx.serviceRate) + .setEScoreRate(ctx.eScoreRate) + .setRows(rows) + .setSummary(summary); + } + + /** 单/多团队共用的上下文(日期、时间窗、费率) */ + private Ctx prepare(TeamDailyReportRequest request) { + LocalDate today = LocalDate.now(ZONE_CN); + LocalDate d; + if (StrUtil.isNotBlank(request.getDate())) { + d = LocalDate.parse(request.getDate(), DATE_FMT); + } else { + d = today.minusDays(1); + } + if (d.isAfter(today)) { + throw new CrmebException("不能查询未来日期"); + } + LocalDate prev = d.minusDays(1); + Ctx ctx = new Ctx(); + ctx.d = d; + ctx.prev = prev; + ctx.dStart = toDate(d, true); + ctx.dEnd = toDate(d, false); + ctx.pStart = toDate(prev, true); + ctx.pEnd = toDate(prev, false); + ctx.serviceRate = readRate("service_rate", DEFAULT_SERVICE_RATE); + ctx.eScoreRate = readRate("e_score_rate", DEFAULT_E_SCORE_RATE); + return ctx; + } + + /** Service 内部上下文 */ + private static class Ctx { + LocalDate d; + LocalDate prev; + Date dStart; + Date dEnd; + Date pStart; + Date pEnd; + BigDecimal serviceRate; + BigDecimal eScoreRate; + } + + // ------------------------------------------------------------------ + // 工具方法 + // ------------------------------------------------------------------ + private static Date toDate(LocalDate date, boolean start) { + return Date.from((start ? date.atStartOfDay() : date.atTime(LocalTime.MAX)) + .atZone(ZONE_CN).toInstant()); + } + + private BigDecimal readRate(String name, BigDecimal fallback) { + String v = teamReportDao.selectSettingValue(name); + if (StrUtil.isBlank(v)) return fallback; + try { + return new BigDecimal(v.trim()); + } catch (Exception e) { + return fallback; + } + } + + // ------------------------------------------------------------------ + // Excel 导出(Apache POI) + // ------------------------------------------------------------------ + private void writeExcel(TeamDailyReportResponse data, OutputStream out) throws IOException { + try (Workbook wb = new XSSFWorkbook()) { + Sheet sheet = wb.createSheet("团队日报"); + + // 表头横幅:团队长 昵称 · N 人 · 日期 + Row banner = sheet.createRow(0); + Cell bannerCell = banner.createCell(0); + String leaderShown = data.getLeaderNickname() != null && !data.getLeaderNickname().isEmpty() + ? data.getLeaderNickname() + : (data.getTeamCode() == null ? "" : data.getTeamCode()); + bannerCell.setCellValue(String.format("团队长 %s · %d 人 · %s", + leaderShown, data.getMemberCount(), data.getDate())); + CellStyle bannerStyle = wb.createCellStyle(); + Font bannerFont = wb.createFont(); + bannerFont.setBold(true); + bannerFont.setFontHeightInPoints((short) 14); + bannerStyle.setFont(bannerFont); + bannerStyle.setAlignment(HorizontalAlignment.CENTER); + bannerCell.setCellStyle(bannerStyle); + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 8)); + + // 表头 + String[] headers = { + "昵称", + data.getPreviousDate() + " 买单", + data.getDate() + " 卖单", + data.getDate() + " 买单", + "服务费*" + data.getServiceRate().toPlainString(), + "E积分", + "实际收付", + "团队", + "备注" + }; + CellStyle headStyle = wb.createCellStyle(); + Font headFont = wb.createFont(); + headFont.setBold(true); + headStyle.setFont(headFont); + headStyle.setAlignment(HorizontalAlignment.CENTER); + headStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); + headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + applyBorders(headStyle); + Row headRow = sheet.createRow(1); + for (int i = 0; i < headers.length; i++) { + Cell cell = headRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headStyle); + } + + // 小计行(紧跟表头) + CellStyle summaryStyle = wb.createCellStyle(); + Font summaryFont = wb.createFont(); + summaryFont.setBold(true); + summaryFont.setColor(IndexedColors.RED.getIndex()); + summaryStyle.setFont(summaryFont); + applyBorders(summaryStyle); + + CellStyle moneyStyle = wb.createCellStyle(); + moneyStyle.setDataFormat(wb.createDataFormat().getFormat("#,##0.00;-#,##0.00")); + applyBorders(moneyStyle); + + CellStyle moneyBoldRedStyle = wb.createCellStyle(); + moneyBoldRedStyle.cloneStyleFrom(moneyStyle); + moneyBoldRedStyle.setFont(summaryFont); + + Row summaryRow = sheet.createRow(2); + TeamDailySummary s = data.getSummary(); + summaryRow.createCell(0).setCellValue("小计"); + summaryRow.getCell(0).setCellStyle(summaryStyle); + writeMoney(summaryRow, 1, s.getPrevBuy(), moneyBoldRedStyle); + writeMoney(summaryRow, 2, s.getTodaySell(), moneyBoldRedStyle); + writeMoney(summaryRow, 3, s.getTodayBuy(), moneyBoldRedStyle); + writeMoney(summaryRow, 4, s.getServiceFee(), moneyBoldRedStyle); + writeMoney(summaryRow, 5, s.getEScore(), moneyBoldRedStyle); + writeMoney(summaryRow, 6, s.getActual(), moneyBoldRedStyle); + summaryRow.createCell(7).setCellStyle(summaryStyle); + summaryRow.createCell(8).setCellStyle(summaryStyle); + + // 数据行(团队列填团队长昵称) + int rowIdx = 3; + for (TeamDailyMemberRow r : data.getRows()) { + Row row = sheet.createRow(rowIdx++); + row.createCell(0).setCellValue(r.getNickname() == null ? "" : r.getNickname()); + writeMoney(row, 1, r.getPrevBuy(), moneyStyle); + writeMoney(row, 2, r.getTodaySell(), moneyStyle); + writeMoney(row, 3, r.getTodayBuy(), moneyStyle); + writeMoney(row, 4, r.getServiceFee(), moneyStyle); + writeMoney(row, 5, r.getEScore(), moneyStyle); + writeMoney(row, 6, r.getActual(), moneyStyle); + row.createCell(7).setCellValue(leaderShown); + // 备注列保留空白(运营手填) + row.createCell(8).setCellValue(""); + } + + // 列宽 + int[] widths = { 14, 14, 14, 14, 16, 12, 14, 8, 20 }; + for (int i = 0; i < widths.length; i++) { + sheet.setColumnWidth(i, widths[i] * 256); + } + + wb.write(out); + out.flush(); + } + } + + private void writeMoney(Row row, int col, BigDecimal value, CellStyle style) { + Cell cell = row.createCell(col); + cell.setCellValue(value == null ? 0d : value.doubleValue()); + cell.setCellStyle(style); + } + + private void applyBorders(CellStyle style) { + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + } +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/WaOrderAdminServiceImpl.java b/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/WaOrderAdminServiceImpl.java index 303d822..97c3fd7 100644 --- a/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/WaOrderAdminServiceImpl.java +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/service/impl/WaOrderAdminServiceImpl.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.zbkj.common.exception.CrmebException; +import com.zbkj.common.model.consignment.WaMerchandise; import com.zbkj.common.model.consignment.WaOrder; import com.zbkj.common.model.consignment.WaUsers; import com.zbkj.common.page.CommonPage; @@ -13,6 +14,7 @@ import com.zbkj.common.request.PageParamRequest; import com.zbkj.common.request.WaOrderSearchRequest; import com.zbkj.common.request.WaOrderUpdateRequest; import com.zbkj.common.response.WaOrderResponse; +import com.zbkj.service.dao.consignment.WaMerchandiseDao; import com.zbkj.service.dao.consignment.WaOrderDao; import com.zbkj.service.dao.consignment.WaUsersDao; import com.zbkj.service.service.WaOrderAdminService; @@ -20,7 +22,11 @@ import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -44,6 +50,9 @@ public class WaOrderAdminServiceImpl extends ServiceImpl im @Resource private WaUsersDao waUsersDao; + @Resource + private WaMerchandiseDao waMerchandiseDao; + /** * 分页列表查询 */ @@ -108,31 +117,58 @@ public class WaOrderAdminServiceImpl extends ServiceImpl im wrapper.orderByDesc(WaOrder::getCreatedAt); List list = waOrderDao.selectList(wrapper); + + // 批量收集所需关联 ID,避免循环 N+1 查询 + Set userIds = new HashSet<>(); + Set merchandiseIds = new HashSet<>(); + for (WaOrder o : list) { + if (o.getSellerId() != null && o.getSellerId() > 0) userIds.add(o.getSellerId()); + if (o.getBuyerId() != null && o.getBuyerId() > 0) userIds.add(o.getBuyerId()); + if (o.getMerchandiseId() != null && o.getMerchandiseId() > 0) merchandiseIds.add(o.getMerchandiseId()); + } + Map userMap = new HashMap<>(); + if (!userIds.isEmpty()) { + for (WaUsers u : waUsersDao.selectBatchIds(userIds)) { + userMap.put(u.getId(), u); + } + } + Map merchandiseMap = new HashMap<>(); + if (!merchandiseIds.isEmpty()) { + for (WaMerchandise m : waMerchandiseDao.selectBatchIds(merchandiseIds)) { + merchandiseMap.put(m.getId(), m); + } + } + List responseList = list.stream().map(item -> { WaOrderResponse response = new WaOrderResponse(); BeanUtils.copyProperties(item, response); - - // 获取卖家名称 + + // 卖家名称 if (item.getSellerId() != null && item.getSellerId() > 0) { - WaUsers seller = waUsersDao.selectById(item.getSellerId()); - if (seller != null) { - response.setSellerName(seller.getNickname()); - } + WaUsers seller = userMap.get(item.getSellerId()); + if (seller != null) response.setSellerName(seller.getNickname()); } else { response.setSellerName("平台"); } - - // 获取买家名称 + + // 买家名称 if (item.getBuyerId() != null && item.getBuyerId() > 0) { - WaUsers buyer = waUsersDao.selectById(item.getBuyerId()); - if (buyer != null) { - response.setBuyerName(buyer.getNickname()); + WaUsers buyer = userMap.get(item.getBuyerId()); + if (buyer != null) response.setBuyerName(buyer.getNickname()); + } + + // 商品名称 / 图片 + if (item.getMerchandiseId() != null && item.getMerchandiseId() > 0) { + WaMerchandise mh = merchandiseMap.get(item.getMerchandiseId()); + if (mh != null) { + response.setMerchandiseTitle(mh.getTitle()); + response.setMerchandiseImage(mh.getImage()); } } - + return response; }).collect(Collectors.toList()); - + return CommonPage.copyPageInfo((PageInfo) new PageInfo<>(list), responseList); } @@ -166,7 +202,16 @@ public class WaOrderAdminServiceImpl extends ServiceImpl im response.setBuyerName(buyer.getNickname()); } } - + + // 商品名称 / 图片 + if (order.getMerchandiseId() != null && order.getMerchandiseId() > 0) { + WaMerchandise mh = waMerchandiseDao.selectById(order.getMerchandiseId()); + if (mh != null) { + response.setMerchandiseTitle(mh.getTitle()); + response.setMerchandiseImage(mh.getImage()); + } + } + return response; } diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/util/GrabUserFormatter.java b/backend/crmeb-service/src/main/java/com/zbkj/service/util/GrabUserFormatter.java new file mode 100644 index 0000000..4e2c98f --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/util/GrabUserFormatter.java @@ -0,0 +1,48 @@ +package com.zbkj.service.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 今日抢单用户列表 — 字段格式化工具。 + * 抽出为单独类便于单元测试。 + */ +public final class GrabUserFormatter { + + public static final int AMOUNT_SCALE = 3; + + private GrabUserFormatter() {} + + /** + * 金额格式化:保留 3 位小数(与上传参考图一致)。 + */ + public static String formatAmount(BigDecimal raw) { + BigDecimal v = raw == null ? BigDecimal.ZERO : raw; + return v.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP).toPlainString(); + } + + /** + * 用户等级映射;未知等级回退为「普通用户」。 + */ + public static String mapLevelName(Integer level) { + if (level == null) return "普通用户"; + switch (level) { + case 0: + case 1: + return "普通用户"; + case 2: + return "VIP"; + case 3: + return "合伙人"; + default: + return "等级" + level; + } + } + + /** + * 状态文案:1=正常 / 其它=禁用。 + */ + public static String mapStatus(Integer status) { + return status != null && status == 1 ? "正常" : "禁用"; + } +} diff --git a/backend/crmeb-service/src/main/java/com/zbkj/service/util/TeamReportFormula.java b/backend/crmeb-service/src/main/java/com/zbkj/service/util/TeamReportFormula.java new file mode 100644 index 0000000..79374f7 --- /dev/null +++ b/backend/crmeb-service/src/main/java/com/zbkj/service/util/TeamReportFormula.java @@ -0,0 +1,78 @@ +package com.zbkj.service.util; + +import com.zbkj.common.response.TeamDailyMemberRow; +import com.zbkj.common.response.TeamDailySummary; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +/** + * 团队每日对账日报 — 公式工具类。 + * 设计要点: + * - 所有金额按 HALF_UP 舍入到 2 位; + * - 小计的服务费 / E积分 / 实际收付 由"成员级别已舍入数值"再求和, + * 避免逐行二次舍入。 + * + * 抽出为单独类便于单元测试(无需 DB / Spring 上下文)。 + */ +public final class TeamReportFormula { + + public static final int SCALE = 2; + + private TeamReportFormula() {} + + /** + * 单成员金额舍入与公式计算。 + * 修改入参对象的 prevBuy / todaySell / todayBuy / serviceFee / eScore / actual。 + * + * @param row 成员行(prevBuy / todaySell / todayBuy 已由 SQL 填充) + * @param serviceRate 服务费率 + * @param eScoreRate E 积分率 + */ + public static void applyFormula(TeamDailyMemberRow row, + BigDecimal serviceRate, + BigDecimal eScoreRate) { + if (row == null) return; + row.setPrevBuy(scale(row.getPrevBuy())); + row.setTodaySell(scale(row.getTodaySell())); + row.setTodayBuy(scale(row.getTodayBuy())); + + BigDecimal serviceFee = scale(row.getTodayBuy().multiply(serviceRate)); + BigDecimal eScore = scale(row.getTodayBuy().multiply(eScoreRate)); + BigDecimal actual = scale(row.getTodaySell() + .subtract(row.getTodayBuy()) + .subtract(serviceFee) + .subtract(eScore)); + + row.setServiceFee(serviceFee); + row.setEScore(eScore); + row.setActual(actual); + } + + /** + * 累加每行已舍入数值得到小计;不再做二次舍入。 + */ + public static TeamDailySummary aggregateSummary(List rows) { + TeamDailySummary s = new TeamDailySummary(); + if (rows == null) return s; + for (TeamDailyMemberRow r : rows) { + s.setPrevBuy(s.getPrevBuy().add(nz(r.getPrevBuy()))); + s.setTodaySell(s.getTodaySell().add(nz(r.getTodaySell()))); + s.setTodayBuy(s.getTodayBuy().add(nz(r.getTodayBuy()))); + s.setServiceFee(s.getServiceFee().add(nz(r.getServiceFee()))); + s.setEScore(s.getEScore().add(nz(r.getEScore()))); + s.setActual(s.getActual().add(nz(r.getActual()))); + } + return s; + } + + public static BigDecimal scale(BigDecimal v) { + return v == null ? BigDecimal.ZERO.setScale(SCALE) + : v.setScale(SCALE, RoundingMode.HALF_UP); + } + + private static BigDecimal nz(BigDecimal v) { + return v == null ? BigDecimal.ZERO : v; + } +} diff --git a/backend/crmeb-service/src/main/resources/mapper/consignment/ExternalGrabUserMapper.xml b/backend/crmeb-service/src/main/resources/mapper/consignment/ExternalGrabUserMapper.xml new file mode 100644 index 0000000..3f6f979 --- /dev/null +++ b/backend/crmeb-service/src/main/resources/mapper/consignment/ExternalGrabUserMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/crmeb-service/src/main/resources/mapper/consignment/TeamReportMapper.xml b/backend/crmeb-service/src/main/resources/mapper/consignment/TeamReportMapper.xml new file mode 100644 index 0000000..eb7e470 --- /dev/null +++ b/backend/crmeb-service/src/main/resources/mapper/consignment/TeamReportMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/backend/crmeb-service/src/test/java/com/zbkj/service/util/GrabUserFormatterTest.java b/backend/crmeb-service/src/test/java/com/zbkj/service/util/GrabUserFormatterTest.java new file mode 100644 index 0000000..b0970b4 --- /dev/null +++ b/backend/crmeb-service/src/test/java/com/zbkj/service/util/GrabUserFormatterTest.java @@ -0,0 +1,63 @@ +package com.zbkj.service.util; + +import org.junit.Test; + +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; + +/** + * 今日抢单用户列表 - 格式化工具单元测试。 + */ +public class GrabUserFormatterTest { + + @Test + public void formatAmount_null_should_be_zero() { + assertEquals("0.000", GrabUserFormatter.formatAmount(null)); + } + + @Test + public void formatAmount_should_keep_three_decimals() { + assertEquals("0.000", GrabUserFormatter.formatAmount(new BigDecimal("0"))); + assertEquals("226.383", GrabUserFormatter.formatAmount(new BigDecimal("226.383"))); + assertEquals("100.000", GrabUserFormatter.formatAmount(new BigDecimal("100"))); + } + + @Test + public void formatAmount_should_round_half_up() { + // 0.0005 → 0.001 + assertEquals("0.001", GrabUserFormatter.formatAmount(new BigDecimal("0.0005"))); + // 0.0014 → 0.001 + assertEquals("0.001", GrabUserFormatter.formatAmount(new BigDecimal("0.0014"))); + // 0.0015 → 0.002 + assertEquals("0.002", GrabUserFormatter.formatAmount(new BigDecimal("0.0015"))); + } + + @Test + public void formatAmount_should_handle_negative() { + assertEquals("-1.234", GrabUserFormatter.formatAmount(new BigDecimal("-1.2340"))); + } + + @Test + public void mapLevelName_known_levels() { + assertEquals("普通用户", GrabUserFormatter.mapLevelName(null)); + assertEquals("普通用户", GrabUserFormatter.mapLevelName(0)); + assertEquals("普通用户", GrabUserFormatter.mapLevelName(1)); + assertEquals("VIP", GrabUserFormatter.mapLevelName(2)); + assertEquals("合伙人", GrabUserFormatter.mapLevelName(3)); + } + + @Test + public void mapLevelName_unknown_levels_fallback() { + assertEquals("等级5", GrabUserFormatter.mapLevelName(5)); + assertEquals("等级99", GrabUserFormatter.mapLevelName(99)); + } + + @Test + public void mapStatus_should_match_spec() { + assertEquals("正常", GrabUserFormatter.mapStatus(1)); + assertEquals("禁用", GrabUserFormatter.mapStatus(0)); + assertEquals("禁用", GrabUserFormatter.mapStatus(null)); + assertEquals("禁用", GrabUserFormatter.mapStatus(2)); + } +} diff --git a/backend/crmeb-service/src/test/java/com/zbkj/service/util/TeamReportFormulaTest.java b/backend/crmeb-service/src/test/java/com/zbkj/service/util/TeamReportFormulaTest.java new file mode 100644 index 0000000..f226302 --- /dev/null +++ b/backend/crmeb-service/src/test/java/com/zbkj/service/util/TeamReportFormulaTest.java @@ -0,0 +1,155 @@ +package com.zbkj.service.util; + +import com.zbkj.common.response.TeamDailyMemberRow; +import com.zbkj.common.response.TeamDailySummary; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * 团队每日对账日报 公式工具单元测试。 + * 覆盖: + * - 上传参考图样例 4 名成员(王珍华 / 柯美燕 / 王兵启 / 胡晓彩) + * - 仅有卖单 / 仅有买单 / 全空 等边界 + * - 小计精度:与逐行二次舍入对比 + */ +public class TeamReportFormulaTest { + + private static final BigDecimal SERVICE_RATE = new BigDecimal("0.02"); + private static final BigDecimal E_SCORE_RATE = new BigDecimal("0.005"); + + /** 王珍华:22151 - 21418 - 428.36 - 107.09 = 197.55 */ + @Test + public void member_with_buy_and_sell_should_match_doc() { + TeamDailyMemberRow r = new TeamDailyMemberRow() + .setNickname("王珍华") + .setPrevBuy(new BigDecimal("21506")) + .setTodaySell(new BigDecimal("22151")) + .setTodayBuy(new BigDecimal("21418")); + TeamReportFormula.applyFormula(r, SERVICE_RATE, E_SCORE_RATE); + + assertEquals(new BigDecimal("428.36"), r.getServiceFee()); + assertEquals(new BigDecimal("107.09"), r.getEScore()); + assertEquals(new BigDecimal("197.55"), r.getActual()); + } + + /** 胡晓彩:28259 - 27708 - 554.16 - 138.54 = -141.70 */ + @Test + public void member_with_negative_actual() { + TeamDailyMemberRow r = new TeamDailyMemberRow() + .setNickname("胡晓彩") + .setPrevBuy(new BigDecimal("27436")) + .setTodaySell(new BigDecimal("28259")) + .setTodayBuy(new BigDecimal("27708")); + TeamReportFormula.applyFormula(r, SERVICE_RATE, E_SCORE_RATE); + + assertEquals(new BigDecimal("554.16"), r.getServiceFee()); + assertEquals(new BigDecimal("138.54"), r.getEScore()); + assertEquals(new BigDecimal("-141.70"), r.getActual()); + } + + /** 柯美燕 / 王兵启:仅有卖单。服务费/E积分=0;实际=卖单。 */ + @Test + public void member_only_sell_should_zero_fees() { + TeamDailyMemberRow r = new TeamDailyMemberRow() + .setNickname("柯美燕") + .setPrevBuy(new BigDecimal("22519")) + .setTodaySell(new BigDecimal("23195")) + .setTodayBuy(BigDecimal.ZERO); + TeamReportFormula.applyFormula(r, SERVICE_RATE, E_SCORE_RATE); + + assertEquals(new BigDecimal("0.00"), r.getServiceFee()); + assertEquals(new BigDecimal("0.00"), r.getEScore()); + assertEquals(new BigDecimal("23195.00"), r.getActual()); + } + + /** 仅有买单:实际收付为负值(-买 - 服务费 - E积分)。 */ + @Test + public void member_only_buy_should_negative_actual() { + TeamDailyMemberRow r = new TeamDailyMemberRow() + .setTodaySell(BigDecimal.ZERO) + .setTodayBuy(new BigDecimal("1000")); + TeamReportFormula.applyFormula(r, SERVICE_RATE, E_SCORE_RATE); + + assertEquals(new BigDecimal("20.00"), r.getServiceFee()); + assertEquals(new BigDecimal("5.00"), r.getEScore()); + assertEquals(new BigDecimal("-1025.00"), r.getActual()); + } + + /** Null 字段:当作 0 处理,不抛异常。 */ + @Test + public void member_with_null_amounts_should_be_zero() { + TeamDailyMemberRow r = new TeamDailyMemberRow(); + TeamReportFormula.applyFormula(r, SERVICE_RATE, E_SCORE_RATE); + + assertEquals(new BigDecimal("0.00"), r.getPrevBuy()); + assertEquals(new BigDecimal("0.00"), r.getTodaySell()); + assertEquals(new BigDecimal("0.00"), r.getTodayBuy()); + assertEquals(new BigDecimal("0.00"), r.getServiceFee()); + assertEquals(new BigDecimal("0.00"), r.getEScore()); + assertEquals(new BigDecimal("0.00"), r.getActual()); + } + + /** 小计:4 名成员全量样例对账。 */ + @Test + public void summary_should_match_doc_team_F_sample() { + List rows = Arrays.asList( + row("王珍华", "21506", "22151", "21418"), + row("柯美燕", "22519", "23195", "0"), + row("王兵启", "34266", "35294", "0"), + row("胡晓彩", "27436", "28259", "27708") + ); + rows.forEach(r -> TeamReportFormula.applyFormula(r, SERVICE_RATE, E_SCORE_RATE)); + TeamDailySummary s = TeamReportFormula.aggregateSummary(rows); + + // 文档样例核对:D-1 买单合计 = 105727 + assertEquals(new BigDecimal("105727.00"), s.getPrevBuy()); + // D 卖单合计 = 108899 + assertEquals(new BigDecimal("108899.00"), s.getTodaySell()); + // D 买单合计 = 49126 + assertEquals(new BigDecimal("49126.00"), s.getTodayBuy()); + // 服务费合计:428.36 + 0 + 0 + 554.16 = 982.52 + assertEquals(new BigDecimal("982.52"), s.getServiceFee()); + // E积分合计:107.09 + 0 + 0 + 138.54 = 245.63 + assertEquals(new BigDecimal("245.63"), s.getEScore()); + // 实际收付合计:197.55 + 23195 + 35294 + (-141.70) = 58544.85 + assertEquals(new BigDecimal("58544.85"), s.getActual()); + } + + /** 空成员列表:小计全 0,不抛异常。 */ + @Test + public void summary_empty_rows_should_zero() { + TeamDailySummary s = TeamReportFormula.aggregateSummary(Collections.emptyList()); + assertNotNull(s); + assertEquals(BigDecimal.ZERO, s.getPrevBuy()); + assertEquals(BigDecimal.ZERO, s.getTodayBuy()); + assertEquals(BigDecimal.ZERO, s.getActual()); + } + + /** 不同费率:service_rate=0.03 / e_score_rate=0.01。 */ + @Test + public void custom_rates_should_be_applied() { + TeamDailyMemberRow r = new TeamDailyMemberRow() + .setTodaySell(new BigDecimal("0")) + .setTodayBuy(new BigDecimal("1000")); + TeamReportFormula.applyFormula(r, new BigDecimal("0.03"), new BigDecimal("0.01")); + + assertEquals(new BigDecimal("30.00"), r.getServiceFee()); + assertEquals(new BigDecimal("10.00"), r.getEScore()); + assertEquals(new BigDecimal("-1040.00"), r.getActual()); + } + + private static TeamDailyMemberRow row(String name, String prev, String sell, String buy) { + return new TeamDailyMemberRow() + .setNickname(name) + .setPrevBuy(new BigDecimal(prev)) + .setTodaySell(new BigDecimal(sell)) + .setTodayBuy(new BigDecimal(buy)); + } +} diff --git a/docs/今日抢单用户列表_开发说明文档.docx b/docs/今日抢单用户列表_开发说明文档.docx new file mode 100644 index 0000000000000000000000000000000000000000..dc41d97394f8858fb301fafe90c24948dfb27894 GIT binary patch literal 22840 zcmZ^~b97`;_bpl#+qP}nHacdj-1?)c#;Co| zIkmC&oO8{kBnt`#1Ni&m%+1sN_vL@yz&}6T?VU^*mHw|yVE^01z{%9c`F}Np`gg6N zbVbvb&j!R`007?q+0ew^*wxn5&V|9l)`sD4*Q$gK1yEw7V1rpVarx2(8UI#NosWpQw8SV|Jsh|a7~}p% z&Lztowib_+)fkjK$C_D$H=rjC)$@qhU%8KT8EFO#+ufLF%S^=UD<}Kjbl9FmG$Dhq zlV^hyi_@4xB;PNcPCPJZu;l~%@8JGt%<4$%QO&slfI(R>0P=sw?0+Avi!5LF`JrFO z-D7+%8}+%NVojjbGnBpv@Oci5;MpSHm!0NWgDN(0rAhQl$uk_RCH;;Fp#WK>7nri|~#!-U2OR0)&!CQl^H}i4qlykTi z@R;xGQ>>U7e04GQi6c++kEo@CekhF|7nIRNDlNnQ#e-*+ymOUQo2+<8?i{don+6vc zSpqAJTVKkv>M|1SH_@^{mPlT8GyBb&v32xw&e7idoG2eUjxeq`Bc1IT5^XEkBje7Z!F;k=TS>}qImH;c`|YTnzUuF+ zOP910u{}!L3l-egSiRsq5$0)Denj1-(G$U?qsu-Ye{(|)FeW2IGVQRQJHb&%I zd#f*I`v`q4?^c&syDcV4p7?&v@+7^@oBW-^ddD&K+jUoZajD}4<&%0qK?A^kzBlMD zOA)~XwkRBu_9Fi%#YZdgnLjZJ)yZ-34YEDWda{F4G57BA5@wv=y`cj&vp(&Ky6-{+ z`;i~hDV}#SYgISam=HV;hUYvZvV6r^xe>3kLLU6$R*Qi;Y<;mf-tAc_CHYISQ{yY` zXl-j@&7+3O=%|;Heq?Gc>pG(9zX0 zyvodaB|`1!mho5S@v_MjQjD$X54+-0K)0+T`&AiQ=nALvy|(^NhNr)?W4QO(6jY6w zW0l>U#KN^@qF&Ygy!Bh_X#t@h;}zL=VS4l584*I~V8hTxQgP1U`4J^KKE29jG^zNwmB*M5jhwq?h%EO!xhZFyVg zd2R^r3cb~bH632ZLQw(bU531&v_LMB$88A`3%}?Wwu+=dp3b8cF3F!#1{~jzFh?aF zQ(i4oXPb=>iIpgU+X|VAl;jCc=tIO5d%G+4A~lqWwGN_dm;Hh4iXIAAvo)f~bnP`t z`^PFa4SDPbgL>JA9H=hDCR544(=*xbi-{h!&1 zXbn{78N?Wk+n-sbW4f~u5-#NGbPT+TYoeG)l{(j=uewPQ#VKySZ?1e1d}ik7s2+r1 z3AfAa3PgqW<;#>NTfCIqm)L^5LJCNW?=7gu;jTXtIH7lR=@LUWRVT2vS4|rLWnWFu z!e7xMjeAvv9Fpp{5T)*zlPnJ^aO7`F#wU^x#Zr_Q*Q)j0_G zJ~By?b8RhjO`CF14=lpoF_*UGD;-u}{6KeF;uSoeWFVQ&$QJ|i`grq{^nH)>5u}6e zu#b!`ScqK<>Nj5r#oMO-aQ1+UlMFbKz%!sQ8m?us%ON+bU2T^U8-z=oIF-93Cd?8~ z$q<{i4Wi86t!8$)p_vO&cTfxx@XzyG|KLgR3Ez9Fk11ZLy*ZC$LQWyb`DMP^pi$%S zDtmE&6-@d|+AnA%jUUD82@ZiC1`UW@iqZM!8 zEU(vf^(v-HPBe;u)}H}~!HwzbdC1o~RAJ$B%qqEj+601r(uzWZVf@h!o{{G>GlOc1 z)lr5E)c%W>Bl23sm7B;TDZy$+xxHhQ6(z_mOV+!gAv$`BxoPIBoKkcrw5K2k#Z>*7 zU$QTjjsDmr1tA2-bMdSa%BL1guTHTpecWoZk~PMr$FR7%`N@m075Ep|`L_Kid@5 zMQ(n=8so$lYj%}WLc^S0oR6)gHq5i&VE(9VvxTn|yp})ullbKlr9Ds6K0%V8xtlyb zQ2%)ELB#VldMw06pdd>O4y`1O3t_{2&WIApC+wCYH7+}-1;fKCci?yo*Vf|&p%zS9 zQB6HorXcZkWJ9yy{;8wjsBLoI%({%%scpz5ZWMZJOdw_Z4%fSGsgeX&nRcb8;l$wKI!(1lWOT)3x*9pqsYNbq}gi;#@c? zT4m7zuX!(nig*091rjRK9xx{Af*(F&?@$6f23$nYA8)TWA?s&1r#bIp%gxBgHEoNv+=YSq*5kK1 zU?!kMPn4V@ppZAEx|6=18}@01CKSXW|i8Pd{<3Wa#}-0MNQLjKvvx?7SgSFRbCGl zrR02BiYUCtnux`4{EFj3qU+*93|0hf3iAr`;Y^_N${Ubq=oFFkZu%#FPFaULiNjsUiI)gVU&DaQ*Q34oi zI8^kIj~bXkZg>D1QBhE$wVoa2{+-7qnQ&eP1a2L5wt|*F#}mAZd>jH0gteUx&~8 zamqF$(7a~_a-XhS0{oc75DaT2ieYtynMCOu3&;$hf&cqZ@|1Bg-Wr52*yjWtEREo6 z7SfvY%kDmpy3VeEN{u_bG{5s=y`+r)a^v-GpoksG7hSreaF3XA@#E%?<9;c( z^DW!+pByS)CP{`-1iF9-kjH>d+xMbNNiJ;gMlcHiPJ6nqd-aYszuu`M**EV#F-~Q2 zu?`9^tOj@@a4B2sli*A9;jM2?KsG=W3R{OyLI4+8xScX_mpli1=`h8LUDo@4ba}Af zIg3fxTtPd&6k*O_0xX@4K0aRm-9ewCHqp>4Yt&6s4jt}Z=ipaK`IUZ38$vdc4hL5B zyaJ+y#$6FTQBcUny#Qpxfw>5Ha6JBKj!gLLCyKg!U}t5|MHb@?EpVS0cLi9N(^J?ua_KqLyC_HSHZ-ARoA7a{s# z6o4?P2U0(M=;q*D22uWWqxq-p5BT52J#b9U`?$VeaU@fEVE^+6ORElcHb8!p03ds6 zlNg;e69Xhc5;ddawAfu#-s8Z;3>D`0c-&H$%Gq)3~mQ6I3duPpLjR~ zv(K zDi!WcpTPBIhgL0J1D8$<&l6{NVyIN}&*9TnmVd;3 z4l0uR^TGHqatE9eO$cjH7=v2KPV1>{?|~1pJcN5LN*6gA!#oYGix2lu8ORX!h5$N% z^^>uwZRhG*;%fJgdB`b9ga80hGN?&O+2D2?00%unV$+oyPol>WC_y(26+P?WncOq3 zxmC)Wa6DExd_7`CfxN(r@oRoZjwFA}yXsMVX+CSa7N9J&;3kIb*RT&)_yocQh@M0p zBPFj@0AZmS zg&KviDqF-4n9m)X7;6-=oZ~tU46B0_10a9C3jt#JwREf%8@hr)KKC=Q`|oLlFlf)b z)k=i>n-+isXuub;0eab1tIAFVWPp4QB6)yRAH8rJTx1mhUuc+Pz86kzCg+Ken2$4H z4RyN;KrbX~8Sp+v=yinK*d53u@`mP0l?;!-3R|h*{|4sk*yq-^^X7T24Il$p>NNYC zLg`lkZ~`P+@eB#_(0$N}Ku4B&z?X@MKoDSvjJU-(x)p#1V5vZ}CQ}Oj(oOvSJWxbW zAihxO!Lh>_u;%=8ZGNtoCVlw>CM;km7*;JF>2@rDwP%7LO}b-*EWx8(aP0=k2jy?L z1r9Juads#10|i^ig&=2YlXOIwTW3A6G6SqZGBu-79pnSB4bAK)N;m2~JmGaw|Ms~` zXS)*s`k?lJUN3KQ=4A#rgX99Jwln|Uf|#kmmA<@}!$B4RFI2EVuk1m--Cf+1PWAWV zjT%OVAPCS*AU_hBjA~SmviHP{`0?jdtj|sq5LQkbPB^OElL=%LVnxqReiErs`=I`& z`P^0w7mA`b2nuZjFOR9HBi>`d|z90WQtlVakzi>YIBxE4Dn-=DN?!D zSJfu(?RbU-xgue4-7i<8`RaWEfy}G0BGAbv51kxVT>Qhr%8fET{S+q=-(X@hYw=K` zu2%t1A=z@>$2vYn*D*?^_43R`zU$R0(QU>51}MHMd!=^xhW}E{qG7Y;oYX2r#^MN# z`RF&Q*xntqwrsOL^!PDF*>-G(`dpb(qzn0&Bz+pvkD&7z$gQnyg9%w++4Mf%7?oMT z3@-WZuI=!$YiPzw?>O5>mN>=3b^R}00^d9K!n86~Q9N>1&@j{-6?#RdUMF}45%J+T z5~S$~mY-^Y6anGE%(E(pjyAt@Q&Y!a{)77n43r?-5ua@3OtlP?i+~}B9Q40EZt#}% zJ_DI@8wLLs74(sBT{{m^ThBg-NI)!#2w0iSy$2`^%Y;S5xED|5Rw6K{l`KbfzX2Pa zpdLKX?0^!|t3&Cu|7*nidl&=~bX@OG!opOEf<@gmYR`Fdjc|!*2K|ENr(tCFOfi zl2u5t6_uK~^Q1WDRue#;4zCNfJ||=ooGy!~3n$JKDI))(^qR8R>9>-E6u;z z^r!bl9%Yp)e&UgAR&@9fKZ%Gz65Em6&61L_tmbO_7{Adz8>)nO>A+W#r;TuwS`exm z0<7DJE?Jj>k`7MKzgl!D_@RP>fI+aZb5N*xuiq@QrjoqL5Gj8Sf>^WJONn(>Ukem5 zK)UE_ey74t2_ixS^e5WaOhf7T6+LV~;L$LptZa<%_xD@kU#6vWf`fHznhx9Me=_kz z`krB&s2;Qqrjh=nEV=dKQd0g+>eyL)tEAPP{i#_UcA=C0t^q5$~u zJV*JPxFJwZun_=D4?K_|AQB&xs#(WN68^zIchJ3u5tvvRGDloV1EK)!@Rgfk`v58z zXc3_CH^4!9aXTGbD}2KQIo5>7XWSB7nn)6B_Sn`mc8~-hjRQe0|Fz})+tynf3QT#v zeCk7B*$hj=^)cb>1lbtX6h&_(k3g)jwjfYMkcfLQR8FDxSEOXNRoU?Ay!Y9OQs=qb zcy@)Di+@~V4QEODwYO=E!Ef1w+Xs$k8ji4XDxUK<>6b0!JA}ym{zsx2{)3;@laBuNt(gda)Y*lP8M`4&4y;SgQI>yU@Bdth)~Wt( z=bxY@_(=VyPoPRe4YTIj>{GlfOn)dC9hi7|?^k&c#YoT)Hp?GRTt2~(ys)Gd*3gxA zo*Egd9`ZY_NQkbnvQ}8g{HdR#s_Pqb{ATL>u$a_4;Y6A3Yj-hLUnXj{s9ZFak zGYmB`wpyjFEZssbm83jAp5c9;r^&%JajT}Cp|dLtTNNz8@4v1t@8fQ*u|W;lQCHG( ziLUC}O|1ob($5!dom$vBDa{RT=l-bf$e|O9umvTF< z`|zE}wskDV5gxilK9?-|&52BY&7yQQmLKb63ugBlue>=ETKafJnKQ2Nvhf+l2*L&- zk2(7=hx)|9FIJiKh|%7Cr#<=qAn{e;*Cr%sGtcbIK&}Bd$HJV8^1%9My+->FU#7Zj zgbF>qN^c{|Gg#++C-&4aH@*o4zC2V|CyK;>aIWzaA9%}LY4zrLihlfl$a-)&-aQzH z7tHFOE6&;c|29e!Lp9g)82(AH@GCy@bQSzF^QShZ(7qtL=hM=|7ob z?Xu=GFV?&giAlQx3&gn*t}yGOU@jls>iRm-O?o)6Vm*Rq*{t3f1vIDl5;HTF9)KXK zU6JhKb0a~DZ^SB=MrhZdNq-__RGr?C{Ol+msWy)#&&or^(c%)QVc~lxDz9E{_nuUr zf>XW7iAUbnmhPSqJ?b{$5yFW;rBycurb=yKUOyI$OeXs)En?+lh3?QR@>dE1+5xTH zsScZiN!n@lpPjL%=VcYNpK_fMeBlMjWoRN3*(FnPfB7!lqDZAE>>|@sjE9e86mlJI z$c_26WV_DUV#CK+OiB&m`>o~&psZ|Ogda}CjL?{i4x@Z&i;?YZCP3^DJmr-m3`%+Z zvUN~>$}}?2S^X59f%Db?(YItp9N~l?S zn#=wN-x^Z8{>QJ0J?peFeAZL3#c*aMu$TRx*33 zTgq<&&+cXG%*GCBr>Mp=E8ky&1TyI-zfViCd`;6P2IaY-P=RuGZM8fQ^Vr|=Um#W# z8k@Xk&9pQzk_V4O5>Lu}^|rI4p$H5XINbd^D(m+327Wk3tDO0VzlI-G`gDB^My*Q1 z7a7Lw4ROK~_QY+>Zcq9RNgUU@Y@?3b{?-t}(yAs$1}L5-hpG=Q)c_Y~QD5%V>sMmR zH!E0Fu8mMtX?_eHMZ^q-9p$^Mp_>j$$8+DG^nYA8mpAV_na#JUdY$_P&I(8Kcs*XC z2{L-(x@;uT6sNW=@M5OsZrr1jN3k&&v(HB5g)e_>zap=*hcM_%e8yxvTz$pR1&0!z z7aHCV2KTxCBsn7m+%Cy1w;)l_m)|xN*i#4$<+bb7pAh_Zo>xGF3B7yb%NtJN=eadQ ziq~e)1azi;9g(cgl_=AF9G_=@K2M37W0*I%1&SB9x6Au!eWFekgXC7D*MSsM2BdH{ z-M-*XgWLH_#vKk0+~(TO?z?b?wiPjfGt+SPmd>GbDh@mW$GY>K^n}KyKEyQebj!WF zZm~2Zr*cYv56+W1YZ@9#2gl0Z)_M5HyJ%7~NAX&S;$Ii;2Cm;rGz#zCvd{BeOsMpiVROtO}Y!b?Zfz@v+uLU(*K<*tnGZv5~0dTjJ3 zb76i)GA&v(-~ikMKtS%1`ur9p?(5U>rN{IW{>a*2uM-hu=_!UjG@`Gw7e6vq*uVao z&oayb$^lf%ytf2`3*7$+@Yk<~83O(qXIbJ)BPsc+nu;-{8~d+EFDiI7o8yJWNRnaJ zmHEb7P$hvLPQVFnAvmNpgjzf~!*L*P&k#maW-EtkH2OeZq+Jad2JR=nON`EEM!i^H zp&b~URe8EUx;X7Iax)b6W){NBl`Dr;{OBzUtl#fXC721(ATUJ{#=A5>8NL3i)1^q0 zjfOm0@g#VFa^QWEEL%wYJot0YZIG7bUO(R9@#C6f@69uGpT&KcpWB}_rx*-}eLZ|a zu7~UK%UQO8V$6(kOPA*tsI0>YB1=1CEIbXo>mhJ4%;Cdar_K1^)J*#U{Dw6_orFOK zph1L8hXL@0#*oIBv6?_!1kGAJS~ikUR%1_YuqI3<(1~*k@8{0CVMKcoAVPq#`QDj> zA@B^=uojQ*9G&74x&U?Y5g(rMrPs)ln8GVG^%%93DFeeYVRYg&kYy6~Ofkv6yF2S4 zLZjn*jE2~XbD}Obk`g|RKDFFMJ~qhm?ae6XRiX2hfSNMgZ_o<;5dw#NR@%_1-4s#z04SYTrnvl})^hNx%`YaSn*%< zU0cy9o23O=tJj?!7r_OL_Z~DFo2#ho>&bt~K(H^RR-OIVcODyDG6U6DY+kfRQ@n^( zJ!tBL^P)RNUeeWZu8fnP-RhM#&F3bK_$RWCRZopLT?I9jLY#NVOHl+_;(j>^ccZ|$ z_)Hx)aI)U%@YZG3`Ogrw5@V6iVO~$w>hm4a6vdhD4Ncu)GZLmTzUb^A>5#RRtlQI` zGi75@WYJ~JZnqJJ-~f)t3vLlRx!B4L4dq=YdwCbvUEbc_)V2=NClBK%;e`$7raE~Ezau3AM8&`_iB64$~uUNVs z!e778pe0wDbfrfD#pC!#peo1dbIwpVI%*c#inHn{pTL8I&efiw0Sb50e=<~2vby)F zx3eZwZh;gH2g`kFhY9_DRV3OjqWv@VNw6KmKYJxvwWGbI07&(GBakEjg)-P>(&bl?5%0it%+&lQUCBEt&JWs@z*3h<8jiFUjL?I?N2vZt*&hj#4K=vcGaa%5`UZ2B9w zvQ%+JRfQ&(-7#`EGRh5B2K}AL&K+ly?sS5QETxY)D~ybE^7Q(GG3@JSuo#tt1Rc8N zbnvW5QBW?9Os&hp6Eo{_{4nxKCCK6Hw@#a%p*}Sfo2AnP^QK@g#g(O*ux9x;g_;e1 zgUViYasr8Xfr#$4RzS;YkWYuIoGR4>`#@ z4wZgTklW)mS|t6E5Z61fIplvX=~}_cYL|#!!VRs`c1Hcpl2l`Epw;s6%UPX{m&II} zaiVk~nH7;cb*3Xjf=|mA#8NNP>Zd%uycx>I%0ezd#z60VbQCnJ;Uq_)z?-tH1d&#m z%zzU8kI_uwp$1pl!D6CBPA*HR_VM3u!-6rS{V#lhHtG=?(;6u}8VzkgW6q`$eJIwP;VVtBOF<+H$crxd6uZCDpK^7#aM>;_VD&jy*8$k(hD??K)3XCyOM_~nDiZQoIO@6+x)xf%G>>N3kGgaO-^|)HqD;{ zabv0TziEK%brH`k2<=ghF|v5>Z<~h`*v8MU(ZVf+dM&NwC8D6(r&<)q6P5h4FCj|s zW0hjpK3^^}N@Ue3+gQV^<-FixD(hS#Kap9VmBMdpd@Cyy#7UA(dPqJQc#_2V0rErb zo8J%k=_8JGQS-`pmnA27!rM9VW`|I$0T-^P>&}@{xq?@MB14fa#k*x$tsUJq=KTCd z74u<67g-y-E|~a`_ri+r(5BX4wwBIW+D;+Tn)o{Eq|9W{&0x^TxsFVeo6M-+Z|?EM zvIG9{)M_z<%%53yT>b=(Bg#+ngB8b-A*=P#>0WgPs**2{a$Zyka`}u_-R15Jnc&#| zD7JicxsTJRpwzrSg-qmlwr#d>E^$N`7Z*LVe!NXO$tL;8UT-9hgpiEKlXwv;+J#%! zw)3^N_+I+{l=sCOt^6_PiTf1lw3hJHkp0RVLoDJJ)bI7?cppvBAqyAxovAIStf0nW zT2@5DjK27hp`MFu&KQ~V`@Ne>zk|&2`E#pUT}9FB!(u2H{&&zzDYN*Emv-*~l)hSp zbphG2rBDK#t`S<-`!t^As;J?Hv@dQJ@h_YbY5Usj>uJ7d1)ddq<6J9|YnBRDZA$kh z{*RY*v9@=r{1R8hp_#e_-t96w9 z1I#a`cbFTrb_W_vNv4j}jq4~v;&?c}5r#OO1wXl1Gwz8+%kh*_nT9GlaNKoK0BiXU z?@dTSn|XMv1+)q*Bb*bQk)!W-9@__8%wlo~azuh#)4Z1OFQ!<0g^I6^t@*vCzfho$ zMnkN;#~*3LFvevphNyTH#bEgq8b)zh@js1L?Hv2`YBC|jzkGs#^PSb^7#YlP(0v>+*hn;ID+Rg-*nt!Kgj~dEL{)u zX9`w*z=SE2w?AWzO5kE+96 zX4aqmpLrjX`U`N1e-qw8;Bd@B1%0!}X`hU2S;zBHq`^gvmTB@clYKD4=?~%EI|m{K zh`(5r{;Gnttn;a4gMb?JfIHMKeXFtgsp@+jx+<6h>${H>nM{M1*UMt_2Ah@_ZI}%- z6Sj(jGb z3C1<80he7M1f3p{FyfL zDh$mEctj>gu)u=-R)1J<)$!wzt92Oh zfO9cRk#H3#0kHE9%TmSjioBAX5L$!M*Pk%$+a+ra56dogV~RJOJ)$yr8&UN=p$~g5 zh~!QnAY(de+n{u>7v<;T>2WY=U#%Cc3%m+*`_fJkJ`mwd&WvqNSQ9_BGLiAcN=?O> zG6$XLh9p6*&Ie$U;_~9*8Fdk#ErgF{d;*s0IB*LeV8tJ1_3jqPqvtoYTcKHEH*^?k z0p&j}%UbGZo`DMBTlaHw!C+sfXneSy6OSQ@4M{vbA!_?;CD{otVY{1Lq+P1UEYUP< zJ+0`-j|?!0Zq@h%CNm-k{}N?Lj?cN0@5dmfo-9s!*eCQ+n=x%Wtd4kXiX56TB~fG! zuR6(e(Oz3mp;9p^(-q&Q^t$79n51#Fd-;)fC(Th*Pc_pYXKKI3o()5?XAR_uucMYc zo>pk24d{K^&zQe6=!<(I=XI0(Z%(z(aVok*zRGUvv73%+Nqrk!rzh3p<}#*@WGMY;2Q{8!ZLOG}pq%>fKT7Mo#f{f?Zf;QL04+)`NBX zC3ON6*Yc_kWzlz&456r$%~%247_z%+v=HrbMT0X#0vx@3`)bsuAl6a4^^XV-XRECaobZEAXJaa7 zcz20wZErWeBKMBLJ48pi$`G$Gb0Ji3Y$2OL;6BU?pEEeOLZTwf7gd2^r3=VO^e-)G z5gtX5p7=EMHKJ1%$Dg^V<8NF3OP@LS$l$NDd$6a5Pv1%u7u6Tokb4T}n7-PpJ34s{ zQ{&Tnr^f1hElxH4_H!F9_PxNVRr-EiVzA%SFz7RvHhPGu&V@5tm_6@mzdJ4!pT#$M zIB90TA6h-Q-cE-w%ZZUEp4^*(Kp>9l5g(n1ve4rL+5`AGhrH7JxcjHq0n`wXYRj%N zOS|;o5*EcsC~=C$v<1Kz@14W`sw&s8&lU~87f1FS`Qe_#4Ow=|lU1H!P#>(IIQGdO z=yKqxCZ&9tszUU)KV5JcdR1Ln^x5t4g2MQ?(_K)3QIKdL!E4JNhTA}Tq4CGNfbXXj zj_h0olU2LfP|L2LIkLKy>VhVX7SuSIKj@!&!t)tsrhu@p&AXG!&TIVxTUe-;Wr*Vt zhSs7!T<)KzmqMNUfLaI(ptcUSwP6{MCSa^dyVOE}FV*rh5~_ee!TOhgTF+v_p@zWe zXlB?PUmdcB4{JL`y`G$!G7STWg`kF}0x)t-a~gccU&SeX9YGJVJ`=-mu@&HpAn#^7pOwfhjzhYIr%k zR`UD|Q~1D-iwrl8u>e67%hqZptaJB?Ky$PC<@tDE=(bCYKwtKLCsu|l!hOOyCm-Ut zkhP)9YgJAJo08?|^hxNE&e!=8 zo7`3s;zgu7UNZWHI~`wh##mx?&)xR)O~kB7C&xfVG6xy@thfDTM%0$4h+;7j>(j!DqpP7@R>pw! z#fuBBiQ7ub_i?)UM9tH7NUx5VhWt-F?q8+3V)>zu*!tNbH9eXQy#oh4}u2{?!WogdQY$EA%$|u^5>jrX}R+?F6_Q?S!VG6f4d9 zKCzpW@!32~c?d&gGe(hII3#OabBayWh9Tj_j1zJ8+)bk0XHr&(`j(7@7O;*+H3^rA z0jbgga}p?4N6zcW;L3g^%2or3XXNP&`x>7Xhm!K@gvSDioU|`wTdg>CQQJ$tV&E!} ztD5;iMc~t9r79-mvta3?(4BYi``)3 z0rM_S@h8S}Cklg$P;)!K?Uf5zz#(y}FC!~t9H8+L2Con=_u#Qisg4>&P z#{9gnkpxrxoJ*#W^Mdh-c5Lchb!2c64FslSl`-nP#nFi|W0J;9&_}EEW$)938#$|4 zspgFY{8q%>*WVa@+MnWZ^y>EBm4i#TOQ_8PS}4!L2pBM$nuda^2?y2U-Ph_JXCR&; zL4pFY9q060==+|)`E!x;g1fRWMNp{Ozw}E)(NBrzb1t=KAMWQyd@SM>O4NTG1PM~o zKZ*igEf&H*nC-5hZo$8-$nTyp?XQ;eX1b>-4e;tJf$ zlj5uQADKt@k(oXU%T_`HIZ^_H*t)464K65(U59m@qF#K_nldTh3(v1tZT;_LNNin% zgYWV4HIKi;RE%pIw1VN&$Hx6(og6XPqss8={`DcOdWGrNNODLolD;n^oA5(2;dJI3 zW)>$km>dAr+K9zd@vOD`uV$PbSWP3>sF@6riefM@+W4m{=zh@6)zFb2-zWj);YQ1q zK8!M(KmEpjMC+^e4SWOnXIg-K7y}bYQF*Sd;?0< zmyv+_`h}{^jQSF3CS1HNr9+#hbAepzh+{CHMw%wjn9h*-C7x@O)|39+G(hYaL=gFe z@tI(OATK4drCV_X6x%bi<=U%ZDn!(eZc@CRXDZNBUxy5rq}*=+CIFovF6>js0#C}K zCT2G7jJ|EX5%w{OP?3o8*R#>Re;I8LPC6t{e*BlN2OWLn_s@h5mRxuJ@h_V-9J?4I z9)jMw`#=_uzJQ;$lrOQLMzfOdVHo)ZP~n9f@jW-DzSPkgSKm}XOaN*0A(sn~mW|#p zdZ=i@Y==+ZYhEan#kX$#c7_QVW-afn74JQC?7pU1WKI@~(&rD8H-8z||C({XmzxD( z7rGeX1lB}qfGuf(BM%1g0i{8r{yHOzMBOAzPF@XLPJNmBD5mRuV0no^tss5_{Vd*O zz}%ze+20^A9J$h@U5;)i_T5O+#FVZy0UMy_0A3vr(cgaFpWF=hr)Y`^C1Vz8u}-6; z9rugBX^g|H8vPdEONs$#-7R|+wR=_C6C3*-39rj%Rfu+kpAgUFMlgTuM+5~Pu5X?| znhNh9qQ_n@94k;}uCkI?uh>%4)p5lu6_I`3pPZ9AmC;8*s zf7JP=#Z6Q8g}~CMPvDCdy_x6v;?j%W4iolN5UCL(Zvwd%URl@6L9^zOXc&HOkX5^a#G5|eW4b>lwiNE_jBQy)ob0Qn zlLeOM>yl=g<6g1Bda4;pkin_4=vx0%kOLQ+lJLy7vt?Y$%__5>Y$UfokKAi&{O7Mf zOrd#kTA=w1Pquz!{R3|!WR$FB!tApO+RZkiA`1sc*sfZd*cQeM2PcR)u3BHukcGLl zih>g?YgD=JZ+ilzN@%T+pqa|pta$c(2KR{WI^FN?9xkOPEWuXwBo@CEiiJy zsHn5Hj-6qn)P{HlbX{H9mtw~a-I!B)DgzWBrr+qZA((2aW|qg~(6rMAA zy}{8Cw}BouH6FSh6Wpdw)A9uAZXvX4Vn-e;4q-C}vripMvg@QOG*o|W`Pt!&;@}G_Rk8) z_ODVXvJ-^_H*@P~-^VGSf$S(*4=8W~BmC z7#BzoFl^U=tY>NKjZQ{YL@-C5S^yXTI<~7x*-T0+N&mGr;U>ArqG`XaRqe@iIYT#B z*n=DXK!5`6YeEFm_6%FwB0xJ{O$4bN>vR z-BPqnh@Xu@T&px!BW{ptJ2&C-QR+7%|2iJiw>7egcS@XB*5%05pA8Gf52LJN2#Im9 z)H)6{+)C-y@3f%_?+sPIN-0*V`PG~w?8XJ+*qEQ;S4w_+D1U7}v?b{v2K8~egdR%x zX8ASctKM>n68d90d~?g?hz7@=x!qWuO2ZnO(ht3iXXg~q%d;Rq;}||)wocW{+7=p` zsl#0tQ?o@bFW72z<3L@M+;3o8Z~p&hRi+pMsc8l%03d|{03iOiD%07;)5g^KZw2dt z=7z!=6PjNV)l=*iL@5;%47!wsC|rZ}lB0Iyz?4mB-{ljaeUsM)k;&Gc)v{yo{H<{x z5(Xa5cK7=W-qHB;l)HlO`o+c>)j~DWUTXX|yQ++~5U4@{iiB@|jsn_i3+xPl8(}GE zoMX4REXWO z2ufG{SI~F7NTecZVj_`2Z&quZDJ+NmZt~!xs5$NlT`;{!iq}wOZMY#3jw3d2{VX#x zo!qpLX1ZWfurI?}>=>=0^=&2~mQq?}(>o#UTNN}XY*Z&-l6+vq+mrI&`_L{R&@ULi zxM)-JOiRq-v_j%$ut(T|iZ_Es*wKnN%SLeNZ4+}|&bo-xbgI*IVp5Hu2R>;BKI!K! zWaGT?_EM!Sh)hr75Ajd^oO|)K`bn7@IX|ZwrRCY!`kJ?V#M~4LEI~nl8MuUD@TD8r zudDVrvGnrxOHC#&k0V-3-Bt~uPUu&!b+@K{ilu&>eWfPBM5$;*n#eway9=bI{$Zs% zHnkCox;fz@Rz~zCmlchO!hH5+mSCTkP2KJ!f=3crpsy=t1T-y{w0;@(rqBmhu@DZA z1us&~tH98op`s(JMQJlMLVi)~t3eR{SVK*AC|6E|5n%Bt3XcV}tgsSBT(S$Nte{vo zll;^RfFgXizaO(?M!ROiRX4$KT2`n(X;mw%%G==SmdsGnB<8MeGo6+#y1TVyOSN{* z$e}aUS-S+Y8e`W8KJ%w?zI>vBdh$K%ekI?fregBr6uzcP-G-GumKlaBftg2NA(lb} zeR5PyGy62k^iD*OoDRXJv37pte(Y7wlhU?jfLl*@H~Tevl_K$A19@!I27Sz{NMv+e z-hQhBdT~Zs0TcgWz4<~tZ@pb)rAlO_<_p!*7ypVkIu)N?UVp%U_woO~x*hs+AAc5B zD>>Oa{C^AAcNw(`!+`)mF9_gY5%<W^C_d`gg-8#S6>!GocN4%Zzr)^KQ({nQjjx zeBTzk!H7S?u$hY&JF?qE6BbuGQWL*E6u-_T999zyvEASiXCz4YbM@|VMXqdZFf>JA zL5i{{_U(ui5>2*6X@f%himugpggKc;VNXmL6}~4IBU}@wPOQINL1jigz|kp&g;+MD z)Z7-w$VL;9j9p)$ly6<3p^!uHltF=dEl;n9eaqeuI@!sJnjy$e8+%rBRI5nN-BlNc zs~;&4%oHA>1E8n{tDP$MdIbN=+I?_Vr-_W|0`TGyc-eF~#y z69FVaHqS%nfVh2h%7nkF-&>mR-y6e~*O z@c`m-zaYdQGK~zE^a1CZH-KwEbYwyPTZo#c=S9I+I!Ln z%6Qox+r^hz6Nw%RC1l3%TcF);)34v;!_vjPk95nu7&n$a&~B@Rh#`6M*l6n2zE}K5 zzgM5yT3g!oCTPQ^O3|2w_z6A4$XJSu2m%okGU}19Spvl)($L5tWaGb8j7PGdaOa@d zZt!J`?X||@D5kkmDIX_UY$w44_tYPgM9L%$7=HZ`K{OE$zQc9qm~N3ans!1SFUp-U z8`Pt6iU#pn$ZH@o9%Pbs;1MnUtsW=(PXq-z@-ME0bYtTZF9hyi&8$c3cQFg|D~9fh`eu-Kor(Ptffgj69HE&D!dl_Ly~yT!VKcD; zaOtWv{C|qM?w}^OrXN9yh?EONkPcD=L?VbFy%*_KK%_`-p+pcts(^IqMVf$gL`sAU zgkD67^cGNh6QoxuZxED_>wV|@{(8^MnM@}6?X!FKch1?}XJ-37hifG?FE&NL$)EP( zk)2gt?c*>bF?~IG1q{sOecZtHlE{7_*3I=`B(GX-L0!_s(rHtn!ZV<8cu6u1xKJN5 zLc#jMXHP`RMqv=F3PpTR@cf!#@wp=Puu{G+d2UVs5FL)*CxKV zVE;~w@1{t#|J=T8)7o{H=ogd!c~D^@nEa>q8q{gw(KCqOIuMLM?!$5NpObDH;h$yE zK?Ht!3~ewzxQ2MT`7M<>`E)^HX%i>!c4|h|d@z_fKx1K8EHrL^ruD8EFjs6aTXSMU zwU@(MTZ!1(UU2JLQt7uA%Dm`6reU&d|Jyqc)vWy`>S@B42?HsaW4qJk3co=7y~CbO zQ56^7%3w->F5O&H#7wx|*JM5x{Mu`{%C!7NVtI$oGigU^=1*}^ow_PmTuFJ=ZF;#0mzrN8^m$exO` z{O^pg(W^4!$t0AVda_T~Krz(6mh5Enr-X#OjYk~XJlFsL5n?%X#K1NXM2itUHD_C6 z2i@b{57lT@sOKQqk7CyBEwdHIhECT^VpEKvKXi$>W>{bh#@QG6wn2*#uafe&u>dC@ z(X!;HsfE%0p}$1A<}!B8K^>g7I8eJjE_b(ba@{G-;+7sizgSRTqj?q0T8yYT6u|n} zlZt|#0h86gRq4{LzT7=T z7KNO~M~#7g@6rW!pfgM@BYPoqeLqN|Ztw-DEo(LyKdIXuR9@98kbR~{&fxEFIKL6Y z8`RIqjMjUTi9IOvD=uj;7OyKw#$IU4d$wS-;G9CX8@7+Q!-i6p%Ve4_-cfe3#;<%+ zYqk^mnH+Noo#ka%47|S)SlC_G*UtY*c)Qyk95!coU#t2%4KLmEUIHhq2L?w)U)j9s z0QB5j1RobyCOtxfB*R`dyBhR~i@L-=41J;~?-E&y5^{t6imgYkqfPSmFtF zw>i0)i74aEtu4um_+x9U_05*$F?s=4(s&rJG#7cHwXBVqb+62U{Vo%S<6bc_5WT{O z1F?1prQl%nQ!(-ouTN0Piu#K$?XJp<+}}YIFd642nN^;9ftnw3rDPY>)J=^wWft+r zn`;MhOE+%uzCWmpFs=4m%-*_k-h_$$#Nh~p>c<41@!OJaMm`F?dPPR!!Qnq&Lc2Bw)14ZMUFPc>HPxEh4H}EG-yIHBpc#lX;F}ys{Ol`cY8NZAChTBSmypTq=z* zuFGW*zMME0%SE*LXpCK!C|nn#%)3~)#OZd7p)VV+XF0VX4ciQ#N)3@7cztkE* zTNlKnM9iOUpiVBBa~$}2+sFrr;GxmtN)(8eI|Z-@f$H0A@botTS2Sqwe3T6NtJri8 zE4L3T-PUacN4QArY!lc>fqF?YUf#{m`5x-)&X6zn`n>_E-=QabLf}%MDZUxTIj_dj zN>k8%dlRHnBYP@Zxg*Bz`VWKEh!LxPU3NKh8tmj0*#DWC9s? zw4}{7+D9kk;Ud&KyE3Zq9TvCnu5zQqovg>bmmS`&8ikUMw}zdL_X3%e|;h4KvQ6*-nc!Rlel6oEE#! zs6(ijxr7~=L}BXE}M(Z%0k_jAkvVZ6{EM2qc(63Tpzv0&gXO$4+3kmbRew#n}&lD zrC9n5LqIxZuj}wucxcFEnUHW8mu9%gDxXw(<2Sf_zYz>anJ@1_VTV?R5>Iwv_TX0` zAq_n(AW_Q2NhNiwT)GO_-&CKnR8qx2^nfP&l~hA(*{Ui201d5|mbntPU47;^6Q78mG-Y z0!sm369y9e#_m;jZa9}njZi$8tPu2sg4bGe8`sf1IRBRSVE#9sx-e?hu*ER zqlP|{@Q$V=R@rk=*48Rgov(ClTNDbnlcNP?ZE~{}?c*8x2Bk4|lJ8m>p}W^w8Z?TB6%%V<5V~jX2pym%6E83wRC4=4qUIo z%G>mW>R!YZ9kzZfuJg0ubI+%^HoU_gSrTLkABfA@4P>KfP6pZ$%a;}O?KUiRvxt>f z6%~MondR~~DFyX%C~9u3790f5gzuYzJ~QsqQ+6rJ`L7(r(fY4|$5+PEEhMLBi11$l zhzXSp@2%G0H*Hx6^DlOGj1!eg+J5>nZZwtdLv|sWr_SVt#2krZ@kd%;Hu6Boqb&K) ziH%wGuxe}iO428t-c8`1M}Cjyyq)cVX2W_iiFqhwPQDTB zyW>_0i@dWZ&GuFxDuv}s!(g=ND+^p{Z9_}fy*!Xd&^%cPnh{ST+(*>RxCHB)Ri!_F zE6$;Q9V<>hRDFiBk1Qzk`J;=I;V+Y}!Mjs*A9b;lLdccKQhPyV6-yz5Kf|GF!p+`)l2v6z$FW{g1c%C8=A9Ck` z$bBk*E)l9E)b5sNk~|O;{;2}83{*JkYnU@Qkna?J_EP2y{LhlgaqahH9Gm||5Glnx zgPjDl7lH?g2CVZ;$xdP=G{-0VSV%*blthJxmuaeIh=0s)G z!q6`i0Fe6|^GElfmQc=s<>G%VsGvR>HE}-^;3J8=SijSFQ~{`I^O*o?DI@`>Ni-@N zHD^6TUy?pu>~q-*DjxaBKhAp22A1q8{@-#SR8gpj$(g8vU!sr$R#Z5$CdYB^*+@q0 zIi$UcEaZRHP@VT*H;-xC>DxYPqlzOE}U$*x94XUIyU4B{~@j~X%8%1rS+MnhQ#Y#=8BhMs*W(ft$7TY z9<;6;uKMd!Z|G!yZi;>s$IE-cSM#hII9k}2;C zSO|!8eWu`);R;!bK})XFBL&xfi`rGXjdmJl3Jlp?nPN?gBj_yt&a-02+Y)znJ}>(K<=@ zRL1YC+i#O5K5L}m`MlrJHP5we)3@GTpVqx3f6HI!lAZ%&VP&dSAxUl&6HDvIKwcSW zoSF#459~k__*7Yi?jbnCwf0GN?APli-SVl06dY+Fpp8Xybk&@7@>hZ9O~st%N{Ra0 z(_4#0eKvmc5;-SfS)c4puThArMw(iOec8>tf9OG8tkA`xZ_ZvisKXl;k5%MxXh_Fi-6B|fDIqjo}I7dj@rV`ty9-Lm4_b>r(y%lUoe8xJp^hwm2Oei5d!9liJ70>86ICp|CKQSb@wb||}f0naRq3uR)vetqs z=*=C-8X0kpO+3L@ySi6%iC*c=Xuc$r>!tl9Y^(S;Cq-iZA@zZ(%F*BGDHv6iO)UPq zCHFk?a6X5f4o`|*t36Lk`AHMJMAH&<-%V!=^%d-2b@~1tu`7ZTgK{VPatObK)R`6f zxzqg@Jmdt%QHD#7`ZELWbGa0xLS%*}TVww2a7o}u!AiX)=XqHQ@^z8R%J0ECZ3ps8 zm8Me_lU&9Y<>XE_;#2wE6<2QOzdUTmt;$0ir!^&9(GlB^q&5^*UwRktOJQ5PXaZI& zcC3+?(zA;eI@LoZGHMeDN@0dv-1of$X)~#OGj}_^12^}$mb*Tzv>RcS>A*IIajkLS z@7JdFJKJZJZ}Je8@{l+0+RhnKIg27^$`{rKw>H)YBrad~Y;DquGXH);a#h_9P1m>i z^y9+=qk;kzw3QB zz5>(D1u0*4=g8Hi%?0m>J>Jc4k?kk1c}JyP${v^}e%uRdjzlLjS2C{`DJhXVqiI*i zyk^wAIg)g0bi6dVLnEGUcgU~v>TV9~Jp2rOU6=E`5fw#r3Il$;oC*~m7H&$%Vas}V z1+*yG@m$r(cbGdE92ux(=vJ)>2XITIJnf)-V%n}xsY zK~DDOP%Dj8xyx;&oIcU)N@#g^>J+Po<*9u=y3EG^*0oczJd>=U)LDOUG$Q}7y~BT__SNU3fLpFLqjk-8x?C2D zZ-BcxV%>#$Po!S-Lt(?<0`GH`S8W2bcquVhGt~+p3o@+cVn^8 zG`Wa@623VO*K%>LMZ?C(bT`!c!JWwbp16FYdROKbRC!T zA*H~~h05!ddg^IqePBW1rMi>o^~g9Dv@rX!}K$pg}2`=wt{Eq*YUeKs6M$sGvcidT&E2T zB_dP1Ponb)kh$}1^c0y}_vyzk?(UQ+-q?A3^3O$$<0Z{*ej+V-p)_M=?A}5I_#|y2 zDUe4=+u6_rZcPV>s^qJGV1nFHvr}ALaCQ?E)MJoMe&FL38?knt5K4l~sxjy{jlaUW=n zF_rZ>@H59B3AL^rd=M*qAyNMu^j!z`5#CR7ZP3^AT;^5xh2ls0jOqNhwY9&{bbQU? zx?^0{4G3c#*@R|ty$N?NjXNZ5ZpF`usR*YkiVdP_UtP1?Yb1778qRhUwC2~IoBo6| zzvbrS4^rOWxIeB5;TbH@-CF!GsVG`T8`3tUrctkoo^WYgaaz?L?MGu+vp+qtq%Ql@ zeC}Yv_%a_?F(C|p*C^D=>ABqT)eWPo?gQOnK)T3Xc;;bJzr2p7oP?hEKuU@7pUZoE z6N4gI4(s_{BVXydP-s~ICZXY^n%U&@3pxq3Bv6Sqx)Y`cgt-pIS=0@`|Ng$PCNzLL z=PM7p6!}vI@$Z#jScHXznd7%CRq(Jp2rAB7&ZqIQf|E#uA)0ZT_MaKwCMe$`N|jPl zfSB!~eM!SNLlro8y`rgZ)8)YfeSiA*v41F#|1q}xFxukyA)(Wf9zhQ5HI>#*S$?KAT#H~TDMUywKi1Y za{HJ+*Dlasx=Yyc?0gEVeqNuUTPD-}ICGX1n*E`fE30#^M|Q~*%07*;eRg6>E} zTHsSO1b6!iHAlsSF*owR85!B}pYcO%Fg!I)S7GA$-LKLF-=>-QzGX6(X|g*P)s|fe@31K@0rg zxNy;v9G3llJ_n9I|7X&^^7~F@DtvtKxc^M*Fu4qMr0a*QpPRPIX%gs_@g6TvnSugx z)Q!$OV!Lb}MpV6G9Lk29CS5}^>rbLmzJPyait{p$*!!R+Uaj%y=e*I@Roe~2 z*tbwP*@i-DtdNDjB4o%Ty^(q^4+u;}13z|G*ak*Vv|^Q5{vI5~V~DufuzG$+e-g2@ zk`KSzJjlbiDXMaIXU&ye?tPsbJW@%vWSaisKJfCjuS_xM%Sq2m*dhi+hzZFB3<%|y)<^Ujon^e2OfEj zOh(r29J(vaogghGPB+(6&xa}1->D~hW$?~h2Y+HBD-hTnyq6AKrrfWp{7H?rxLjLl zor>f5v5c%G+U|W+71$dKWP2kl<9BGFz{^X=FsHot;?dez(9%ncsG>6Qm^7!xQnqg& z8_~|VRrk?KJ93H+Z}Eh&YA?q{+qY}THXrhG{#Q*a z$yi`$FKM5fO%P}Gv4(q6XEOT2TvI#|fDI}RX!2A-YS=>4H3SAXaUO_+aLhp2L27ai zMS z9Kn|`d*=TNlm*1HJoeEcut#=`ydCfGZQhMm>sEyw5YV8<=rI|-@nWRUxx?BA>?b!V zDCSN;NyF_!G!`of?;qzQ!g-NoK~XO#!`d?%p!P|u^sjf|x;x4F@Igq!+CikQ_~-P?K%GHXqv~|3n`h3 z;OdI{wruYf^=&&Lwzc;YM8R&W7G?T0dZ8=6Q6noN2*Q6=k`CQPzu;<7u?rV$0&ujC zyn^S0sr3DPgi3=8R|Nw;EhX944p-d`2Hkp+TQsx1Vmo9VR<>kxUGMlCIW(KE?k`67 z&yL%hD=tF3->a?j11{%fT?VO8D>gcMyqz(<{c4?5;UuJytR6jfivHsHN~uo=Wv<^W zak7(^#XB-J8SE@mH9Cw=tGMhe6Coa^;H@3`H=YC+!Lz~81zB3>qa86tTAs`#DMKYq z<54PY=JPIQo)FX9$`QaNG!H^(0I+pBbDHI8iY!b)ZBteCc-6g5Lr_SxA0k`iMos$) ztI6^81A&Bs5=D&3+R?$--yl9wI4VfV(O}l^B(phJDjaA*DkDV!jiqCKt-zRzH$!u{ zZ1>wcbPU8Zo`UIM)To~nqS;V}WXcm&%81CaDesagznMlG50-jv!0Fa?g)tekAnRA7uz6nx=8K0(Q z-I(xKS7z1R{Yx6c+sV|oJ)c^3{mExhf?CN(VY6MtLCoMU@__PKzHEq>g8Rh>SxykL ze#ysZcD{;Uvz;n;cT2olKXlj-0GWxI+n-k8!|9omXVQI2<7FH=lk(S;kX3p5$WWx+&W)T zjUbz)$j83gKsNtqnf-cMb9;JPbAn}eoNLE1jASGcBlq@%R zk#MI-?QK|~xKyY>Jsi^Mj@E^@Va3l`Qkl|3cmyj$qI5VYw0UzqH;wF`dS zS)7dU6o<7`!=q?A?`oPq?y|_OX~!dNzeD?W2(zS5dEpX+{UXNVtR*8#8bsf6Qt{*; z;#5HSi7^3?T!|EzF=msZ`kb87FG+>ozb~$(Vv{-9AX8gg{}={(KfJ0uU9u$ztc(Mm zE?aoWA#cAH*Yd?_D0r%9HKT(Q^!2X+e(?=IeGBv7G<{h5MO+%2beH#N!$j@+3Jh+| z5+9F?zOWIfG@VK%m9I^{bG^ruj@zKpd83O9teDteE|31 z6UV-9-&gxi?8-h6mVW-RsgLGmPTBSLTxTu&D%m_2{^RX&&lRD7j<6+3;L8GM1Soa0p2QsCHX3Y(ASKWg1MwBoQC&P5>_*D#Y9a zAQk+*sei>Is||Jg5s zFXjk8;^c7PloSouC)ClB2{Ct5Rn0an{^AOy72OZKn-rrXI*yLzjJI|u73to}l`EB4 zcwaZF5obGN_lTP9I$kBSW1tdbr+F>jD&^Z~0o#|@zS+Mp+I_2(ce@%r$YiYx=wh+JWEl6*R^iSed|rKPc>X3A-)0kDYlnbKsR_#m>m9=EeAY`Fu54;+*ECDOV3a;J%g|Slxm@Pi&~l zRQMDZ^dXx07IvR;Uj5YteS^oxFz8EeXFELt7iSg{)1aT~^~esfPh()~t3u#hpPhba zNSt@O!;4KmRXwb#7W%{KopoQmS(nB0jFd6IP}{D|1g{rneSMp4KWaxz?&fVF64O?H z)Fp4kS>QH~;wAlM|Ie7lveNF0btDT@C9*vJlO9OY%k)wv;idU?qzeMtPe?xq7O_&{ zvK^=9=*k3?)_x6=%Q#R|GKpVoH3gT*ToE~2EDlsQ>|mA;u@rcSI2U+eRuMEcpfI@~ zWqWUKTe)~~?;Stt(N!pWI{QXVKTgS7TC*pP=z0%c>sG7VzFEp483`6lXb!PH+Wb-9 z`!7ViB*M6yO|qO|K<2)7KphL!hgN+D0Qp-xRv-6-@y*c$p}!Ud{zO!Pvjb`$tC9NE zOFLv|N!CpQO&r9CG~+M=wx63utssk`(IyFnQ92w}Iu;_EKYh4HY|rN5+`ix#?8K0Z zF9=eze)K;cE?^3TG|JKguMl(Mz z$xWa0$u8n+Z@aKl;R;Xoao3WYo%qUrqOzltG3g}%I*Gbo)f$B!p0Md7QJ=p9iIRae zM(GpCnV9YSn24Zf8FWooBWgPo^8ya0#vSmL`M&GPF=nj;;{!rF&@RrS^Scb^Z5^jm zeJv+35Ti7Nvws@-cFgwCpWd;rfC(!00|2MJcI^|iZMEMakQ)^7$HvF}nxr@wIWgc6 z_!m(AvVrlx?2bxJ3@(|}guGLxXO~{664=?ISsM_=oyDUB2eONh2aFvPIr|s7ED}Mhu3~pJ;a7J23DFUMSHJFe; z230>U$6q0h>DkLv2+-=aC~fNfdQUvu6hGy!NyARZvzh!B0E6aazAhJ1{VPo5c`JJS z%7&Jf?T9J4@H38x>Z$@HcDS!4wSZdJXxp4#7awRzOJs~XM#QH{2?~meGSCMSvBbRQ zNVmO3mJTNw$X^J@A>58@X!KsesXQK<33y-H08yHNKpSqjS!@tcKaLSb5@|4U?N2%f zVw2tAg@MU$y`@4s9qHycv&WseFeNgn>dM`=D=x7^??6+3Z_5k_Ge&gJltY2~~&4Ev9-vxV~S@;-ox|0A>pj4#NLd3#ZHR7KP)?p|J;r z$F;hP{24<0Bba$O{^`kr*8v5|2ny3NGUk5}i_1StDBPR5FXi3oa>P9BWzO&#PGB4& zcdE`gzNe$G3-&6*>nio)rObP#k6zkP_9?#jfE-Jx({*sJ<^dt`BW};$DJaNU*Ff3y zX3niDydKVF)FlmuDP2_Um3FT-lxm#V^^}~_Dej4-Gn$h~JFahaHzm;4&-7{7*iN6u zcg-;^KMZ^+{-w>BErUxwryW}y^wzDh+u(EU5I*dYu@dOG&GWy!4KX*-yVFb*XVOLW zoRzeZj3@E9PHXEr%H^HX_!v6$O4BiT2+CEb~&*(C`lzWQQw+G_1i^1c-j8c5OPbFJ{SvExrd2HULgmvPti5LmSD^1hqaj0v@ ztL_41Xo7-w@<$@9vY4WH*_jmNzGFggUV)msF4&xRXqNhrhrf`(OF~>B{RJ?thPjKk zimZX+VUU0#`D&WH;ZL!@UkZZit@EkuIWumAwnro)o1-xtl{Rt1KK8E;jsX+ctb$l%PPiw{k)H5Za7x6<&L zi){}3`$?Lp+sx!_t+Y&c8+OED-=3oPQ`w8n7{7DxvAk6$-i1W%o{nK{8_*JuR`t2` zcm@VGuWh>)EV67GG?vd69BCwasPMG(#&|$d*8T|nlhzq|MuZi+h(`l0Z5fWyywNwY zCqcwnR(7KSk}5N}BofZE99DQQ83fThyj1KX7HxLh)}h~rd4Qajzz9UA({R@S83=O# z9tC1b@78(%IPLUgZ1}Bc2PdXoPa|UeqO%hL6I{FV>a(s<04!}ZfQYNUHu_55WyD>S zuk)I>x>;=oz!r)cC@o!D-miQv4nkbdnU*p8b}-M9UsraW7b8!r+*0W*BmoNrcZl{>dfXD6bR0-@{Y#YI=7%j1u6wKD)&0mybT@3Z_I5b=jWd9>f=x(rF% zzp25CX0cD*87}x1Q(IOUeJi?N08nUGrpP#MZi$7lcW)Q^Pv&AoAiDUHdT+v*a#a0& zGjY8XKna59q-%$^tE#BRIPR;0r?Kbo6zF-zbjl7tC1gQU%ILdTxhb_3{FmN?*Op;f zl;Q>?CqVP!($a^1&-X43zdjHpPHk+IEHvf20p}kmAJ?j$uRbsUcmxX)A!i=BE|cW( z_Xg(NE}l*n8TnI74myim=hA#v6xAC9(i>`!eT!pAuK)EwCO+_po~|&-zjk|Lxfyou z{CBulB53v0Zar2yJBG)j2*4*9kILITLoDxS?i}c_G^|5 zUAovCw=)OaidWqiu>R5c_A2>~g|v0um>ybZfz_{JJDaeC*Cf-xsbzK>5d}6+AeN8~P>-{SxIr5P#~q zbrd)(SQ6>iV$x8YXscF7OGn*DEA^NhSbcn6_sx!bz~CKi0%z-2|v5yiu-Lg#O~WD;u2={ppK z&dQ5diN5#cQK)F{&dTLsCwso4Z)d@XYq?vEG?>D&PvnyTHsm52nH=hg_pXqUTWoaJ zz?J(x1M?bk(#~1s(u-$Cc-dK{+}CT#r-2YY0|jxf@|=D8=Pzzr!UNVeTM@=dv!4*3 zz4spr)UFftiej3{9BeM?W5%qPGV6ItA1_YPA9pWN!M&K?o0*@^O591H=~WhS>zwY9 zl&>gWRXuC%ab`nc<1EFr8>C52q{B=eOO@K>C$*O1Xo$K-nq~MhS(Qk08_NA?0kPzE zDj?hC9xdT-bZ<}cJV}+%@hTp|L$uz4`qg}_6Q;IZ^`yfhXQjiy63J~oLpnsg`}!EG z!Ul6W|MK3F`V5mpGB(4>a&|a%*BzyYFLMK^iA#%2%DS}+f2!3-PpFfDrVVR*^!0yO z!luYM<5i5R%C1FyDxW!p&btdLIBWp~V|V#fh>u2neb8aveS6+d(iofkgWcY&q{7Mj zE|aukgW5`(O!Yh*Q0Q?LZEYFO@B)9`IH(Zh`|KU^6RC8lj#L&H6&-l#BS*IWaY~dQ zF8(9)u1KR+R-}*1tv$}!x?S*$?&NqTSN_4>PjgP!8rn9foGc~spsOBQvcRMdB;~mn zVZ0)$kdqOqlfcH58~e#4Fck|s^!jphmL``K_Eu)D&i8ywQ3vNxJ%`C1X6Aa8Gj}pS z@$ryPH!SGR?V8GD@mI3Sl5NZ>8042%-tRqrZ`N3Dz3Sx8CZ@wMltnf9&Pr~=6$E)_ zzb?OkFibH*#?*lCyf|o*#k2CYUvSQ%gaw*#4=erJx)y$a(91XT+R>!^cbZrEFf>H; zkNCIS^~C3vM;RoZl99^l);4=YH4nG&<_yP0ji6M#OPltHDdfVz44dK5zRIm14+Nt@ zdXH*X^^R*pndOuXjv{h9JM?w6OY-%pqp3p+i;W{n<{O0Do|zi1_Zf~(jERmW$Sbh@ z6Af~d%n-l!qtGoSM1>`K@~W~6lolY~E8il%ih9Ozcxd+1y65-lgok`BJSWc2_HOcZ z!R;NF5WIj$4Bksfuf<(Fo6)*(#PkUb?;yR1t)iYXr?3AX#jNyMk$c_@pd8-$&LS*R zjfqx|`KPw(Iv*skG>14C?dcZ-`9TU4UboQD$E^Cf;tTio6*S+B1#ylU@vCOHCn(aS z{jFiZQjUpLbq6k6tfKmCT(*y4DoxSSA!Zw%UVP%y?;`9l+N#g)w1Kd>+dMy9Oh_Xj zU6K9?Voa!Q@k(1T%^xV&AbC)}LQd0W&$`^gR&Ic+pP>SNdAK7+z==R>Q;7Lj2Ey-< zxyT_vLzj~kuF5R&042k(D;kCi=oUm`)~qR~^r4qpA~=T8ZBs6KO~=RO%`&VSmhCv_ z&c`L%^KjU`ULPVB4_`40I|VayS3)DUJKWhCHDXRBCB$aO3L2i}EJbol zbc2HmDuw6ng^%?^)4i8_8n~Jy^te17a6KJpEL&YOZhq?Y-KYo#Hrp)WUgPdLc(iUi z#xPv(m|Qwq=uJ-%iNYoCQHoRBmdu_x-Ff&?`0 z@5?Wh>{${w0Pb@IO-ffAx2HZLoJ<_O!jXj{frkV2wJuJ*_$@EfZgHs{{mhhscFDD1 z;sZyXmOM&im%^FvTYM(4FL9wu{gV{)r8cWTa!~LQ@wKr@17{UoMe~)D!-os>5lb~IiOlSQ&SrKN4{BEBB0Ru#_HG!E6j-0>yOn3AX2>a z-WFrOupt_1I+~4FNPZC2%7$<-+BCYlT<7Hp@R?G`miehv>#e+0-8ui}H8qzS-a5da zqH1)slL#LWbd^C*;9jFA-9llCz~)$dT6kpZ_8_O1$@@omXLhXuiOJ^C|1y`l0SAS0 z0!OlyqZ~|`WdCLvR{+z8ad;GK@aSKpq*^ouyK^-rC@^uy?SZ*zdeQ0on7fN^PC^^!mI%ZUOz`f_D_*L8+)+# zL2gV8$I*Yf&EFST?q24$vPdhidZ=1jtv_un#y93yHnhRf5op^IOajJ=kXAuXmONuaNhfaWoOR*U z)~v=?z|&s*h8S0+^fA*XSn+Caezw-);cJ^mTB}aDRm5>)xk2v(i8 z6Lepjr>yt7m;3m(^;TThX1Zl__T zOSeYpwoj834%7rCC~4LZL)63c16W*|*fMN{YMlJEAbtaAv0(dHZJ}&=6Gci6#oN+S zAjhDqEwo*I6TwOmc0dO8SP#!Pr38>j(MW`F38MTR~pIj7D6j86988E^#zaY9NpIkEs0s zZ7?+u zrr#O5=uv)=CN3RtUvi2f7MD4g*NpMfN%m3Qus8% zKfAO;R8rq8byalO!h#7HB}Uz$gr7&7Q27zePnGr@wL z5$8jGi0hk`+8{lYb^!AXq=+6AqHjhp);Ekik{jk`1C83fll*#o%(+Acfyy}8s>{V+ili#JjwsIyzAM2g zpv{nU1`37uA00YTPTw8c3B-X0ABIbgJsyE;7I-Q45rU6a-;@x{eyvh4r-@xVRH6{R zkFe#cZINKQCuxU02MV2m*C4Q*3rqnM@Je3nV5Uh%^(|iC6w^(0^QgyEAGXt7Cps=F zL{BXY)YEVA+@A?e%pcQpjSV;X$s5X^F++er0Q}0&o5|^t&dOA*1np)ni zKvxyKg*AvdABh2-Aa9sZ_$w(Lo&jZjdkO)Tgfpg3+yIulIZ?2Ly>ZC(A8J?Iz<BtCiTv_H4-N$XI#w&OZS?&s_tf{%KsHkbkdK}zDw}r|ik1=>} zY*u%i!fqaR@7CVJeO^vv2(jiy!gTA&nT9=h_PBl0nz{Tm1)3;*is%N&K52{9*xfMJ zPxUnQ5KS*G{^Z}%mLE&}j}+m4dn($hJFit>IA4_!IA*!M#`{KJ9`{BTRM*rx{hBoy z`5La0RF&s)t-;g)5yQVc8Y zMo8&oOv^l`@9@)BE%g0PJ<5vSAsi++(VtpcgzJ>rrf@w&E2To`@q{Q+JaPGMyJIJ+ znJJp+^N4OSI*rNwGg`X1Dv98@D>f3{tQHX^ue>*Y!{m@t?jbIiCJlE%8nr#7o0Mn5 zG;qseIa3tuqxEXQdIrsVipu(k!BYNt$6^ULa#2Q<$YAG9M2x&W8ArzWsJjO%kkt7F z7_assm%2wu*hP4fsAOyZDNv*4@d2fJVp-cPFV}O?HEZp8UDyQj@4^C2BmGDv-)1nZ zS)97jw5EHylG2py>ZPHIPYo%Bm*pFRqJPi?CBerbtS+HZ^HCXikupW@#iZ(bMbSuA!4WyXWj(2BtL-HyI9Pq3 zf37GivL!02jLd|um-B)1+^Z9?2+?&q26Y*!@H%*fmyrgL&Bse(szVE>bIY=kv$Q+Q&RnWH2C72JkWko| z$8+68Y;qxnW13CRmWf|C5^gps?z1x9Oe5=*t#kDuCzBve^yz`Lc;ROD`?1fj6OL`NPzF4E_tm6lGZ8YAd*&jt$0PR zG<7GUPd_Ta?zhl&I!Cdc`xX`)I8j=)*QEfx%ezCS&&Q?gU89e$y$l)$8*G;zqZUS3 zt6xJF=ItBZn>w&{lJ#y?|55~j#^hE%$g885G`y6sp_hCk*!iyqm2ps{9v!?vL#XvG z^v3s$%f^wGM&MWC0X#A+yoHU;@1iKv_Pkx@M+|I*C0{sR8}45g11>$dEB%=zGy6c8g9;q7OSyQ@b$#Cse$V7-^&> zYjtqjF2#dVAjNDObO-qfCW*iu5RV=YSt%muoH+eYn4Av!UREr zk&Ou!%4$C~dAFTsZvw)=mx<$Qk`Zs$>~It9ESdwNKy%9AZW)T3C`S6T-DMP|v#eOa zXTef3Lwd!o+~2%^@>*}E^4)s46poiiP$JKVqFra9ZlI3$;|OW6VqmeS&7*c7CPpi3 zQ7+H9BTW9Fl5?IXl+h-#xH=q^KpHt2n5@gQKZ-B9eS-B|n7dU~zcz85wbTe!w;{}k zJD)p!Ncnq*rqDAqD$&gV?E6hFYp ze0>93(*K!4qbU(W!Ag~G!j-gCIGSqA*mt&p-WkcQKKxfJ*8JPHUYR>PdTLlQQQ3uk zJfgIYSWi=lfZCYMT}>%RqlMZSIb?9xnX-0-G5GGv!-SmuF|)kLToxH8F=xl^rQ+FK zRL8ymsbPtX&DKg5iAsnS3%2F`b99oSG8AXS$*av<9ix%wvzh)pz=r#HLLp2azO8z*!hGjwd_=4&v{i*a#&=n2Lh^oES#V@k%3xTV?A+U_s$ZzW+FAJc zn7KpQv<|?0_j2E|OtkYussQ4D8o|0gOYiF5&gR6pL-)RmwQ+z>cJG2N(+x8u%pXXE z@>2yoGaNEXn|?9N%+D{Aclr@PVG!D{Bp`#}5UIsO$`u?oHv{DE$8n%j9P%!Gcfm#n zZa2CbH#ZHN6KMtZm#lK$o{bsTC={gQ&-#+CNUFBKCU7+BbVrz_f2&Kw9Plr+Lf@!~ zOC!-7z}}lQoQTlQP*|0f#&4e=Eml0Aki}p?f(1bzT!RMk{NW#9rjWaZuenL25|6ie zq=3+SW7C6Mv}2UK*7c(V#NZ&keB}5;!Z=)d^>lgffB_!URkGYOMs$JI9ZB6|dNE!d z;T18YW5~7EqR%kQr^u0NCXN@#=qGB-`uyPlE8nDjvU!WAt}`oQy{_o2xby7iR!2Pz zu4oBSdf8^t#dm?q?I*$mi!GM<&-%n)I@4CyXrJ}=JT+^Fxz*$=Z?-Rw(jM4+yVgdZ zrEMQl6KITMtVupaGFGkNo~H-T3q*E}*_q-Bz*P}8=1-GYP#h=K)!Av|>WYIkz=B}( zCUH%@tmHrRV0l^M8M%Wj23BUHLyT)A9kYnPl>4Df=G z2$81tHh~p_AXJ$qyoI8mC}m*19iy=`jI5~4Q6)ii{hP1kK)0Qnc80b=@hj2HxhBSCqXf z+L!{*`!QaRUoq#i;e??2mER2ZXTh3upY5t|jdUxE7&8YrO>;?Z2_AWP}2VwE|@wL<1=zh)Np-1L()Hdxv8$-1pP z-5d07^HHo@W|}7q#D_Cym#db>cLnTR6V@a&GdxHsVoNV#3o_);(RTMy*sG{-jB6_< zh)Uj)Q@rHs(LP4T8Z|fm()N#koKe~=kI@EY92rS|=BXn?9r*%(Od*U{e_6v+k2=wQ z=H?!lg1tuWAf?;bv79xBwYyKo`;=6`taVDhW-v<1 zx5#THThQvpTsWg)YbB^z^P)rGq4SZn+wXG<$B#6HrzH2f14L}AhOqDzDFu7--wJ$NI~lpfEmA}T{Sp*k~v00nD|nwp;w z&+8?7^j@+J*>)ZZ0|c~HD2oW&G0bv>dovn^QHZ(I75gYze*2+$$9l64vGLS+8IlCAJOL3P8)TpE0s+0f&(`E;!Da>F>r_6M<|gcFF9(&A``MA{!%0hA-M`L zJytT=WMA3xv>u4c3`5+wPubK1*_;`{RhxfwBs*$~{Bl?8XOWgnL0e*+q>O?xUhlWZ zX(u7I(ko*y2`6YpEM)a^c_hX)PgnL}Zx^SD^>6@}!nV8f5!v;lPG@!GLBGxgqpQv8 zXhQ`ivbw`6xwfNS&fG`0|sheNsfQo-(tntOO00#1YR1 zQ)Xi;e%-exN9*cYFT7i2PuK6W`e&({OHbn8z8QJL>PTe*+ zq@$NQl`l>ryT}?i$VWNfJe(+;Q~zdX>AW+DP*L)u4(YBMM>T2Y+wJT2T1a5BeMyQrmjxEg;CmmQqb|Y-nAfLgwXE0+4~+0A=w*m4HSBG@$6e{2qZ|f3LKc&ObQ8ec+LN_ZRNl`XktgA?40yY`=IS z4}0bHlMLJCizsUP%Jvyn#mZ=drywfWNvuK9u7M&y&IcyE75#f%Y*ehN^2}w!M<%?> zG+u)?or=I`u!)qqZEcSL^Iu06`kEjCntJqKI!2DNX1eB{BkXR3S$uN}{XB8pj*^N$ zt?-6*C-Ja1S7YcS`>gD->6a(qT{J&o!K7M*cfRqd!P|GkWs6kHfK{LDdOMI_tt&P0wi=11<347$0>z}(l^r5+c?PT-YZ)i2BU>~EZ@b}DcO`$sgNiA|n zzo%$q#}Qh$r33x6r*?Dxs~*u;-gYcIRYkDbe`ta#%K=P18qKLGF?}TDbj*BKebOT@ zncDoU@qnLFelU*xxLxRWY59BAW<~vI948^aE~h5NRkFn0)niBDbK%-X;9Snj5=C>@ zp7m=N$FHR8eWJ$50(34#amMY{X0hRD$CE)Ou2$K?@Da_=1NM$GB8cF9i_l6v+Z-V^ zD`_NuBph|f0c2%VS@7f9t0$5a_QJfJ$;*P~!!YG+$R_$g!keC+u(m!{bm0T)7&@Ob+iAxBL-OV#{19Hg6?P*49)F;^WGW!LQo5D+A# zyQC2%M39h1x7ltCr zLSGp2Z1Xl@nmo!a(gh4Zq9zXM0$07xq+arR3=+9$;rH+N9Nd%TC>0trIhWzbEeJ{p z44u2^gyTA;g!R1eV5B7QJM&;f@L4-d$%m<9cv7$>_^e{qGU?*r_DB(R@c<0xe4dDO z0CBo<^_(nIY2~We%-^cxV=u!#IH1mtKaI{S$#OZ9XI(K|w;II(pvwUe^k(|Pv z6Z>JLi&Kybwm7nx6ZYU+FV<0et_1XRb!pzx@}oKRmRCn<735f9chrtIGSWzj@hW5T zq`IZlruGQ8La3zD0M_)GTGp32ud10GhBbD&HKhnAWRa?fl=DXHO$3}xW+jk1{gW@E zeS{T=L?0gcTEU5w!TDM-ij+zEa%#;|JpR@P5@D!OVW`2Q`;6m$sP2BKoivz0c*fm8 z7c(gMrJJOcx2LfG#KEkPHrj8XUok+<0bFsKJi5a2$r~o~E*f(4H)Q6wOwEhM`Sxq3 zZycSYBPp^&$S0y_6}{-gnpBGH%oz`Z=@0r&=_&E<=72G}5>`-l-RbH5%``iDro6GH zyFkKOQypI}H|^~!JtVBB<3Z9)Pg(}3%2{gEXZ_WEKjs$f@uVCy4#hX zC#(k;?g~DoWr67i{m82F~Ol z4fV>);F0W4mTjsePgV(zio}L&s zkivn)U0OYKm9tDM(ong#!4KK-`I#T>%Dkaot;|>Z9YzZc<8|NZYU*QN2m9SxpA|cU zc?)+AN@YuIdp=wKqVPLImZ?iUDW@=8tqLusp$sOeeE96L`jH|`Lt7%51SX92oatgQ z?+qJ9XI*Lrp3<(1bzjJ2F0h*fY-}UYF9N^kcZw9bEG?!2k7rGIsu^X_{T6gu z##$Ck9@p_WAK8Rm@ZisA2YSzZKYUFl=E!1Y5fNpY#4%(*k*q zum0&ld$BD}ME#4iCpp%r6DhI0NMi*ycTzIFq{AYH8#8+-DU->1Du4KY zMU3waI;)_I+ci?p180}w1sZ3?VQ({kRT5n9&~X;qfePWlhAc_35PRxE;`iM@A>8Vf zrM{u9sg?1iQHNAJ0|y>rpGSPY+nKe%zNyq_AX;xChxx!~2gYjt)9?{>n|KEI0n=y$ z?zX8D((CawbgyJ-0&JE1x8HeJj>ZowE6OVux)|U0e8gtq=OwcjzQ>xbPwI{y_MrrG zi`8AkHwOD2f@G*$PNyI1U9QBtmQy?l!V49_gt9TO=;T);OlBjHJ?l$WDg7iOnsxR3 zeaX8*;paHN+Qgc~w8yMbhYE3h=<}#oSjR@-+sV<9==3m%S+WllaVsGVyzr&L!5roc zi+%AT^9>r;a9PEZl2v4)C+YsN=`9D|kNo`w@ zcx2%x({z~yQ)M zy>~aEL<=eGRAZcr_C>YH8ML6rLk)zxeE>6fR@&r{Y?#ksvd3OpRBlK(-qJfxi511|p@$|o#vc|? zfw-AmFPhokW7&>GJ3F0E=GIBAD~lSL+wIF#x%m!`PuimNz-8W3*JUY)1-Yzp>y*Ft z!Wpi$-{YKrO2SC$sds0;A@$?M&gS!d7{X6-Gv7bAAc>~pt~Uiv5;K1CJP{DHk{Jam zf+6qY+-5S&zEq|BsFofIe3IgfPpSg8UJ*H4cu8=U_+_c>d(fUHX-uuN-9S{Rhi%ul z?isAXQ{!VS zEuD_B{ym5xKs@=MD5=O&LnD_VpY=dI{*t@u*LQv~P=$VxfMdb%)}ijI;hs4UgRwWM9oV zHf8ByvqlEn46(a7R}yJ0s+P15czZ{)47HfnlJ7(b3WEWRFWg9psi+YceLCfk9t`E= z^2Y_Vtq3MlS+l1MQYA`TC51~%kL|DXfD@KmX=?416Q*i+w;5!zTeMsJyz|p|kHE{c z?UN^gltZUD5%k=?O5atR4PP}Lj>>PV6-d0%A)@y2(O=t(;_@G6qk|p1Ps{8dG=qX4 zfXL;9lW`K%UdR*x8<1VZL~pIl5i*wayrmaNwvCfqhz zBkppiK2}(ks_-YS5qD@6-{yScr}`;JOk!P0+1OB3d;@*1tzjgmZ0`YA;dynKah>-@ z)&c6xDa@b6kB322zucf0*QcOoZGBUJj}6Q0J0bn0L^hi#mFVrIsO)SMSdxy8RPPY2 zkB{aSz~{KQMS}XWAD4=<(*dQ6JTmHhUK4Ya@W$>vOdd%~XiTU1HMG9L#fZ4@{KK|A zTpSWb@f7&^60HiN5)F&218FA2HJ*%Ps(`L_r4eIz4Fu3E+BRZG!D#NW&S1#-*}_U; z+kDnHs0qaRw1@OSwo@J?{QNyjjetOq6%h2RA{q+smRWd+(WXPaQ}apzMqNaj%awl; zW9ltnfiu8!v&e!r8CVzAEM3Wzjm&G~Q)lG7n;0|tb;2$$RE6_F1NJ?QjC+_~LiFAF zfgWH@T4vK@G43b(7=zuC-83D!Pr4y9m7HXW$ z1d5|U=3_(@>@*vip!Y5JKH9a53D|Jjm%p43WIn7Xx-W%KsX&Wu23RmRU16`4Y-&0I zn95T3nTcfJ8mLC?&rTPic@x+Np4o!>&6J#DqtlH+X<|~OE6^>?||DaWSH z8;$6|9 zF*RhoF%o4Fzz=bWOa!lzc@5613emsm~_gdYxHQ6#BL& zC&zTVe~&^9%eA*VR_5CRZBLnA!O7}!d(n;1*Zqu-ohv0kvdD=KrUA9d^8bl-lvvocGF+d4un63F$FI`P*5bHfLI zo;K3{0%))lC?W-dm3F?&{wz&hR%p6=08|xnG*3Bwo?0fYAJvCHs-1VO_$JwLY^>v$ z@Nsn##NWBMz2(-_)mkR{KIomyqWp*o^A(0;fvWI+2-}(xLnnDbKkGh=M!m!jX}4{I zO@fIF#MvFVn|eN#2n=X|cxfoaOaD>~8d@3rb<*ze39AKi3@@FQdA^3g%ApS50Y6fe znw;1v#3cN-kM-pB2g{pwPPcnLDTi}Bu3^cH0se?#$p!u}izsb$K*}H=W2N?aNGxIc z8vc0qRBtU3ZVVS1_&6c%^s`x7n z1!|vBH<6C5?Zd#qo5r=%Hd7+6YwgJQvJ>-6a@mMQanAD7?DV0v_?R(hS|43jC9e)L)Rx2%e56H z@lt7WqiqF{QwFH2%H^sfW`CX0-f0rF{HWOf2;1Cj$HwdjE6YTzTD4p3N)R_(U=5v; z$9>7db@2<0uv9$SwVlE;lUYy0KzQq0Wd>@&A{?V*bGwTt4H~U10K!6s+trh%z+8jG zoplE0gb#d_=CBx*Y6Gi$&KbuX8_HxghS}tYsnN#rNZ#L5qt9qGFl940Z~LVzz>r`M ztnSHY6H5hW5RgW1PLvLOPu%xDkyX-7IPHFV)85zp5{icNy4C;NZYiDE(UA;s%a;(^ z(Z6o_(hvlKlp#O6N$KHJRud0VJat-n-%?gR_iOm50b(59*B=!rL)4`n!=;pecW%hw5)VS^fEdG6fjMLlX6a-3O3S)(&gpQ`An;bJA-T98w= z$f2yNsUYq6sE{JWLIr5G-Ad87l&D?g@l{cKZ=NG!-8W=CzirzT=P7;cPHNM7Xl(|!+Xv1r-JuOu< zHT1dS&xZ$lm3&X7Ziqt7mry>BP;gGjMr$`ATtA1m)H?8sYj?j+Ee*ti_41rtRh3SDD`L08+B)a+H z5hAdh+mVeC&b|Ch>e*uE8-d(*i#bwc5t9-c0r^Lq_-a}!QRhy0HI-dDPa8dAihp!| zE@||(;&#a+CLBLv4lngLpBRbBKK5fGZ%f3r!ImyB7&>m=>}L?FtSv48j?+oy?UV57 zWE0oZZx@{VEr*^NvwWdBqax{(mGap-kD>6{0?uvCrkRN@En}ev1F$jW^q*|kqqiQI z@$+o-_RL|GiCT9|&lxPHc@n@xayA;#i>%_jF8NI1#YE%>(*7v@CBEe&RZX2GRW<&r zUiVhufVTIGyog=g=!lC?`J0w{KOUyhy9T*HvDf}Dq2{{f|J$MF#isT!3B)ypAg$|b zd72eSNl)M6XCo`VZ_28X92;>JpoSPjiBg^|_J|8fJ zgRyh5N;CHLrXq)oqxKx7RJT9s}AdL22J)r3DfdB&8^ z8p87;J{}OkLmH=_HrM)qjm35Y~V*e!^4G#IO|ct3|aBPbcyZ-9|>_VX*Qt z$q<2m&|B?W3!(252q*fJw6yz}@j*m#1Sx|o7{hB;2dt^k8Tp zbPMGQ_)YR38!FHOpbPga0Tt5M7wc~s4=n(?Y`zlUBy&x`?;;u+4PCQdp?PJ0FZPY< z1sZ=nzZ8VxmROA@rEYXh5VyUp;OT- zNpF;XOS+zzLMsBD`CP#zRDQ#+pN7!zf1d~6^BeQHF61BQ0e#M0DYEoOk`big_xIHM i-!!>tTUPZOn%t1`TNU`9TMrk23vhyD^y3f!0PsIZpjQ?E literal 0 HcmV?d00001 diff --git a/docs/寄卖外部免认证三件功能_开发计划.docx b/docs/寄卖外部免认证三件功能_开发计划.docx new file mode 100644 index 0000000000000000000000000000000000000000..e152b900e39c70b39d312fc41471e6e76a410538 GIT binary patch literal 24985 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 literal 0 HcmV?d00001 diff --git a/docs/寄卖订单管理_开发说明文档.docx b/docs/寄卖订单管理_开发说明文档.docx new file mode 100644 index 0000000000000000000000000000000000000000..cbe9b1f1e6ff51d6784f069811e79ffa29b5f507 GIT binary patch literal 24914 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 literal 0 HcmV?d00001 diff --git a/restart-backend.command b/restart-backend.command new file mode 100755 index 0000000..03b124d --- /dev/null +++ b/restart-backend.command @@ -0,0 +1,59 @@ +#!/bin/bash +# 双击此文件即可在新终端启动 backend: +# 1) 杀掉占用 20600 的旧进程 +# 2) 用 Maven 把 crmeb-common / crmeb-service 安装到本地 m2(这样 crmeb-admin 单模块运行时能找到依赖) +# 3) 进入 crmeb-admin 目录,用完整 GAV 调用 spring-boot:run +set -e +cd "$(dirname "$0")" + +PROFILE="${BACKEND_PROFILE:-byjyw149}" + +echo "🛑 Stopping any process listening on :20600 ..." +PIDS=$(lsof -t -iTCP:20600 -sTCP:LISTEN 2>/dev/null || true) +if [ -n "$PIDS" ]; then + echo " killing PIDs: $PIDS" + kill -9 $PIDS 2>/dev/null || true + sleep 2 +else + echo " no existing process on :20600" +fi + +# 自动定位 Java(沿用 start-backend.sh 的逻辑) +find_java() { + if /usr/libexec/java_home &>/dev/null; then + echo "$(/usr/libexec/java_home)/bin/java"; return + fi + for p in /opt/homebrew/opt/openjdk*/bin/java /opt/homebrew/opt/openjdk/bin/java \ + /usr/local/opt/openjdk*/bin/java /usr/local/opt/openjdk/bin/java; do + [ -x "$p" ] && echo "$p" && return + done + [ -n "$SDKMAN_DIR" ] && [ -x "$SDKMAN_DIR/candidates/java/current/bin/java" ] && \ + echo "$SDKMAN_DIR/candidates/java/current/bin/java" && return + command -v java 2>/dev/null +} +JAVA_BIN=$(find_java) +if [ -z "$JAVA_BIN" ]; then + echo "❌ 未找到 Java,请先安装 JDK 11(brew install openjdk@11)" + exit 1 +fi +export JAVA_HOME="$(dirname "$(dirname "$JAVA_BIN")")" +echo "☕ Java: $JAVA_BIN" +"$JAVA_BIN" -version +echo "" + +cd backend + +# 第一步:把依赖模块编译并安装到本地 m2(首次执行会下载依赖;只在源代码变更后需要重跑) +echo "🔧 Step 1: install crmeb-common + crmeb-service to local m2 ..." +echo "" +./mvnw install -pl crmeb-common,crmeb-service -am -Dmaven.test.skip=true -q + +# 第二步:进入 crmeb-admin 单模块跑 spring-boot:run(避免根 pom 触发 main class 错误) +echo "" +echo "🚀 Step 2: launch crmeb-admin (profile=$PROFILE) ..." +echo "" +cd crmeb-admin +exec ../mvnw \ + org.springframework.boot:spring-boot-maven-plugin:2.3.0.RELEASE:run \ + -Dmaven.test.skip=true \ + -Dspring-boot.run.profiles="$PROFILE"