From 60d4614695c02aec0c2749b6d783bd62641dd659 Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Sun, 18 Oct 2026 14:16:27 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=A7=E5=93=81=E4=BB=B7?= =?UTF-8?q?=E5=92=8C=E7=BA=A2=E7=BA=BF=E4=BB=B7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/bms/BmsApplication.java | 1 + .../controller/price/PriceSetController.java | 27 +- .../controller/price/ProductController.java | 141 +++- .../price/RedlineProductController.java | 141 +++- .../src/main/resources/application.yml | 10 +- .../java/com/bms/price/domain/BmsProduct.java | 7 +- .../bms/price/domain/BmsProductCountry.java | 4 + .../price/domain/BmsProductImportTask.java | 12 + .../bms/price/domain/BmsRedlineProduct.java | 6 +- .../domain/BmsRedlineProductCountry.java | 10 +- .../domain/BmsRedlineProductImportTask.java | 12 + .../domain/CatalogPreviewApplyRequest.java | 48 ++ .../bms/price/domain/PrcPriceExcelFile.java | 2 + .../com/bms/price/domain/PrcPriceSet.java | 6 +- .../bms/price/domain/PrcPriceSetDetail.java | 30 +- .../bms/price/domain/PrcPriceSetProduct.java | 29 +- .../bms/price/domain/PrcPriceSetVersion.java | 8 + .../com/bms/price/domain/PrcSurchargeRel.java | 10 +- .../java/com/bms/price/domain/PriceType.java | 20 + .../RedlineCatalogPreviewApplyRequest.java | 48 ++ .../price/mapper/BmsProductCountryMapper.java | 22 +- .../bms/price/mapper/BmsProductMapper.java | 3 + .../BmsRedlineProductCountryMapper.java | 22 +- .../price/mapper/BmsRedlineProductMapper.java | 3 + .../price/mapper/PrcPriceExcelFileMapper.java | 10 +- .../price/mapper/PrcPriceSetDetailMapper.java | 71 +- .../mapper/PrcPriceSetProductMapper.java | 20 +- .../mapper/PrcPriceSetVersionMapper.java | 22 +- .../price/mapper/PrcSurchargeRelMapper.java | 3 + .../bms/price/service/IBmsProductService.java | 39 + .../service/IBmsRedlineProductService.java | 40 + .../service/IPrcPriceExcelFileService.java | 13 +- .../price/service/IPrcPriceSetService.java | 9 + .../service/impl/BmsProductServiceImpl.java | 620 ++++++++++++++-- .../impl/BmsRedlineProductServiceImpl.java | 566 +++++++++++++- .../impl/PrcPriceExcelFileServiceImpl.java | 38 +- .../service/impl/PrcPriceSetServiceImpl.java | 227 ++++-- .../impl/TmsPriceQuoteServiceImpl.java | 4 +- .../support/CatalogVersionSupport.java | 415 +++++++++++ .../service/support/UploadFileBatch.java | 44 ++ .../task/BmsProductImportAsyncRunner.java | 246 ++++++- .../BmsRedlineProductImportAsyncRunner.java | 254 ++++++- .../task/PrcPriceSetSaveAsyncRunner.java | 32 + .../mapper/price/BmsProductCountryMapper.xml | 432 ++++++----- .../mapper/price/BmsProductMapper.xml | 208 +++--- .../price/BmsRedlineProductCountryMapper.xml | 392 ++++++---- .../mapper/price/BmsRedlineProductMapper.xml | 212 +++--- .../mapper/price/PrcPriceExcelFileMapper.xml | 47 +- .../mapper/price/PrcPriceSetDetailMapper.xml | 457 ++++++++---- .../mapper/price/PrcPriceSetMapper.xml | 6 +- .../mapper/price/PrcPriceSetProductMapper.xml | 140 ++-- .../mapper/price/PrcPriceSetVersionMapper.xml | 90 ++- .../mapper/price/PrcSurchargeRelMapper.xml | 16 +- bms-back/sql/bms_init.sql | 234 ++---- bms-back/sql/price/prc_price_excel_file.sql | 42 +- .../sql/price/prc_price_excel_psv_alter.sql | 40 + .../sql/price/prc_price_set_is_del_alter.sql | 52 ++ .../prc_price_set_product_effect_alter.sql | 75 ++ .../sql/price/unify_price_type_version.sql | 514 +++++++++++++ bms-front/src/api/price/product.js | 149 +++- bms-front/src/api/price/redline.js | 149 +++- bms-front/src/api/price/set.js | 17 +- .../components/ExcelImportDialog/index.vue | 92 ++- bms-front/src/router/index.js | 28 + .../components/CatalogActivateDialog.vue | 160 ++++ .../basic/components/CatalogPriceCreate.vue | 689 ++++++++++++++++++ .../components/CatalogVersionExcelDrawer.vue | 82 +++ .../CatalogVersionProductsDrawer.vue | 103 +++ .../components/ProductCountryDrawer.vue | 10 +- .../components/ProductExcelFileDrawer.vue | 65 -- .../components/ProductExpiredDrawer.vue | 148 ---- .../product/components/ProductFormDialog.vue | 167 ----- .../src/views/price/basic/product/create.vue | 27 + .../src/views/price/basic/product/index.vue | 503 +++++-------- .../components/RedlineCountryDrawer.vue | 10 +- .../components/RedlineExcelFileDrawer.vue | 65 -- .../components/RedlineExpiredDrawer.vue | 148 ---- .../redline/components/RedlineFormDialog.vue | 167 ----- .../src/views/price/basic/redline/create.vue | 27 + .../src/views/price/basic/redline/index.vue | 490 +++++-------- .../set/components/create/StepDetails.vue | 29 +- bms-front/src/views/price/set/create.vue | 14 +- .../src/views/price/set/utils/priceSet.js | 1 - bms-front/vite.config.js | 4 +- 84 files changed, 6753 insertions(+), 2843 deletions(-) create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/domain/CatalogPreviewApplyRequest.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/domain/PriceType.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/domain/RedlineCatalogPreviewApplyRequest.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/service/support/UploadFileBatch.java create mode 100644 bms-back/sql/price/prc_price_excel_psv_alter.sql create mode 100644 bms-back/sql/price/prc_price_set_is_del_alter.sql create mode 100644 bms-back/sql/price/prc_price_set_product_effect_alter.sql create mode 100644 bms-back/sql/price/unify_price_type_version.sql create mode 100644 bms-front/src/views/price/basic/components/CatalogActivateDialog.vue create mode 100644 bms-front/src/views/price/basic/components/CatalogPriceCreate.vue create mode 100644 bms-front/src/views/price/basic/components/CatalogVersionExcelDrawer.vue create mode 100644 bms-front/src/views/price/basic/components/CatalogVersionProductsDrawer.vue delete mode 100644 bms-front/src/views/price/basic/product/components/ProductExcelFileDrawer.vue delete mode 100644 bms-front/src/views/price/basic/product/components/ProductExpiredDrawer.vue delete mode 100644 bms-front/src/views/price/basic/product/components/ProductFormDialog.vue create mode 100644 bms-front/src/views/price/basic/product/create.vue delete mode 100644 bms-front/src/views/price/basic/redline/components/RedlineExcelFileDrawer.vue delete mode 100644 bms-front/src/views/price/basic/redline/components/RedlineExpiredDrawer.vue delete mode 100644 bms-front/src/views/price/basic/redline/components/RedlineFormDialog.vue create mode 100644 bms-front/src/views/price/basic/redline/create.vue diff --git a/bms-back/bms-admin/src/main/java/com/bms/BmsApplication.java b/bms-back/bms-admin/src/main/java/com/bms/BmsApplication.java index 12cc0015..7264394b 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/BmsApplication.java +++ b/bms-back/bms-admin/src/main/java/com/bms/BmsApplication.java @@ -28,6 +28,7 @@ public class BmsApplication " | (_,_).' __ ___(_ o _)' \n" + " | |\\ \\ | || |(_,_)' \n" + " | | \\ `' /| `-' / \n" + + " | | \\ / \\ / \n" + " ''-' `'-' `-..-' "); } diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java index e46d58f1..29a739ee 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import com.bms.price.service.support.UploadFileBatch; import com.bms.common.annotation.Log; import com.bms.common.core.controller.BaseController; import com.bms.common.core.domain.AjaxResult; @@ -167,7 +168,7 @@ public class PriceSetController extends BaseController return toAjax(priceSetService.deleteVersion(psvId, getUsername())); } - /** Import excel attachments for a version (via prc_price_set_product.psv_id / pef_id). */ + /** Import excel attachments for a version (prc_price_excel_file.psv_id). */ @PreAuthorize("@ss.hasPermi('price:set:list')") @GetMapping("/version/{psvId}/excelFiles") public AjaxResult versionExcelFiles(@PathVariable Long psvId) @@ -255,7 +256,9 @@ public class PriceSetController extends BaseController @PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit')") @Log(title = "\u5957\u9910\u4ef7\u683c\u5bfc\u5165", businessType = BusinessType.IMPORT) @PostMapping("/detail/importPrices") - public AjaxResult importDetailPrices(MultipartFile file, + public AjaxResult importDetailPrices( + @RequestParam(value = "files", required = false) MultipartFile[] files, + @RequestParam(value = "file", required = false) MultipartFile file, @RequestParam("priceSetId") Long priceSetId, @RequestParam(value = "psvId", required = false) Long psvId, @RequestParam(value = "updateOnly", required = false, defaultValue = "false") boolean updateOnly, @@ -263,8 +266,8 @@ public class PriceSetController extends BaseController @RequestParam(value = "countryCode", required = false) String countryCode) throws Exception { - String taskId = priceSetService.submitImportDetailPricesAsync(priceSetId, psvId, file, getUsername(), updateOnly, - replaceByProductCountry, countryCode); + String taskId = priceSetService.submitImportDetailPricesAsync(priceSetId, psvId, UploadFileBatch.collect(files, file), + getUsername(), updateOnly, replaceByProductCountry, countryCode); Map payload = new HashMap(); payload.put("taskId", taskId); return success(payload); @@ -286,23 +289,29 @@ public class PriceSetController extends BaseController */ @PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit') or @ss.hasPermi('price:set:add')") @PostMapping("/detail/previewImportPrices") - public AjaxResult previewImportPrices(MultipartFile file, + public AjaxResult previewImportPrices( + @RequestParam(value = "files", required = false) MultipartFile[] files, + @RequestParam(value = "file", required = false) MultipartFile file, @RequestParam(value = "updateOnly", required = false, defaultValue = "false") boolean updateOnly, @RequestParam(value = "replaceByProductCountry", required = false, defaultValue = "false") boolean replaceByProductCountry, @RequestParam(value = "countryCode", required = false) String countryCode, @RequestParam(value = "detailsJson", required = false) String detailsJson) throws Exception { - if (file == null || file.isEmpty()) + java.util.List batch = UploadFileBatch.collect(files, file); + java.util.List bytesList = new java.util.ArrayList(); + java.util.List names = new java.util.ArrayList(); + for (MultipartFile item : batch) { - return error("file required"); + bytesList.add(item.getBytes()); + names.add(item.getOriginalFilename()); } List existing = null; if (StringUtils.isNotEmpty(detailsJson)) { existing = com.alibaba.fastjson2.JSON.parseArray(detailsJson, PrcPriceSetDetail.class); } - return success(priceSetService.previewImportDetailPrices( - file.getBytes(), file.getOriginalFilename(), updateOnly, existing, replaceByProductCountry, countryCode)); + return success(priceSetService.previewImportDetailPricesMany( + bytesList, names, updateOnly, existing, replaceByProductCountry, countryCode)); } /** diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java index b458ab6c..8c1427a2 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java @@ -7,7 +7,6 @@ import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -24,12 +23,17 @@ import com.bms.common.core.domain.AjaxResult; import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.common.utils.MessageUtils; +import com.bms.price.service.support.UploadFileBatch; import com.bms.price.domain.BmsProduct; import com.bms.price.domain.BmsProductCountry; import com.bms.price.domain.BmsProductImportTask; +import com.bms.price.domain.CatalogPreviewApplyRequest; +import com.bms.price.domain.PriceType; import com.bms.price.domain.PrcPriceExcelFile; +import com.bms.price.domain.PrcPriceSetVersion; import com.bms.price.service.IBmsProductService; import com.bms.price.service.IPrcPriceExcelFileService; +import com.bms.price.service.support.CatalogVersionSupport; /** * Product list (async Excel import) @@ -46,6 +50,9 @@ public class ProductController extends BaseController @Autowired private IPrcPriceExcelFileService priceExcelFileService; + @Autowired + private CatalogVersionSupport catalogVersionSupport; + @PreAuthorize("@ss.hasPermi('price:product:list')") @GetMapping("/list") public TableDataInfo list(BmsProduct product) @@ -55,15 +62,6 @@ public class ProductController extends BaseController return getDataTable(list); } - /** Imported quote Excel attachments for this product_code (via product.pef_id). */ - @PreAuthorize("@ss.hasPermi('price:product:query')") - @GetMapping("/{productCode}/excelFiles") - public AjaxResult excelFiles(@PathVariable String productCode) - { - List list = priceExcelFileService.selectByProductCode(productCode); - return success(list); - } - /** * 产品下拉选项(非分页)。 * 供计费列表、客户绑定等场景选择产品;不做权限控制(仍需登录)。 @@ -97,29 +95,93 @@ public class ProductController extends BaseController return success(list); } - @PreAuthorize("@ss.hasPermi('price:product:query')") - @GetMapping("/{productId}") - public AjaxResult getInfo(@PathVariable Long productId) + @PreAuthorize("@ss.hasPermi('price:product:list')") + @GetMapping("/versions") + public TableDataInfo versions(@RequestParam(required = false) String status, + @RequestParam(required = false) String statusGroup) { - return success(productService.selectBmsProductById(productId)); + startPage(); + List list = catalogVersionSupport.listVersions(PriceType.PRODUCT, status, statusGroup); + return getDataTable(list); + } + + @PreAuthorize("@ss.hasPermi('price:product:remove')") + @Log(title = "product version delete", businessType = BusinessType.DELETE) + @DeleteMapping("/version/{psvId}") + public AjaxResult deleteVersion(@PathVariable Long psvId) + { + return toAjax(catalogVersionSupport.deleteDraftVersion(psvId)); + } + + @PreAuthorize("@ss.hasPermi('price:product:query')") + @GetMapping("/version/draft") + public AjaxResult draftVersion() + { + return success(catalogVersionSupport.currentDraft(PriceType.PRODUCT)); } @PreAuthorize("@ss.hasPermi('price:product:add')") - @Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.INSERT) - @PostMapping - public AjaxResult add(@Validated @RequestBody BmsProduct product) + @Log(title = "product version prepare", businessType = BusinessType.INSERT) + @PostMapping("/version/prepare") + public AjaxResult prepareVersion(@RequestParam(defaultValue = "false") boolean copyFromLatest) { - product.setCreateBy(getUsername()); - return toAjax(productService.insertBmsProduct(product)); + return success(catalogVersionSupport.prepareDraftVersion(PriceType.PRODUCT, copyFromLatest)); } @PreAuthorize("@ss.hasPermi('price:product:edit')") - @Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@Validated @RequestBody BmsProduct product) + @Log(title = "product version activate", businessType = BusinessType.UPDATE) + @PutMapping("/version/activate/{psvId}") + public AjaxResult activateVersion(@PathVariable Long psvId, + @RequestBody(required = false) PrcPriceSetVersion body) { - product.setUpdateBy(getUsername()); - return toAjax(productService.updateBmsProduct(product)); + Date effectiveTime = body == null ? null : body.getEffectiveTime(); + catalogVersionSupport.activateVersion(psvId, effectiveTime); + return success(); + } + + /** Persist create-page preview details into draft/pending version. */ + @PreAuthorize("@ss.hasPermi('price:product:add')") + @Log(title = "product preview apply", businessType = BusinessType.UPDATE) + @PostMapping("/version/applyPreview") + public AjaxResult applyPreview(@RequestBody CatalogPreviewApplyRequest body) + { + return success(productService.applyCatalogPreview(body)); + } + + /** Async parallel save of create-page preview. Returns taskId; poll /import/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:product:add')") + @Log(title = "product preview apply", businessType = BusinessType.UPDATE) + @PostMapping("/version/applyPreviewAsync") + public AjaxResult applyPreviewAsync(@RequestBody CatalogPreviewApplyRequest body) + { + String taskId = productService.submitApplyCatalogPreviewAsync(body); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } + + /** Save import Excel attachment onto a draft/pending version (prc_price_excel_file). */ + @PreAuthorize("@ss.hasPermi('price:product:import')") + @Log(title = "product version excel", businessType = BusinessType.IMPORT) + @PostMapping("/version/{psvId}/excel") + public AjaxResult attachExcel(@PathVariable Long psvId, MultipartFile file) throws Exception + { + catalogVersionSupport.requireEditableCatalogVersion(psvId, PriceType.PRODUCT); + if (file == null || file.isEmpty()) + { + return error(MessageUtils.message("price.product.import.fileEmpty")); + } + PrcPriceExcelFile row = priceExcelFileService.saveImportExcel( + PrcPriceExcelFile.TYPE_PRODUCT, null, file.getBytes(), file.getOriginalFilename(), getUsername(), psvId); + return success(row); + } + + /** List Excel attachments of a version. */ + @PreAuthorize("@ss.hasPermi('price:product:query')") + @GetMapping("/version/{psvId}/excelFiles") + public AjaxResult listVersionExcelFiles(@PathVariable Long psvId) + { + return success(priceExcelFileService.selectByPsvId(psvId)); } @PreAuthorize("@ss.hasPermi('price:product:edit')") @@ -146,7 +208,7 @@ public class ProductController extends BaseController @PreAuthorize("@ss.hasPermi('price:product:import')") @PostMapping("/importData") public AjaxResult importData(MultipartFile file, boolean updateSupport, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception { String taskId = productService.submitImportProductExcelAsync(file, updateSupport, getUsername(), effectiveDate); Map payload = new HashMap(); @@ -162,7 +224,7 @@ public class ProductController extends BaseController @PreAuthorize("@ss.hasPermi('price:product:import')") @PostMapping("/importCountryData") public AjaxResult importCountryData(MultipartFile file, String countryCode, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception { String taskId = productService.submitImportCountryProductExcelAsync(file, countryCode, getUsername(), effectiveDate); @@ -170,4 +232,31 @@ public class ProductController extends BaseController payload.put("taskId", taskId); return success(payload); } + + /** Async parse-only preview; returns taskId, poll /import/task/{taskId} for result. */ + @PreAuthorize("@ss.hasPermi('price:product:import')") + @PostMapping("/previewImport") + public AjaxResult previewImport( + @RequestParam(value = "files", required = false) MultipartFile[] files, + @RequestParam(value = "file", required = false) MultipartFile file) throws Exception + { + String taskId = productService.submitPreviewImportAsync(UploadFileBatch.collect(files, file)); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } + + /** Async single-country parse-only preview; returns taskId, poll /import/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:product:import')") + @PostMapping("/previewImportCountry") + public AjaxResult previewImportCountry( + @RequestParam(value = "files", required = false) MultipartFile[] files, + @RequestParam(value = "file", required = false) MultipartFile file, + String countryCode) throws Exception + { + String taskId = productService.submitPreviewImportCountryAsync(UploadFileBatch.collect(files, file), countryCode); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } } diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/RedlineProductController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/RedlineProductController.java index 48ae379d..7e7b6706 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/RedlineProductController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/RedlineProductController.java @@ -7,7 +7,6 @@ import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -24,12 +23,17 @@ import com.bms.common.core.domain.AjaxResult; import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.common.utils.MessageUtils; +import com.bms.price.service.support.UploadFileBatch; import com.bms.price.domain.BmsRedlineProduct; import com.bms.price.domain.BmsRedlineProductCountry; import com.bms.price.domain.BmsRedlineProductImportTask; +import com.bms.price.domain.PriceType; import com.bms.price.domain.PrcPriceExcelFile; +import com.bms.price.domain.PrcPriceSetVersion; +import com.bms.price.domain.RedlineCatalogPreviewApplyRequest; import com.bms.price.service.IBmsRedlineProductService; import com.bms.price.service.IPrcPriceExcelFileService; +import com.bms.price.service.support.CatalogVersionSupport; /** * redline product list (async Excel import) @@ -46,6 +50,9 @@ public class RedlineProductController extends BaseController @Autowired private IPrcPriceExcelFileService priceExcelFileService; + @Autowired + private CatalogVersionSupport catalogVersionSupport; + @PreAuthorize("@ss.hasPermi('price:redline:list')") @GetMapping("/list") public TableDataInfo list(BmsRedlineProduct product) @@ -55,15 +62,6 @@ public class RedlineProductController extends BaseController return getDataTable(list); } - /** Imported quote Excel attachments for this product_code (via product.pef_id). */ - @PreAuthorize("@ss.hasPermi('price:redline:query')") - @GetMapping("/{productCode}/excelFiles") - public AjaxResult excelFiles(@PathVariable String productCode) - { - List list = priceExcelFileService.selectByRedlineProductCode(productCode); - return success(list); - } - /** * 产品下拉选项(非分页)。 * 供计费列表、客户绑定等场景选择产品;不做权限控制(仍需登录)。 @@ -97,29 +95,93 @@ public class RedlineProductController extends BaseController return success(list); } - @PreAuthorize("@ss.hasPermi('price:redline:query')") - @GetMapping("/{productId}") - public AjaxResult getInfo(@PathVariable Long productId) + @PreAuthorize("@ss.hasPermi('price:redline:list')") + @GetMapping("/versions") + public TableDataInfo versions(@RequestParam(required = false) String status, + @RequestParam(required = false) String statusGroup) { - return success(productService.selectBmsRedlineProductById(productId)); + startPage(); + List list = catalogVersionSupport.listVersions(PriceType.REDLINE, status, statusGroup); + return getDataTable(list); + } + + @PreAuthorize("@ss.hasPermi('price:redline:remove')") + @Log(title = "redline version delete", businessType = BusinessType.DELETE) + @DeleteMapping("/version/{psvId}") + public AjaxResult deleteVersion(@PathVariable Long psvId) + { + return toAjax(catalogVersionSupport.deleteDraftVersion(psvId)); + } + + @PreAuthorize("@ss.hasPermi('price:redline:query')") + @GetMapping("/version/draft") + public AjaxResult draftVersion() + { + return success(catalogVersionSupport.currentDraft(PriceType.REDLINE)); } @PreAuthorize("@ss.hasPermi('price:redline:add')") - @Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.INSERT) - @PostMapping - public AjaxResult add(@Validated @RequestBody BmsRedlineProduct product) + @Log(title = "redline version prepare", businessType = BusinessType.INSERT) + @PostMapping("/version/prepare") + public AjaxResult prepareVersion(@RequestParam(defaultValue = "false") boolean copyFromLatest) { - product.setCreateBy(getUsername()); - return toAjax(productService.insertBmsRedlineProduct(product)); + return success(catalogVersionSupport.prepareDraftVersion(PriceType.REDLINE, copyFromLatest)); } @PreAuthorize("@ss.hasPermi('price:redline:edit')") - @Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@Validated @RequestBody BmsRedlineProduct product) + @Log(title = "redline version activate", businessType = BusinessType.UPDATE) + @PutMapping("/version/activate/{psvId}") + public AjaxResult activateVersion(@PathVariable Long psvId, + @RequestBody(required = false) PrcPriceSetVersion body) { - product.setUpdateBy(getUsername()); - return toAjax(productService.updateBmsRedlineProduct(product)); + Date effectiveTime = body == null ? null : body.getEffectiveTime(); + catalogVersionSupport.activateVersion(psvId, effectiveTime); + return success(); + } + + /** Persist create-page preview details into draft/pending version. */ + @PreAuthorize("@ss.hasPermi('price:redline:add')") + @Log(title = "redline preview apply", businessType = BusinessType.UPDATE) + @PostMapping("/version/applyPreview") + public AjaxResult applyPreview(@RequestBody RedlineCatalogPreviewApplyRequest body) + { + return success(productService.applyCatalogPreview(body)); + } + + /** Async parallel save of create-page preview. Returns taskId; poll /import/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:redline:add')") + @Log(title = "redline preview apply", businessType = BusinessType.UPDATE) + @PostMapping("/version/applyPreviewAsync") + public AjaxResult applyPreviewAsync(@RequestBody RedlineCatalogPreviewApplyRequest body) + { + String taskId = productService.submitApplyCatalogPreviewAsync(body); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } + + /** Save import Excel attachment onto a draft/pending version (prc_price_excel_file). */ + @PreAuthorize("@ss.hasPermi('price:redline:import')") + @Log(title = "redline version excel", businessType = BusinessType.IMPORT) + @PostMapping("/version/{psvId}/excel") + public AjaxResult attachExcel(@PathVariable Long psvId, MultipartFile file) throws Exception + { + catalogVersionSupport.requireEditableCatalogVersion(psvId, PriceType.REDLINE); + if (file == null || file.isEmpty()) + { + return error(MessageUtils.message("price.product.import.fileEmpty")); + } + PrcPriceExcelFile row = priceExcelFileService.saveImportExcel( + PrcPriceExcelFile.TYPE_REDLINE, null, file.getBytes(), file.getOriginalFilename(), getUsername(), psvId); + return success(row); + } + + /** List Excel attachments of a version. */ + @PreAuthorize("@ss.hasPermi('price:redline:query')") + @GetMapping("/version/{psvId}/excelFiles") + public AjaxResult listVersionExcelFiles(@PathVariable Long psvId) + { + return success(priceExcelFileService.selectByPsvId(psvId)); } @PreAuthorize("@ss.hasPermi('price:redline:edit')") @@ -146,7 +208,7 @@ public class RedlineProductController extends BaseController @PreAuthorize("@ss.hasPermi('price:redline:import')") @PostMapping("/importData") public AjaxResult importData(MultipartFile file, boolean updateSupport, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception { String taskId = productService.submitImportProductExcelAsync(file, updateSupport, getUsername(), effectiveDate); Map payload = new HashMap(); @@ -162,7 +224,7 @@ public class RedlineProductController extends BaseController @PreAuthorize("@ss.hasPermi('price:redline:import')") @PostMapping("/importCountryData") public AjaxResult importCountryData(MultipartFile file, String countryCode, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date effectiveDate) throws Exception { String taskId = productService.submitImportCountryProductExcelAsync(file, countryCode, getUsername(), effectiveDate); @@ -170,4 +232,31 @@ public class RedlineProductController extends BaseController payload.put("taskId", taskId); return success(payload); } + + /** Async parse-only preview; returns taskId, poll /import/task/{taskId} for result. */ + @PreAuthorize("@ss.hasPermi('price:redline:import')") + @PostMapping("/previewImport") + public AjaxResult previewImport( + @RequestParam(value = "files", required = false) MultipartFile[] files, + @RequestParam(value = "file", required = false) MultipartFile file) throws Exception + { + String taskId = productService.submitPreviewImportAsync(UploadFileBatch.collect(files, file)); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } + + /** Async single-country parse-only preview; returns taskId, poll /import/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:redline:import')") + @PostMapping("/previewImportCountry") + public AjaxResult previewImportCountry( + @RequestParam(value = "files", required = false) MultipartFile[] files, + @RequestParam(value = "file", required = false) MultipartFile file, + String countryCode) throws Exception + { + String taskId = productService.submitPreviewImportCountryAsync(UploadFileBatch.collect(files, file), countryCode); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } } diff --git a/bms-back/bms-admin/src/main/resources/application.yml b/bms-back/bms-admin/src/main/resources/application.yml index 70c5d026..8dd70f1a 100644 --- a/bms-back/bms-admin/src/main/resources/application.yml +++ b/bms-back/bms-admin/src/main/resources/application.yml @@ -58,11 +58,13 @@ server: uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 + connection-timeout: 600000 + max-swallow-size: -1 threads: # tomcat最大线程数,默认为200 - max: 800 + max: 200 # Tomcat启动初始化的线程数,默认值10 - min-spare: 100 + min-spare: 10 # 日志配置 logging: @@ -94,9 +96,9 @@ spring: servlet: multipart: # 单个文件大小 - max-file-size: 10MB + max-file-size: 50MB # 设置总上传的文件大小 - max-request-size: 20MB + max-request-size: 260MB jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProduct.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProduct.java index 31cafbae..62a75d04 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProduct.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProduct.java @@ -7,14 +7,17 @@ import com.bms.common.core.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; -/** BMS产品主表 product */ +/** Catalog product view over prc_price_set_product (price_type=PRODUCT/REDLINE) */ @Data @EqualsAndHashCode(callSuper = true) public class BmsProduct extends BaseEntity { private static final long serialVersionUID = 1L; + /** maps to prc_price_set_product.psp_id */ private Long productId; + private Long psvId; + private String priceType; private String productCode; private String productName; private String serviceLevel; @@ -38,8 +41,6 @@ public class BmsProduct extends BaseEntity /** 0未删除 1已删除 */ private String isDel; private String sourceSheet; - /** FK prc_price_excel_file.pef_id */ - private Long pefId; private Long tmsId; private Integer countryCount; private Integer surchargeCount; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java index 8564bedd..79187a9f 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java @@ -12,7 +12,11 @@ public class BmsProductCountry implements Serializable { private static final long serialVersionUID = 1L; + /** maps to prc_price_set_detail.psd_id */ private Long detailId; + private Long psvId; + private Long pspId; + private String priceType; /** FK to product.product_id */ private Long productId; private String productCode; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductImportTask.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductImportTask.java index 5a842a1b..d2cc2701 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductImportTask.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductImportTask.java @@ -19,6 +19,8 @@ public class BmsProductImportTask implements Serializable private int percent; private String message; private String error; + /** Preview parse payload (products / details); not persisted to DB. */ + private Object result; public String getTaskId() { @@ -69,4 +71,14 @@ public class BmsProductImportTask implements Serializable { this.error = error; } + + public Object getResult() + { + return result; + } + + public void setResult(Object result) + { + this.result = result; + } } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProduct.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProduct.java index 3bef5d68..05cf7c47 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProduct.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProduct.java @@ -7,7 +7,7 @@ import com.bms.common.core.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; -/** Redline catalog main table redline_product */ +/** Redline catalog product view over prc_price_set_product (price_type=REDLINE) */ @Data @EqualsAndHashCode(callSuper = true) public class BmsRedlineProduct extends BaseEntity @@ -15,6 +15,8 @@ public class BmsRedlineProduct extends BaseEntity private static final long serialVersionUID = 1L; private Long productId; + private Long psvId; + private String priceType; private String productCode; private String productName; private String serviceLevel; @@ -38,8 +40,6 @@ public class BmsRedlineProduct extends BaseEntity /** 0未删除 1已删除 */ private String isDel; private String sourceSheet; - /** FK prc_price_excel_file.pef_id */ - private Long pefId; private Long tmsId; private Integer countryCount; private Integer surchargeCount; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductCountry.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductCountry.java index 99a6fd43..ac153ef4 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductCountry.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductCountry.java @@ -6,16 +6,22 @@ import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; -/** Redline country detail table redline_product_country */ +/** Redline catalog country detail over prc_price_set_detail (price_type=REDLINE) */ @Data public class BmsRedlineProductCountry implements Serializable { private static final long serialVersionUID = 1L; + /** maps to prc_price_set_detail.psd_id */ private Long detailId; - /** FK to product.product_id */ + private Long psvId; + private Long pspId; + private String priceType; + /** FK to product.product_id / psp_id */ private Long productId; private String productCode; + /** Parse/import helper only (not a DB column). */ + private String productName; private String countryName; private String countryCode; /** prc_zone.zone_id; null when product country has no zone */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductImportTask.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductImportTask.java index 11fce955..a47fe189 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductImportTask.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsRedlineProductImportTask.java @@ -19,6 +19,8 @@ public class BmsRedlineProductImportTask implements Serializable private int percent; private String message; private String error; + /** Preview parse payload (products / details); not persisted to DB. */ + private Object result; public String getTaskId() { @@ -69,4 +71,14 @@ public class BmsRedlineProductImportTask implements Serializable { this.error = error; } + + public Object getResult() + { + return result; + } + + public void setResult(Object result) + { + this.result = result; + } } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/CatalogPreviewApplyRequest.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/CatalogPreviewApplyRequest.java new file mode 100644 index 00000000..45d19652 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/CatalogPreviewApplyRequest.java @@ -0,0 +1,48 @@ +package com.bms.price.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * Apply create-page Excel preview into a draft/pending catalog version. + */ +public class CatalogPreviewApplyRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + private Long psvId; + private List products; + /** key = upper-case productCode */ + private Map> detailsByProductCode; + + public Long getPsvId() + { + return psvId; + } + + public void setPsvId(Long psvId) + { + this.psvId = psvId; + } + + public List getProducts() + { + return products; + } + + public void setProducts(List products) + { + this.products = products; + } + + public Map> getDetailsByProductCode() + { + return detailsByProductCode; + } + + public void setDetailsByProductCode(Map> detailsByProductCode) + { + this.detailsByProductCode = detailsByProductCode; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceExcelFile.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceExcelFile.java index aa0bfbc5..f67ab3cc 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceExcelFile.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceExcelFile.java @@ -22,6 +22,8 @@ public class PrcPriceExcelFile extends BaseEntity private Long pefId; /** PRODUCT / REDLINE / SALES */ private String excelType; + /** FK prc_price_set_version.psv_id */ + private Long psvId; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date effectiveTime; private String fileName; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java index fc0209c6..8f260747 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java @@ -49,8 +49,10 @@ public class PrcPriceSet extends BaseEntity /** Y: even if version already has details, delete+re-pull from library */ private String forcePullLibrary; /** - * Request-only: stamp this Excel attachment id onto prc_price_set_product.pef_id - * when saving IMPORT details (not a column on prc_price_set). + * Request-only: bind this Excel attachment (pef_id) to the saved version.psv_id + * (not a column on prc_price_set). */ private Long pefId; + /** 请求专用:多个 Excel 附件 pef_id,保存时绑到版本(非表字段) */ + private java.util.List pefIds; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetDetail.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetDetail.java index bfc3cb39..b01df1a4 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetDetail.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetDetail.java @@ -7,63 +7,57 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; -/** 价格套餐明细 prc_price_set_detail */ +/** Price detail prc_price_set_detail (PRODUCT / REDLINE / SALES) */ @Data public class PrcPriceSetDetail implements Serializable { private static final long serialVersionUID = 1L; private Long psdId; - private Long priceSetId; - /** prc_price_set_version.psv_id */ + /** PRODUCT / REDLINE / SALES */ + private String priceType; private Long psvId; - /** prc_price_set_product.psp_id */ private Long pspId; private String productCode; private String productName; private String countryCode; private String countryName; - /** prc_zone.zone_id; null when country has no zone */ private Long zoneId; - /** e.g. Z1; null when no zone */ private String zoneCode; - /** zone display name; null when no zone */ private String zoneName; private Long zdId; private Long wsdId; private String weightLabel; - /** KG inclusive, same as product_country */ private BigDecimal weightMin; private BigDecimal weightMax; - /** product_country.detail_id */ + /** legacy migrate source id */ private Long productCountryId; private BigDecimal fvValue; private BigDecimal fvRegistrationFee; - /** product base freight from product_country (display only) */ + private String sizeLimit; + private String lastMile; + private String leadTime; + /** display-only base from PRODUCT type when comparing discounts */ private BigDecimal baseFreightPrice; - /** product base registration from product_country (display only) */ private BigDecimal baseRegistrationFee; private BigDecimal discountValue; - /** UP / DOWN / HALF_UP */ private String roundMode; private Integer prId; @JsonFormat(pattern = "yyyy-MM-dd") private Date effectiveDate; - /** null = open */ @JsonFormat(pattern = "yyyy-MM-dd") private Date expireDate; - /** SALES rels for this detail (detail_id=psd_id, product_id=psp_id) */ + /** 0 not deleted / 1 deleted */ + public static final String IS_DEL_NO = "0"; + public static final String IS_DEL_YES = "1"; + private String isDel; private List surcharges; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; - /** query only: product/country/zone/weight keyword */ private String keyword; - /** query only: product code/name like */ private String productKeyword; - /** query only: country code/name like */ private String countryKeyword; - /** query only: zone code/name like */ private String zoneKeyword; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetProduct.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetProduct.java index c73066ca..5d6ce5f6 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetProduct.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetProduct.java @@ -6,39 +6,38 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; /** - * 价格套餐产品主表 prc_price_set_product(与产品库快照独立) + * Price product row prc_price_set_product (PRODUCT / REDLINE / SALES) */ @Data public class PrcPriceSetProduct implements Serializable { private static final long serialVersionUID = 1L; - /** 套餐产品主表id */ private Long pspId; - /** 价格套餐id */ - private Long priceSetId; - /** 套餐版本id */ + /** PRODUCT / REDLINE / SALES */ + private String priceType; private Long psvId; - /** 产品对接代码 */ private String productCode; - /** 产品名称 */ private String productName; - /** 时效等级 */ private String serviceLevel; - /** 走货属性 */ private String cargoAttr; - /** 目的区域 */ private String destRegion; - /** 更新说明 */ private String updateNote; /** TAX_INCLUDED / TAX_EXCLUDED */ private String taxMode; - /** 来源产品库 product.product_id */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date effectiveTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date expireTime; + /** 0 draft / 3 pending / 1 effect / 2 expired */ + private String status; + /** legacy migrate source id */ private Long sourceProductId; - /** 来源Excel sheet */ private String sourceSheet; - /** 关联导入价表Excel附件 prc_price_excel_file.pef_id */ - private Long pefId; + /** 0 not deleted / 1 deleted */ + public static final String IS_DEL_NO = "0"; + public static final String IS_DEL_YES = "1"; + private String isDel; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java index f674724f..d6001eba 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java @@ -16,6 +16,8 @@ public class PrcPriceSetVersion extends BaseEntity private Long psvId; private Long priceSetId; + /** PRODUCT / REDLINE / SALES */ + private String priceType; /** V1 / V2 */ private String versionNo; private Integer versionSeq; @@ -34,4 +36,10 @@ public class PrcPriceSetVersion extends BaseEntity private List details; private List products; + /** query display: active product rows */ + private Integer productCount; + /** query display: active detail rows */ + private Integer detailCount; + /** query only: pending / expired / effect */ + private String statusGroup; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcSurchargeRel.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcSurchargeRel.java index a73f8f3f..30b25115 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcSurchargeRel.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcSurchargeRel.java @@ -9,10 +9,10 @@ import lombok.EqualsAndHashCode; * 附加费与产品/明细/套餐关联 prc_surcharge_rel * 同时携带附加费展示字段(JOIN prc_surcharge)与明细展示字段(LEFT JOIN product_country,仅 PRODUCT/REDLINE)。 * - * price_type 语义: - * PRODUCT / REDLINE : product_id=product.product_id,detail_id=product_country.detail_id(空=产品全部明细) - * SALES : product_id=prc_price_set_product.psp_id,detail_id=prc_price_set_detail.psd_id + * price_type semantics: + * PRODUCT / REDLINE / SALES : product_id=psp_id, detail_id=psd_id (null=all details of product) */ + @Data @EqualsAndHashCode(callSuper = true) public class PrcSurchargeRel extends BaseEntity @@ -28,10 +28,10 @@ public class PrcSurchargeRel extends BaseEntity private Long relId; private Long scId; - /** PRODUCT/REDLINE=product_id;SALES=psp_id */ + /** psp_id for PRODUCT/REDLINE/SALES */ private Long productId; private String productCode; - /** PRODUCT/REDLINE=product_country.detail_id;SALES=psd_id */ + /** psd_id for PRODUCT/REDLINE/SALES (null=all) */ private Long detailId; /** PRODUCT / SALES / REDLINE */ private String priceType; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PriceType.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PriceType.java new file mode 100644 index 00000000..fbb51e1e --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PriceType.java @@ -0,0 +1,20 @@ +package com.bms.price.domain; + +/** + * Unified price type for prc_price_set_version / product / detail. + */ +public final class PriceType +{ + public static final String PRODUCT = "PRODUCT"; + public static final String REDLINE = "REDLINE"; + public static final String SALES = "SALES"; + + private PriceType() + { + } + + public static boolean isCatalog(String priceType) + { + return PRODUCT.equals(priceType) || REDLINE.equals(priceType); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/RedlineCatalogPreviewApplyRequest.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/RedlineCatalogPreviewApplyRequest.java new file mode 100644 index 00000000..01afbc87 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/RedlineCatalogPreviewApplyRequest.java @@ -0,0 +1,48 @@ +package com.bms.price.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * Apply create-page Excel preview into a draft/pending redline version. + */ +public class RedlineCatalogPreviewApplyRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + private Long psvId; + private List products; + /** key = upper-case productCode */ + private Map> detailsByProductCode; + + public Long getPsvId() + { + return psvId; + } + + public void setPsvId(Long psvId) + { + this.psvId = psvId; + } + + public List getProducts() + { + return products; + } + + public void setProducts(List products) + { + this.products = products; + } + + public Map> getDetailsByProductCode() + { + return detailsByProductCode; + } + + public void setDetailsByProductCode(Map> detailsByProductCode) + { + this.detailsByProductCode = detailsByProductCode; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java index 0731c149..84dc0da6 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java @@ -17,6 +17,8 @@ public interface BmsProductCountryMapper public int insertBmsProductCountry(BmsProductCountry country); + public int insertBmsProductCountryBatch(@Param("list") List list); + /** 按明细行主键更新可编辑字段(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */ public int updateBmsProductCountry(BmsProductCountry country); @@ -65,9 +67,18 @@ public interface BmsProductCountryMapper @Param("productCode") String productCode, @Param("effectiveDate") Date effectiveDate); + /** Hard-delete all detail rows of one product (psp_id). */ + public int physicalDeleteByPspId(@Param("productId") Long productId); + + /** Hard-delete detail rows of one product + country (psp_id scope). */ + public int physicalDeleteByPspIdAndCountry( + @Param("productId") Long productId, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName); + /** - * Delete open rows for one product + country on a given effective_date (same-day replace). - * Needed because unique key keeps expired same-day weight bands. + * Hard-delete draft/pending detail rows for one product + country (same-version replace). + * Only versions with status draft(0) or pending(3) are affected. */ public int deleteOpenByProductCodeAndCountryOnDate( @Param("productCode") String productCode, @@ -75,6 +86,13 @@ public interface BmsProductCountryMapper @Param("countryName") String countryName, @Param("effectiveDate") Date effectiveDate); + /** Hard-delete surcharge_rel rows linked to draft/pending details for product+country. */ + public int deleteSurchargeRelByProductCodeAndCountryOnDate( + @Param("productCode") String productCode, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName, + @Param("effectiveDate") Date effectiveDate); + /** Open country rows (expire_date is null) for product */ public List selectOpenByProductCode(@Param("productCode") String productCode); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductMapper.java index d337e1f8..9e0a79d9 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductMapper.java @@ -21,6 +21,9 @@ public interface BmsProductMapper public BmsProduct selectBmsProductByCodeAndDate(@Param("productCode") String productCode, @Param("effectiveDate") Date effectiveDate); + /** Product row in a specific version */ + public BmsProduct selectByCodeAndPsvId(@Param("productCode") String productCode, @Param("psvId") Long psvId); + /** Product version effective on bizDate */ public BmsProduct selectEffectiveBmsProductByCode(@Param("productCode") String productCode, @Param("bizDate") Date bizDate); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductCountryMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductCountryMapper.java index 0f01c5a3..49508e20 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductCountryMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductCountryMapper.java @@ -17,6 +17,8 @@ public interface BmsRedlineProductCountryMapper public int insertBmsRedlineProductCountry(BmsRedlineProductCountry country); + public int insertBmsRedlineProductCountryBatch(@Param("list") List list); + /** 按明细行主键更新可编辑字段(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */ public int updateBmsRedlineProductCountry(BmsRedlineProductCountry country); @@ -62,9 +64,18 @@ public interface BmsRedlineProductCountryMapper @Param("productCode") String productCode, @Param("effectiveDate") Date effectiveDate); + /** Hard-delete all detail rows of one product (psp_id). */ + public int physicalDeleteByPspId(@Param("productId") Long productId); + + /** Hard-delete detail rows of one product + country (psp_id scope). */ + public int physicalDeleteByPspIdAndCountry( + @Param("productId") Long productId, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName); + /** - * Delete open rows for one product + country on a given effective_date (same-day replace). - * Needed because unique key keeps expired same-day weight bands. + * Hard-delete draft/pending detail rows for one product + country (same-version replace). + * Only versions with status draft(0) or pending(3) are affected. */ public int deleteOpenByProductCodeAndCountryOnDate( @Param("productCode") String productCode, @@ -72,6 +83,13 @@ public interface BmsRedlineProductCountryMapper @Param("countryName") String countryName, @Param("effectiveDate") Date effectiveDate); + /** Hard-delete surcharge_rel rows linked to draft/pending details for product+country. */ + public int deleteSurchargeRelByProductCodeAndCountryOnDate( + @Param("productCode") String productCode, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName, + @Param("effectiveDate") Date effectiveDate); + /** Open country rows (expire_date is null) for product */ public List selectOpenByProductCode(@Param("productCode") String productCode); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductMapper.java index b9a63ab1..054c125c 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsRedlineProductMapper.java @@ -21,6 +21,9 @@ public interface BmsRedlineProductMapper public BmsRedlineProduct selectBmsRedlineProductByCodeAndDate(@Param("productCode") String productCode, @Param("effectiveDate") Date effectiveDate); + /** Product row in a specific version */ + public BmsRedlineProduct selectByCodeAndPsvId(@Param("productCode") String productCode, @Param("psvId") Long psvId); + /** Product version effective on bizDate */ public BmsRedlineProduct selectEffectiveBmsRedlineProductByCode(@Param("productCode") String productCode, @Param("bizDate") Date bizDate); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceExcelFileMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceExcelFileMapper.java index 9a62518c..1f8ef94d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceExcelFileMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceExcelFileMapper.java @@ -13,14 +13,12 @@ public interface PrcPriceExcelFileMapper public List selectPrcPriceExcelFileList(PrcPriceExcelFile query); - /** Distinct PRODUCT excel files linked by product.pef_id for this product_code */ - public List selectByProductCode(@Param("productCode") String productCode); - - public List selectByRedlineProductCode(@Param("productCode") String productCode); - - /** Distinct excel files linked by prc_price_set_product.pef_id for a version. */ + /** Excel files bound to a version via psv_id */ public List selectByPsvId(@Param("psvId") Long psvId); + /** Hard delete attachments of a version. */ + public int physicalDeleteByPsvId(@Param("psvId") Long psvId); + public int insertPrcPriceExcelFile(PrcPriceExcelFile row); public int updatePrcPriceExcelFile(PrcPriceExcelFile row); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java index a9a6a15e..a211e660 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java @@ -7,9 +7,7 @@ import org.apache.ibatis.annotations.Param; import com.bms.price.domain.PrcPriceSetDetail; /** - * 价格套餐明细 数据层 - * - * @author bms + * Price detail mapper (PRODUCT / REDLINE / SALES) */ public interface PrcPriceSetDetailMapper { @@ -17,23 +15,24 @@ public interface PrcPriceSetDetailMapper public List selectPrcPriceSetDetailList(PrcPriceSetDetail detail); - /** 版本内去重后的 产品/国家/维度组合行(用于明细搜索下拉选项) */ public List selectDetailOptionRows(@Param("psvId") Long psvId); public int insertPrcPriceSetDetail(PrcPriceSetDetail detail); - /** Batch insert details; fills generated psdId on each item. */ public int insertPrcPriceSetDetailBatch(@Param("list") List list); public int updatePrcPriceSetDetail(PrcPriceSetDetail detail); public int deletePrcPriceSetDetailByIds(Long[] psdIds); + /** Delete SALES details for all versions of a price set */ public int deletePrcPriceSetDetailByPriceSetId(Long priceSetId); public int deletePrcPriceSetDetailByPsvId(Long psvId); - /** legacy rows: price_set_id match but psv_id is null */ + /** Hard delete all detail rows under a version. */ + public int physicalDeleteByPsvId(@Param("psvId") Long psvId); + public int deleteOrphanDetailsByPriceSetId(Long priceSetId); public int countProductByPriceSetId(Long priceSetId); @@ -42,32 +41,23 @@ public interface PrcPriceSetDetailMapper public int countByPsvId(Long psvId); - /** Copy all details from src version into new version (new psd_id, same price_set_id) */ public int copyDetailsFromVersion(@Param("srcPsvId") Long srcPsvId, @Param("newPsvId") Long newPsvId); - /** FIXED: insert details from product country library (asOf window or openOnly). */ + /** Copy version effective time, expire time and status onto catalog detail rows. */ + public int syncEffectByPriceType(@Param("priceType") String priceType); + public int insertFixedDetailsFromLibrary( - @Param("priceSetId") Long priceSetId, @Param("psvId") Long psvId, @Param("asOf") Date asOf, @Param("openOnly") boolean openOnly); - /** Preview: FIXED details from product country library (no insert). */ public List selectFixedDetailsFromLibrary( @Param("asOf") Date asOf, @Param("openOnly") boolean openOnly); - /** DISCOUNT: one detail row per effective product */ - public int insertDiscountDetailsFromLibrary( - @Param("priceSetId") Long priceSetId, - @Param("psvId") Long psvId); + public int insertDiscountDetailsFromLibrary(@Param("psvId") Long psvId); - /** - * FIXED match on package snapshot: product + country + zone + weight band. - * Does not depend on live product_country.detail_id. - */ public PrcPriceSetDetail selectFixedMatch( - @Param("priceSetId") Long priceSetId, @Param("psvId") Long psvId, @Param("productCode") String productCode, @Param("countryCode") String countryCode, @@ -75,22 +65,16 @@ public interface PrcPriceSetDetailMapper @Param("zoneCode") String zoneCode, @Param("weight") BigDecimal weight); - /** Count version details that have zone for product + country (postcode gate). */ public int countZonedByPsvProductCountry( @Param("psvId") Long psvId, @Param("productCode") String productCode, @Param("countryCode") String countryCode, @Param("countryName") String countryName); - /** Count FIXED details by version + product (ignore country/zone/weight). */ public int countByPsvProduct( @Param("psvId") Long psvId, @Param("productCode") String productCode); - /** - * Count FIXED details by version + product + country + zone (ignore weight). - * Used to diagnose country miss vs weight miss. - */ public int countByPsvProductCountryZone( @Param("psvId") Long psvId, @Param("productCode") String productCode, @@ -98,15 +82,46 @@ public interface PrcPriceSetDetailMapper @Param("countryName") String countryName, @Param("zoneCode") String zoneCode); - /** DISCOUNT by product; optional psvId scopes to version */ public PrcPriceSetDetail selectDiscountMatch( - @Param("priceSetId") Long priceSetId, @Param("psvId") Long psvId, @Param("productCode") String productCode); - /** Update package freight / registration; null args keep existing columns. */ public int updateDetailPrices( @Param("psdId") Long psdId, @Param("fvValue") BigDecimal fvValue, @Param("fvRegistrationFee") BigDecimal fvRegistrationFee); + + public int updatePriceTypeByPsvId(@Param("psvId") Long psvId, @Param("priceType") String priceType); + + /** + * Match PRODUCT/REDLINE detail by catalog type + bizDate window on version. + */ + public PrcPriceSetDetail selectCatalogMatch( + @Param("priceType") String priceType, + @Param("productCode") String productCode, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName, + @Param("zoneCode") String zoneCode, + @Param("weight") BigDecimal weight, + @Param("bizDate") Date bizDate); + + public int countCatalogByProduct( + @Param("priceType") String priceType, + @Param("productCode") String productCode, + @Param("bizDate") Date bizDate); + + public int countCatalogZonedByProductCountry( + @Param("priceType") String priceType, + @Param("productCode") String productCode, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName, + @Param("bizDate") Date bizDate); + + public int countCatalogByProductCountryZone( + @Param("priceType") String priceType, + @Param("productCode") String productCode, + @Param("countryCode") String countryCode, + @Param("countryName") String countryName, + @Param("zoneCode") String zoneCode, + @Param("bizDate") Date bizDate); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java index fc4a685a..20fcd9a6 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java @@ -5,7 +5,7 @@ import org.apache.ibatis.annotations.Param; import com.bms.price.domain.PrcPriceSetProduct; /** - * Package intermediate product mapper + * Price product mapper (PRODUCT / REDLINE / SALES) */ public interface PrcPriceSetProductMapper { @@ -23,19 +23,21 @@ public interface PrcPriceSetProductMapper public int deleteByPsvIdAndCode(@Param("psvId") Long psvId, @Param("productCode") String productCode); - /** Copy active library products into this version (ignore duplicates). */ - public int insertFromLibrary(@Param("priceSetId") Long priceSetId, @Param("psvId") Long psvId); + /** Hard delete all product rows under a version. */ + public int physicalDeleteByPsvId(@Param("psvId") Long psvId); - /** Preview: active library products without writing set-product rows. */ + /** Copy active PRODUCT catalog into this SALES version. */ + public int insertFromLibrary(@Param("psvId") Long psvId); + + /** Preview: active PRODUCT catalog products. */ public List selectFromLibrary(); - /** Copy set products from one version to another. */ public int copyFromVersion(@Param("srcPsvId") Long srcPsvId, @Param("newPsvId") Long newPsvId); - /** Bind detail.psp_id from set product by psv + product_code. */ public int bindDetailPspByPsvId(@Param("psvId") Long psvId); - /** Stamp Excel attachment id on set products under this version (optional code filter). */ - public int updatePefIdByPsvId(@Param("psvId") Long psvId, @Param("pefId") Long pefId, - @Param("productCodes") List productCodes); + public int updatePriceTypeByPsvId(@Param("psvId") Long psvId, @Param("priceType") String priceType); + + /** Copy version effective_time and status onto catalog product rows. */ + public int syncEffectByPriceType(@Param("priceType") String priceType); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetVersionMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetVersionMapper.java index f521e181..bd3b02e8 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetVersionMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetVersionMapper.java @@ -6,7 +6,7 @@ import org.apache.ibatis.annotations.Param; import com.bms.price.domain.PrcPriceSetVersion; /** - * Price set version + * Price version (PRODUCT / REDLINE / SALES) */ public interface PrcPriceSetVersionMapper { @@ -14,8 +14,16 @@ public interface PrcPriceSetVersionMapper public List selectPrcPriceSetVersionList(PrcPriceSetVersion version); + /** Catalog versions (price_set_id null) with product/detail counts. */ + public List selectCatalogVersionList(PrcPriceSetVersion version); + + /** Max seq for SALES by price_set_id */ public Integer selectMaxVersionSeq(@Param("priceSetId") Long priceSetId); + /** Max seq for catalog type (price_set_id null) or SALES when priceSetId set */ + public Integer selectMaxVersionSeqByType(@Param("priceType") String priceType, + @Param("priceSetId") Long priceSetId); + public int insertPrcPriceSetVersion(PrcPriceSetVersion version); public int updatePrcPriceSetVersion(PrcPriceSetVersion version); @@ -27,10 +35,18 @@ public interface PrcPriceSetVersionMapper public List selectPriceSetIds(); /** - * Version effective at asOf: effective_time <= asOf and (expire_time null or > asOf). - * Prefer latest effective_time. + * SALES version effective at asOf for a price set. */ public PrcPriceSetVersion selectActiveByPriceSetAt( @Param("priceSetId") Long priceSetId, @Param("asOf") Date asOf); + + /** + * Catalog (PRODUCT/REDLINE) version effective at asOf. + */ + public PrcPriceSetVersion selectActiveByTypeAt( + @Param("priceType") String priceType, + @Param("asOf") Date asOf); + + public int refreshCatalogStatusByDates(@Param("priceType") String priceType); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcSurchargeRelMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcSurchargeRelMapper.java index 70afde5b..c0300f41 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcSurchargeRelMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcSurchargeRelMapper.java @@ -35,6 +35,9 @@ public interface PrcSurchargeRelMapper @Param("productIds") List productIds, @Param("priceType") String priceType); + /** Hard delete surcharge rows linked to products/details under a version. */ + public int physicalDeleteByPsvId(@Param("psvId") Long psvId); + /** 附加费版本化编辑后,把产品关联从旧记录重指向新记录 */ public int rebindRelScId(@Param("oldScId") Long oldScId, @Param("newScId") Long newScId, @Param("operator") String operator); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java index 80acfa43..940b7283 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java @@ -6,6 +6,7 @@ import org.springframework.web.multipart.MultipartFile; import com.bms.price.domain.BmsProduct; import com.bms.price.domain.BmsProductCountry; import com.bms.price.domain.BmsProductImportTask; +import com.bms.price.domain.CatalogPreviewApplyRequest; /** * Product list service @@ -61,4 +62,42 @@ public interface IBmsProductService */ public java.util.Map> parseCountryQuoteExcelByProduct(byte[] bytes, String filename, String countryCode) throws Exception; + + /** Parse multi-country workbook; return product list only (no DB write). */ + public java.util.Map previewImportCatalog(byte[] bytes, String filename) throws Exception; + + /** Parse single-country workbook; return product list only (no DB write). */ + public java.util.Map previewImportCountryCatalog(byte[] bytes, String filename, String countryCode) + throws Exception; + + /** Submit async multi-country parse-only preview; poll getImportTask for result. */ + public String submitPreviewImportAsync(org.springframework.web.multipart.MultipartFile file) throws Exception; + + /** One task parses up to 5 workbooks and merges by product code. */ + public String submitPreviewImportAsync(java.util.List files) + throws Exception; + + /** Submit async single-country parse-only preview; poll getImportTask for result. */ + public String submitPreviewImportCountryAsync(org.springframework.web.multipart.MultipartFile file, String countryCode) + throws Exception; + + /** One task parses up to 5 single-country workbooks and merges by product code. */ + public String submitPreviewImportCountryAsync(java.util.List files, + String countryCode) throws Exception; + + /** Later workbook overwrites the same product code. */ + public java.util.Map mergeCatalogPreview(java.util.Map base, + java.util.Map incoming); + + /** + * Persist create-page preview into draft/pending version (no activate, no effectiveDate required). + * Replaces only countries present in detailsByProductCode for each product. + */ + public int applyCatalogPreview(CatalogPreviewApplyRequest request); + + /** One product in its own transaction, for parallel apply. */ + public int applyOneCatalogProduct(Long psvId, String code, BmsProduct meta, java.util.List details); + + /** Returns taskId; poll import task. Products are saved on a worker pool. */ + public String submitApplyCatalogPreviewAsync(CatalogPreviewApplyRequest request); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsRedlineProductService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsRedlineProductService.java index e4a98756..553a373d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsRedlineProductService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsRedlineProductService.java @@ -6,6 +6,7 @@ import org.springframework.web.multipart.MultipartFile; import com.bms.price.domain.BmsRedlineProduct; import com.bms.price.domain.BmsRedlineProductCountry; import com.bms.price.domain.BmsRedlineProductImportTask; +import com.bms.price.domain.RedlineCatalogPreviewApplyRequest; /** * redline product list service @@ -54,4 +55,43 @@ public interface IBmsRedlineProductService public java.util.Map> parseQuoteExcelByProduct(MultipartFile file) throws Exception; public java.util.Map> parseQuoteExcelByProduct(byte[] bytes, String filename) throws Exception; + + /** Parse multi-country workbook; return product list only (no DB write). */ + public java.util.Map previewImportCatalog(byte[] bytes, String filename) throws Exception; + + /** Parse single-country workbook; return product list only (no DB write). */ + public java.util.Map previewImportCountryCatalog(byte[] bytes, String filename, String countryCode) + throws Exception; + + /** Submit async multi-country parse-only preview; poll getImportTask for result. */ + public String submitPreviewImportAsync(org.springframework.web.multipart.MultipartFile file) throws Exception; + + /** One task parses up to 5 workbooks and merges by product code. */ + public String submitPreviewImportAsync(java.util.List files) + throws Exception; + + /** Submit async single-country parse-only preview; poll getImportTask for result. */ + public String submitPreviewImportCountryAsync(org.springframework.web.multipart.MultipartFile file, String countryCode) + throws Exception; + + /** One task parses up to 5 single-country workbooks and merges by product code. */ + public String submitPreviewImportCountryAsync(java.util.List files, + String countryCode) throws Exception; + + /** Later workbook overwrites the same product code. */ + public java.util.Map mergeCatalogPreview(java.util.Map base, + java.util.Map incoming); + + /** + * Persist create-page preview into draft/pending version (no activate, no effectiveDate required). + * Replaces only countries present in detailsByProductCode for each product. + */ + public int applyCatalogPreview(RedlineCatalogPreviewApplyRequest request); + + /** One product in its own transaction, for parallel apply. */ + public int applyOneCatalogProduct(Long psvId, String code, BmsRedlineProduct meta, + java.util.List details); + + /** Returns taskId; poll import task. Products are saved on a worker pool. */ + public String submitApplyCatalogPreviewAsync(RedlineCatalogPreviewApplyRequest request); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceExcelFileService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceExcelFileService.java index ffb6fa2d..28e47a80 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceExcelFileService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceExcelFileService.java @@ -11,20 +11,19 @@ public interface IPrcPriceExcelFileService { public PrcPriceExcelFile selectPrcPriceExcelFileById(Long pefId); - /** Import Excel attachments linked to this product_code via product.pef_id */ - public List selectByProductCode(String productCode); - - List selectByRedlineProductCode(String productCode); - - /** Import excel files for a price-set version via prc_price_set_product.psv_id. */ + /** Excel files bound to a price version */ List selectByPsvId(Long psvId); /** * Persist uploaded Excel under upload/price/excel and insert metadata row. * * @param excelType PRODUCT / REDLINE / SALES + * @param psvId version id (nullable for draft preview; bind later) * @return inserted row with pefId */ public PrcPriceExcelFile saveImportExcel(String excelType, Date effectiveTime, byte[] bytes, - String filename, String operName) throws Exception; + String filename, String operName, Long psvId) throws Exception; + + /** Bind an existing attachment row to a version */ + public int bindPsvId(Long pefId, Long psvId); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java index 534a5b89..08a19764 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java @@ -128,6 +128,15 @@ public interface IPrcPriceSetService public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception; + /** One task imports up to 5 workbooks in order. */ + public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, List files, String operName, + boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception; + + /** Parse up to 5 workbooks in one call. Later file wins on the same product+country scope. */ + public Map previewImportDetailPricesMany(List bytesList, List names, + boolean updateOnly, List existingDetails, boolean replaceByProductCountry, String countryCode) + throws Exception; + /** * Sync worker: parse bytes and upsert FIXED detail prices matched by * product + country + zone(if any) + weight band (insert when missing unless updateOnly). diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java index 885e0d5e..db007b9f 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java @@ -37,6 +37,7 @@ import com.bms.common.utils.uuid.IdUtils; import com.bms.price.domain.BmsProduct; import com.bms.price.domain.BmsProductCountry; import com.bms.price.domain.BmsProductImportTask; +import com.bms.price.domain.CatalogPreviewApplyRequest; import com.bms.price.domain.PrcZone; import com.bms.price.mapper.BmsProductCountryMapper; import com.bms.price.mapper.BmsProductMapper; @@ -45,6 +46,9 @@ import com.bms.price.domain.PrcPriceExcelFile; import com.bms.price.service.IBmsProductService; import com.bms.price.service.IPrcPriceExcelFileService; import com.bms.price.service.IPrcSurchargeRelService; +import com.bms.price.domain.PriceType; +import com.bms.price.domain.PrcPriceSetVersion; +import com.bms.price.service.support.CatalogVersionSupport; import com.bms.price.task.BmsProductImportAsyncRunner; import com.bms.price.util.ZoneCodeAbbrevs; import com.bms.system.domain.BmsRegion; @@ -74,6 +78,9 @@ public class BmsProductServiceImpl implements IBmsProductService "(\\d+(?:\\.\\d+)?)\\s*[<\\u2264]=?\\s*W\\s*[<\\u2264]=?\\s*(\\d+(?:\\.\\d+)?)", Pattern.CASE_INSENSITIVE); + @Autowired + private CatalogVersionSupport catalogVersionSupport; + @Autowired private BmsProductMapper productMapper; @@ -131,9 +138,12 @@ public class BmsProductServiceImpl implements IBmsProductService } applyProductStatus(product); assertCodeDateUnique(product, null); + product.setPriceType(PriceType.PRODUCT); + Long psvId = catalogVersionSupport.requirePsvId(PriceType.PRODUCT, product.getPsvId(), product.getEffectiveDate()); + product.setPsvId(psvId); int rows = productMapper.insertBmsProduct(product); productMapper.expireOpenByProductCode(product.getProductCode(), product.getEffectiveDate(), product.getProductId()); - productMapper.refreshStatusByDates(); + catalogVersionSupport.activateVersion(psvId, product.getEffectiveDate()); return rows; } @@ -297,7 +307,7 @@ public class BmsProductServiceImpl implements IBmsProductService Date effectiveDate) throws Exception { validateImportFile(file); - Date eff = requireImportEffectiveDate(effectiveDate); + Date eff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); byte[] bytes = file.getBytes(); String filename = file.getOriginalFilename(); String taskId = IdUtils.fastSimpleUUID(); @@ -321,7 +331,7 @@ public class BmsProductServiceImpl implements IBmsProductService String taskId, Date effectiveDate) throws Exception { validateImportBytes(bytes, filename); - Date forcedEff = requireImportEffectiveDate(effectiveDate); + Date forcedEff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); reportImportProgress(taskId, 10, "parsing excel"); try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) @@ -354,9 +364,11 @@ public class BmsProductServiceImpl implements IBmsProductService assertImportProductsNoDupCountryWeight(workbook, products, regionByNameCn, euRegions, zoneByCountryAndCode); + Long importPsvId = forcedEff == null + ? catalogVersionSupport.ensureDraftVersion(PriceType.PRODUCT, null, false).getPsvId() + : catalogVersionSupport.requirePsvId(PriceType.PRODUCT, null, forcedEff); PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel( - PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName); - Long pefId = excelFile != null ? excelFile.getPefId() : null; + PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName, importPsvId); int successNum = 0; int skipNum = 0; @@ -376,7 +388,10 @@ public class BmsProductServiceImpl implements IBmsProductService try { product.setEffectiveDate(forcedEff); - product.setPefId(pefId); + if (importPsvId != null) + { + product.setPsvId(importPsvId); + } int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries, forcedEff); transactionManager.commit(tx); @@ -432,7 +447,7 @@ public class BmsProductServiceImpl implements IBmsProductService { throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); } - Date eff = requireImportEffectiveDate(effectiveDate); + Date eff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); byte[] bytes = file.getBytes(); String filename = file.getOriginalFilename(); String taskId = IdUtils.fastSimpleUUID(); @@ -446,7 +461,7 @@ public class BmsProductServiceImpl implements IBmsProductService String taskId, Date effectiveDate) throws Exception { validateImportBytes(bytes, filename); - Date forcedEff = requireImportEffectiveDate(effectiveDate); + Date forcedEff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); if (StringUtils.isEmpty(countryCode)) { throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); @@ -477,9 +492,11 @@ public class BmsProductServiceImpl implements IBmsProductService // Pre-check all products: reject whole import before any write if any is earlier than prev version assertImportProductsEffectiveNotBeforePrev(products, forcedEff); + Long importPsvId = forcedEff == null + ? catalogVersionSupport.ensureDraftVersion(PriceType.PRODUCT, null, false).getPsvId() + : catalogVersionSupport.requirePsvId(PriceType.PRODUCT, null, forcedEff); PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel( - PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName); - Long pefId = excelFile != null ? excelFile.getPefId() : null; + PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName, importPsvId); int successNum = 0; int skipNum = 0; @@ -508,7 +525,10 @@ public class BmsProductServiceImpl implements IBmsProductService product.setDestRegion(countryLabel); product.setSourceSheet(block.sheetName); product.setEffectiveDate(forcedEff); - product.setPefId(pefId); + if (importPsvId != null) + { + product.setPsvId(importPsvId); + } if (StringUtils.isEmpty(product.getTaxMode())) { product.setTaxMode(resolveTaxMode(product.getProductName())); @@ -608,6 +628,12 @@ public class BmsProductServiceImpl implements IBmsProductService @Override public Map> parseQuoteExcelByProduct(byte[] bytes, String filename) throws Exception + { + return readQuoteWorkbook(bytes, filename).byCode; + } + + /** One workbook open; each price sheet is parsed once. */ + private QuoteWorkbook readQuoteWorkbook(byte[] bytes, String filename) throws Exception { if (bytes == null || bytes.length == 0) { @@ -630,13 +656,7 @@ public class BmsProductServiceImpl implements IBmsProductService { throw new ServiceException(MessageUtils.message("price.product.import.noData")); } - Map regionByNameCn = loadRegionByNameCn(); - List euRegions = loadEuRegions(regionByNameCn); - Map zoneByCountryAndCode = loadZoneByCountryAndCode(); - // Fail whole import when any price sheet has duplicate country+weight (incl. unmatched sheets). - assertAllPriceSheetsNoDupCountryWeight(workbook, regionByNameCn, euRegions, zoneByCountryAndCode); - Set unmatchedCountries = new HashSet<>(); - Map> out = new LinkedHashMap<>(); + Map> ownersBySheet = new LinkedHashMap>(); for (BmsProduct product : products) { if (product == null || StringUtils.isEmpty(product.getProductCode())) @@ -648,24 +668,89 @@ public class BmsProductServiceImpl implements IBmsProductService { continue; } - List rows = parseProductSheet(productSheet, product, - regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries); - if (rows == null || rows.isEmpty()) + List owners = ownersBySheet.get(productSheet.getSheetName()); + if (owners == null) + { + owners = new ArrayList(); + ownersBySheet.put(productSheet.getSheetName(), owners); + } + owners.add(product); + } + Map regionByNameCn = loadRegionByNameCn(); + List euRegions = loadEuRegions(regionByNameCn); + Map zoneByCountryAndCode = loadZoneByCountryAndCode(); + Set unmatchedCountries = new HashSet(); + Map> out = new LinkedHashMap>(); + List tips = new ArrayList(); + for (int i = 0; i < workbook.getNumberOfSheets(); i++) + { + String sheetName = workbook.getSheetName(i); + if (shouldSkipCountryPriceSheet(sheetName)) { continue; } - assertNoDuplicateCountryWeightRows(product, rows); - String code = product.getProductCode().trim().toUpperCase(Locale.ROOT); - out.put(code, rows); + Sheet sheet = workbook.getSheetAt(i); + if (sheet == null || findHeaderCell(sheet, "\u56fd\u5bb6") == null) + { + continue; + } + List owners = ownersBySheet.get(sheetName); + BmsProduct parseAs = (owners == null || owners.isEmpty()) ? null : owners.get(0); + if (parseAs == null) + { + parseAs = new BmsProduct(); + parseAs.setProductCode(sheetName); + parseAs.setProductName(sheetName); + } + List rows = parseProductSheet(sheet, parseAs, + regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries); + String tip = buildDuplicateCountryWeightTip(parseAs, rows); + if (StringUtils.isNotEmpty(tip)) + { + tips.add(tip); + } + if (owners == null || owners.isEmpty() || rows == null || rows.isEmpty()) + { + continue; + } + for (int p = 0; p < owners.size(); p++) + { + BmsProduct owner = owners.get(p); + List use = rows; + if (p > 0) + { + use = parseProductSheet(sheet, owner, + regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries); + } + if (use == null || use.isEmpty()) + { + continue; + } + out.put(owner.getProductCode().trim().toUpperCase(Locale.ROOT), use); + } + } + if (!tips.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.dupCountry", + String.join("
", tips))); } if (out.isEmpty()) { throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); } - return out; + QuoteWorkbook parsed = new QuoteWorkbook(); + parsed.products = products; + parsed.byCode = out; + return parsed; } } + private static final class QuoteWorkbook + { + private List products; + private Map> byCode; + } + @Override public Map> parseCountryQuoteExcelByProduct(byte[] bytes, String filename, String countryCode) throws Exception @@ -727,6 +812,415 @@ public class BmsProductServiceImpl implements IBmsProductService } } + @Override + public Map previewImportCatalog(byte[] bytes, String filename) throws Exception + { + QuoteWorkbook parsed = readQuoteWorkbook(bytes, filename); + return buildCatalogPreview(parsed.products, parsed.byCode); + } + + @Override + public Map previewImportCountryCatalog(byte[] bytes, String filename, String countryCode) + throws Exception + { + Map> byCode = parseCountryQuoteExcelByProduct(bytes, filename, countryCode); + String countryLabel = resolveCountryLabel(countryCode); + try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) + { + Sheet catalog = workbook.getSheet(CATALOG_SHEET); + List catalogProducts = dedupeCatalogProducts(parseCountryCatalog(catalog)); + if (StringUtils.isNotEmpty(countryLabel) && catalogProducts != null) + { + for (BmsProduct p : catalogProducts) + { + if (p != null) + { + p.setDestRegion(countryLabel); + } + } + } + return buildCatalogPreview(catalogProducts, byCode); + } + } + + @Override + public String submitPreviewImportAsync(MultipartFile file) throws Exception + { + return submitPreviewImportAsync(java.util.Collections.singletonList(file)); + } + + @Override + public String submitPreviewImportAsync(List files) throws Exception + { + List bytesList = new ArrayList(); + List names = new ArrayList(); + readPreviewFiles(files, bytesList, names); + String taskId = IdUtils.fastSimpleUUID(); + importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null); + importAsyncRunner.runPreviewAll(taskId, bytesList, names); + return taskId; + } + + @Override + public String submitPreviewImportCountryAsync(MultipartFile file, String countryCode) throws Exception + { + return submitPreviewImportCountryAsync(java.util.Collections.singletonList(file), countryCode); + } + + @Override + public String submitPreviewImportCountryAsync(List files, String countryCode) throws Exception + { + if (StringUtils.isEmpty(countryCode)) + { + throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); + } + List bytesList = new ArrayList(); + List names = new ArrayList(); + readPreviewFiles(files, bytesList, names); + String taskId = IdUtils.fastSimpleUUID(); + importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null); + importAsyncRunner.runPreviewCountry(taskId, bytesList, names, countryCode.trim()); + return taskId; + } + + private void readPreviewFiles(List files, List bytesList, List names) throws Exception + { + if (files == null || files.isEmpty()) + { + throw new ServiceException("file required"); + } + if (files.size() > com.bms.price.service.support.UploadFileBatch.MAX_FILES) + { + throw new ServiceException("\u5355\u6b21\u6700\u591a\u5bfc\u5165 5 \u4e2a\u6587\u4ef6"); + } + for (MultipartFile file : files) + { + validateImportFile(file); + bytesList.add(file.getBytes()); + names.add(file.getOriginalFilename()); + } + } + + @Override + public Map mergeCatalogPreview(Map base, Map incoming) + { + if (base == null || base.isEmpty()) + { + return incoming; + } + if (incoming == null || incoming.isEmpty()) + { + return base; + } + Map products = new LinkedHashMap(); + putPreviewProducts(products, castProductList(base.get("products"))); + putPreviewProducts(products, castProductList(incoming.get("products"))); + Map> details = new LinkedHashMap>(); + putPreviewDetails(details, castDetailMap(base.get("detailsByProductCode"))); + putPreviewDetails(details, castDetailMap(incoming.get("detailsByProductCode"))); + int detailCount = 0; + for (List rows : details.values()) + { + if (rows != null) + { + detailCount += rows.size(); + } + } + Map out = new HashMap(); + out.put("products", new ArrayList(products.values())); + out.put("productCount", Integer.valueOf(products.size())); + out.put("detailCount", Integer.valueOf(detailCount)); + out.put("detailsByProductCode", details); + return out; + } + + @SuppressWarnings("unchecked") + private static List castProductList(Object value) + { + if (value instanceof List) + { + return (List) value; + } + return null; + } + + @SuppressWarnings("unchecked") + private static Map> castDetailMap(Object value) + { + if (value instanceof Map) + { + return (Map>) value; + } + return null; + } + + private static void putPreviewProducts(Map products, List list) + { + if (list == null) + { + return; + } + for (BmsProduct p : list) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + products.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + + private static void putPreviewDetails(Map> details, + Map> incoming) + { + if (incoming == null) + { + return; + } + for (Map.Entry> e : incoming.entrySet()) + { + if (e == null || StringUtils.isEmpty(e.getKey()) || e.getValue() == null) + { + continue; + } + details.put(e.getKey().trim().toUpperCase(Locale.ROOT), e.getValue()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int applyCatalogPreview(CatalogPreviewApplyRequest request) + { + if (request == null || request.getPsvId() == null) + { + throw new ServiceException("psvId required"); + } + Long psvId = request.getPsvId(); + catalogVersionSupport.requireEditableCatalogVersion(psvId, PriceType.PRODUCT); + Map> byCode = request.getDetailsByProductCode(); + if (byCode == null || byCode.isEmpty()) + { + return 0; + } + Map productMeta = new LinkedHashMap(); + if (request.getProducts() != null) + { + for (BmsProduct p : request.getProducts()) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + productMeta.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + int detailRows = 0; + for (Map.Entry> e : byCode.entrySet()) + { + if (e == null || StringUtils.isEmpty(e.getKey()) || e.getValue() == null || e.getValue().isEmpty()) + { + continue; + } + String code = e.getKey().trim().toUpperCase(Locale.ROOT); + detailRows += applyOneCatalogProduct(psvId, code, productMeta.get(code), e.getValue()); + } + return detailRows; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int applyOneCatalogProduct(Long psvId, String code, BmsProduct meta, List details) + { + if (psvId == null || StringUtils.isEmpty(code) || details == null || details.isEmpty()) + { + return 0; + } + String key = code.trim().toUpperCase(Locale.ROOT); + PrcPriceSetVersion ver = catalogVersionSupport.findVersion(psvId); + Date eff = ver == null ? null : ver.getEffectiveTime(); + Date exp = ver == null ? null : ver.getExpireTime(); + String st = ver == null || StringUtils.isEmpty(ver.getStatus()) ? "0" : ver.getStatus(); + String destRegion = resolveDestRegion(meta == null ? null : meta.getDestRegion(), details); + BmsProduct existing = productMapper.selectByCodeAndPsvId(key, psvId); + if (existing == null) + { + BmsProduct row = new BmsProduct(); + row.setPsvId(psvId); + row.setPriceType(PriceType.PRODUCT); + row.setProductCode(meta != null && StringUtils.isNotEmpty(meta.getProductCode()) + ? meta.getProductCode().trim() : key); + row.setProductName(meta == null ? null : meta.getProductName()); + row.setServiceLevel(meta == null ? null : meta.getServiceLevel()); + row.setCargoAttr(meta == null ? null : meta.getCargoAttr()); + row.setDestRegion(destRegion); + row.setTaxMode(meta != null && StringUtils.isNotEmpty(meta.getTaxMode()) + ? meta.getTaxMode() : "TAX_INCLUDED"); + row.setEffectiveDate(eff); + row.setExpireDate(exp); + row.setStatus(st); + row.setIsDel(BmsProduct.IS_DEL_NO); + productMapper.insertBmsProduct(row); + existing = row; + } + else + { + if (meta != null) + { + if (StringUtils.isNotEmpty(meta.getProductName())) + { + existing.setProductName(meta.getProductName()); + } + if (StringUtils.isNotEmpty(meta.getServiceLevel())) + { + existing.setServiceLevel(meta.getServiceLevel()); + } + if (StringUtils.isNotEmpty(meta.getCargoAttr())) + { + existing.setCargoAttr(meta.getCargoAttr()); + } + } + if (StringUtils.isNotEmpty(destRegion)) + { + existing.setDestRegion(destRegion); + } + existing.setEffectiveDate(eff); + existing.setExpireDate(exp); + existing.setStatus(st); + productMapper.updateBmsProduct(existing); + } + + Set replaced = new HashSet(); + List toInsert = new ArrayList(); + for (BmsProductCountry d : details) + { + if (d == null) + { + continue; + } + String ckey = countryIdentityKey(d); + if (StringUtils.isNotEmpty(ckey) && !replaced.contains(ckey)) + { + countryMapper.physicalDeleteByPspIdAndCountry(existing.getProductId(), d.getCountryCode(), + d.getCountryName()); + replaced.add(ckey); + } + d.setDetailId(null); + d.setProductId(existing.getProductId()); + d.setPsvId(psvId); + d.setProductCode(existing.getProductCode()); + if (StringUtils.isEmpty(d.getProductName())) + { + d.setProductName(existing.getProductName()); + } + d.setEffectiveDate(eff); + d.setExpireDate(exp); + d.setStatus(st); + d.setIsDel(BmsProduct.IS_DEL_NO); + toInsert.add(d); + } + insertProductCountryChunks(toInsert); + return toInsert.size(); + } + + @Override + public String submitApplyCatalogPreviewAsync(CatalogPreviewApplyRequest request) + { + if (request == null || request.getPsvId() == null) + { + throw new ServiceException("psvId required"); + } + catalogVersionSupport.requireEditableCatalogVersion(request.getPsvId(), PriceType.PRODUCT); + String taskId = IdUtils.fastSimpleUUID(); + importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null); + importAsyncRunner.runApplyPreview(taskId, request); + return taskId; + } + + private void insertProductCountryChunks(List rows) + { + if (rows == null || rows.isEmpty()) + { + return; + } + int size = 200; + for (int i = 0; i < rows.size(); i += size) + { + int end = Math.min(i + size, rows.size()); + countryMapper.insertBmsProductCountryBatch(new ArrayList(rows.subList(i, end))); + } + } + + private String resolveCountryLabel(String countryCode) + { + if (StringUtils.isEmpty(countryCode)) + { + return null; + } + BmsRegion region = regionMapper.selectRegionByCode2(countryCode.trim().toUpperCase(Locale.ROOT)); + if (region == null) + { + return countryCode.trim().toUpperCase(Locale.ROOT); + } + return StringUtils.isNotEmpty(region.getCountryNameCn()) ? region.getCountryNameCn() + : region.getCountryCode2(); + } + + private String resolveDestRegion(String preferred, List details) + { + if (StringUtils.isNotEmpty(preferred)) + { + return preferred.trim(); + } + if (details == null) + { + return null; + } + for (BmsProductCountry d : details) + { + if (d != null && StringUtils.isNotEmpty(d.getCountryName())) + { + return d.getCountryName().trim(); + } + } + return null; + } + + private Map buildCatalogPreview(List catalogProducts, + Map> byCode) + { + List products = new ArrayList(); + int detailCount = 0; + if (catalogProducts != null) + { + for (BmsProduct src : catalogProducts) + { + if (src == null || StringUtils.isEmpty(src.getProductCode())) + { + continue; + } + String code = src.getProductCode().trim().toUpperCase(Locale.ROOT); + List details = byCode == null ? null : byCode.get(code); + if (details == null || details.isEmpty()) + { + continue; + } + BmsProduct row = new BmsProduct(); + row.setProductCode(src.getProductCode()); + row.setProductName(src.getProductName()); + row.setServiceLevel(src.getServiceLevel()); + row.setCargoAttr(src.getCargoAttr()); + row.setDestRegion(src.getDestRegion()); + products.add(row); + detailCount += details.size(); + } + } + Map out = new HashMap(); + out.put("products", products); + out.put("productCount", Integer.valueOf(products.size())); + out.put("detailCount", Integer.valueOf(detailCount)); + out.put("detailsByProductCode", byCode == null ? new LinkedHashMap>() : byCode); + return out; + } /** @return int[]{added, skipped, countryRows} */ private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName, @@ -753,22 +1247,28 @@ public class BmsProductServiceImpl implements IBmsProductService } assertNoDuplicateCountryWeightRows(product, countries); // UI-selected effective date wins over Excel; previous open product/country rows expire at this date - Date newEff = requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate - : product.getEffectiveDate()); - applyForcedEffectiveDate(product, countries, newEff); - assertImportEffectiveNotBeforePrev(product, newEff); + Date newEff = forcedEffectiveDate == null && product.getEffectiveDate() == null + ? null + : requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate + : product.getEffectiveDate()); + if (newEff != null) + { + applyForcedEffectiveDate(product, countries, newEff); + assertImportEffectiveNotBeforePrev(product, newEff); + } BmsProduct existing = productMapper.selectBmsProductByCodeAndDate( product.getProductCode(), newEff); if (existing != null) { product.setProductId(existing.getProductId()); - patchProductPefId(product, operName); int countryRows = saveCountries(product, countries); return new int[] { 0, 1, countryRows }; } applyProductStatus(product); + product.setPriceType(PriceType.PRODUCT); + product.setPsvId(catalogVersionSupport.requirePsvId(PriceType.PRODUCT, product.getPsvId(), newEff)); try { productMapper.insertBmsProduct(product); @@ -781,26 +1281,22 @@ public class BmsProductServiceImpl implements IBmsProductService } throw e; } - productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); - productMapper.refreshStatusByDates(); + if (newEff != null) + { + productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); + if (product.getPsvId() != null) + { + catalogVersionSupport.activateVersion(product.getPsvId(), newEff); + } + } + else + { + productMapper.refreshStatusByDates(); + } int countryRows = saveCountries(product, countries); return new int[] { 1, 0, countryRows }; } - private void patchProductPefId(BmsProduct product, String operName) - { - if (product == null || product.getProductId() == null || product.getPefId() == null) - { - return; - } - BmsProduct pefPatch = new BmsProduct(); - pefPatch.setProductId(product.getProductId()); - pefPatch.setPefId(product.getPefId()); - pefPatch.setUpdateBy(operName); - productMapper.updateBmsProduct(pefPatch); - } - - /** * Save country rows by effective_date. * For each product_code + country in the Excel: @@ -842,6 +1338,8 @@ public class BmsProductServiceImpl implements IBmsProductService { countryMapper.expireOpenByProductCodeAndCountry( productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff); + countryMapper.deleteSurchargeRelByProductCodeAndCountryOnDate( + productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff); countryMapper.deleteOpenByProductCodeAndCountryOnDate( productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff); replacedCountries.add(countryKey); @@ -2127,6 +2625,10 @@ public class BmsProductServiceImpl implements IBmsProductService /** Batch pre-check: fail whole import before any DB write. */ private void assertImportProductsEffectiveNotBeforePrev(List products, Date forcedEff) { + if (forcedEff == null) + { + return; + } Date newEff = requireImportEffectiveDate(forcedEff); if (products == null || products.isEmpty()) { @@ -2346,18 +2848,22 @@ public class BmsProductServiceImpl implements IBmsProductService { product.setSourceSheet(product.getProductName()); } - Date newEff = requireImportEffectiveDate(product.getEffectiveDate()); - applyForcedEffectiveDate(product, countries, newEff); - assertImportEffectiveNotBeforePrev(product, newEff); + Date newEff = product.getEffectiveDate() == null ? null : requireImportEffectiveDate(product.getEffectiveDate()); + if (newEff != null) + { + applyForcedEffectiveDate(product, countries, newEff); + assertImportEffectiveNotBeforePrev(product, newEff); + } BmsProduct existing = productMapper.selectBmsProductByCodeAndDate(product.getProductCode(), newEff); if (existing != null) { product.setProductId(existing.getProductId()); - patchProductPefId(product, operName); int countryRows = saveCountries(product, countries); return new int[] { 0, 1, countryRows }; } applyProductStatus(product); + product.setPriceType(PriceType.PRODUCT); + product.setPsvId(catalogVersionSupport.requirePsvId(PriceType.PRODUCT, product.getPsvId(), newEff)); try { productMapper.insertBmsProduct(product); @@ -2370,8 +2876,18 @@ public class BmsProductServiceImpl implements IBmsProductService } throw e; } - productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); - productMapper.refreshStatusByDates(); + if (newEff != null) + { + productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); + if (product.getPsvId() != null) + { + catalogVersionSupport.activateVersion(product.getPsvId(), newEff); + } + } + else + { + productMapper.refreshStatusByDates(); + } int countryRows = saveCountries(product, countries); return new int[] { 1, 0, countryRows }; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java index 6a951a0f..6bef77d0 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java @@ -37,6 +37,7 @@ import com.bms.common.utils.uuid.IdUtils; import com.bms.price.domain.BmsRedlineProduct; import com.bms.price.domain.BmsRedlineProductCountry; import com.bms.price.domain.BmsRedlineProductImportTask; +import com.bms.price.domain.RedlineCatalogPreviewApplyRequest; import com.bms.price.domain.PrcZone; import com.bms.price.domain.PrcSurchargeRel; import com.bms.price.mapper.BmsRedlineProductCountryMapper; @@ -46,6 +47,9 @@ import com.bms.price.mapper.PrcZoneMapper; import com.bms.price.domain.PrcPriceExcelFile; import com.bms.price.service.IBmsRedlineProductService; import com.bms.price.service.IPrcPriceExcelFileService; +import com.bms.price.domain.PriceType; +import com.bms.price.domain.PrcPriceSetVersion; +import com.bms.price.service.support.CatalogVersionSupport; import com.bms.price.task.BmsRedlineProductImportAsyncRunner; import com.bms.price.util.ZoneCodeAbbrevs; import com.bms.system.domain.BmsRegion; @@ -75,6 +79,9 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService "(\\d+(?:\\.\\d+)?)\\s*[<\\u2264]=?\\s*W\\s*[<\\u2264]=?\\s*(\\d+(?:\\.\\d+)?)", Pattern.CASE_INSENSITIVE); + @Autowired + private CatalogVersionSupport catalogVersionSupport; + @Autowired private BmsRedlineProductMapper productMapper; @@ -132,9 +139,12 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService } applyProductStatus(product); assertCodeDateUnique(product, null); + product.setPriceType(PriceType.REDLINE); + Long psvId = catalogVersionSupport.requirePsvId(PriceType.REDLINE, product.getPsvId(), product.getEffectiveDate()); + product.setPsvId(psvId); int rows = productMapper.insertBmsRedlineProduct(product); productMapper.expireOpenByProductCode(product.getProductCode(), product.getEffectiveDate(), product.getProductId()); - productMapper.refreshStatusByDates(); + catalogVersionSupport.activateVersion(psvId, product.getEffectiveDate()); return rows; } @@ -290,7 +300,7 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService Date effectiveDate) throws Exception { validateImportFile(file); - Date eff = requireImportEffectiveDate(effectiveDate); + Date eff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); byte[] bytes = file.getBytes(); String filename = file.getOriginalFilename(); String taskId = IdUtils.fastSimpleUUID(); @@ -314,7 +324,7 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService String taskId, Date effectiveDate) throws Exception { validateImportBytes(bytes, filename); - Date forcedEff = requireImportEffectiveDate(effectiveDate); + Date forcedEff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); reportImportProgress(taskId, 10, "parsing excel"); try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) @@ -346,9 +356,11 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService assertImportProductsNoDupCountryWeight(workbook, products, regionByNameCn, euRegions, zoneByCountryAndCode); + Long importPsvId = forcedEff == null + ? catalogVersionSupport.ensureDraftVersion(PriceType.REDLINE, null, false).getPsvId() + : catalogVersionSupport.requirePsvId(PriceType.REDLINE, null, forcedEff); PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel( - PrcPriceExcelFile.TYPE_REDLINE, forcedEff, bytes, filename, operName); - Long pefId = excelFile != null ? excelFile.getPefId() : null; + PrcPriceExcelFile.TYPE_REDLINE, forcedEff, bytes, filename, operName, importPsvId); int successNum = 0; int skipNum = 0; @@ -368,7 +380,10 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService try { product.setEffectiveDate(forcedEff); - product.setPefId(pefId); + if (importPsvId != null) + { + product.setPsvId(importPsvId); + } int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries, forcedEff); transactionManager.commit(tx); @@ -424,7 +439,7 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService { throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); } - Date eff = requireImportEffectiveDate(effectiveDate); + Date eff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); byte[] bytes = file.getBytes(); String filename = file.getOriginalFilename(); String taskId = IdUtils.fastSimpleUUID(); @@ -438,7 +453,7 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService String taskId, Date effectiveDate) throws Exception { validateImportBytes(bytes, filename); - Date forcedEff = requireImportEffectiveDate(effectiveDate); + Date forcedEff = effectiveDate == null ? null : requireImportEffectiveDate(effectiveDate); if (StringUtils.isEmpty(countryCode)) { throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); @@ -469,9 +484,11 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService // Pre-check all products: reject whole import before any write if any is earlier than prev version assertImportProductsEffectiveNotBeforePrev(products, forcedEff); + Long importPsvId = forcedEff == null + ? catalogVersionSupport.ensureDraftVersion(PriceType.REDLINE, null, false).getPsvId() + : catalogVersionSupport.requirePsvId(PriceType.REDLINE, null, forcedEff); PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel( - PrcPriceExcelFile.TYPE_REDLINE, forcedEff, bytes, filename, operName); - Long pefId = excelFile != null ? excelFile.getPefId() : null; + PrcPriceExcelFile.TYPE_REDLINE, forcedEff, bytes, filename, operName, importPsvId); int successNum = 0; int skipNum = 0; @@ -500,7 +517,10 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService product.setDestRegion(countryLabel); product.setSourceSheet(block.sheetName); product.setEffectiveDate(forcedEff); - product.setPefId(pefId); + if (importPsvId != null) + { + product.setPsvId(importPsvId); + } if (StringUtils.isEmpty(product.getTaxMode())) { product.setTaxMode(resolveTaxMode(product.getProductName())); @@ -656,6 +676,454 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService } } + @Override + public Map previewImportCatalog(byte[] bytes, String filename) throws Exception + { + Map> byCode = parseQuoteExcelByProduct(bytes, filename); + try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) + { + Sheet catalog = workbook.getSheet(CATALOG_SHEET); + List catalogProducts = dedupeCatalogProducts(parseCatalog(catalog)); + return buildCatalogPreview(catalogProducts, byCode); + } + } + + @Override + public String submitPreviewImportAsync(MultipartFile file) throws Exception + { + return submitPreviewImportAsync(java.util.Collections.singletonList(file)); + } + + @Override + public String submitPreviewImportAsync(List files) throws Exception + { + List bytesList = new ArrayList(); + List names = new ArrayList(); + readPreviewFiles(files, bytesList, names); + String taskId = IdUtils.fastSimpleUUID(); + importAsyncRunner.put(taskId, BmsRedlineProductImportTask.STATUS_PENDING, 0, "queued", null); + importAsyncRunner.runPreviewAll(taskId, bytesList, names); + return taskId; + } + + @Override + public String submitPreviewImportCountryAsync(MultipartFile file, String countryCode) throws Exception + { + return submitPreviewImportCountryAsync(java.util.Collections.singletonList(file), countryCode); + } + + @Override + public String submitPreviewImportCountryAsync(List files, String countryCode) throws Exception + { + if (StringUtils.isEmpty(countryCode)) + { + throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); + } + List bytesList = new ArrayList(); + List names = new ArrayList(); + readPreviewFiles(files, bytesList, names); + String taskId = IdUtils.fastSimpleUUID(); + importAsyncRunner.put(taskId, BmsRedlineProductImportTask.STATUS_PENDING, 0, "queued", null); + importAsyncRunner.runPreviewCountry(taskId, bytesList, names, countryCode.trim()); + return taskId; + } + + private void readPreviewFiles(List files, List bytesList, List names) throws Exception + { + if (files == null || files.isEmpty()) + { + throw new ServiceException("file required"); + } + if (files.size() > com.bms.price.service.support.UploadFileBatch.MAX_FILES) + { + throw new ServiceException("\u5355\u6b21\u6700\u591a\u5bfc\u5165 5 \u4e2a\u6587\u4ef6"); + } + for (MultipartFile file : files) + { + validateImportFile(file); + bytesList.add(file.getBytes()); + names.add(file.getOriginalFilename()); + } + } + + @Override + public Map mergeCatalogPreview(Map base, Map incoming) + { + if (base == null || base.isEmpty()) + { + return incoming; + } + if (incoming == null || incoming.isEmpty()) + { + return base; + } + Map products = new LinkedHashMap(); + putPreviewProducts(products, castProductList(base.get("products"))); + putPreviewProducts(products, castProductList(incoming.get("products"))); + Map> details = new LinkedHashMap>(); + putPreviewDetails(details, castDetailMap(base.get("detailsByProductCode"))); + putPreviewDetails(details, castDetailMap(incoming.get("detailsByProductCode"))); + int detailCount = 0; + for (List rows : details.values()) + { + if (rows != null) + { + detailCount += rows.size(); + } + } + Map out = new HashMap(); + out.put("products", new ArrayList(products.values())); + out.put("productCount", Integer.valueOf(products.size())); + out.put("detailCount", Integer.valueOf(detailCount)); + out.put("detailsByProductCode", details); + return out; + } + + @SuppressWarnings("unchecked") + private static List castProductList(Object value) + { + if (value instanceof List) + { + return (List) value; + } + return null; + } + + @SuppressWarnings("unchecked") + private static Map> castDetailMap(Object value) + { + if (value instanceof Map) + { + return (Map>) value; + } + return null; + } + + private static void putPreviewProducts(Map products, List list) + { + if (list == null) + { + return; + } + for (BmsRedlineProduct p : list) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + products.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + + private static void putPreviewDetails(Map> details, + Map> incoming) + { + if (incoming == null) + { + return; + } + for (Map.Entry> e : incoming.entrySet()) + { + if (e == null || StringUtils.isEmpty(e.getKey()) || e.getValue() == null) + { + continue; + } + details.put(e.getKey().trim().toUpperCase(Locale.ROOT), e.getValue()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int applyCatalogPreview(RedlineCatalogPreviewApplyRequest request) + { + if (request == null || request.getPsvId() == null) + { + throw new ServiceException("psvId required"); + } + Long psvId = request.getPsvId(); + catalogVersionSupport.requireEditableCatalogVersion(psvId, PriceType.REDLINE); + Map> byCode = request.getDetailsByProductCode(); + if (byCode == null || byCode.isEmpty()) + { + return 0; + } + Map productMeta = new LinkedHashMap(); + if (request.getProducts() != null) + { + for (BmsRedlineProduct p : request.getProducts()) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + productMeta.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + int detailRows = 0; + for (Map.Entry> e : byCode.entrySet()) + { + if (e == null || StringUtils.isEmpty(e.getKey()) || e.getValue() == null || e.getValue().isEmpty()) + { + continue; + } + String code = e.getKey().trim().toUpperCase(Locale.ROOT); + detailRows += applyOneCatalogProduct(psvId, code, productMeta.get(code), e.getValue()); + } + return detailRows; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int applyOneCatalogProduct(Long psvId, String code, BmsRedlineProduct meta, List details) + { + if (psvId == null || StringUtils.isEmpty(code) || details == null || details.isEmpty()) + { + return 0; + } + String key = code.trim().toUpperCase(Locale.ROOT); + PrcPriceSetVersion ver = catalogVersionSupport.findVersion(psvId); + Date eff = ver == null ? null : ver.getEffectiveTime(); + Date exp = ver == null ? null : ver.getExpireTime(); + String st = ver == null || StringUtils.isEmpty(ver.getStatus()) ? "0" : ver.getStatus(); + String destRegion = resolveDestRegion(meta == null ? null : meta.getDestRegion(), details); + BmsRedlineProduct existing = productMapper.selectByCodeAndPsvId(key, psvId); + if (existing == null) + { + BmsRedlineProduct row = new BmsRedlineProduct(); + row.setPsvId(psvId); + row.setPriceType(PriceType.REDLINE); + row.setProductCode(meta != null && StringUtils.isNotEmpty(meta.getProductCode()) + ? meta.getProductCode().trim() : key); + row.setProductName(meta == null ? null : meta.getProductName()); + row.setServiceLevel(meta == null ? null : meta.getServiceLevel()); + row.setCargoAttr(meta == null ? null : meta.getCargoAttr()); + row.setDestRegion(destRegion); + row.setTaxMode(meta != null && StringUtils.isNotEmpty(meta.getTaxMode()) + ? meta.getTaxMode() : "TAX_INCLUDED"); + row.setEffectiveDate(eff); + row.setExpireDate(exp); + row.setStatus(st); + row.setIsDel(BmsRedlineProduct.IS_DEL_NO); + productMapper.insertBmsRedlineProduct(row); + existing = row; + } + else + { + if (meta != null) + { + if (StringUtils.isNotEmpty(meta.getProductName())) + { + existing.setProductName(meta.getProductName()); + } + if (StringUtils.isNotEmpty(meta.getServiceLevel())) + { + existing.setServiceLevel(meta.getServiceLevel()); + } + if (StringUtils.isNotEmpty(meta.getCargoAttr())) + { + existing.setCargoAttr(meta.getCargoAttr()); + } + } + if (StringUtils.isNotEmpty(destRegion)) + { + existing.setDestRegion(destRegion); + } + existing.setEffectiveDate(eff); + existing.setExpireDate(exp); + existing.setStatus(st); + productMapper.updateBmsRedlineProduct(existing); + } + + Set replaced = new HashSet(); + List toInsert = new ArrayList(); + for (BmsRedlineProductCountry d : details) + { + if (d == null) + { + continue; + } + String ckey = countryIdentityKey(d); + if (StringUtils.isNotEmpty(ckey) && !replaced.contains(ckey)) + { + countryMapper.physicalDeleteByPspIdAndCountry(existing.getProductId(), d.getCountryCode(), + d.getCountryName()); + replaced.add(ckey); + } + d.setDetailId(null); + d.setProductId(existing.getProductId()); + d.setPsvId(psvId); + d.setProductCode(existing.getProductCode()); + if (StringUtils.isEmpty(d.getProductName())) + { + d.setProductName(existing.getProductName()); + } + d.setEffectiveDate(eff); + d.setExpireDate(exp); + d.setStatus(st); + d.setIsDel(BmsRedlineProduct.IS_DEL_NO); + toInsert.add(d); + } + insertRedlineCountryChunks(toInsert); + return toInsert.size(); + } + + @Override + public String submitApplyCatalogPreviewAsync(RedlineCatalogPreviewApplyRequest request) + { + if (request == null || request.getPsvId() == null) + { + throw new ServiceException("psvId required"); + } + catalogVersionSupport.requireEditableCatalogVersion(request.getPsvId(), PriceType.REDLINE); + String taskId = IdUtils.fastSimpleUUID(); + importAsyncRunner.put(taskId, BmsRedlineProductImportTask.STATUS_PENDING, 0, "queued", null); + importAsyncRunner.runApplyPreview(taskId, request); + return taskId; + } + + private void insertRedlineCountryChunks(List rows) + { + if (rows == null || rows.isEmpty()) + { + return; + } + int size = 200; + for (int i = 0; i < rows.size(); i += size) + { + int end = Math.min(i + size, rows.size()); + countryMapper.insertBmsRedlineProductCountryBatch(new ArrayList(rows.subList(i, end))); + } + } + + @Override + public Map previewImportCountryCatalog(byte[] bytes, String filename, String countryCode) + throws Exception + { + validateImportBytes(bytes, filename); + if (StringUtils.isEmpty(countryCode)) + { + throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); + } + BmsRegion region = regionMapper.selectRegionByCode2(countryCode.trim().toUpperCase(Locale.ROOT)); + if (region == null || "2".equals(region.getDelFlag()) + || (StringUtils.isNotEmpty(region.getStatus()) && !"0".equals(region.getStatus()))) + { + throw new ServiceException(MessageUtils.message("price.product.import.countryNotFound", countryCode)); + } + try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) + { + Sheet catalog = workbook.getSheet(CATALOG_SHEET); + if (catalog == null) + { + throw new ServiceException(MessageUtils.message("price.product.import.countrySheetNotFound")); + } + List products = dedupeCatalogProducts(parseCountryCatalog(catalog)); + if (products.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.noData")); + } + Map priceBlocks = parseCountryPriceBlocks(workbook); + Map> byCode = new LinkedHashMap>(); + for (BmsRedlineProduct product : products) + { + if (product == null || StringUtils.isEmpty(product.getProductCode())) + { + continue; + } + PriceBlock block = findPriceBlock(product.getProductName(), priceBlocks); + if (block == null || block.rows == null || block.rows.isEmpty()) + { + continue; + } + List countries = stampSelectedCountry(block.rows, region, null); + for (BmsRedlineProductCountry c : countries) + { + if (c == null) + { + continue; + } + c.setProductCode(product.getProductCode()); + c.setProductName(product.getProductName()); + } + assertNoDuplicateCountryWeightRows(product, countries); + byCode.put(product.getProductCode().trim().toUpperCase(Locale.ROOT), countries); + } + if (byCode.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); + } + String countryLabel = StringUtils.isNotEmpty(region.getCountryNameCn()) + ? region.getCountryNameCn() : region.getCountryCode2(); + if (StringUtils.isNotEmpty(countryLabel)) + { + for (BmsRedlineProduct p : products) + { + if (p != null) + { + p.setDestRegion(countryLabel); + } + } + } + return buildCatalogPreview(products, byCode); + } + } + + private String resolveDestRegion(String preferred, List details) + { + if (StringUtils.isNotEmpty(preferred)) + { + return preferred.trim(); + } + if (details == null) + { + return null; + } + for (BmsRedlineProductCountry d : details) + { + if (d != null && StringUtils.isNotEmpty(d.getCountryName())) + { + return d.getCountryName().trim(); + } + } + return null; + } + + private Map buildCatalogPreview(List catalogProducts, + Map> byCode) + { + List products = new ArrayList(); + int detailCount = 0; + if (catalogProducts != null) + { + for (BmsRedlineProduct src : catalogProducts) + { + if (src == null || StringUtils.isEmpty(src.getProductCode())) + { + continue; + } + String code = src.getProductCode().trim().toUpperCase(Locale.ROOT); + List details = byCode == null ? null : byCode.get(code); + if (details == null || details.isEmpty()) + { + continue; + } + BmsRedlineProduct row = new BmsRedlineProduct(); + row.setProductCode(src.getProductCode()); + row.setProductName(src.getProductName()); + row.setServiceLevel(src.getServiceLevel()); + row.setCargoAttr(src.getCargoAttr()); + row.setDestRegion(src.getDestRegion()); + products.add(row); + detailCount += details.size(); + } + } + Map out = new HashMap(); + out.put("products", products); + out.put("productCount", Integer.valueOf(products.size())); + out.put("detailCount", Integer.valueOf(detailCount)); + out.put("detailsByProductCode", byCode == null ? new LinkedHashMap>() : byCode); + return out; + } /** @return int[]{added, skipped, countryRows} */ private int[] importOneProduct(Workbook workbook, BmsRedlineProduct product, String operName, @@ -682,22 +1150,28 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService } assertNoDuplicateCountryWeightRows(product, countries); // UI-selected effective date wins over Excel; previous open product/country rows expire at this date - Date newEff = requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate - : product.getEffectiveDate()); - applyForcedEffectiveDate(product, countries, newEff); - assertImportEffectiveNotBeforePrev(product, newEff); + Date newEff = forcedEffectiveDate == null && product.getEffectiveDate() == null + ? null + : requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate + : product.getEffectiveDate()); + if (newEff != null) + { + applyForcedEffectiveDate(product, countries, newEff); + assertImportEffectiveNotBeforePrev(product, newEff); + } BmsRedlineProduct existing = productMapper.selectBmsRedlineProductByCodeAndDate( product.getProductCode(), newEff); if (existing != null) { product.setProductId(existing.getProductId()); - patchProductPefId(product, operName); int countryRows = saveCountries(product, countries); return new int[] { 0, 1, countryRows }; } applyProductStatus(product); + product.setPriceType(PriceType.REDLINE); + product.setPsvId(catalogVersionSupport.requirePsvId(PriceType.REDLINE, product.getPsvId(), newEff)); try { productMapper.insertBmsRedlineProduct(product); @@ -710,26 +1184,22 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService } throw e; } - productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); - productMapper.refreshStatusByDates(); + if (newEff != null) + { + productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); + if (product.getPsvId() != null) + { + catalogVersionSupport.activateVersion(product.getPsvId(), newEff); + } + } + else + { + productMapper.refreshStatusByDates(); + } int countryRows = saveCountries(product, countries); return new int[] { 1, 0, countryRows }; } - private void patchProductPefId(BmsRedlineProduct product, String operName) - { - if (product == null || product.getProductId() == null || product.getPefId() == null) - { - return; - } - BmsRedlineProduct pefPatch = new BmsRedlineProduct(); - pefPatch.setProductId(product.getProductId()); - pefPatch.setPefId(product.getPefId()); - pefPatch.setUpdateBy(operName); - productMapper.updateBmsRedlineProduct(pefPatch); - } - - /** * Save country rows by effective_date. * For each product_code + country in the Excel: @@ -771,6 +1241,8 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService { countryMapper.expireOpenByProductCodeAndCountry( productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff); + countryMapper.deleteSurchargeRelByProductCodeAndCountryOnDate( + productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff); countryMapper.deleteOpenByProductCodeAndCountryOnDate( productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff); replacedCountries.add(countryKey); @@ -2035,6 +2507,10 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService /** Batch pre-check: fail whole import before any DB write. */ private void assertImportProductsEffectiveNotBeforePrev(List products, Date forcedEff) { + if (forcedEff == null) + { + return; + } Date newEff = requireImportEffectiveDate(forcedEff); if (products == null || products.isEmpty()) { @@ -2209,18 +2685,22 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService { product.setSourceSheet(product.getProductName()); } - Date newEff = requireImportEffectiveDate(product.getEffectiveDate()); - applyForcedEffectiveDate(product, countries, newEff); - assertImportEffectiveNotBeforePrev(product, newEff); + Date newEff = product.getEffectiveDate() == null ? null : requireImportEffectiveDate(product.getEffectiveDate()); + if (newEff != null) + { + applyForcedEffectiveDate(product, countries, newEff); + assertImportEffectiveNotBeforePrev(product, newEff); + } BmsRedlineProduct existing = productMapper.selectBmsRedlineProductByCodeAndDate(product.getProductCode(), newEff); if (existing != null) { product.setProductId(existing.getProductId()); - patchProductPefId(product, operName); int countryRows = saveCountries(product, countries); return new int[] { 0, 1, countryRows }; } applyProductStatus(product); + product.setPriceType(PriceType.REDLINE); + product.setPsvId(catalogVersionSupport.requirePsvId(PriceType.REDLINE, product.getPsvId(), newEff)); try { productMapper.insertBmsRedlineProduct(product); @@ -2233,8 +2713,18 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService } throw e; } - productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); - productMapper.refreshStatusByDates(); + if (newEff != null) + { + productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId()); + if (product.getPsvId() != null) + { + catalogVersionSupport.activateVersion(product.getPsvId(), newEff); + } + } + else + { + productMapper.refreshStatusByDates(); + } int countryRows = saveCountries(product, countries); return new int[] { 1, 0, countryRows }; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceExcelFileServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceExcelFileServiceImpl.java index 4f3f7ccd..d0e8ce32 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceExcelFileServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceExcelFileServiceImpl.java @@ -37,28 +37,6 @@ public class PrcPriceExcelFileServiceImpl implements IPrcPriceExcelFileService return excelFileMapper.selectPrcPriceExcelFileById(pefId); } - @Override - public List selectByProductCode(String productCode) - { - if (StringUtils.isEmpty(productCode)) - { - return Collections.emptyList(); - } - List list = excelFileMapper.selectByProductCode(productCode.trim()); - return list == null ? Collections.emptyList() : list; - } - - @Override - public List selectByRedlineProductCode(String productCode) - { - if (productCode == null || productCode.trim().isEmpty()) - { - return java.util.Collections.emptyList(); - } - List list = excelFileMapper.selectByRedlineProductCode(productCode.trim()); - return list == null ? java.util.Collections.emptyList() : list; - } - @Override public List selectByPsvId(Long psvId) { @@ -72,7 +50,7 @@ public class PrcPriceExcelFileServiceImpl implements IPrcPriceExcelFileService @Override public PrcPriceExcelFile saveImportExcel(String excelType, Date effectiveTime, byte[] bytes, - String filename, String operName) throws Exception + String filename, String operName, Long psvId) throws Exception { if (StringUtils.isEmpty(excelType)) { @@ -111,6 +89,7 @@ public class PrcPriceExcelFileServiceImpl implements IPrcPriceExcelFileService PrcPriceExcelFile row = new PrcPriceExcelFile(); row.setExcelType(excelType.trim().toUpperCase(Locale.ROOT)); + row.setPsvId(psvId); row.setEffectiveTime(effectiveTime); row.setFileName(originalName); row.setFilePath(storedPath); @@ -122,6 +101,19 @@ public class PrcPriceExcelFileServiceImpl implements IPrcPriceExcelFileService return row; } + @Override + public int bindPsvId(Long pefId, Long psvId) + { + if (pefId == null || psvId == null) + { + return 0; + } + PrcPriceExcelFile row = new PrcPriceExcelFile(); + row.setPefId(pefId); + row.setPsvId(psvId); + return excelFileMapper.updatePrcPriceExcelFile(row); + } + private static String resolveContentType(String ext) { if ("xls".equals(ext)) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java index 5caea269..f5b52381 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java @@ -399,6 +399,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService int curSeq = version.getVersionSeq() == null ? 0 : version.getVersionSeq().intValue(); PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(version.getPriceSetId()); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List list = versionMapper.selectPrcPriceSetVersionList(q); if (list == null || list.isEmpty()) { @@ -455,6 +456,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService int seq = maxSeq == null ? 1 : maxSeq.intValue() + 1; PrcPriceSetVersion nv = new PrcPriceSetVersion(); nv.setPriceSetId(set.getPriceSetId()); + nv.setPriceType(com.bms.price.domain.PriceType.SALES); nv.setVersionSeq(Integer.valueOf(seq)); nv.setVersionNo("V" + seq); nv.setStatus(VER_DRAFT); @@ -492,6 +494,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService int seq = maxSeq == null ? 1 : maxSeq.intValue() + 1; PrcPriceSetVersion nv = new PrcPriceSetVersion(); nv.setPriceSetId(set.getPriceSetId()); + nv.setPriceType(com.bms.price.domain.PriceType.SALES); nv.setVersionSeq(Integer.valueOf(seq)); nv.setVersionNo("V" + seq); nv.setStatus(VER_DRAFT); @@ -908,10 +911,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService @Override public int insertDetail(PrcPriceSetDetail detail) { - PrcPriceSet db = requireSet(detail.getPriceSetId()); + Long psvId = detail.getPsvId(); + Long priceSetId = resolvePriceSetIdByPsvId(psvId); + PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); + detail.setPriceType(com.bms.price.domain.PriceType.SALES); + detail.setPsvId(resolvePsvId(priceSetId, psvId)); validateDetail(db, detail); - detail.setPsvId(resolvePsvId(detail.getPriceSetId(), detail.getPsvId())); detail.setPspId(lookupSetProductId(detail.getPsvId(), detail.getProductCode())); int rows = detailMapper.insertPrcPriceSetDetail(detail); if (detail.getSurcharges() != null) @@ -955,14 +961,52 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { throw new ServiceException(MessageUtils.message("price.product.import.fileType")); } - byte[] bytes = file.getBytes(); + return submitImportDetailPricesAsync(priceSetId, psvId, java.util.Collections.singletonList(file), operName, + updateOnly, replaceByProductCountry, countryCode); + } + + @Override + public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, List files, String operName, + boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception + { + PrcPriceSet db = requireSet(priceSetId); + assertChildEditable(db); + if (!TYPE_FIXED.equals(db.getSetType())) + { + throw new ServiceException(MessageUtils.message("price.set.import.fixedOnly")); + } + if (files == null || files.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.fileEmpty")); + } + if (files.size() > com.bms.price.service.support.UploadFileBatch.MAX_FILES) + { + throw new ServiceException("\u5355\u6b21\u6700\u591a\u5bfc\u5165 5 \u4e2a\u6587\u4ef6"); + } + List bytesList = new ArrayList(); + List names = new ArrayList(); + for (MultipartFile file : files) + { + if (file == null || file.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.fileEmpty")); + } + String filename = file.getOriginalFilename(); + if (filename == null || !(filename.toLowerCase(Locale.ROOT).endsWith(".xlsx") + || filename.toLowerCase(Locale.ROOT).endsWith(".xls"))) + { + throw new ServiceException(MessageUtils.message("price.product.import.fileType")); + } + bytesList.add(file.getBytes()); + names.add(filename); + } if (psvId == null) { psvId = resolveCurrentPsvId(db); } String taskId = IdUtils.fastSimpleUUID(); saveAsyncRunner.put(taskId, PrcPriceSetSaveTask.STATUS_PENDING, 0, "queued", priceSetId, null); - saveAsyncRunner.runImportPrices(taskId, priceSetId, psvId, bytes, filename, operName, updateOnly, + saveAsyncRunner.runImportPricesFiles(taskId, priceSetId, psvId, bytesList, names, operName, updateOnly, replaceByProductCountry, countryCode); return taskId; } @@ -1093,7 +1137,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService operName = null; } PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel( - PrcPriceExcelFile.TYPE_SALES, new Date(), bytes, filename, operName); + PrcPriceExcelFile.TYPE_SALES, new Date(), bytes, filename, operName, null); Long pefId = excelFile != null ? excelFile.getPefId() : null; Map productMap = new LinkedHashMap(); @@ -1210,22 +1254,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { throw new ServiceException(MessageUtils.message("price.set.import.noDetailWritten")); } - - if (pefId != null) - { - for (String code : byProduct.keySet()) - { - if (StringUtils.isEmpty(code)) - { - continue; - } - PrcPriceSetProduct sp = productMap.get(code.trim().toUpperCase(Locale.ROOT)); - if (sp != null) - { - sp.setPefId(pefId); - } - } - } if (replaceScope) { for (PrcPriceSetDetail d : details) @@ -1253,6 +1281,46 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return result; } + @Override + @SuppressWarnings("unchecked") + public Map previewImportDetailPricesMany(List bytesList, List names, + boolean updateOnly, List existingDetails, boolean replaceByProductCountry, String countryCode) + throws Exception + { + if (bytesList == null || bytesList.isEmpty()) + { + throw new ServiceException("file required"); + } + if (bytesList.size() > com.bms.price.service.support.UploadFileBatch.MAX_FILES) + { + throw new ServiceException("\u5355\u6b21\u6700\u591a\u5bfc\u5165 5 \u4e2a\u6587\u4ef6"); + } + List existing = existingDetails; + Map last = null; + List pefIds = new ArrayList(); + for (int i = 0; i < bytesList.size(); i++) + { + String filename = names != null && i < names.size() ? names.get(i) : null; + last = previewImportDetailPrices(bytesList.get(i), filename, updateOnly, existing, replaceByProductCountry, + countryCode); + Object details = last == null ? null : last.get("details"); + if (details instanceof List) + { + existing = (List) details; + } + Object pefId = last == null ? null : last.get("pefId"); + if (pefId instanceof Number) + { + pefIds.add(Long.valueOf(((Number) pefId).longValue())); + } + } + if (last != null) + { + last.put("pefIds", pefIds); + } + return last; + } + private Map> parseSetImportExcel(byte[] bytes, String filename, String countryCode) throws Exception { @@ -1385,7 +1453,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } PrcPriceSetDetail q = new PrcPriceSetDetail(); - q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); q.setPsvId(psvId); List details = detailMapper.selectPrcPriceSetDetailList(q); if (details == null) @@ -1424,9 +1492,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService PrcPriceSetVersion verForFile = versionMapper.selectPrcPriceSetVersionById(psvId); Date excelEff = verForFile != null && verForFile.getEffectiveTime() != null ? verForFile.getEffectiveTime() : new Date(); - PrcPriceExcelFile excelFile = priceExcelFileService.saveImportExcel( - PrcPriceExcelFile.TYPE_SALES, excelEff, bytes, filename, operName); - Long pefId = excelFile != null ? excelFile.getPefId() : null; + priceExcelFileService.saveImportExcel( + PrcPriceExcelFile.TYPE_SALES, excelEff, bytes, filename, operName, psvId); int updated = 0; int inserted = 0; @@ -1461,7 +1528,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService Long pspId = null; if (!updateOnly) { - pspId = ensureSetProduct(priceSetId, psvId, productCode, productName, pefId); + pspId = ensureSetProduct(priceSetId, psvId, productCode, productName); } for (BmsProductCountry row : e.getValue()) { @@ -1525,11 +1592,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { setProductMapper.bindDetailPspByPsvId(psvId); } - if (pefId != null && (inserted > 0 || updated > 0)) - { - List codes = new ArrayList(byProduct.keySet()); - setProductMapper.updatePefIdByPsvId(psvId, pefId, codes); - } reportImportProgress(taskId, priceSetId, 95, "finishing"); if (inserted == 0 && updated == 0) { @@ -1576,7 +1638,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService String productName, BmsProductCountry row, BigDecimal freight, BigDecimal registration) { PrcPriceSetDetail d = new PrcPriceSetDetail(); - d.setPriceSetId(priceSetId); + d.setPriceType(com.bms.price.domain.PriceType.SALES); d.setPsvId(psvId); d.setProductCode(trimToLen(productCode, 20)); d.setProductName(trimToLen(productName, 50)); @@ -1636,6 +1698,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(set.getPriceSetId()); + q.setPriceType(com.bms.price.domain.PriceType.SALES); versions = versionMapper.selectPrcPriceSetVersionList(q); } if (versions == null || versions.isEmpty()) @@ -2011,13 +2074,14 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { throw new ServiceException(MessageUtils.message("price.set.detail.notExists")); } - PrcPriceSet db = requireSet(old.getPriceSetId()); + Long priceSetId = resolvePriceSetIdByPsvId(old.getPsvId()); + PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); - detail.setPriceSetId(old.getPriceSetId()); if (detail.getPsvId() == null) { detail.setPsvId(old.getPsvId()); } + detail.setPriceType(com.bms.price.domain.PriceType.SALES); validateDetail(db, detail); Long pspId = lookupSetProductId(detail.getPsvId(), detail.getProductCode()); if (pspId == null) @@ -2045,7 +2109,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { continue; } - PrcPriceSet db = requireSet(old.getPriceSetId()); + PrcPriceSet db = requireSet(resolvePriceSetIdByPsvId(old.getPsvId())); assertChildEditable(db); if (old.getPsvId() != null && StringUtils.isNotEmpty(old.getProductCode())) { @@ -2817,6 +2881,16 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } } + private Long resolvePriceSetIdByPsvId(Long psvId) + { + if (psvId == null) + { + return null; + } + PrcPriceSetVersion v = versionMapper.selectPrcPriceSetVersionById(psvId); + return v == null ? null : v.getPriceSetId(); + } + private PrcPriceSet requireSet(Long priceSetId) { // Header only: avoid count(detail) waiting on long pull/import locks. @@ -2850,6 +2924,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List versions = versionMapper.selectPrcPriceSetVersionList(q); PrcPriceSetVersion current = pickCurrentVersion(versions); if (current == null) @@ -2896,6 +2971,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService Long priceSetId = set.getPriceSetId(); PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List versions = versionMapper.selectPrcPriceSetVersionList(q); if (versions == null) { @@ -2960,6 +3036,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } // Draft versions may omit effective_time; set on activate version.setPriceSetId(set.getPriceSetId()); + version.setPriceType(com.bms.price.domain.PriceType.SALES); version.setCreateBy(set.getCreateBy()); version.setUpdateBy(set.getUpdateBy()); // Reuse existing version when client omitted psvId (avoids V2/V3 + leftover details) @@ -3002,6 +3079,28 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService versionMapper.updatePrcPriceSetVersion(version); } set.setVersion(version); + if (version.getPsvId() != null) + { + java.util.LinkedHashSet excelIds = new java.util.LinkedHashSet(); + if (set.getPefIds() != null) + { + for (Long excelId : set.getPefIds()) + { + if (excelId != null) + { + excelIds.add(excelId); + } + } + } + if (set.getPefId() != null) + { + excelIds.add(set.getPefId()); + } + for (Long excelId : excelIds) + { + priceExcelFileService.bindPsvId(excelId, version.getPsvId()); + } + } if (detailsProvided) { upsertVersionDetails(set, version, details); @@ -3077,6 +3176,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List list = versionMapper.selectPrcPriceSetVersionList(q); if (list == null || list.isEmpty()) { @@ -3122,6 +3222,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List list = versionMapper.selectPrcPriceSetVersionList(q); if (list == null || list.isEmpty()) { @@ -3169,7 +3270,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService setProductMapper.deleteByPsvId(version.getPsvId()); // clean legacy rows without psv_id under this set (would look like duplicates in UI) detailMapper.deleteOrphanDetailsByPriceSetId(set.getPriceSetId()); - int productRows = setProductMapper.insertFromLibrary(set.getPriceSetId(), version.getPsvId()); + int productRows = setProductMapper.insertFromLibrary(version.getPsvId()); if (productRows <= 0) { throw new ServiceException(MessageUtils.message("price.set.library.empty")); @@ -3178,18 +3279,16 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService int inserted; if (TYPE_DISCOUNT.equals(set.getSetType())) { - inserted = detailMapper.insertDiscountDetailsFromLibrary( - set.getPriceSetId(), version.getPsvId()); + inserted = detailMapper.insertDiscountDetailsFromLibrary(version.getPsvId()); } else { // Prefer currently effective open country rows; fallback asOf window if needed - inserted = detailMapper.insertFixedDetailsFromLibrary( - set.getPriceSetId(), version.getPsvId(), null, true); + inserted = detailMapper.insertFixedDetailsFromLibrary(version.getPsvId(), null, true); if (inserted <= 0) { inserted = detailMapper.insertFixedDetailsFromLibrary( - set.getPriceSetId(), version.getPsvId(), new Date(), false); + version.getPsvId(), new Date(), false); } } if (inserted <= 0) @@ -3267,10 +3366,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService for (PrcPriceSetDetail d : details) { validateDetail(set, d); - d.setPriceSetId(set.getPriceSetId()); - d.setPsvId(version.getPsvId()); + d.setPsvId(version.getPsvId()); + d.setPriceType(com.bms.price.domain.PriceType.SALES); Long pspId = ensureSetProduct(set.getPriceSetId(), version.getPsvId(), - d.getProductCode(), d.getProductName(), set.getPefId()); + d.getProductCode(), d.getProductName()); if (pspId != null) { d.setPspId(pspId); @@ -3391,12 +3490,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService * Uses request-scoped cache during bulk upsert to avoid N selects on same code. */ private Long ensureSetProduct(Long priceSetId, Long psvId, String productCode, String productNameHint) - { - return ensureSetProduct(priceSetId, psvId, productCode, productNameHint, null); - } - - private Long ensureSetProduct(Long priceSetId, Long psvId, String productCode, String productNameHint, - Long pefId) { if (psvId == null || StringUtils.isEmpty(productCode)) { @@ -3407,13 +3500,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService Map cache = setProductCacheTl.get(); if (cache != null && cache.containsKey(key)) { - if (pefId != null) - { - PrcPriceSetProduct pefPatch = new PrcPriceSetProduct(); - pefPatch.setPspId(cache.get(key)); - pefPatch.setPefId(pefId); - setProductMapper.updatePrcPriceSetProduct(pefPatch); - } return cache.get(key); } PrcPriceSetProduct existing = setProductMapper.selectByPsvIdAndCode(psvId, code); @@ -3426,11 +3512,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService existing.setProductName(productNameHint); needUpdate = true; } - if (pefId != null && (existing.getPefId() == null || !pefId.equals(existing.getPefId()))) - { - existing.setPefId(pefId); - needUpdate = true; - } if (needUpdate) { setProductMapper.updatePrcPriceSetProduct(existing); @@ -3442,10 +3523,9 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return existing.getPspId(); } PrcPriceSetProduct row = new PrcPriceSetProduct(); - row.setPriceSetId(priceSetId); + row.setPriceType(com.bms.price.domain.PriceType.SALES); row.setPsvId(psvId); row.setProductCode(code); - row.setPefId(pefId); BmsProduct lib = null; try { @@ -3792,6 +3872,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List list = versionMapper.selectPrcPriceSetVersionList(q); if (list == null) { @@ -3815,6 +3896,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { PrcPriceSetVersion q = new PrcPriceSetVersion(); q.setPriceSetId(priceSetId); + q.setPriceType(com.bms.price.domain.PriceType.SALES); List versions = versionMapper.selectPrcPriceSetVersionList(q); if (versions == null || versions.isEmpty()) { @@ -3914,15 +3996,18 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService public int refreshAllVersionStatus() { List ids = versionMapper.selectPriceSetIds(); - if (ids == null || ids.isEmpty()) + int n = 0; + if (ids != null) { - return 0; + for (Long id : ids) + { + refreshVersionStatus(id); + n++; + } } - for (Long id : ids) - { - refreshVersionStatus(id); - } - return ids.size(); + n += versionMapper.refreshCatalogStatusByDates(com.bms.price.domain.PriceType.PRODUCT); + n += versionMapper.refreshCatalogStatusByDates(com.bms.price.domain.PriceType.REDLINE); + return n; } private static boolean sameTime(Date a, Date b) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java index 96806f60..19008a3d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java @@ -923,7 +923,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService } PrcPriceSetDetail detail = detailMapper.selectFixedMatch( - set.getPriceSetId(), psvId, productCode, + psvId, productCode, countryCode, countryName, zoneCode, weight); if (detail == null) { @@ -954,7 +954,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService PrcPriceSetDetail detail = null; if (detailCount > 0) { - detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), psvId, productCode); + detail = detailMapper.selectDiscountMatch(psvId, productCode); if (detail == null) { return null; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java b/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java new file mode 100644 index 00000000..84b24c92 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java @@ -0,0 +1,415 @@ +package com.bms.price.service.support; + +import java.util.Date; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.bms.common.exception.ServiceException; +import com.bms.common.utils.StringUtils; +import com.bms.price.domain.PriceType; +import com.bms.price.domain.PrcPriceSetVersion; +import com.bms.price.mapper.PrcPriceExcelFileMapper; +import com.bms.price.mapper.PrcPriceSetProductMapper; +import com.bms.price.mapper.PrcPriceSetDetailMapper; +import com.bms.price.mapper.PrcPriceSetVersionMapper; +import com.bms.price.mapper.PrcSurchargeRelMapper; + +/** + * Resolve / create global PRODUCT or REDLINE versions (price_set_id null). + */ +@Component +public class CatalogVersionSupport +{ + public static final String STATUS_DRAFT = "0"; + public static final String STATUS_EFFECT = "1"; + public static final String STATUS_EXPIRED = "2"; + public static final String STATUS_PENDING = "3"; + + @Autowired + private PrcPriceSetVersionMapper versionMapper; + + @Autowired + private PrcPriceSetProductMapper productMapper; + + @Autowired + private PrcPriceSetDetailMapper detailMapper; + + @Autowired + private PrcSurchargeRelMapper surchargeRelMapper; + + @Autowired + private PrcPriceExcelFileMapper excelFileMapper; + + public void assertCatalogType(String priceType) + { + if (!PriceType.isCatalog(priceType)) + { + throw new ServiceException("priceType must be PRODUCT or REDLINE"); + } + } + + /** Latest version for list: prefer EFFECT, else max version_seq. */ + public PrcPriceSetVersion resolveLatestForList(String priceType) + { + assertCatalogType(priceType); + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(priceType); + q.setStatus(STATUS_EFFECT); + List effect = versionMapper.selectPrcPriceSetVersionList(q); + if (effect != null && !effect.isEmpty()) + { + return effect.get(0); + } + q.setStatus(null); + List all = versionMapper.selectPrcPriceSetVersionList(q); + return (all == null || all.isEmpty()) ? null : all.get(0); + } + + public PrcPriceSetVersion selectActiveAt(String priceType, Date asOf) + { + assertCatalogType(priceType); + return versionMapper.selectActiveByTypeAt(priceType, asOf); + } + + /** + * Ensure a draft version for import/manual edit at effectiveTime. + * If an open draft exists, reuse it; else create next seq and optionally copy from latest. + */ + public PrcPriceSetVersion ensureDraftVersion(String priceType, Date effectiveTime, boolean copyFromLatest) + { + assertCatalogType(priceType); + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(priceType); + q.setStatus(STATUS_DRAFT); + List drafts = versionMapper.selectPrcPriceSetVersionList(q); + if (drafts != null && !drafts.isEmpty()) + { + PrcPriceSetVersion draft = drafts.get(0); + if (effectiveTime != null) + { + draft.setEffectiveTime(effectiveTime); + versionMapper.updatePrcPriceSetVersion(draft); + } + return draft; + } + + Integer max = versionMapper.selectMaxVersionSeqByType(priceType, null); + int next = max == null ? 1 : max + 1; + PrcPriceSetVersion nv = new PrcPriceSetVersion(); + nv.setPriceSetId(null); + nv.setPriceType(priceType); + nv.setVersionSeq(next); + nv.setVersionNo("V" + next); + nv.setEffectiveTime(effectiveTime); + nv.setStatus(STATUS_DRAFT); + nv.setPriceBuildMode("IMPORT"); + nv.setCreateBy("system"); + versionMapper.insertPrcPriceSetVersion(nv); + + if (copyFromLatest) + { + PrcPriceSetVersion src = resolveLatestForList(priceType); + if (src != null && src.getPsvId() != null && !src.getPsvId().equals(nv.getPsvId())) + { + productMapper.copyFromVersion(src.getPsvId(), nv.getPsvId()); + detailMapper.copyDetailsFromVersion(src.getPsvId(), nv.getPsvId()); + productMapper.bindDetailPspByPsvId(nv.getPsvId()); + // stamp price_type on copied rows + productMapper.updatePriceTypeByPsvId(nv.getPsvId(), priceType); + detailMapper.updatePriceTypeByPsvId(nv.getPsvId(), priceType); + } + } + return nv; + } + + /** Activate draft: set EFFECT, chain expire on previous EFFECT versions of same type. */ + public void activateVersion(Long psvId, Date effectiveTime) + { + PrcPriceSetVersion ver = versionMapper.selectPrcPriceSetVersionById(psvId); + if (ver == null) + { + throw new ServiceException("版本不存在"); + } + assertCatalogType(ver.getPriceType()); + if (!STATUS_DRAFT.equals(ver.getStatus()) && !STATUS_PENDING.equals(ver.getStatus())) + { + throw new ServiceException("仅草稿或待生效版本可以启动"); + } + Date eff = effectiveTime != null ? effectiveTime : ver.getEffectiveTime(); + if (eff == null) + { + throw new ServiceException("请选择生效时间"); + } + Date prevEff = findPrevEffectiveTime(ver); + if (prevEff != null && eff.before(prevEff)) + { + throw new ServiceException("生效时间不能早于上一版本"); + } + + boolean future = eff.after(new Date()); + if (!future) + { + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(ver.getPriceType()); + q.setStatus(STATUS_EFFECT); + List prevList = versionMapper.selectPrcPriceSetVersionList(q); + if (prevList != null) + { + for (PrcPriceSetVersion prev : prevList) + { + if (prev.getPsvId().equals(psvId)) + { + continue; + } + prev.setExpireTime(eff); + prev.setStatus(STATUS_EXPIRED); + versionMapper.updatePrcPriceSetVersion(prev); + } + } + } + + ver.setEffectiveTime(eff); + ver.setExpireTime(null); + ver.setStatus(future ? STATUS_PENDING : STATUS_EFFECT); + versionMapper.updatePrcPriceSetVersion(ver); + refreshCatalogStatuses(ver.getPriceType()); + } + + public List listVersions(String priceType, String status) + { + return listVersions(priceType, status, null); + } + + public List listVersions(String priceType, String status, String statusGroup) + { + assertCatalogType(priceType); + refreshCatalogStatuses(priceType); + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(priceType); + q.setStatus(status); + q.setStatusGroup(statusGroup); + List list = versionMapper.selectCatalogVersionList(q); + return list == null ? java.util.Collections.emptyList() : list; + } + + /** + * Hard-delete draft/pending catalog version with products, details, + * surcharge links and excel attachments. + */ + public int deleteDraftVersion(Long psvId) + { + PrcPriceSetVersion ver = versionMapper.selectPrcPriceSetVersionById(psvId); + if (ver == null) + { + throw new ServiceException("\u7248\u672c\u4e0d\u5b58\u5728"); + } + assertCatalogType(ver.getPriceType()); + if (!STATUS_DRAFT.equals(ver.getStatus()) && !STATUS_PENDING.equals(ver.getStatus())) + { + throw new ServiceException("\u4ec5\u8349\u7a3f\u6216\u5f85\u751f\u6548\u7248\u672c\u53ef\u4ee5\u5220\u9664"); + } + surchargeRelMapper.physicalDeleteByPsvId(psvId); + detailMapper.physicalDeleteByPsvId(psvId); + productMapper.physicalDeleteByPsvId(psvId); + excelFileMapper.physicalDeleteByPsvId(psvId); + return versionMapper.deletePrcPriceSetVersionByIds(new Long[] { psvId }); + } + + /** Current draft version (status=0), or null. */ + public PrcPriceSetVersion currentDraft(String priceType) + { + List drafts = listVersions(priceType, STATUS_DRAFT); + return drafts.isEmpty() ? null : drafts.get(0); + } + + /** Draft/pending catalog version that can accept preview apply / edit. */ + public PrcPriceSetVersion requireEditableCatalogVersion(Long psvId, String priceType) + { + PrcPriceSetVersion ver = versionMapper.selectPrcPriceSetVersionById(psvId); + if (ver == null) + { + throw new ServiceException("\u7248\u672c\u4e0d\u5b58\u5728"); + } + assertCatalogType(ver.getPriceType()); + if (StringUtils.isNotEmpty(priceType) && !priceType.equals(ver.getPriceType())) + { + throw new ServiceException("\u7248\u672c\u7c7b\u578b\u4e0d\u5339\u914d"); + } + if (!STATUS_DRAFT.equals(ver.getStatus()) && !STATUS_PENDING.equals(ver.getStatus())) + { + throw new ServiceException("\u4ec5\u8349\u7a3f\u6216\u5f85\u751f\u6548\u7248\u672c\u53ef\u4ee5\u7f16\u8f91"); + } + return ver; + } + + /** + * Prepare draft for catalog price create page. + * copyFromLatest=true: copy from current EFFECT version when draft is empty. + * copyFromLatest=false: empty draft (Excel import / rebuild). + */ + public PrcPriceSetVersion prepareDraftVersion(String priceType, boolean copyFromLatest) + { + assertCatalogType(priceType); + List drafts = listVersions(priceType, STATUS_DRAFT); + PrcPriceSetVersion draft; + if (drafts.isEmpty()) + { + if (copyFromLatest) + { + PrcPriceSetVersion src = findEffectVersion(priceType); + if (src == null) + { + throw new ServiceException("\u5f53\u524d\u6ca1\u6709\u5df2\u751f\u6548\u7248\u672c\u53ef\u4ee5\u6cbf\u7528"); + } + } + draft = ensureDraftVersion(priceType, null, copyFromLatest); + draft.setPriceBuildMode(copyFromLatest ? "COPY" : "IMPORT"); + versionMapper.updatePrcPriceSetVersion(draft); + return reloadCatalogVersion(draft.getPsvId(), priceType); + } + + draft = drafts.get(0); + int count = draft.getProductCount() == null ? 0 : draft.getProductCount().intValue(); + if (count == 0 && copyFromLatest) + { + PrcPriceSetVersion src = findEffectVersion(priceType); + if (src == null) + { + throw new ServiceException("\u5f53\u524d\u6ca1\u6709\u5df2\u751f\u6548\u7248\u672c\u53ef\u4ee5\u6cbf\u7528"); + } + if (!src.getPsvId().equals(draft.getPsvId())) + { + productMapper.copyFromVersion(src.getPsvId(), draft.getPsvId()); + detailMapper.copyDetailsFromVersion(src.getPsvId(), draft.getPsvId()); + productMapper.bindDetailPspByPsvId(draft.getPsvId()); + productMapper.updatePriceTypeByPsvId(draft.getPsvId(), priceType); + detailMapper.updatePriceTypeByPsvId(draft.getPsvId(), priceType); + } + draft.setPriceBuildMode("COPY"); + versionMapper.updatePrcPriceSetVersion(draft); + } + else if (count == 0) + { + draft.setPriceBuildMode("IMPORT"); + versionMapper.updatePrcPriceSetVersion(draft); + } + return reloadCatalogVersion(draft.getPsvId(), priceType); + } + + private PrcPriceSetVersion findEffectVersion(String priceType) + { + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(priceType); + q.setStatus(STATUS_EFFECT); + List effect = versionMapper.selectPrcPriceSetVersionList(q); + return (effect == null || effect.isEmpty()) ? null : effect.get(0); + } + + private PrcPriceSetVersion reloadCatalogVersion(Long psvId, String priceType) + { + List list = listVersions(priceType, null); + for (PrcPriceSetVersion v : list) + { + if (v != null && psvId.equals(v.getPsvId())) + { + return v; + } + } + return versionMapper.selectPrcPriceSetVersionById(psvId); + } + + private Date findPrevEffectiveTime(PrcPriceSetVersion version) + { + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(version.getPriceType()); + List list = versionMapper.selectPrcPriceSetVersionList(q); + if (list == null) + { + return null; + } + int cur = version.getVersionSeq() == null ? 0 : version.getVersionSeq().intValue(); + Date prev = null; + for (PrcPriceSetVersion v : list) + { + if (v == null || v.getEffectiveTime() == null) + { + continue; + } + if (version.getPsvId() != null && version.getPsvId().equals(v.getPsvId())) + { + continue; + } + int seq = v.getVersionSeq() == null ? 0 : v.getVersionSeq().intValue(); + if (seq >= cur) + { + continue; + } + if (prev == null || v.getEffectiveTime().after(prev)) + { + prev = v.getEffectiveTime(); + } + } + return prev; + } + + public PrcPriceSetVersion findVersion(Long psvId) + { + if (psvId == null) + { + return null; + } + return versionMapper.selectPrcPriceSetVersionById(psvId); + } + + public void refreshCatalogStatuses(String priceType) + { + versionMapper.refreshCatalogStatusByDates(priceType); + productMapper.syncEffectByPriceType(priceType); + detailMapper.syncEffectByPriceType(priceType); + } + + public Long requirePsvId(String priceType, Long psvId, Date effectiveTime) + { + if (psvId != null) + { + return psvId; + } + PrcPriceSetVersion latest = resolveLatestForList(priceType); + if (latest != null && STATUS_DRAFT.equals(latest.getStatus())) + { + if (effectiveTime != null) + { + latest.setEffectiveTime(effectiveTime); + versionMapper.updatePrcPriceSetVersion(latest); + } + return latest.getPsvId(); + } + if (latest != null && STATUS_EFFECT.equals(latest.getStatus())) + { + if (effectiveTime == null || sameTime(latest.getEffectiveTime(), effectiveTime)) + { + return latest.getPsvId(); + } + } + PrcPriceSetVersion draft = ensureDraftVersion(priceType, effectiveTime, true); + return draft.getPsvId(); + } + + private static boolean sameTime(Date a, Date b) + { + if (a == null && b == null) + { + return true; + } + if (a == null || b == null) + { + return false; + } + return a.getTime() == b.getTime(); + } + + public static String normalizeType(String priceType, String defaultType) + { + return StringUtils.isEmpty(priceType) ? defaultType : priceType; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/support/UploadFileBatch.java b/bms-back/bms-system/src/main/java/com/bms/price/service/support/UploadFileBatch.java new file mode 100644 index 00000000..a8eb0420 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/support/UploadFileBatch.java @@ -0,0 +1,44 @@ +package com.bms.price.service.support; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.web.multipart.MultipartFile; +import com.bms.common.exception.ServiceException; + +/** Collect multipart parts named files and/or file. At most 5. */ +public final class UploadFileBatch +{ + public static final int MAX_FILES = 5; + + private UploadFileBatch() + { + } + + public static List collect(MultipartFile[] files, MultipartFile file) + { + List list = new ArrayList(); + if (files != null) + { + for (MultipartFile item : files) + { + if (item != null && !item.isEmpty()) + { + list.add(item); + } + } + } + if (file != null && !file.isEmpty()) + { + list.add(file); + } + if (list.isEmpty()) + { + throw new ServiceException("file required"); + } + if (list.size() > MAX_FILES) + { + throw new ServiceException("\u5355\u6b21\u6700\u591a\u5bfc\u5165 5 \u4e2a\u6587\u4ef6"); + } + return list; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/BmsProductImportAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/BmsProductImportAsyncRunner.java index c5c13ac3..42753e73 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/task/BmsProductImportAsyncRunner.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/BmsProductImportAsyncRunner.java @@ -1,7 +1,17 @@ package com.bms.price.task; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -9,11 +19,14 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import com.bms.common.core.redis.RedisCache; import com.bms.common.utils.StringUtils; +import com.bms.price.domain.BmsProduct; +import com.bms.price.domain.BmsProductCountry; import com.bms.price.domain.BmsProductImportTask; +import com.bms.price.domain.CatalogPreviewApplyRequest; import com.bms.price.service.IBmsProductService; /** - * Async runner for product Excel import (avoids HTTP timeout on large workbooks). + * Async runner for product Excel import / preview (avoids HTTP timeout on large workbooks). */ @Component public class BmsProductImportAsyncRunner @@ -23,6 +36,8 @@ public class BmsProductImportAsyncRunner public static final String CACHE_KEY_PREFIX = "price:product:import:task:"; public static final int TTL_MINUTES = 60; + private final ConcurrentHashMap previewResults = new ConcurrentHashMap(); + @Autowired private IBmsProductService productService; @@ -69,6 +84,215 @@ public class BmsProductImportAsyncRunner } } + /** Background multi-country parse-only preview. One task merges up to 5 workbooks. */ + @Async("threadPoolTaskExecutor") + public void runPreviewAll(String taskId, List bytesList, List names) + { + try + { + Map merged = parseCatalogFiles(taskId, bytesList, names, false, null); + putSuccess(taskId, "preview ok", merged); + log.info("async product preview done, taskId={}, files={}", taskId, Integer.valueOf(bytesList == null ? 0 : bytesList.size())); + } + catch (Exception e) + { + log.error("async product preview failed, taskId={}", taskId, e); + previewResults.remove(taskId); + put(taskId, BmsProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e)); + } + } + + /** Background single-country parse-only preview. One task merges up to 5 workbooks. */ + @Async("threadPoolTaskExecutor") + public void runPreviewCountry(String taskId, List bytesList, List names, String countryCode) + { + try + { + Map merged = parseCatalogFiles(taskId, bytesList, names, true, countryCode); + putSuccess(taskId, "preview ok", merged); + log.info("async product country preview done, taskId={}, country={}, files={}", taskId, countryCode, + Integer.valueOf(bytesList == null ? 0 : bytesList.size())); + } + catch (Exception e) + { + log.error("async product country preview failed, taskId={}, country={}", taskId, countryCode, e); + previewResults.remove(taskId); + put(taskId, BmsProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e)); + } + } + + /** Parse workbooks together. Merge stays in upload order so a later file wins. */ + private Map parseCatalogFiles(String taskId, List bytesList, List names, + boolean country, String countryCode) throws Exception + { + int n = bytesList == null ? 0 : bytesList.size(); + if (n == 0) + { + return null; + } + Map merged = null; + for (int i = 0; i < n; i++) + { + put(taskId, BmsProductImportTask.STATUS_RUNNING, 10 + (70 * i / n), "parsing " + (i + 1) + "/" + n, null); + String filename = names != null && i < names.size() ? names.get(i) : null; + byte[] bytes = bytesList.get(i); + bytesList.set(i, null); + Map one = country + ? productService.previewImportCountryCatalog(bytes, filename, countryCode) + : productService.previewImportCatalog(bytes, filename); + merged = productService.mergeCatalogPreview(merged, one); + } + return merged; + } + + /** Save preview products in parallel. Each product is its own transaction. */ + @Async("threadPoolTaskExecutor") + public void runApplyPreview(String taskId, CatalogPreviewApplyRequest request) + { + ExecutorService pool = null; + try + { + Map meta = new HashMap(); + if (request.getProducts() != null) + { + for (BmsProduct p : request.getProducts()) + { + if (p != null && StringUtils.isNotEmpty(p.getProductCode())) + { + meta.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + } + List>> jobs = + new ArrayList>>(); + Map> byCode = request.getDetailsByProductCode(); + if (byCode != null) + { + for (Map.Entry> e : byCode.entrySet()) + { + if (e != null && StringUtils.isNotEmpty(e.getKey()) && e.getValue() != null && !e.getValue().isEmpty()) + { + jobs.add(e); + } + } + } + int n = jobs.size(); + if (n == 0) + { + put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, "\u5df2\u4fdd\u5b58", null); + return; + } + put(taskId, BmsProductImportTask.STATUS_RUNNING, 5, "\u6b63\u5728\u4fdd\u5b58 0/" + n, null); + Long psvId = request.getPsvId(); + AtomicInteger done = new AtomicInteger(); + pool = Executors.newFixedThreadPool(Math.min(4, n)); + List> futures = new ArrayList>(); + for (Map.Entry> e : jobs) + { + final String code = e.getKey().trim().toUpperCase(Locale.ROOT); + final List details = e.getValue(); + final BmsProduct product = meta.get(code); + futures.add(pool.submit(new Runnable() + { + @Override + public void run() + { + applyWithDeadlockRetry(new Runnable() + { + @Override + public void run() + { + productService.applyOneCatalogProduct(psvId, code, product, details); + } + }); + int d = done.incrementAndGet(); + put(taskId, BmsProductImportTask.STATUS_RUNNING, 5 + (90 * d / n), + "\u6b63\u5728\u4fdd\u5b58 " + d + "/" + n, null); + } + })); + } + for (Future future : futures) + { + future.get(); + } + put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, "\u5df2\u4fdd\u5b58\u5230\u5f85\u751f\u6548\u8349\u7a3f", null); + log.info("async product apply preview done, taskId={}, products={}", taskId, Integer.valueOf(n)); + } + catch (Exception e) + { + log.error("async product apply preview failed, taskId={}", taskId, e); + Throwable cause = e.getCause() == null ? e : e.getCause(); + String msg = cause.getMessage(); + if (StringUtils.isEmpty(msg)) + { + msg = cause.getClass().getSimpleName(); + } + put(taskId, BmsProductImportTask.STATUS_FAIL, 100, "failed", msg); + } + finally + { + if (pool != null) + { + pool.shutdownNow(); + } + } + } + + /** InnoDB rolls the product transaction back on deadlock; start a new one. */ + private void applyWithDeadlockRetry(Runnable action) + { + int max = 5; + for (int attempt = 1; ; attempt++) + { + try + { + action.run(); + return; + } + catch (RuntimeException ex) + { + if (attempt >= max || !isDeadlock(ex)) + { + throw ex; + } + log.warn("product catalog save deadlock, retry {}/{}", Integer.valueOf(attempt), Integer.valueOf(max)); + try + { + Thread.sleep(50L * attempt); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + throw ex; + } + } + } + } + + private static boolean isDeadlock(Throwable ex) + { + Throwable cur = ex; + while (cur != null) + { + if (cur instanceof org.springframework.dao.PessimisticLockingFailureException) + { + return true; + } + String name = cur.getClass().getName(); + if (name.indexOf("MySQLTransactionRollbackException") >= 0) + { + return true; + } + String msg = cur.getMessage(); + if (msg != null && msg.indexOf("Deadlock found when trying to get lock") >= 0) + { + return true; + } + cur = cur.getCause(); + } + return false; + } + public void put(String taskId, String status, int percent, String message, String error) { BmsProductImportTask task = new BmsProductImportTask(); @@ -80,9 +304,27 @@ public class BmsProductImportAsyncRunner redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES); } + private void putSuccess(String taskId, String message, Object result) + { + if (result != null) + { + previewResults.put(taskId, result); + } + put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, message, null); + } + public BmsProductImportTask get(String taskId) { - return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId); + BmsProductImportTask task = redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId); + if (task != null) + { + Object result = previewResults.get(taskId); + if (result != null) + { + task.setResult(result); + } + } + return task; } private static String errorMessage(Exception e) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/BmsRedlineProductImportAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/BmsRedlineProductImportAsyncRunner.java index d85f10fc..34969250 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/task/BmsRedlineProductImportAsyncRunner.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/BmsRedlineProductImportAsyncRunner.java @@ -1,7 +1,17 @@ package com.bms.price.task; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -9,11 +19,14 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import com.bms.common.core.redis.RedisCache; import com.bms.common.utils.StringUtils; +import com.bms.price.domain.BmsRedlineProduct; +import com.bms.price.domain.BmsRedlineProductCountry; import com.bms.price.domain.BmsRedlineProductImportTask; +import com.bms.price.domain.RedlineCatalogPreviewApplyRequest; import com.bms.price.service.IBmsRedlineProductService; /** - * Async runner for product Excel import (avoids HTTP timeout on large workbooks). + * Async runner for redline Excel import / preview (avoids HTTP timeout on large workbooks). */ @Component public class BmsRedlineProductImportAsyncRunner @@ -23,6 +36,8 @@ public class BmsRedlineProductImportAsyncRunner public static final String CACHE_KEY_PREFIX = "price:redline:import:task:"; public static final int TTL_MINUTES = 60; + private final ConcurrentHashMap previewResults = new ConcurrentHashMap(); + @Autowired private IBmsRedlineProductService productService; @@ -40,11 +55,11 @@ public class BmsRedlineProductImportAsyncRunner String message = productService.importProductExcel(bytes, filename, updateSupport, operName, taskId, effectiveDate); put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, message, null); - log.info("async product import done, taskId={}", taskId); + log.info("async redline import done, taskId={}", taskId); } catch (Exception e) { - log.error("async product import failed, taskId={}", taskId, e); + log.error("async redline import failed, taskId={}", taskId, e); put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e)); } } @@ -60,15 +75,224 @@ public class BmsRedlineProductImportAsyncRunner String message = productService.importCountryProductExcel(bytes, filename, countryCode, operName, taskId, effectiveDate); put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, message, null); - log.info("async product country import done, taskId={}, country={}", taskId, countryCode); + log.info("async redline country import done, taskId={}, country={}", taskId, countryCode); } catch (Exception e) { - log.error("async product country import failed, taskId={}, country={}", taskId, countryCode, e); + log.error("async redline country import failed, taskId={}, country={}", taskId, countryCode, e); put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e)); } } + /** Background multi-country parse-only preview. One task merges up to 5 workbooks. */ + @Async("threadPoolTaskExecutor") + public void runPreviewAll(String taskId, List bytesList, List names) + { + try + { + Map merged = parseCatalogFiles(taskId, bytesList, names, false, null); + putSuccess(taskId, "preview ok", merged); + log.info("async redline preview done, taskId={}, files={}", taskId, Integer.valueOf(bytesList == null ? 0 : bytesList.size())); + } + catch (Exception e) + { + log.error("async redline preview failed, taskId={}", taskId, e); + previewResults.remove(taskId); + put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e)); + } + } + + /** Background single-country parse-only preview. One task merges up to 5 workbooks. */ + @Async("threadPoolTaskExecutor") + public void runPreviewCountry(String taskId, List bytesList, List names, String countryCode) + { + try + { + Map merged = parseCatalogFiles(taskId, bytesList, names, true, countryCode); + putSuccess(taskId, "preview ok", merged); + log.info("async redline country preview done, taskId={}, country={}, files={}", taskId, countryCode, + Integer.valueOf(bytesList == null ? 0 : bytesList.size())); + } + catch (Exception e) + { + log.error("async redline country preview failed, taskId={}, country={}", taskId, countryCode, e); + previewResults.remove(taskId); + put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e)); + } + } + + /** Parse workbooks together. Merge stays in upload order so a later file wins. */ + private Map parseCatalogFiles(String taskId, List bytesList, List names, + boolean country, String countryCode) throws Exception + { + int n = bytesList == null ? 0 : bytesList.size(); + if (n == 0) + { + return null; + } + Map merged = null; + for (int i = 0; i < n; i++) + { + put(taskId, BmsRedlineProductImportTask.STATUS_RUNNING, 10 + (70 * i / n), "parsing " + (i + 1) + "/" + n, null); + String filename = names != null && i < names.size() ? names.get(i) : null; + byte[] bytes = bytesList.get(i); + bytesList.set(i, null); + Map one = country + ? productService.previewImportCountryCatalog(bytes, filename, countryCode) + : productService.previewImportCatalog(bytes, filename); + merged = productService.mergeCatalogPreview(merged, one); + } + return merged; + } + + /** Save preview products in parallel. Each product is its own transaction. */ + @Async("threadPoolTaskExecutor") + public void runApplyPreview(String taskId, RedlineCatalogPreviewApplyRequest request) + { + ExecutorService pool = null; + try + { + Map meta = new HashMap(); + if (request.getProducts() != null) + { + for (BmsRedlineProduct p : request.getProducts()) + { + if (p != null && StringUtils.isNotEmpty(p.getProductCode())) + { + meta.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + } + List>> jobs = + new ArrayList>>(); + Map> byCode = request.getDetailsByProductCode(); + if (byCode != null) + { + for (Map.Entry> e : byCode.entrySet()) + { + if (e != null && StringUtils.isNotEmpty(e.getKey()) && e.getValue() != null && !e.getValue().isEmpty()) + { + jobs.add(e); + } + } + } + int n = jobs.size(); + if (n == 0) + { + put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, "\u5df2\u4fdd\u5b58", null); + return; + } + put(taskId, BmsRedlineProductImportTask.STATUS_RUNNING, 5, "\u6b63\u5728\u4fdd\u5b58 0/" + n, null); + Long psvId = request.getPsvId(); + AtomicInteger done = new AtomicInteger(); + pool = Executors.newFixedThreadPool(Math.min(4, n)); + List> futures = new ArrayList>(); + for (Map.Entry> e : jobs) + { + final String code = e.getKey().trim().toUpperCase(Locale.ROOT); + final List details = e.getValue(); + final BmsRedlineProduct product = meta.get(code); + futures.add(pool.submit(new Runnable() + { + @Override + public void run() + { + applyWithDeadlockRetry(new Runnable() + { + @Override + public void run() + { + productService.applyOneCatalogProduct(psvId, code, product, details); + } + }); + int d = done.incrementAndGet(); + put(taskId, BmsRedlineProductImportTask.STATUS_RUNNING, 5 + (90 * d / n), + "\u6b63\u5728\u4fdd\u5b58 " + d + "/" + n, null); + } + })); + } + for (Future future : futures) + { + future.get(); + } + put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, "\u5df2\u4fdd\u5b58\u5230\u5f85\u751f\u6548\u8349\u7a3f", null); + log.info("async redline apply preview done, taskId={}, products={}", taskId, Integer.valueOf(n)); + } + catch (Exception e) + { + log.error("async redline apply preview failed, taskId={}", taskId, e); + Throwable cause = e.getCause() == null ? e : e.getCause(); + String msg = cause.getMessage(); + if (StringUtils.isEmpty(msg)) + { + msg = cause.getClass().getSimpleName(); + } + put(taskId, BmsRedlineProductImportTask.STATUS_FAIL, 100, "failed", msg); + } + finally + { + if (pool != null) + { + pool.shutdownNow(); + } + } + } + + /** InnoDB rolls the product transaction back on deadlock; start a new one. */ + private void applyWithDeadlockRetry(Runnable action) + { + int max = 5; + for (int attempt = 1; ; attempt++) + { + try + { + action.run(); + return; + } + catch (RuntimeException ex) + { + if (attempt >= max || !isDeadlock(ex)) + { + throw ex; + } + log.warn("redline catalog save deadlock, retry {}/{}", Integer.valueOf(attempt), Integer.valueOf(max)); + try + { + Thread.sleep(50L * attempt); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + throw ex; + } + } + } + } + + private static boolean isDeadlock(Throwable ex) + { + Throwable cur = ex; + while (cur != null) + { + if (cur instanceof org.springframework.dao.PessimisticLockingFailureException) + { + return true; + } + String name = cur.getClass().getName(); + if (name.indexOf("MySQLTransactionRollbackException") >= 0) + { + return true; + } + String msg = cur.getMessage(); + if (msg != null && msg.indexOf("Deadlock found when trying to get lock") >= 0) + { + return true; + } + cur = cur.getCause(); + } + return false; + } + public void put(String taskId, String status, int percent, String message, String error) { BmsRedlineProductImportTask task = new BmsRedlineProductImportTask(); @@ -80,9 +304,27 @@ public class BmsRedlineProductImportAsyncRunner redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES); } + private void putSuccess(String taskId, String message, Object result) + { + if (result != null) + { + previewResults.put(taskId, result); + } + put(taskId, BmsRedlineProductImportTask.STATUS_SUCCESS, 100, message, null); + } + public BmsRedlineProductImportTask get(String taskId) { - return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId); + BmsRedlineProductImportTask task = redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId); + if (task != null) + { + Object result = previewResults.get(taskId); + if (result != null) + { + task.setResult(result); + } + } + return task; } private static String errorMessage(Exception e) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java index 2f69d5f4..d1249bb4 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java @@ -116,6 +116,38 @@ public class PrcPriceSetSaveAsyncRunner } } + /** Import up to 5 workbooks in one task. Each file commits before the next. */ + @Async("threadPoolTaskExecutor") + public void runImportPricesFiles(String taskId, Long priceSetId, Long psvId, List bytesList, List names, + String operName, boolean updateOnly, boolean replaceByProductCountry, String countryCode) + { + try + { + int n = bytesList == null ? 0 : bytesList.size(); + String message = null; + for (int i = 0; i < n; i++) + { + int pct = 10 + (80 * i / Math.max(n, 1)); + put(taskId, PrcPriceSetSaveTask.STATUS_RUNNING, pct, "parsing " + (i + 1) + "/" + n, priceSetId, null); + String filename = names != null && i < names.size() ? names.get(i) : null; + message = priceSetService.importDetailPrices(priceSetId, psvId, bytesList.get(i), filename, operName, taskId, + updateOnly, replaceByProductCountry, countryCode); + } + put(taskId, PrcPriceSetSaveTask.STATUS_SUCCESS, 100, message, priceSetId, null); + log.info("async import prices files done, taskId={}, priceSetId={}, files={}", taskId, priceSetId, Integer.valueOf(n)); + } + catch (Exception e) + { + log.error("async import prices files failed, taskId={}, priceSetId={}", taskId, priceSetId, e); + String msg = e.getMessage(); + if (StringUtils.isEmpty(msg)) + { + msg = e.getClass().getSimpleName(); + } + put(taskId, PrcPriceSetSaveTask.STATUS_FAIL, 100, "failed", priceSetId, msg); + } + } + /** Background pull of product library into package version details. */ @Async("threadPoolTaskExecutor") public void runPullLibrary(String taskId, Long priceSetId, Long psvId, boolean force, String operName) diff --git a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml index 5da98ca7..107a6826 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml @@ -29,21 +29,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select c.detail_id, c.product_id, c.product_code, c.country_name, c.country_code, + select c.psd_id as detail_id, c.psp_id as product_id, c.product_code, c.country_name, c.country_code, c.zone_id, c.zone_code, c.zone_name, c.weight_min, c.weight_max, - c.freight_price, c.registration_fee, c.size_limit, c.last_mile, c.lead_time, - c.effective_date, c.expire_date, c.status, c.is_del, c.create_time, c.update_time - from product_country c + c.fv_value as freight_price, c.fv_registration_fee as registration_fee, + c.size_limit, c.last_mile, c.lead_time, + c.effective_date, c.expire_date, c.status, + ifnull(c.is_del, '0') as is_del, c.create_time, c.update_time + from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + + + + and v.price_set_id is null + + and v.effective_time <= #{bizDate} + and (v.expire_time is null or v.expire_time > #{bizDate}) + + + and v.expire_time is null + - insert into product_country ( - product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, + insert into prc_price_set_detail ( + price_type, psv_id, psp_id, product_code, product_name, + country_name, country_code, zone_id, zone_code, zone_name, weight_min, weight_max, - freight_price, registration_fee, size_limit, last_mile, lead_time, + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, effective_date, expire_date, status, is_del, create_time ) values ( - #{productId}, #{productCode}, #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName}, + 'PRODUCT', + IFNULL(#{psvId}, (select psv_id from prc_price_set_product where psp_id = #{productId} and ifnull(is_del, '0') = '0')), + #{productId}, #{productCode}, #{productName}, + #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName}, #{weightMin}, #{weightMax}, #{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime}, - #{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), IFNULL(#{isDel}, '0'), sysdate() + #{effectiveDate}, #{expireDate}, ifnull(#{status}, '0'), ifnull(#{isDel}, '0'), sysdate() ) + + insert into prc_price_set_detail ( + price_type, psv_id, psp_id, product_code, product_name, + country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, + effective_date, expire_date, status, is_del, create_time + ) values + + ( + 'PRODUCT', #{item.psvId}, #{item.productId}, #{item.productCode}, #{item.productName}, + #{item.countryName}, #{item.countryCode}, #{item.zoneId}, #{item.zoneCode}, #{item.zoneName}, + #{item.weightMin}, #{item.weightMax}, + #{item.freightPrice}, #{item.registrationFee}, #{item.sizeLimit}, #{item.lastMile}, #{item.leadTime}, + #{item.effectiveDate}, #{item.expireDate}, ifnull(#{item.status}, '0'), ifnull(#{item.isDel}, '0'), sysdate() + ) + + + + weight_max may be null (= open-ended). Version dates are source of truth for list. --> - update product_country - set weight_min = #{weightMin}, - weight_max = #{weightMax}, - freight_price = #{freightPrice}, - registration_fee = #{registrationFee}, - size_limit = #{sizeLimit}, - last_mile = #{lastMile}, - lead_time = #{leadTime}, - effective_date = #{effectiveDate}, - expire_date = #{expireDate}, - update_time = sysdate() - where detail_id = #{detailId} + update prc_price_set_detail + set weight_min = #{weightMin}, + weight_max = #{weightMax}, + fv_value = #{freightPrice}, + fv_registration_fee = #{registrationFee}, + size_limit = #{sizeLimit}, + last_mile = #{lastMile}, + lead_time = #{leadTime}, + effective_date = #{effectiveDate}, + expire_date = #{expireDate}, + update_time = sysdate() + where psd_id = #{detailId} + and price_type = 'PRODUCT' - update product_country - set product_id = #{productId}, - country_code = #{countryCode}, - zone_id = #{zoneId}, - zone_code = #{zoneCode}, - zone_name = #{zoneName}, - weight_min = #{weightMin}, - weight_max = #{weightMax}, - freight_price = #{freightPrice}, - registration_fee = #{registrationFee}, - size_limit = #{sizeLimit}, - last_mile = #{lastMile}, - lead_time = #{leadTime}, - effective_date = #{effectiveDate}, - expire_date = #{expireDate}, - status = IFNULL(#{status}, status), - update_time = sysdate() - where detail_id = #{detailId} + update prc_price_set_detail + set psp_id = #{productId}, + country_code = #{countryCode}, + zone_id = #{zoneId}, + zone_code = #{zoneCode}, + zone_name = #{zoneName}, + weight_min = #{weightMin}, + weight_max = #{weightMax}, + fv_value = #{freightPrice}, + fv_registration_fee = #{registrationFee}, + size_limit = #{sizeLimit}, + last_mile = #{lastMile}, + lead_time = #{leadTime}, + effective_date = #{effectiveDate}, + expire_date = #{expireDate}, + update_time = sysdate() + where psd_id = #{detailId} + and price_type = 'PRODUCT' - - update product_country + + update prc_price_set_detail set is_del = '1', update_time = sysdate() - where product_id = #{productId} + where psp_id = #{productId} + and price_type = 'PRODUCT' and ifnull(is_del, '0') = '0' - + - - update product_country + + update prc_price_set_detail set is_del = '1', update_time = sysdate() where product_code = #{productCode} + and price_type = 'PRODUCT' and ifnull(is_del, '0') = '0' - + - update product_country + update prc_price_set_detail set is_del = '1', update_time = sysdate() - where ifnull(is_del, '0') = '0' + where price_type = 'PRODUCT' + and ifnull(is_del, '0') = '0' and product_code in #{code} @@ -159,92 +202,78 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - update product_country - set is_del = '1', update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' + update prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + set c.is_del = '1', c.update_time = sysdate() + where c.product_code = #{productCode} + and c.price_type = 'PRODUCT' + and ifnull(c.is_del, '0') = '0' and ( - (#{effectiveDate} is null and effective_date is null) - or effective_date = #{effectiveDate} + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} ) + - update product_country - set expire_date = #{newEffectiveDate}, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{newEffectiveDate}) + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update product_country - set expire_date = null, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date = #{expireDate} + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update product_country - set expire_date = #{newEffectiveDate}, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{newEffectiveDate}) - - - and upper(ifnull(country_code,'')) = upper(#{countryCode}) - - - and country_name = #{countryName} - - + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update product_country - set status = case - when expire_date is not null and expire_date <= now() then '2' - when effective_date is null or effective_date > now() then '0' - else '1' - end - where ifnull(is_del, '0') = '0' + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update product_country - set is_del = '1', update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null + update prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + set c.is_del = '1', c.update_time = sysdate() + where c.product_code = #{productCode} + and c.price_type = 'PRODUCT' + and ifnull(c.is_del, '0') = '0' + and v.expire_time is null and ( - (#{effectiveDate} is null and effective_date is null) - or effective_date = #{effectiveDate} + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} ) - - update product_country - set is_del = '1', update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and ( - (#{effectiveDate} is null and effective_date is null) - or effective_date = #{effectiveDate} - ) + + delete from prc_price_set_detail + where psp_id = #{productId} + and price_type = 'PRODUCT' + + + + delete from prc_price_set_detail + where psp_id = #{productId} + and price_type = 'PRODUCT' and upper(ifnull(country_code,'')) = upper(#{countryCode}) @@ -253,14 +282,58 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and country_name = #{countryName} - + + + + + delete c from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.product_code = #{productCode} + and c.price_type = 'PRODUCT' + and v.status in ('0', '3') + and ( + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} + ) + + + and upper(ifnull(c.country_code,'')) = upper(#{countryCode}) + + + and c.country_name = #{countryName} + + + + + + delete r from prc_surcharge_rel r + inner join prc_price_set_detail c on c.psd_id = r.detail_id + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.product_code = #{productCode} + and c.price_type = 'PRODUCT' + and v.status in ('0', '3') + and ( + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} + ) + + + and upper(ifnull(c.country_code,'')) = upper(#{countryCode}) + + + and c.country_name = #{countryName} + + + + - insert into product_country ( - product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, - weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, - effective_date, expire_date, status, is_del, create_time + insert into prc_price_set_detail ( + price_type, psv_id, psp_id, product_code, product_name, + country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, fv_value, fv_registration_fee, size_limit, last_mile, lead_time, + effective_date, expire_date, is_del, create_time ) select - #{productId}, product_code, country_name, country_code, zone_id, zone_code, zone_name, - weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, - #{effectiveDate}, null, IFNULL(#{status}, '0'), '0', sysdate() - from product_country - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{effectiveDate}) - order by detail_id asc + 'PRODUCT', + (select psv_id from prc_price_set_product where psp_id = #{productId} and ifnull(is_del, '0') = '0'), + #{productId}, + c.product_code, c.product_name, + c.country_name, c.country_code, c.zone_id, c.zone_code, c.zone_name, + c.weight_min, c.weight_max, c.fv_value, c.fv_registration_fee, c.size_limit, c.last_mile, c.lead_time, + #{effectiveDate}, null, '0', sysdate() + from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.price_type = 'PRODUCT' + and ifnull(c.is_del, '0') = '0' + and c.product_code = #{productCode} + and v.price_set_id is null + and v.status = '1' + and c.psv_id = ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'PRODUCT' and v2.price_set_id is null and v2.status = '1' + order by v2.version_seq desc, v2.psv_id desc + limit 1 + ) + and c.psv_id <> ifnull((select psv_id from prc_price_set_product where psp_id = #{productId} and ifnull(is_del, '0') = '0'), -1) + and (v.effective_time is null or v.effective_time < #{effectiveDate}) + order by c.psd_id asc diff --git a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductMapper.xml index 46159be8..24e368a6 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductMapper.xml @@ -6,6 +6,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -18,9 +20,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - @@ -29,69 +28,110 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'PRODUCT' and v2.price_set_id is null + order by case when v2.status = '1' then 0 when v2.status = '0' then 1 else 2 end, + v2.version_seq desc, v2.psv_id desc + limit 1 + ) + + - select p.product_id, p.product_code, p.product_name, p.service_level, p.cargo_attr, - p.dest_region, p.update_note, p.tax_mode, p.effective_date, p.expire_date, p.status, p.is_del, - p.source_sheet, p.pef_id, p.remark, p.tms_id, p.created_by, p.create_time, p.updated_by, p.update_time, - (select count(distinct c.country_name) from product_country c + select p.psp_id as product_id, p.psv_id, p.price_type, p.product_code, p.product_name, p.service_level, p.cargo_attr, + p.dest_region, p.update_note, p.tax_mode, + p.effective_time as effective_date, p.expire_time as expire_date, p.status, + ifnull(p.is_del, '0') as is_del, + p.source_sheet, null as remark, null as tms_id, + v.created_by, p.create_time, v.updated_by, p.update_time, + (select count(distinct ifnull(c.country_code, c.country_name)) from prc_price_set_detail c where ifnull(c.is_del, '0') = '0' - and (c.product_id = p.product_id - or (c.product_id is null and c.product_code = p.product_code - and ( - (p.effective_date is null and c.effective_date is null) - or c.effective_date = p.effective_date - ))) + and (c.psp_id = p.psp_id or (c.psv_id = p.psv_id and c.product_code = p.product_code)) ) as country_count, (select count(*) from prc_surcharge_rel r inner join prc_surcharge s on s.sc_id = r.sc_id and ifnull(s.is_del, '0') = '0' where ifnull(r.is_del, '0') = '0' - and r.product_id = p.product_id and r.price_type = 'PRODUCT') as surcharge_count - from product p + and r.product_id = p.psp_id and r.price_type = 'PRODUCT') as surcharge_count + from prc_price_set_product p + inner join prc_price_set_version v on v.psv_id = p.psv_id + + - insert into product ( - product_code, - product_name, - service_level, - cargo_attr, - dest_region, - update_note, - tax_mode, - effective_date, - expire_date, - status, - is_del, - source_sheet, - pef_id, - remark, - tms_id, - created_by, - create_time + insert into prc_price_set_product ( + price_type, psv_id, product_code, product_name, service_level, cargo_attr, + dest_region, update_note, tax_mode, effective_time, expire_time, status, source_sheet, is_del, create_time ) values ( - #{productCode}, - #{productName}, - #{serviceLevel}, - #{cargoAttr}, - #{destRegion}, - #{updateNote}, - #{taxMode}, - #{effectiveDate}, - #{expireDate}, - #{status}, - ifnull(#{isDel}, '0'), - #{sourceSheet}, - #{pefId}, - #{remark}, - #{tmsId}, - #{createBy}, - sysdate() + ifnull(#{priceType}, 'PRODUCT'), #{psvId}, #{productCode}, #{productName}, #{serviceLevel}, #{cargoAttr}, + #{destRegion}, #{updateNote}, ifnull(#{taxMode}, 'TAX_INCLUDED'), #{effectiveDate}, #{expireDate}, + ifnull(#{status}, '0'), #{sourceSheet}, + ifnull(#{isDel}, '0'), sysdate() ) - update product + update prc_price_set_product product_code = #{productCode}, product_name = #{productName}, @@ -187,60 +198,57 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" dest_region = #{destRegion}, update_note = #{updateNote}, tax_mode = #{taxMode}, - effective_date = #{effectiveDate}, - expire_date = #{expireDate}, + effective_time = #{effectiveDate}, + expire_time = #{expireDate}, status = #{status}, source_sheet = #{sourceSheet}, - pef_id = #{pefId}, - remark = #{remark}, - tms_id = #{tmsId}, - updated_by = #{updateBy}, update_time = sysdate() - where product_id = #{productId} + where psp_id = #{productId} - update product - set is_del = '1', - update_time = sysdate() - where ifnull(is_del, '0') = '0' - and product_id in + update prc_price_set_product + set is_del = '1', update_time = sysdate() + where price_type = 'PRODUCT' + and ifnull(is_del, '0') = '0' + and psp_id in #{productId} + - update product - set expire_date = #{newEffectiveDate}, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{newEffectiveDate}) - - and product_id <> #{excludeId} - + update prc_price_set_version + set expire_time = #{newEffectiveDate}, status = '2', update_time = sysdate() + where price_type = 'PRODUCT' + and price_set_id is null + and status = '1' + and (expire_time is null) + and (effective_time is null or effective_time < #{newEffectiveDate}) - update product - set expire_date = null, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date = #{expireDate} + update prc_price_set_version + set expire_time = null, status = '1', update_time = sysdate() + where price_type = 'PRODUCT' + and price_set_id is null + and expire_time = #{expireDate} - update product + update prc_price_set_version set status = case - when expire_date is not null and expire_date <= now() then '2' - when effective_date is null or effective_date > now() then '0' + when expire_time is not null and expire_time <= now() then '2' + when effective_time is not null and effective_time > now() then '3' + when effective_time is null then '0' else '1' - end - where ifnull(is_del, '0') = '0' + end, + update_time = sysdate() + where price_type = 'PRODUCT' + and price_set_id is null + and status in ('0','1','2','3') diff --git a/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductCountryMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductCountryMapper.xml index 45badd61..2a4db837 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductCountryMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductCountryMapper.xml @@ -29,21 +29,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select c.detail_id, c.product_id, c.product_code, c.country_name, c.country_code, + select c.psd_id as detail_id, c.psp_id as product_id, c.product_code, c.country_name, c.country_code, c.zone_id, c.zone_code, c.zone_name, c.weight_min, c.weight_max, - c.freight_price, c.registration_fee, c.size_limit, c.last_mile, c.lead_time, - c.effective_date, c.expire_date, c.status, c.is_del, c.create_time, c.update_time - from redline_product_country c + c.fv_value as freight_price, c.fv_registration_fee as registration_fee, + c.size_limit, c.last_mile, c.lead_time, + c.effective_date, c.expire_date, c.status, + ifnull(c.is_del, '0') as is_del, c.create_time, c.update_time + from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + + + + and v.price_set_id is null + + and v.effective_time <= #{bizDate} + and (v.expire_time is null or v.expire_time > #{bizDate}) + + + and v.expire_time is null + - insert into redline_product_country ( - product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, + insert into prc_price_set_detail ( + price_type, psv_id, psp_id, product_code, product_name, + country_name, country_code, zone_id, zone_code, zone_name, weight_min, weight_max, - freight_price, registration_fee, size_limit, last_mile, lead_time, + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, effective_date, expire_date, status, is_del, create_time ) values ( - #{productId}, #{productCode}, #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName}, + 'REDLINE', + IFNULL(#{psvId}, (select psv_id from prc_price_set_product where psp_id = #{productId} and ifnull(is_del, '0') = '0')), + #{productId}, #{productCode}, #{productName}, + #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName}, #{weightMin}, #{weightMax}, #{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime}, - #{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), IFNULL(#{isDel}, '0'), sysdate() + #{effectiveDate}, #{expireDate}, ifnull(#{status}, '0'), ifnull(#{isDel}, '0'), sysdate() ) + + insert into prc_price_set_detail ( + price_type, psv_id, psp_id, product_code, product_name, + country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, + effective_date, expire_date, status, is_del, create_time + ) values + + ( + 'REDLINE', #{item.psvId}, #{item.productId}, #{item.productCode}, #{item.productName}, + #{item.countryName}, #{item.countryCode}, #{item.zoneId}, #{item.zoneCode}, #{item.zoneName}, + #{item.weightMin}, #{item.weightMax}, + #{item.freightPrice}, #{item.registrationFee}, #{item.sizeLimit}, #{item.lastMile}, #{item.leadTime}, + #{item.effectiveDate}, #{item.expireDate}, ifnull(#{item.status}, '0'), ifnull(#{item.isDel}, '0'), sysdate() + ) + + + + weight_max may be null (= open-ended). Version dates are source of truth for list. --> - update redline_product_country - set weight_min = #{weightMin}, - weight_max = #{weightMax}, - freight_price = #{freightPrice}, - registration_fee = #{registrationFee}, - size_limit = #{sizeLimit}, - last_mile = #{lastMile}, - lead_time = #{leadTime}, - effective_date = #{effectiveDate}, - expire_date = #{expireDate}, - update_time = sysdate() - where detail_id = #{detailId} + update prc_price_set_detail + set weight_min = #{weightMin}, + weight_max = #{weightMax}, + fv_value = #{freightPrice}, + fv_registration_fee = #{registrationFee}, + size_limit = #{sizeLimit}, + last_mile = #{lastMile}, + lead_time = #{leadTime}, + effective_date = #{effectiveDate}, + expire_date = #{expireDate}, + update_time = sysdate() + where psd_id = #{detailId} + and price_type = 'REDLINE' - update redline_product_country - set product_id = #{productId}, - country_code = #{countryCode}, - zone_id = #{zoneId}, - zone_code = #{zoneCode}, - zone_name = #{zoneName}, - weight_min = #{weightMin}, - weight_max = #{weightMax}, - freight_price = #{freightPrice}, - registration_fee = #{registrationFee}, - size_limit = #{sizeLimit}, - last_mile = #{lastMile}, - lead_time = #{leadTime}, - effective_date = #{effectiveDate}, - expire_date = #{expireDate}, - status = IFNULL(#{status}, status), - update_time = sysdate() - where detail_id = #{detailId} + update prc_price_set_detail + set psp_id = #{productId}, + country_code = #{countryCode}, + zone_id = #{zoneId}, + zone_code = #{zoneCode}, + zone_name = #{zoneName}, + weight_min = #{weightMin}, + weight_max = #{weightMax}, + fv_value = #{freightPrice}, + fv_registration_fee = #{registrationFee}, + size_limit = #{sizeLimit}, + last_mile = #{lastMile}, + lead_time = #{leadTime}, + effective_date = #{effectiveDate}, + expire_date = #{expireDate}, + update_time = sysdate() + where psd_id = #{detailId} + and price_type = 'REDLINE' - - update redline_product_country + + update prc_price_set_detail set is_del = '1', update_time = sysdate() - where product_id = #{productId} + where psp_id = #{productId} + and price_type = 'REDLINE' and ifnull(is_del, '0') = '0' - + - - update redline_product_country + + update prc_price_set_detail set is_del = '1', update_time = sysdate() where product_code = #{productCode} + and price_type = 'REDLINE' and ifnull(is_del, '0') = '0' - + - update redline_product_country + update prc_price_set_detail set is_del = '1', update_time = sysdate() - where ifnull(is_del, '0') = '0' + where price_type = 'REDLINE' + and ifnull(is_del, '0') = '0' and product_code in #{code} @@ -159,92 +202,78 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - update redline_product_country - set is_del = '1', update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' + update prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + set c.is_del = '1', c.update_time = sysdate() + where c.product_code = #{productCode} + and c.price_type = 'REDLINE' + and ifnull(c.is_del, '0') = '0' and ( - (#{effectiveDate} is null and effective_date is null) - or effective_date = #{effectiveDate} + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} ) + - update redline_product_country - set expire_date = null, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date = #{expireDate} + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update redline_product_country - set expire_date = #{newEffectiveDate}, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{newEffectiveDate}) + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update redline_product_country - set expire_date = #{newEffectiveDate}, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{newEffectiveDate}) - - - and upper(ifnull(country_code,'')) = upper(#{countryCode}) - - - and country_name = #{countryName} - - + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update redline_product_country - set status = case - when expire_date is not null and expire_date <= now() then '2' - when effective_date is null or effective_date > now() then '0' - else '1' - end - where ifnull(is_del, '0') = '0' + update prc_price_set_version + set update_time = update_time + where 1 = 0 - update redline_product_country - set is_del = '1', update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null + update prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + set c.is_del = '1', c.update_time = sysdate() + where c.product_code = #{productCode} + and c.price_type = 'REDLINE' + and ifnull(c.is_del, '0') = '0' + and v.expire_time is null and ( - (#{effectiveDate} is null and effective_date is null) - or effective_date = #{effectiveDate} + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} ) - - update redline_product_country - set is_del = '1', update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and ( - (#{effectiveDate} is null and effective_date is null) - or effective_date = #{effectiveDate} - ) + + delete from prc_price_set_detail + where psp_id = #{productId} + and price_type = 'REDLINE' + + + + delete from prc_price_set_detail + where psp_id = #{productId} + and price_type = 'REDLINE' and upper(ifnull(country_code,'')) = upper(#{countryCode}) @@ -253,14 +282,58 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and country_name = #{countryName} - + + + + + delete c from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.product_code = #{productCode} + and c.price_type = 'REDLINE' + and v.status in ('0', '3') + and ( + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} + ) + + + and upper(ifnull(c.country_code,'')) = upper(#{countryCode}) + + + and c.country_name = #{countryName} + + + + + + delete r from prc_surcharge_rel r + inner join prc_price_set_detail c on c.psd_id = r.detail_id + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.product_code = #{productCode} + and c.price_type = 'REDLINE' + and v.status in ('0', '3') + and ( + (#{effectiveDate} is null and v.effective_time is null) + or v.effective_time = #{effectiveDate} + ) + + + and upper(ifnull(c.country_code,'')) = upper(#{countryCode}) + + + and c.country_name = #{countryName} + + + + - insert into redline_product_country ( - product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, - weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, - effective_date, expire_date, status, is_del, create_time + insert into prc_price_set_detail ( + price_type, psv_id, psp_id, product_code, product_name, + country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, fv_value, fv_registration_fee, size_limit, last_mile, lead_time, + effective_date, expire_date, is_del, create_time ) select - #{productId}, product_code, country_name, country_code, zone_id, zone_code, zone_name, - weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, - #{effectiveDate}, null, IFNULL(#{status}, '0'), '0', sysdate() - from redline_product_country - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{effectiveDate}) - order by detail_id asc + 'REDLINE', + (select psv_id from prc_price_set_product where psp_id = #{productId} and ifnull(is_del, '0') = '0'), + #{productId}, + c.product_code, c.product_name, + c.country_name, c.country_code, c.zone_id, c.zone_code, c.zone_name, + c.weight_min, c.weight_max, c.fv_value, c.fv_registration_fee, c.size_limit, c.last_mile, c.lead_time, + #{effectiveDate}, null, '0', sysdate() + from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.price_type = 'REDLINE' + and ifnull(c.is_del, '0') = '0' + and c.product_code = #{productCode} + and v.price_set_id is null + and v.status = '1' + and c.psv_id = ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'REDLINE' and v2.price_set_id is null and v2.status = '1' + order by v2.version_seq desc, v2.psv_id desc + limit 1 + ) + and c.psv_id <> ifnull((select psv_id from prc_price_set_product where psp_id = #{productId} and ifnull(is_del, '0') = '0'), -1) + and (v.effective_time is null or v.effective_time < #{effectiveDate}) + order by c.psd_id asc diff --git a/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductMapper.xml index 7ccf2a1b..6ef3df76 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/BmsRedlineProductMapper.xml @@ -6,6 +6,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -18,9 +20,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - @@ -29,69 +28,110 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'REDLINE' and v2.price_set_id is null + order by case when v2.status = '1' then 0 when v2.status = '0' then 1 else 2 end, + v2.version_seq desc, v2.psv_id desc + limit 1 + ) + + - select p.product_id, p.product_code, p.product_name, p.service_level, p.cargo_attr, - p.dest_region, p.update_note, p.tax_mode, p.effective_date, p.expire_date, p.status, p.is_del, - p.source_sheet, p.pef_id, p.remark, p.tms_id, p.created_by, p.create_time, p.updated_by, p.update_time, - (select count(distinct c.country_name) from redline_product_country c + select p.psp_id as product_id, p.psv_id, p.price_type, p.product_code, p.product_name, p.service_level, p.cargo_attr, + p.dest_region, p.update_note, p.tax_mode, + p.effective_time as effective_date, p.expire_time as expire_date, p.status, + ifnull(p.is_del, '0') as is_del, + p.source_sheet, null as remark, null as tms_id, + v.created_by, p.create_time, v.updated_by, p.update_time, + (select count(distinct ifnull(c.country_code, c.country_name)) from prc_price_set_detail c where ifnull(c.is_del, '0') = '0' - and (c.product_id = p.product_id - or (c.product_id is null and c.product_code = p.product_code - and ( - (p.effective_date is null and c.effective_date is null) - or c.effective_date = p.effective_date - ))) + and (c.psp_id = p.psp_id or (c.psv_id = p.psv_id and c.product_code = p.product_code)) ) as country_count, (select count(*) from prc_surcharge_rel r inner join prc_surcharge s on s.sc_id = r.sc_id and ifnull(s.is_del, '0') = '0' where ifnull(r.is_del, '0') = '0' - and r.product_id = p.product_id and r.price_type = 'REDLINE') as surcharge_count - from redline_product p + and r.product_id = p.psp_id and r.price_type = 'REDLINE') as surcharge_count + from prc_price_set_product p + inner join prc_price_set_version v on v.psv_id = p.psv_id + + - insert into redline_product ( - product_code, - product_name, - service_level, - cargo_attr, - dest_region, - update_note, - tax_mode, - effective_date, - expire_date, - status, - is_del, - source_sheet, - pef_id, - remark, - tms_id, - created_by, - create_time + insert into prc_price_set_product ( + price_type, psv_id, product_code, product_name, service_level, cargo_attr, + dest_region, update_note, tax_mode, effective_time, expire_time, status, source_sheet, is_del, create_time ) values ( - #{productCode}, - #{productName}, - #{serviceLevel}, - #{cargoAttr}, - #{destRegion}, - #{updateNote}, - #{taxMode}, - #{effectiveDate}, - #{expireDate}, - #{status}, - ifnull(#{isDel}, '0'), - #{sourceSheet}, - #{pefId}, - #{remark}, - #{tmsId}, - #{createBy}, - sysdate() + ifnull(#{priceType}, 'REDLINE'), #{psvId}, #{productCode}, #{productName}, #{serviceLevel}, #{cargoAttr}, + #{destRegion}, #{updateNote}, ifnull(#{taxMode}, 'TAX_INCLUDED'), #{effectiveDate}, #{expireDate}, + ifnull(#{status}, '0'), #{sourceSheet}, + ifnull(#{isDel}, '0'), sysdate() ) - update redline_product + update prc_price_set_product product_code = #{productCode}, product_name = #{productName}, @@ -187,60 +198,57 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" dest_region = #{destRegion}, update_note = #{updateNote}, tax_mode = #{taxMode}, - effective_date = #{effectiveDate}, - expire_date = #{expireDate}, + effective_time = #{effectiveDate}, + expire_time = #{expireDate}, status = #{status}, source_sheet = #{sourceSheet}, - pef_id = #{pefId}, - remark = #{remark}, - tms_id = #{tmsId}, - updated_by = #{updateBy}, update_time = sysdate() - where product_id = #{productId} + where psp_id = #{productId} - update redline_product - set is_del = '1', - update_time = sysdate() - where ifnull(is_del, '0') = '0' - and product_id in + update prc_price_set_product + set is_del = '1', update_time = sysdate() + where price_type = 'REDLINE' + and ifnull(is_del, '0') = '0' + and psp_id in #{productId} - - update redline_product - set expire_date = null, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date = #{expireDate} + + + update prc_price_set_version + set expire_time = #{newEffectiveDate}, status = '2', update_time = sysdate() + where price_type = 'REDLINE' + and price_set_id is null + and status = '1' + and (expire_time is null) + and (effective_time is null or effective_time < #{newEffectiveDate}) - - update redline_product - set expire_date = #{newEffectiveDate}, - update_time = sysdate() - where product_code = #{productCode} - and ifnull(is_del, '0') = '0' - and expire_date is null - and (effective_date is null or effective_date < #{newEffectiveDate}) - - and product_id <> #{excludeId} - + + update prc_price_set_version + set expire_time = null, status = '1', update_time = sysdate() + where price_type = 'REDLINE' + and price_set_id is null + and expire_time = #{expireDate} - update redline_product + update prc_price_set_version set status = case - when expire_date is not null and expire_date <= now() then '2' - when effective_date is null or effective_date > now() then '0' + when expire_time is not null and expire_time <= now() then '2' + when effective_time is not null and effective_time > now() then '3' + when effective_time is null then '0' else '1' - end - where ifnull(is_del, '0') = '0' + end, + update_time = sysdate() + where price_type = 'REDLINE' + and price_set_id is null + and status in ('0','1','2','3') diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceExcelFileMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceExcelFileMapper.xml index c846c54d..d37c713b 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceExcelFileMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceExcelFileMapper.xml @@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select pef_id, excel_type, effective_time, file_name, file_path, file_size, content_type, + select pef_id, excel_type, psv_id, effective_time, file_name, file_path, file_size, content_type, file_extension, oss_id, sheet_names, remark, created_by, create_time, updated_by, update_time from prc_price_excel_file @@ -39,6 +40,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND excel_type = #{excelType} + + AND psv_id = #{psvId} + AND file_name like concat('%', #{fileName}, '%') @@ -46,20 +50,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by pef_id desc - + + where psv_id = #{psvId} + order by effective_time desc, pef_id desc insert into prc_price_excel_file ( excel_type, + psv_id, effective_time, file_name, file_path, @@ -73,6 +73,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" create_time ) values ( #{excelType}, + #{psvId}, #{effectiveTime}, #{fileName}, #{filePath}, @@ -91,6 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update prc_price_excel_file excel_type = #{excelType}, + psv_id = #{psvId}, effective_time = #{effectiveTime}, file_name = #{fileName}, file_path = #{filePath}, @@ -113,27 +115,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - + + delete from prc_price_excel_file where psv_id = #{psvId} + diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml index b91189fa..87de0218 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml @@ -6,7 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -24,6 +24,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + @@ -31,40 +34,68 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select psd_id, price_set_id, psv_id, psp_id, product_code, product_name, country_code, country_name, + select psd_id, price_type, psv_id, psp_id, product_code, product_name, country_code, country_name, zone_id, zone_code, zone_name, - zd_id, wsd_id, weight_label, weight_min, weight_max, product_country_id, fv_value, fv_registration_fee, - discount_value, round_mode, pr_id, effective_date, expire_date, create_time, update_time + zd_id, wsd_id, weight_label, weight_min, weight_max, product_country_id, + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, + discount_value, round_mode, pr_id, effective_date, expire_date, is_del, create_time, update_time from prc_price_set_detail + + and v.price_type = #{priceType} + and v.price_set_id is null + and v.effective_time is not null + and v.effective_time <= #{bizDate} + and (v.expire_time is null or v.expire_time > #{bizDate}) + + - insert ignore into prc_price_set_detail ( - price_set_id, psv_id, product_code, product_name, + price_type, psv_id, product_code, product_name, country_code, country_name, zone_id, zone_code, zone_name, weight_label, weight_min, weight_max, product_country_id, - fv_value, fv_registration_fee, round_mode, - effective_date, expire_date, create_time + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, round_mode, + effective_date, expire_date, is_del, create_time ) select - #{priceSetId}, + 'SALES', #{psvId}, - p.product_code, - p.product_name, + c.product_code, + c.product_name, c.country_code, c.country_name, c.zone_id, c.zone_code, c.zone_name, - case - when c.weight_min is null and c.weight_max is null then null - else concat( - ifnull(trim(trailing '0' from trim(trailing '.' from cast(c.weight_min as char))), ''), - '<W≤', - ifnull(trim(trailing '0' from trim(trailing '.' from cast(c.weight_max as char))), '') - ) - end, + c.weight_label, c.weight_min, c.weight_max, - c.detail_id, - c.freight_price, - c.registration_fee, + c.psd_id, + c.fv_value, + c.fv_registration_fee, + c.size_limit, + c.last_mile, + c.lead_time, 'HALF_UP', c.effective_date, c.expire_date, + '0', sysdate() - from product_country c - inner join product p on p.product_id = c.product_id - - and p.status = '1' - and ifnull(p.is_del, '0') = '0' - and ifnull(c.is_del, '0') = '0' - and p.product_id = ( - select max(p2.product_id) from product p2 - where p2.product_code = p.product_code and p2.status = '1' - and ifnull(p2.is_del, '0') = '0' - ) - and ifnull(c.status, '1') <> '2' - - - and (c.effective_date is null or date(c.effective_date) <= curdate()) - and (c.expire_date is null or date(c.expire_date) > curdate()) - - - and date(c.effective_date) <= date(#{asOf}) - and (c.expire_date is null or date(c.expire_date) > date(#{asOf})) - - - - order by p.product_code asc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc + from prc_price_set_detail c + inner join prc_price_set_version v on v.psv_id = c.psv_id + where c.price_type = 'PRODUCT' + and ifnull(c.is_del, '0') = '0' + and v.price_type = 'PRODUCT' + and v.price_set_id is null + and v.status = '1' + and v.psv_id = ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'PRODUCT' and v2.price_set_id is null and v2.status = '1' + order by v2.version_seq desc limit 1 + ) + order by c.product_code asc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.psd_id asc insert ignore into prc_price_set_detail ( - price_set_id, psv_id, product_code, product_name, round_mode, create_time + price_type, psv_id, product_code, product_name, round_mode, is_del, create_time ) select - #{priceSetId}, + 'SALES', #{psvId}, p.product_code, p.product_name, 'HALF_UP', + '0', sysdate() - from product p - - and p.status = '1' - and ifnull(p.is_del, '0') = '0' - and p.product_id = ( - select max(p2.product_id) from product p2 - where p2.product_code = p.product_code and p2.status = '1' - and ifnull(p2.is_del, '0') = '0' - ) - + from prc_price_set_product p + inner join prc_price_set_version v on v.psv_id = p.psv_id + where p.price_type = 'PRODUCT' + and ifnull(p.is_del, '0') = '0' + and v.price_type = 'PRODUCT' + and v.price_set_id is null + and v.status = '1' + and v.psv_id = ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'PRODUCT' and v2.price_set_id is null and v2.status = '1' + order by v2.version_seq desc limit 1 + ) order by p.product_code asc insert into prc_price_set_detail ( - price_set_id, + price_type, psv_id, psp_id, product_code, @@ -295,14 +308,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" product_country_id, fv_value, fv_registration_fee, + size_limit, + last_mile, + lead_time, discount_value, round_mode, pr_id, effective_date, expire_date, + is_del, create_time ) values ( - #{priceSetId}, + #{priceType}, #{psvId}, #{pspId}, #{productCode}, @@ -320,36 +337,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{productCountryId}, #{fvValue}, #{fvRegistrationFee}, + #{sizeLimit}, + #{lastMile}, + #{leadTime}, #{discountValue}, #{roundMode}, #{prId}, #{effectiveDate}, #{expireDate}, + ifnull(#{isDel}, '0'), sysdate() ) insert into prc_price_set_detail ( - price_set_id, psv_id, psp_id, product_code, product_name, + price_type, psv_id, psp_id, product_code, product_name, country_code, country_name, zone_id, zone_code, zone_name, zd_id, wsd_id, weight_label, weight_min, weight_max, product_country_id, - fv_value, fv_registration_fee, discount_value, round_mode, pr_id, - effective_date, expire_date, create_time + fv_value, fv_registration_fee, size_limit, last_mile, lead_time, + discount_value, round_mode, pr_id, + effective_date, expire_date, is_del, create_time ) values ( - #{item.priceSetId}, #{item.psvId}, #{item.pspId}, #{item.productCode}, #{item.productName}, + ifnull(#{item.priceType}, 'SALES'), #{item.psvId}, #{item.pspId}, #{item.productCode}, #{item.productName}, #{item.countryCode}, #{item.countryName}, #{item.zoneId}, #{item.zoneCode}, #{item.zoneName}, #{item.zdId}, #{item.wsdId}, #{item.weightLabel}, #{item.weightMin}, #{item.weightMax}, #{item.productCountryId}, - #{item.fvValue}, #{item.fvRegistrationFee}, #{item.discountValue}, + #{item.fvValue}, #{item.fvRegistrationFee}, #{item.sizeLimit}, #{item.lastMile}, #{item.leadTime}, + #{item.discountValue}, ifnull(#{item.roundMode}, 'HALF_UP'), #{item.prId}, - #{item.effectiveDate}, #{item.expireDate}, sysdate() + #{item.effectiveDate}, #{item.expireDate}, ifnull(#{item.isDel}, '0'), sysdate() ) - update prc_price_set_detail @@ -363,6 +385,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update prc_price_set_detail + price_type = #{priceType}, psv_id = #{psvId}, psp_id = #{pspId}, product_code = #{productCode}, @@ -380,6 +403,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" product_country_id = #{productCountryId}, fv_value = #{fvValue}, fv_registration_fee = #{fvRegistrationFee}, + size_limit = #{sizeLimit}, + last_mile = #{lastMile}, + lead_time = #{leadTime}, discount_value = #{discountValue}, round_mode = #{roundMode}, pr_id = #{prId}, @@ -390,31 +416,46 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where psd_id = #{psdId} - - delete from prc_price_set_detail where psd_id in + + update prc_price_set_detail + set is_del = '1', update_time = sysdate() + where ifnull(is_del, '0') = '0' + and psd_id in #{psdId} - + - - delete from prc_price_set_detail where price_set_id = #{priceSetId} - + + update prc_price_set_detail d + inner join prc_price_set_version v on v.psv_id = d.psv_id + set d.is_del = '1', d.update_time = sysdate() + where v.price_set_id = #{priceSetId} and v.price_type = 'SALES' + and ifnull(d.is_del, '0') = '0' + - + + update prc_price_set_detail + set is_del = '1', update_time = sysdate() + where psv_id = #{psvId} + and ifnull(is_del, '0') = '0' + + + delete from prc_price_set_detail where psv_id = #{psvId} - - delete from prc_price_set_detail where price_set_id = #{priceSetId} and psv_id is null - + + update prc_price_set_detail + set is_del = '1', update_time = sysdate() + where psv_id is null + and ifnull(is_del, '0') = '0' + @@ -447,6 +486,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select count(1) from prc_price_set_detail where psv_id = #{psvId} + and ifnull(is_del, '0') = '0' and product_code = #{productCode} and zone_code is not null and zone_code <> '' @@ -466,6 +506,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select count(1) from prc_price_set_detail where psv_id = #{psvId} + and ifnull(is_del, '0') = '0' and product_code = #{productCode} @@ -473,6 +514,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select count(1) from prc_price_set_detail where psv_id = #{psvId} + and ifnull(is_del, '0') = '0' and product_code = #{productCode} @@ -497,10 +539,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + select d.psd_id, d.price_type, d.psv_id, d.psp_id, d.product_code, d.product_name, + d.country_code, d.country_name, d.zone_id, d.zone_code, d.zone_name, + d.zd_id, d.wsd_id, d.weight_label, d.weight_min, d.weight_max, d.product_country_id, + d.fv_value, d.fv_registration_fee, d.size_limit, d.last_mile, d.lead_time, + d.discount_value, d.round_mode, d.pr_id, d.effective_date, d.expire_date, + d.create_time, d.update_time + from prc_price_set_detail d + inner join prc_price_set_version v on v.psv_id = d.psv_id + where d.price_type = #{priceType} + and ifnull(d.is_del, '0') = '0' + and d.product_code = #{productCode} + + + + and upper(ifnull(d.country_code,'')) = upper(#{countryCode}) + + + and d.country_name = #{countryName} + + + and 1 = 0 + + + + + and upper(ifnull(d.zone_code,'')) = upper(#{zoneCode}) + + + and (d.zone_code is null or d.zone_code = '') + + + and d.weight_min <= #{weight} + and d.weight_max >= #{weight} + order by v.effective_time desc, d.weight_min asc, d.psd_id asc + limit 1 + + + + + + + + diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml index bf60fab2..283a10b3 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml @@ -28,7 +28,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" s.set_type, s.default_discount, s.default_type, s.calculate_sign, s.status, s.tms_id, s.created_by, s.create_time, s.updated_by, s.update_time, - (select count(distinct p.product_code) from prc_price_set_product p where p.price_set_id = s.price_set_id) as product_count, + (select count(distinct p.product_code) + from prc_price_set_product p + inner join prc_price_set_version v on v.psv_id = p.psv_id + where v.price_set_id = s.price_set_id + and ifnull(p.is_del, '0') = '0') as product_count, (select count(1) from prc_customer_price_set c where c.price_set_id = s.price_set_id) as customer_count from prc_price_set s diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml index acf15419..1eed1186 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml @@ -6,7 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -15,29 +15,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + - + - select psp_id, price_set_id, psv_id, product_code, product_name, service_level, cargo_attr, - dest_region, update_note, tax_mode, source_product_id, source_sheet, pef_id, - create_time, update_time + select psp_id, price_type, psv_id, product_code, product_name, service_level, cargo_attr, + dest_region, update_note, tax_mode, effective_time, expire_time, status, + source_product_id, source_sheet, is_del, create_time, update_time from prc_price_set_product insert into prc_price_set_product ( - price_set_id, psv_id, product_code, product_name, service_level, cargo_attr, - dest_region, update_note, tax_mode, source_product_id, source_sheet, pef_id, create_time + price_type, psv_id, product_code, product_name, service_level, cargo_attr, + dest_region, update_note, tax_mode, source_product_id, source_sheet, is_del, create_time ) values ( - #{priceSetId}, #{psvId}, #{productCode}, #{productName}, #{serviceLevel}, #{cargoAttr}, - #{destRegion}, #{updateNote}, #{taxMode}, #{sourceProductId}, #{sourceSheet}, #{pefId}, sysdate() + #{priceType}, #{psvId}, #{productCode}, #{productName}, #{serviceLevel}, #{cargoAttr}, + #{destRegion}, #{updateNote}, #{taxMode}, #{sourceProductId}, #{sourceSheet}, + ifnull(#{isDel}, '0'), sysdate() ) update prc_price_set_product + price_type = #{priceType}, product_name = #{productName}, service_level = #{serviceLevel}, cargo_attr = #{cargoAttr}, @@ -72,42 +81,54 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" tax_mode = #{taxMode}, source_product_id = #{sourceProductId}, source_sheet = #{sourceSheet}, - pef_id = #{pefId}, update_time = sysdate() where psp_id = #{pspId} - + + update prc_price_set_product + set is_del = '1', update_time = sysdate() + where psv_id = #{psvId} + and ifnull(is_del, '0') = '0' + + + delete from prc_price_set_product where psv_id = #{psvId} - - delete from prc_price_set_product + + update prc_price_set_product + set is_del = '1', update_time = sysdate() where psv_id = #{psvId} and product_code = #{productCode} - + and ifnull(is_del, '0') = '0' + insert into prc_price_set_product ( - price_set_id, psv_id, product_code, product_name, service_level, cargo_attr, - dest_region, update_note, tax_mode, source_product_id, source_sheet, pef_id, create_time + price_type, psv_id, product_code, product_name, service_level, cargo_attr, + dest_region, update_note, tax_mode, effective_time, expire_time, status, + source_product_id, source_sheet, is_del, create_time ) select - price_set_id, #{newPsvId}, product_code, product_name, service_level, cargo_attr, - dest_region, update_note, tax_mode, source_product_id, source_sheet, pef_id, sysdate() - from prc_price_set_product - where psv_id = #{srcPsvId} - order by psp_id asc + p.price_type, #{newPsvId}, p.product_code, p.product_name, p.service_level, p.cargo_attr, + p.dest_region, p.update_note, p.tax_mode, v.effective_time, v.expire_time, v.status, + p.source_product_id, p.source_sheet, '0', sysdate() + from prc_price_set_product p + left join prc_price_set_version v on v.psv_id = #{newPsvId} + where p.psv_id = #{srcPsvId} + and ifnull(p.is_del, '0') = '0' + order by p.psp_id asc insert ignore into prc_price_set_product ( - price_set_id, psv_id, product_code, product_name, service_level, cargo_attr, - dest_region, update_note, tax_mode, source_product_id, source_sheet, create_time + price_type, psv_id, product_code, product_name, service_level, cargo_attr, + dest_region, update_note, tax_mode, source_product_id, source_sheet, is_del, create_time ) select - #{priceSetId}, + 'SALES', #{psvId}, p.product_code, p.product_name, @@ -116,16 +137,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" p.dest_region, p.update_note, ifnull(p.tax_mode, 'TAX_INCLUDED'), - p.product_id, + p.psp_id, p.source_sheet, + '0', sysdate() - from product p - where p.status = '1' + from prc_price_set_product p + inner join prc_price_set_version v on v.psv_id = p.psv_id + where p.price_type = 'PRODUCT' and ifnull(p.is_del, '0') = '0' - and p.product_id = ( - select max(p2.product_id) from product p2 - where p2.product_code = p.product_code and p2.status = '1' - and ifnull(p2.is_del, '0') = '0' + and v.price_type = 'PRODUCT' + and v.price_set_id is null + and v.status = '1' + and v.psv_id = ( + select v2.psv_id from prc_price_set_version v2 + where v2.price_type = 'PRODUCT' and v2.price_set_id is null and v2.status = '1' + order by v2.version_seq desc, v2.psv_id desc + limit 1 ) order by p.product_code asc @@ -133,7 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" @@ -163,19 +195,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" on sp.psv_id = d.psv_id and sp.product_code = d.product_code set d.psp_id = sp.psp_id where d.psv_id = #{psvId} + and ifnull(d.is_del, '0') = '0' + and ifnull(sp.is_del, '0') = '0' and (d.psp_id is null or d.psp_id <> sp.psp_id) - + update prc_price_set_product - set pef_id = #{pefId}, update_time = sysdate() + set price_type = #{priceType}, update_time = sysdate() where psv_id = #{psvId} - - and product_code in - - #{code} - - + + + + update prc_price_set_product p + inner join prc_price_set_version v on v.psv_id = p.psv_id + set p.effective_time = v.effective_time, + p.expire_time = v.expire_time, + p.status = v.status, + p.update_time = sysdate() + where p.price_type = #{priceType} + and v.price_set_id is null + and ( + not (p.status <=> v.status) + or not (p.effective_time <=> v.effective_time) + or not (p.expire_time <=> v.expire_time) + ) diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml index a7ddb0aa..a25414b2 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml @@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -17,10 +18,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + - select psv_id, price_set_id, version_no, version_seq, effective_time, expire_time, status, + select psv_id, price_set_id, price_type, version_no, version_seq, effective_time, expire_time, status, price_build_mode, created_by, create_time, updated_by, update_time from prc_price_set_version @@ -36,6 +39,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND price_set_id = #{priceSetId} + + AND price_set_id is null + + + AND price_type = #{priceType} + AND version_no = #{versionNo} @@ -46,13 +55,56 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by version_seq desc + + + + insert into prc_price_set_version ( price_set_id, + price_type, version_no, version_seq, effective_time, @@ -63,6 +115,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" create_time ) values ( #{priceSetId}, + #{priceType}, #{versionNo}, #{versionSeq}, #{effectiveTime}, @@ -77,6 +130,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update prc_price_set_version + price_type = #{priceType}, version_no = #{versionNo}, version_seq = #{versionSeq}, effective_time = #{effectiveTime}, @@ -97,16 +151,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - delete from prc_price_set_version where price_set_id = #{priceSetId} + delete from prc_price_set_version where price_set_id = #{priceSetId} and price_type = 'SALES' + + + + update prc_price_set_version + set status = case + when expire_time is not null and expire_time <= now() then '2' + when effective_time is not null and effective_time > now() then '3' + when effective_time is null then '0' + else '1' + end, + update_time = sysdate() + where price_type = #{priceType} + and price_set_id is null + and status in ('0','1','2','3') + + diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcSurchargeRelMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcSurchargeRelMapper.xml index 76938a59..ad9686a5 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcSurchargeRelMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcSurchargeRelMapper.xml @@ -39,8 +39,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ifnull(c.country_name, rc.country_name) as country_name, ifnull(c.weight_min, rc.weight_min) as weight_min, ifnull(c.weight_max, rc.weight_max) as weight_max from prc_surcharge_rel r join prc_surcharge s on s.sc_id = r.sc_id and ifnull(s.is_del, '0') = '0' - left join product_country c on c.detail_id = r.detail_id and r.price_type = 'PRODUCT' and ifnull(c.is_del, '0') = '0' - left join redline_product_country rc on rc.detail_id = r.detail_id and r.price_type = 'REDLINE' and ifnull(rc.is_del, '0') = '0' + left join prc_price_set_detail c on c.psd_id = r.detail_id and r.price_type = 'PRODUCT' and ifnull(c.is_del, '0') = '0' + left join prc_price_set_detail rc on rc.psd_id = r.detail_id and r.price_type = 'REDLINE' and ifnull(rc.is_del, '0') = '0'