diff --git a/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java index 0b175add..9c27ac35 100644 --- a/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java +++ b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java @@ -3,12 +3,15 @@ package cn.qihangerp.erp.controller; import cn.qihangerp.common.AjaxResult; import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest; import cn.qihangerp.model.request.ExternalPddGoodsLatestCommitStatusRequest; +import cn.qihangerp.model.request.ExternalPddSkuCanonicalSyncRequest; import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo; +import cn.qihangerp.model.vo.ExternalPddSkuCanonicalSyncResultVo; import cn.qihangerp.security.common.BaseController; import cn.qihangerp.erp.config.ExternalGoodsApiLogProperties; import cn.qihangerp.erp.support.ExternalGoodsRequestLogSupport; import cn.qihangerp.service.external.ExternalPddGoodsDetailAppService; import cn.qihangerp.service.external.ExternalPddGoodsLatestCommitStatusAppService; +import cn.qihangerp.service.external.ExternalPddSkuCanonicalSyncAppService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -22,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; * *

改库存请使用 {@code POST /external/goods/pdd/quantity/update}(与 upsert 相同 out 键)。

*/ @@ -33,6 +37,7 @@ public class ExternalPddGoodsController extends BaseController { private final ExternalPddGoodsDetailAppService externalPddGoodsDetailAppService; private final ExternalPddGoodsLatestCommitStatusAppService externalPddGoodsLatestCommitStatusAppService; + private final ExternalPddSkuCanonicalSyncAppService externalPddSkuCanonicalSyncAppService; private final ExternalGoodsApiLogProperties goodsApiLogProperties; @PostMapping("/detail") @@ -76,9 +81,32 @@ public class ExternalPddGoodsController extends BaseController { return AjaxResult.error("参数错误:pddPopAuth 需提供 appKey、appSecret、accessToken"); } ExternalPddGoodsDetailResultVo vo = externalPddGoodsLatestCommitStatusAppService.fetchLatestCommitStatus(req); + int n = req.getPddGoodsIds().size(); + log.info( + "[external/pdd/goods/latest-commit-status] goodsIdCount={} popBizSuccess={} msg={}", + n, + vo.getPopBizSuccess(), + vo.getMessage()); if (!Boolean.TRUE.equals(vo.getPopBizSuccess())) { return AjaxResult.error(StringUtils.hasText(vo.getMessage()) ? vo.getMessage() : "pdd.goods.latest.commit.status.get 失败"); } return AjaxResult.success(vo); } + + @PostMapping("/sku-canonical-sync") + public AjaxResult skuCanonicalSync(@RequestBody ExternalPddSkuCanonicalSyncRequest req) { + if (goodsApiLogProperties.isLogFullRequest()) { + int rawLen = req != null && req.getGoodsDetailPopRaw() != null ? req.getGoodsDetailPopRaw().length() : 0; + log.info("[external/pdd/goods/sku-canonical-sync] erpGoodsId={} goodsDetailPopRawLen={}", + req != null ? req.getErpGoodsId() : null, rawLen); + } + if (req == null || req.getErpGoodsId() == null || req.getErpGoodsId() <= 0) { + return AjaxResult.error("参数错误:erpGoodsId 不能为空且须为正数"); + } + if (!StringUtils.hasText(req.getGoodsDetailPopRaw())) { + return AjaxResult.error("参数错误:goodsDetailPopRaw 不能为空"); + } + ExternalPddSkuCanonicalSyncResultVo vo = externalPddSkuCanonicalSyncAppService.syncFromGoodsDetailPop(req); + return AjaxResult.success(vo); + } } diff --git a/model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java b/model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java new file mode 100644 index 00000000..8ca6a4b7 --- /dev/null +++ b/model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java @@ -0,0 +1,21 @@ +package cn.qihangerp.model.request; + +import lombok.Data; + +/** + * {@code POST /external/pdd/goods/sku-canonical-sync}:主数据在 {@code detail.get} 成功后回写 + * {@code o_goods_sku.canonical_ext.pddSkuId}。 + *

与 {@link ExternalPddGoodsDetailAppService#fetchGoodsDetail} 返回的 {@code popResponseBody} 同源(拼多多 POP 原始 JSON, + * 须含 {@code goods_detail_get_response.sku_list})。

+ */ +@Data +public class ExternalPddSkuCanonicalSyncRequest { + + /** ERP {@code o_goods.id} */ + private Long erpGoodsId; + + /** + * {@code pdd.goods.detail.get} 成功响应原始 JSON(顶层含 {@code goods_detail_get_response})。 + */ + private String goodsDetailPopRaw; +} diff --git a/model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java new file mode 100644 index 00000000..5fbfd413 --- /dev/null +++ b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java @@ -0,0 +1,25 @@ +package cn.qihangerp.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * {@code /external/pdd/goods/sku-canonical-sync} 业务结果。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ExternalPddSkuCanonicalSyncResultVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 实际更新了 {@code canonical_ext} 的 SKU 行数 */ + private int updatedSkuRows; + + private String message; +} diff --git a/service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java b/service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java new file mode 100644 index 00000000..740eec5e --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java @@ -0,0 +1,12 @@ +package cn.qihangerp.service.external; + +import cn.qihangerp.model.request.ExternalPddSkuCanonicalSyncRequest; +import cn.qihangerp.model.vo.ExternalPddSkuCanonicalSyncResultVo; + +/** + * 主数据回调:将 {@code detail.get} 中的 SKU 映射写入 {@code o_goods_sku.canonical_ext}。 + */ +public interface ExternalPddSkuCanonicalSyncAppService { + + ExternalPddSkuCanonicalSyncResultVo syncFromGoodsDetailPop(ExternalPddSkuCanonicalSyncRequest req); +} diff --git a/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java new file mode 100644 index 00000000..958ab52e --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java @@ -0,0 +1,37 @@ +package cn.qihangerp.service.external.impl; + +import cn.qihangerp.model.entity.OGoods; +import cn.qihangerp.model.request.ExternalPddSkuCanonicalSyncRequest; +import cn.qihangerp.model.vo.ExternalPddSkuCanonicalSyncResultVo; +import cn.qihangerp.module.service.OGoodsService; +import cn.qihangerp.service.external.ExternalPddSkuCanonicalSyncAppService; +import cn.qihangerp.service.external.pdd.OGoodsPddMappingPersistence; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +/** + * @author guochengyu + */ +@Service +@RequiredArgsConstructor +public class ExternalPddSkuCanonicalSyncAppServiceImpl implements ExternalPddSkuCanonicalSyncAppService { + + private final OGoodsService goodsService; + private final OGoodsPddMappingPersistence oGoodsPddMappingPersistence; + + @Override + public ExternalPddSkuCanonicalSyncResultVo syncFromGoodsDetailPop(ExternalPddSkuCanonicalSyncRequest req) { + OGoods g = goodsService.getById(req.getErpGoodsId()); + if (g == null) { + return ExternalPddSkuCanonicalSyncResultVo.builder() + .updatedSkuRows(0) + .message("o_goods 不存在: " + req.getErpGoodsId()) + .build(); + } + int n = oGoodsPddMappingPersistence.mergePddSkuCanonicalFromGoodsDetailPop( + req.getErpGoodsId(), req.getGoodsDetailPopRaw().trim()); + return ExternalPddSkuCanonicalSyncResultVo.builder() + .updatedSkuRows(n) + .message(n > 0 ? "已合并 pddSkuId" : "无匹配 outerErpSkuId 或未解析到 sku_list") + .build(); + } +} diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java b/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java index f3438473..86363718 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java @@ -26,7 +26,8 @@ import java.util.Map; * *

拼多多:仅根据 {@code pdd.goods.add} 成功响应中的 {@code sku_list} 回填 {@value #CANONICAL_PDD_SKU_ID};不在本服务内调用 * {@code pdd.goods.detail.get}。{@code pdd.goods.information.update} 若未带 {@code sku_list},则 {@code pddSkuId} 须由主数据在适当时机 - * 调 {@code POST /external/pdd/goods/detail} 后自行落库或后续扩展同步接口写回 ERP(当前 detail 接口仅返回 JSON 给调用方)。

+ * 调 {@code POST /external/pdd/goods/detail} 成功后,由主数据再调 {@code POST /external/pdd/goods/sku-canonical-sync} 将 + * {@code sku_list} 映射合并写入 {@code o_goods_sku.canonical_ext}(键 {@value #CANONICAL_PDD_SKU_ID})。

*/ @Slf4j @Component @@ -78,6 +79,21 @@ public class OGoodsPddMappingPersistence { } } + /** + * 从 {@code pdd.goods.detail.get} 的 POP 原始 JSON 解析 {@code sku_list},按 {@code outerErpSkuId} 合并写入 + * {@code o_goods_sku.canonicalExt.pddSkuId}(与 {@link #persistAfterPddPublishSuccess} 中 GOODS_ADD 片段逻辑一致)。 + * + * @return 实际更新行数 + */ + public int mergePddSkuCanonicalFromGoodsDetailPop(Long erpGoodsId, String goodsDetailPopRaw) { + if (erpGoodsId == null || erpGoodsId <= 0) { + return 0; + } + Map outerToPddSku = + PddOpenApiSupport.parseOuterKeyToPddSkuIdFromGoodsDetailGetResponse(goodsDetailPopRaw); + return applyPddSkuMappings(erpGoodsId, outerToPddSku); + } + /** * 将 {@code outerKey -> 拼多多 sku_id} 合并写入 {@code o_goods_sku.canonicalExt}(键 {@value #CANONICAL_PDD_SKU_ID})。 *