From e6ead963540bf5f273fba199fec9238657f96507 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 2 Nov 2026 19:59:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=92=E5=BA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/SmHotelAppEntryDeptEntity.java | 60 +++++ .../controller/SmHotelAppController.java | 9 + .../controller/SmHotelAppEntryController.java | 102 ++++++++ .../mapper/SmHotelAppEntryDeptMapper.java | 9 + .../service/SmHotelAppEntryService.java | 41 +++ .../service/SmHotelDownstreamAppService.java | 6 + .../impl/SmHotelAppEntryServiceImpl.java | 244 ++++++++++++++++++ .../impl/SmHotelChannelDeptServiceImpl.java | 8 +- .../impl/SmHotelDownstreamAppServiceImpl.java | 19 ++ .../impl/SmHotelOpenApiServiceImpl.java | 9 +- .../service/impl/SmHotelOrderServiceImpl.java | 97 +++++-- .../service/impl/SmHotelSyncServiceImpl.java | 10 +- 12 files changed, 590 insertions(+), 24 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelAppEntryDeptEntity.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppEntryController.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelAppEntryDeptMapper.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelAppEntryService.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelAppEntryServiceImpl.java diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelAppEntryDeptEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelAppEntryDeptEntity.java new file mode 100644 index 00000000..4471be2b --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelAppEntryDeptEntity.java @@ -0,0 +1,60 @@ +package com.bsd.pjcloud.supermarket.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.bsd.pjcloud.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * App 出行/酒店入口可见部门白名单:配置部门及其子部门用户可见,未配置默认全员隐藏。 + */ +@Data +@TenantTable +@TableName("sm_hotel_app_entry_dept") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "App出行入口可见部门") +public class SmHotelAppEntryDeptEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private Long tenantId; + + @Schema(description = "可见部门 ID(含其下子部门)") + private Long deptId; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "1启用 0停用") + private String status; + + @TableField(fill = FieldFill.INSERT) + private String createBy; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + private String delFlag; + + @TableField(exist = false) + @Schema(description = "部门名称") + private String deptName; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java index 3a0115eb..0f2ff758 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java @@ -22,6 +22,7 @@ import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper; import com.bsd.pjcloud.supermarket.mapper.SmHotelProductMapper; import com.bsd.pjcloud.supermarket.mapper.SmHotelRoomMapper; import com.bsd.pjcloud.supermarket.mapper.SmHotelUserSearchPrefMapper; +import com.bsd.pjcloud.supermarket.service.SmHotelAppEntryService; import com.bsd.pjcloud.supermarket.service.SmHotelAvailabilityNotifyService; import com.bsd.pjcloud.supermarket.service.SmHotelMidService; import com.bsd.pjcloud.supermarket.service.SmHotelOrderService; @@ -124,8 +125,16 @@ public class SmHotelAppController { private final SmHotelAvailabilityNotifyService availabilityNotifyService; + private final SmHotelAppEntryService appEntryService; + private final StringRedisTemplate stringRedisTemplate; + @Operation(summary = "当前用户是否显示出行Tab/订单出行入口(按部门白名单,默认隐藏)") + @GetMapping("/entry-visible") + public R> entryVisible() { + return R.ok(appEntryService.currentUserEntryVisible()); + } + @Operation(summary = "列表筛选项(区域/星级/设施)") @GetMapping("/filter-meta") public R> filterMeta() { diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppEntryController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppEntryController.java new file mode 100644 index 00000000..374eb970 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppEntryController.java @@ -0,0 +1,102 @@ +package com.bsd.pjcloud.supermarket.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.log.annotation.SysLog; +import com.bsd.pjcloud.supermarket.entity.SmHotelAppEntryDeptEntity; +import com.bsd.pjcloud.supermarket.service.SmHotelAppEntryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +/** + * 管理端:App 出行 Tab / 订单出行入口按部门显隐。 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/smHotelAppEntry") +@Tag(name = "酒店App出行入口配置") +public class SmHotelAppEntryController { + + private final SmHotelAppEntryService appEntryService; + + @GetMapping("/page") + @Operation(summary = "分页") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_view')") + public R> page(@ParameterObject Page page, + @ParameterObject SmHotelAppEntryDeptEntity query) { + return R.ok(appEntryService.page(page, query)); + } + + @GetMapping("/listEnabled") + @Operation(summary = "启用中的可见部门") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_view')") + public R> listEnabled() { + return R.ok(appEntryService.listEnabled()); + } + + @GetMapping("/enabledDeptIds") + @Operation(summary = "启用部门 ID 列表(树勾选回显)") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_view')") + public R> enabledDeptIds() { + return R.ok(appEntryService.listEnabledDeptIds()); + } + + @PostMapping("/replaceDepts") + @SysLog("配置App出行入口可见部门") + @Operation(summary = "整表替换可见部门(空=全员隐藏)") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_edit')") + public R replaceDepts(@RequestBody Map body) { + @SuppressWarnings("unchecked") + List raw = body == null ? null : (List) body.get("deptIds"); + List deptIds = new java.util.ArrayList<>(); + if (raw != null) { + for (Object o : raw) { + if (o == null) { + continue; + } + try { + deptIds.add(Long.valueOf(String.valueOf(o))); + } + catch (Exception ignored) { + // skip bad id + } + } + } + String remark = body == null || body.get("remark") == null ? null : String.valueOf(body.get("remark")); + return appEntryService.replaceEnabledDepts(deptIds, remark); + } + + @PostMapping + @SysLog("新增App出行入口可见部门") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_edit')") + public R save(@RequestBody SmHotelAppEntryDeptEntity entity) { + return appEntryService.saveOne(entity); + } + + @PutMapping + @SysLog("修改App出行入口可见部门") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_edit')") + public R update(@RequestBody SmHotelAppEntryDeptEntity entity) { + return appEntryService.updateOne(entity); + } + + @DeleteMapping + @SysLog("删除App出行入口可见部门") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelAppEntry_edit')") + public R remove(@RequestBody List ids) { + return appEntryService.removeByIds(ids); + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelAppEntryDeptMapper.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelAppEntryDeptMapper.java new file mode 100644 index 00000000..d9959401 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelAppEntryDeptMapper.java @@ -0,0 +1,9 @@ +package com.bsd.pjcloud.supermarket.mapper; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.supermarket.entity.SmHotelAppEntryDeptEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SmHotelAppEntryDeptMapper extends PjcloudBaseMapper { +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelAppEntryService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelAppEntryService.java new file mode 100644 index 00000000..363c0c38 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelAppEntryService.java @@ -0,0 +1,41 @@ +package com.bsd.pjcloud.supermarket.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.supermarket.entity.SmHotelAppEntryDeptEntity; + +import java.util.List; +import java.util.Map; + +/** + * App 出行/酒店入口按部门显隐。 + */ +public interface SmHotelAppEntryService { + + Page page(Page page, SmHotelAppEntryDeptEntity query); + + List listEnabled(); + + /** 当前启用的可见部门 id 列表(管理端树勾选回显) */ + List listEnabledDeptIds(); + + /** + * 整表替换启用部门:传入的 deptIds 启用,其余软删。 + * 空列表 = 全员隐藏。 + */ + R replaceEnabledDepts(List deptIds, String remark); + + R saveOne(SmHotelAppEntryDeptEntity entity); + + R updateOne(SmHotelAppEntryDeptEntity entity); + + R removeByIds(List ids); + + /** + * 当前登录用户是否可见出行 Tab / 订单出行入口。 + * 未配置任何启用部门 → false;用户部门命中白名单或其子树 → true。 + */ + boolean isTravelVisibleForCurrentUser(); + + Map currentUserEntryVisible(); +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamAppService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamAppService.java index b8e6ad92..67a0ae8c 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamAppService.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamAppService.java @@ -24,4 +24,10 @@ public interface SmHotelDownstreamAppService extends IService + implements SmHotelAppEntryService { + + private final RemoteDeptService remoteDeptService; + + @Override + public Page page(Page page, + SmHotelAppEntryDeptEntity query) { + Page result = baseMapper.selectPage(page, + Wrappers.lambdaQuery() + .eq(query != null && query.getDeptId() != null, SmHotelAppEntryDeptEntity::getDeptId, + query == null ? null : query.getDeptId()) + .eq(query != null && StrUtil.isNotBlank(query.getStatus()), SmHotelAppEntryDeptEntity::getStatus, + query == null ? null : query.getStatus()) + .orderByDesc(SmHotelAppEntryDeptEntity::getUpdateTime) + .orderByDesc(SmHotelAppEntryDeptEntity::getCreateTime)); + fillDeptNames(result.getRecords()); + return result; + } + + @Override + public List listEnabled() { + List list = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(SmHotelAppEntryDeptEntity::getStatus, "1") + .orderByAsc(SmHotelAppEntryDeptEntity::getDeptId)); + fillDeptNames(list); + return list; + } + + @Override + public List listEnabledDeptIds() { + return listEnabled().stream().map(SmHotelAppEntryDeptEntity::getDeptId).filter(Objects::nonNull).distinct() + .collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R replaceEnabledDepts(List deptIds, String remark) { + Set want = new LinkedHashSet<>(); + if (CollUtil.isNotEmpty(deptIds)) { + for (Long id : deptIds) { + if (id != null && id > 0) { + want.add(id); + } + } + } + List existing = baseMapper.selectList(Wrappers.emptyWrapper()); + Map byDept = new HashMap<>(); + for (SmHotelAppEntryDeptEntity row : existing) { + if (row.getDeptId() != null) { + byDept.put(row.getDeptId(), row); + } + } + // 关掉不在本次勾选里的 + for (SmHotelAppEntryDeptEntity row : existing) { + if (row.getDeptId() == null) { + continue; + } + if (!want.contains(row.getDeptId())) { + if (!"0".equals(row.getStatus())) { + row.setStatus("0"); + baseMapper.updateById(row); + } + } + } + String rm = StrUtil.trim(remark); + for (Long deptId : want) { + SmHotelAppEntryDeptEntity row = byDept.get(deptId); + if (row == null) { + row = new SmHotelAppEntryDeptEntity(); + row.setDeptId(deptId); + row.setStatus("1"); + if (StrUtil.isNotBlank(rm)) { + row.setRemark(rm); + } + baseMapper.insert(row); + } + else { + boolean changed = false; + if (!"1".equals(row.getStatus())) { + row.setStatus("1"); + changed = true; + } + if (StrUtil.isNotBlank(rm) && !rm.equals(row.getRemark())) { + row.setRemark(rm); + changed = true; + } + if (changed) { + baseMapper.updateById(row); + } + } + } + return R.ok(Boolean.TRUE); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R saveOne(SmHotelAppEntryDeptEntity entity) { + if (entity == null || entity.getDeptId() == null) { + return R.failed("请选择部门"); + } + Long cnt = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(SmHotelAppEntryDeptEntity::getDeptId, entity.getDeptId()) + .eq(SmHotelAppEntryDeptEntity::getStatus, "1")); + if (cnt != null && cnt > 0) { + return R.failed("该部门已在可见列表中"); + } + SmHotelAppEntryDeptEntity exist = baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(SmHotelAppEntryDeptEntity::getDeptId, entity.getDeptId()) + .last("limit 1")); + if (exist != null) { + exist.setStatus("1"); + if (StrUtil.isNotBlank(entity.getRemark())) { + exist.setRemark(entity.getRemark()); + } + return R.ok(baseMapper.updateById(exist) > 0); + } + if (StrUtil.isBlank(entity.getStatus())) { + entity.setStatus("1"); + } + return R.ok(baseMapper.insert(entity) > 0); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R updateOne(SmHotelAppEntryDeptEntity entity) { + if (entity == null || entity.getId() == null) { + return R.failed("缺少主键"); + } + return R.ok(baseMapper.updateById(entity) > 0); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R removeByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return R.failed("请选择要删除的记录"); + } + return R.ok(baseMapper.deleteBatchIds(ids) > 0); + } + + @Override + public boolean isTravelVisibleForCurrentUser() { + List whitelist = listEnabledDeptIds(); + if (CollUtil.isEmpty(whitelist)) { + return false; + } + PjcloudUser user = SecurityUtils.getUser(); + if (user == null || user.getDeptId() == null) { + return false; + } + Long userDeptId = user.getDeptId(); + Set allow = new HashSet<>(whitelist); + if (allow.contains(userDeptId)) { + return true; + } + // 用户部门链(含自身与上级):命中任一白名单部门即可见(白名单部门的子部门用户) + try { + R> r = remoteDeptService.getParentDeptList(userDeptId); + if (r != null && r.isOk() && CollUtil.isNotEmpty(r.getData())) { + for (SysDept d : r.getData()) { + if (d != null && d.getDeptId() != null && allow.contains(d.getDeptId())) { + return true; + } + } + } + } + catch (Exception e) { + log.warn("|出行入口|查部门链失败|deptId={}|{}", userDeptId, e.getMessage()); + } + return false; + } + + @Override + public Map currentUserEntryVisible() { + boolean visible = isTravelVisibleForCurrentUser(); + Map data = new LinkedHashMap<>(4); + data.put("travelVisible", visible); + data.put("orderHotelVisible", visible); + return data; + } + + private void fillDeptNames(List records) { + if (CollUtil.isEmpty(records)) { + return; + } + List deptIds = records.stream().map(SmHotelAppEntryDeptEntity::getDeptId).filter(Objects::nonNull) + .distinct().collect(Collectors.toList()); + Map nameById = new HashMap<>(); + if (!deptIds.isEmpty()) { + try { + R> r = remoteDeptService.getDeptByDeptIds(deptIds); + if (r != null && r.isOk() && r.getData() != null) { + r.getData().forEach((id, d) -> { + if (id != null && d != null) { + nameById.put(id, d.getName()); + } + }); + } + } + catch (Exception e) { + log.warn("|出行入口|补全部门名失败|{}", e.getMessage()); + } + } + for (SmHotelAppEntryDeptEntity row : records) { + if (row.getDeptId() != null) { + row.setDeptName(nameById.getOrDefault(row.getDeptId(), String.valueOf(row.getDeptId()))); + } + } + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelChannelDeptServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelChannelDeptServiceImpl.java index 38cfbdeb..97a4e8af 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelChannelDeptServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelChannelDeptServiceImpl.java @@ -171,6 +171,9 @@ public class SmHotelChannelDeptServiceImpl extends ServiceImpl listHotelIdsForDownstreamPartner(String partnerCode) { String partner = normalizeCode(partnerCode); @@ -180,13 +183,15 @@ public class SmHotelChannelDeptServiceImpl extends ServiceImpl scopes = baseMapper.selectList(Wrappers.lambdaQuery() .eq(SmHotelChannelDeptEntity::getDirection, SmHotelChannelDirection.DOWNSTREAM) .eq(SmHotelChannelDeptEntity::getStatus, "1") - .eq(SmHotelChannelDeptEntity::getChannel, partner)); + // 忽略历史大小写差异,避免漏掉同伙伴另一条规则 + .apply("lower(channel) = {0}", partner)); if (CollUtil.isEmpty(scopes)) { return Collections.emptyList(); } Set ids = new LinkedHashSet<>(); for (SmHotelChannelDeptEntity scope : scopes) { String scopeType = SmHotelDownstreamScopeType.normalize(scope.getScopeType()); + // 指定酒店:并入名单 if (SmHotelDownstreamScopeType.isHotel(scopeType)) { if (StrUtil.isBlank(scope.getHotelIds())) { continue; @@ -199,6 +204,7 @@ public class SmHotelChannelDeptServiceImpl extends ServiceImpl apps = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(SmHotelDownstreamAppEntity::getStatus, "1") + .select(SmHotelDownstreamAppEntity::getId, SmHotelDownstreamAppEntity::getPartnerCode, + SmHotelDownstreamAppEntity::getAppKey) + .last("limit 500")); + if (CollUtil.isEmpty(apps)) { + return false; + } + return apps.stream().anyMatch(a -> code.equalsIgnoreCase(StrUtil.trim(a.getPartnerCode())) + || code.equalsIgnoreCase(StrUtil.trim(a.getAppKey()))); + } + private boolean existsAppKey(String appKey, Long excludeId) { Long cnt = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(SmHotelDownstreamAppEntity::getAppKey, appKey) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOpenApiServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOpenApiServiceImpl.java index 0e670d22..ce945ad8 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOpenApiServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOpenApiServiceImpl.java @@ -265,6 +265,7 @@ public class SmHotelOpenApiServiceImpl implements SmHotelOpenApiService { row.put("mealType", p.getMealType()); row.put("mealCount", p.getMealCount()); row.put("sellPrice", p.getSellPrice()); + row.put("stock", p.getStock() == null ? 0 : p.getStock()); productList.add(row); } data.put("products", productList); @@ -307,9 +308,15 @@ public class SmHotelOpenApiServiceImpl implements SmHotelOpenApiService { row.put("productCode", d.getProductCode()); row.put("stayDate", d.getStayDate() == null ? null : d.getStayDate().toString()); row.put("sellPrice", d.getSellPrice()); + row.put("costPrice", d.getCostPrice()); row.put("stock", d.getStock()); row.put("sold", d.getSold()); - row.put("remain", d.getRemainStock()); + // remainStock 非表字段,开放接口按 stock-sold 计算,避免下游永远拿不到剩余 + int stock = d.getStock() == null ? 0 : d.getStock(); + int sold = d.getSold() == null ? 0 : d.getSold(); + int remain = Math.max(0, stock - sold); + row.put("remain", remain); + row.put("remainStock", remain); row.put("dailyStatus", d.getDailyStatus()); list.add(row); } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java index be49abff..bc2044c0 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java @@ -1843,7 +1843,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl missing = new ArrayList<>(); List closed = new ArrayList<>(); - List shortStock = new ArrayList<>(); + List shortStock = new ArrayList<>(); for (LocalDate d : SmHotelStayDates.occupyDates(checkIn, checkOut)) { SmHotelDailyEntity row = dailyMapper.selectOne(Wrappers.lambdaQuery() .eq(SmHotelDailyEntity::getProductId, productId) @@ -1860,29 +1860,40 @@ public class SmHotelOrderServiceImpl extends ServiceImpl badDays = new ArrayList<>(); + for (LocalDate d : closed) { + badDays.add(formatStayDayCn(d)); + } + for (LocalDate d : shortStock) { + badDays.add(formatStayDayCn(d)); + } + if (!badDays.isEmpty()) { + int n = Math.min(badDays.size(), 5); + sb.append("(").append(String.join("、", badDays.subList(0, n))).append(")"); + } } - if (!closed.isEmpty()) { - sb.append(missing.isEmpty() ? ":" : ";").append("已关房 ").append(formatDateList(closed)); + else if (!missing.isEmpty()) { + sb.append("你选择的日期暂无房价,请更换日期"); } - if (!shortStock.isEmpty()) { - sb.append(missing.isEmpty() && closed.isEmpty() ? ":" : ";") - .append("库存不足 ").append(String.join("、", shortStock)); + else { + sb.append("当前日期暂不可预订"); } String hint = buildAvailableDatesHint(productId, need); if (StrUtil.isNotBlank(hint)) { sb.append("。").append(hint); } else { - sb.append("。请更换入住日期或联系商家维护房价房量"); + sb.append("。请更换入住日期后再试"); } return sb.toString(); } @@ -1913,9 +1924,52 @@ public class SmHotelOrderServiceImpl extends ServiceImpl dates) { + if (dates == null || dates.isEmpty()) { + return ""; + } + List parts = new ArrayList<>(); + LocalDate start = dates.get(0); + LocalDate prev = start; + for (int i = 1; i < dates.size(); i++) { + LocalDate cur = dates.get(i); + if (prev.plusDays(1).equals(cur)) { + prev = cur; + continue; + } + parts.add(formatRangePartCn(start, prev)); + if (parts.size() >= 4) { + parts.add("等"); + return String.join("、", parts); + } + start = cur; + prev = cur; + } + parts.add(formatRangePartCn(start, prev)); + return String.join("、", parts); + } + + private static String formatRangePartCn(LocalDate start, LocalDate end) { + if (start.equals(end)) { + return formatStayDayCn(start); + } + if (start.getYear() == end.getYear() && start.getMonth() == end.getMonth()) { + return formatStayDayCn(start) + "至" + end.getDayOfMonth() + "日"; + } + return formatStayDayCn(start) + "至" + formatStayDayCn(end); } private static String formatDateList(List dates) { @@ -2331,15 +2385,16 @@ public class SmHotelOrderServiceImpl extends ServiceImpllambdaQuery() - .eq(SmHotelDailyEntity::getProductId, productId) - .eq(SmHotelDailyEntity::getStayDate, d) - .last("limit 1")); - int stock = row == null || row.getStock() == null ? 0 : row.getStock(); - int sold = row == null || row.getSold() == null ? 0 : row.getSold(); String hint = buildAvailableDatesHint(productId, rooms); - return "所选入住区间不可订:日期 " + d + " 可售 " + Math.max(0, stock - sold) + ",需要 " + rooms - + (StrUtil.isNotBlank(hint) ? "。" + hint : ""); + StringBuilder race = new StringBuilder("你选择的日期房间刚被订完(") + .append(formatStayDayCn(d)).append(")"); + if (StrUtil.isNotBlank(hint)) { + race.append("。").append(hint); + } + else { + race.append("。请更换入住日期后再试"); + } + return race.toString(); } } // 占房成功:下游(环启)需同步剩余可售,避免仍按原配额售卖 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java index a35af70d..111bac76 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java @@ -21,6 +21,7 @@ import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper; import com.bsd.pjcloud.supermarket.mapper.SmHotelSyncLogMapper; import com.bsd.pjcloud.supermarket.service.SmHotelChannelConfigService; import com.bsd.pjcloud.supermarket.service.SmHotelChannelDeptService; +import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamAppService; import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamPushLogService; import com.bsd.pjcloud.supermarket.service.SmHotelMidService; import com.bsd.pjcloud.supermarket.service.SmHotelSyncService; @@ -104,6 +105,8 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService { private final SmHotelChannelDeptService channelDeptService; + private final SmHotelDownstreamAppService downstreamAppService; + private final SmHotelMapper hotelMapper; private final SmHotelSyncLogMapper syncLogMapper; @@ -289,7 +292,12 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService { return R.failed("请选择下游伙伴"); } if (!channelConfigService.listEnabledDownstreamPartners().contains(partner)) { - return R.failed("下游伙伴「" + partner + "」未启用或不存在,请先到「渠道对接配置」维护"); + // 开放应用是对方主动拉数,不走协议推送;误点「全量推送」时给出明确说明 + if (downstreamAppService.isEnabledOpenAppPartner(partner)) { + return R.failed("「" + partner + "」是开放应用(拉模式),无需也不支持「全量推送」。" + + "请在「渠道范围映射」配好授权即可,对方用 appKey 调开放 API 自行拉酒店/下单"); + } + return R.failed("下游伙伴「" + partner + "」未启用或不存在,请先到「渠道对接配置」维护(圣诺/慧订/环启等协议下游)"); } Long running = syncLogMapper.selectCount(Wrappers.lambdaQuery() .eq(SmHotelSyncLogEntity::getSyncType, TYPE_DOWNSTREAM_PUSH)