diff --git a/.codex/skills.zip b/.codex/skills.zip new file mode 100644 index 0000000..a2dcd36 Binary files /dev/null and b/.codex/skills.zip differ diff --git a/.codex/skills/inspect-shz-logs/SKILL.md b/.codex/skills/inspect-shz-logs/SKILL.md new file mode 100644 index 0000000..115dac6 --- /dev/null +++ b/.codex/skills/inspect-shz-logs/SKILL.md @@ -0,0 +1,56 @@ +--- +name: inspect-shz-logs +description: Inspect and follow the SHZ backend's local and production logs through bounded, read-only commands. Use when Codex is working in the shz-backend project and needs to diagnose startup failures, exceptions, request behavior, scheduled jobs, or runtime incidents from local backend.log or the production backend, error, info, and user logs on the shz server. +--- + +# Inspect SHZ Logs + +Use the bundled script instead of assembling SSH commands manually. Keep every initial read bounded; follow logs only when the user explicitly asks to monitor live activity. + +## Inspect local logs + +Read the last 200 lines: + +```bash +.codex/skills/inspect-shz-logs/scripts/view.sh local 200 +``` + +Follow the local log interactively: + +```bash +.codex/skills/inspect-shz-logs/scripts/view.sh local 200 --follow +``` + +## Inspect production logs + +Read the main application log: + +```bash +.codex/skills/inspect-shz-logs/scripts/view.sh online backend 200 +``` + +Read the current error log: + +```bash +.codex/skills/inspect-shz-logs/scripts/view.sh online error 200 +``` + +Choose only one production log type: `backend`, `error`, `info`, or `user`. Add `--follow` as the final argument only for an explicit live-monitoring request. + +For keyword analysis, fetch a bounded window and filter locally, for example: + +```bash +.codex/skills/inspect-shz-logs/scripts/view.sh online error 1000 | rg -i 'exception|error|failed' +``` + +## Diagnose from logs + +1. Establish whether the request concerns local or production behavior. +2. Read 100–300 recent lines from the relevant log. +3. Expand up to the script's 5,000-line cap only when timestamps or stack traces require more context. +4. Correlate timestamps, thread names, exception causes, and nearby requests. +5. Report the evidence and likely cause without exposing tokens, passwords, personal data, or unrelated log content. + +Treat production access as read-only. Never restart services, edit files, deploy code, truncate logs, or run arbitrary remote commands through this skill. + +Read [references/log-locations.md](references/log-locations.md) only when checking server identity, paths, or supported log types. diff --git a/.codex/skills/inspect-shz-logs/agents/openai.yaml b/.codex/skills/inspect-shz-logs/agents/openai.yaml new file mode 100644 index 0000000..15c36a2 --- /dev/null +++ b/.codex/skills/inspect-shz-logs/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "查看 SHZ 项目日志" + short_description: "安全查看并跟踪 SHZ 项目的本地与线上运行日志内容" + default_prompt: "Use $inspect-shz-logs to inspect recent local or production logs for the SHZ backend." diff --git a/.codex/skills/inspect-shz-logs/references/log-locations.md b/.codex/skills/inspect-shz-logs/references/log-locations.md new file mode 100644 index 0000000..03f4b0a --- /dev/null +++ b/.codex/skills/inspect-shz-logs/references/log-locations.md @@ -0,0 +1,19 @@ +# Log locations + +## Local + +- Main log: `/.local/logs/backend.log` +- Producer: `/local.sh` + +## Production: shz + +- SSH: `root@39.98.44.136:6022` +- Application: `/opt/service/project/shz-backend/ruoyi-admin/target/ruoyi-admin.jar` +- Profile: `dev` +- HTTP port: `9091` +- Main process log: `/opt/service/project/shz-backend/logs/backend.log` +- Current error log: `/opt/service/project/shz-backend/logs/sys-error.log` +- Current info log: `/opt/service/project/shz-backend/logs/sys-info.log` +- Current user log: `/opt/service/project/shz-backend/logs/sys-user.log` + +Use only the bundled read-only viewer. Rotated files use date suffixes but are intentionally not exposed by the default script. diff --git a/.codex/skills/inspect-shz-logs/scripts/view.sh b/.codex/skills/inspect-shz-logs/scripts/view.sh new file mode 100755 index 0000000..bc7fd45 --- /dev/null +++ b/.codex/skills/inspect-shz-logs/scripts/view.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +project_dir="$(cd "${skill_dir}/../../.." && pwd)" +local_log="${project_dir}/.local/logs/backend.log" + +ssh_host="root@39.98.44.136" +ssh_port="6022" +remote_log_dir="/opt/service/project/shz-backend/logs" + +usage() { + cat <<'EOF' +Usage: + view.sh local [lines] [--follow] + view.sh online [backend|error|info|user] [lines] [--follow] + +Examples: + view.sh local 200 + view.sh local 100 --follow + view.sh online backend 200 + view.sh online error 500 --follow +EOF +} + +validate_lines() { + local lines="$1" + if [[ ! "${lines}" =~ ^[0-9]+$ ]] || (( lines < 1 || lines > 5000 )); then + echo "Lines must be an integer between 1 and 5000: ${lines}" >&2 + exit 2 + fi +} + +validate_follow() { + local follow="${1:-}" + if [[ -n "${follow}" && "${follow}" != "--follow" ]]; then + echo "Unknown option: ${follow}" >&2 + usage >&2 + exit 2 + fi +} + +view_local() { + local lines="${1:-200}" + local follow="${2:-}" + validate_lines "${lines}" + validate_follow "${follow}" + + if [[ ! -f "${local_log}" ]]; then + echo "Local log does not exist: ${local_log}" >&2 + echo "Start the project first with: ./local.sh start" >&2 + exit 1 + fi + + if [[ "${follow}" == "--follow" ]]; then + tail -n "${lines}" -F -- "${local_log}" + else + tail -n "${lines}" -- "${local_log}" + fi +} + +view_online() { + local kind="${1:-backend}" + local lines="${2:-200}" + local follow="${3:-}" + local remote_file + + case "${kind}" in + backend) remote_file="${remote_log_dir}/backend.log" ;; + error) remote_file="${remote_log_dir}/sys-error.log" ;; + info) remote_file="${remote_log_dir}/sys-info.log" ;; + user) remote_file="${remote_log_dir}/sys-user.log" ;; + *) + echo "Unknown online log type: ${kind}" >&2 + usage >&2 + exit 2 + ;; + esac + + validate_lines "${lines}" + validate_follow "${follow}" + + if [[ "${follow}" == "--follow" ]]; then + ssh -p "${ssh_port}" -o BatchMode=yes -o ConnectTimeout=10 "${ssh_host}" \ + tail -n "${lines}" -F -- "${remote_file}" + else + ssh -p "${ssh_port}" -o BatchMode=yes -o ConnectTimeout=10 "${ssh_host}" \ + tail -n "${lines}" -- "${remote_file}" + fi +} + +target="${1:-}" +case "${target}" in + local) + [[ $# -le 3 ]] || { usage >&2; exit 2; } + view_local "${2:-200}" "${3:-}" + ;; + online) + [[ $# -le 4 ]] || { usage >&2; exit 2; } + view_online "${2:-backend}" "${3:-200}" "${4:-}" + ;; + help|-h|--help) + usage + ;; + *) + usage >&2 + exit 2 + ;; +esac diff --git a/.codex/skills/query-shz-highgo/SKILL.md b/.codex/skills/query-shz-highgo/SKILL.md new file mode 100644 index 0000000..1d1b059 --- /dev/null +++ b/.codex/skills/query-shz-highgo/SKILL.md @@ -0,0 +1,42 @@ +--- +name: query-shz-highgo +description: Query the SHZ HighGo Security Enterprise Edition database through its SSH gateway using a guarded read-only psql workflow. Use when Codex is working in the shz-backend project and needs to inspect database schemas, tables, columns, row counts, or application data; diagnose data-related behavior; or execute SELECT, WITH, SHOW, or EXPLAIN statements against the project's HighGo database. +--- + +# Query SHZ HighGo + +Use the bundled script to run read-only SQL through the configured SSH gateway. + +## Query workflow + +1. Form the narrowest useful query. Add a small `LIMIT` when inspecting rows. +2. For unfamiliar tables, inspect `information_schema.columns` before selecting business data. +3. Run: + + ```bash + .codex/skills/query-shz-highgo/scripts/query.sh 'SELECT current_database(), current_user, current_schema()' + ``` + +4. Summarize the result and distinguish returned facts from inferences. + +The script accepts only statements beginning with `SELECT`, `WITH`, `SHOW`, or `EXPLAIN`, rejects multiple statements, sets `search_path` to `shz,public`, enforces database-level read-only mode, and applies a 30-second timeout. Never bypass these controls or perform writes unless the user explicitly requests a separate write-capable workflow. + +## Schema discovery + +List user tables: + +```bash +.codex/skills/query-shz-highgo/scripts/query.sh "SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') ORDER BY 1, 2" +``` + +Inspect columns: + +```bash +.codex/skills/query-shz-highgo/scripts/query.sh "SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'shz' AND table_name = 'lowercase_table_name' ORDER BY ordinal_position" +``` + +HighGo may expose unquoted identifiers in lowercase and quoted identifiers with exact case. Query `information_schema` first when the spelling is uncertain. + +## Connection details + +Read [references/connection.md](references/connection.md) only when troubleshooting connectivity or updating the endpoint. Do not print, quote, or expose the password file. diff --git a/.codex/skills/query-shz-highgo/agents/openai.yaml b/.codex/skills/query-shz-highgo/agents/openai.yaml new file mode 100644 index 0000000..330b230 --- /dev/null +++ b/.codex/skills/query-shz-highgo/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "查询 SHZ 翰高数据库" + short_description: "通过 SSH 网关安全执行本项目翰高数据库只读 SQL 查询" + default_prompt: "Use $query-shz-highgo to run a read-only query against this project's SHZ HighGo database." diff --git a/.codex/skills/query-shz-highgo/references/connection.md b/.codex/skills/query-shz-highgo/references/connection.md new file mode 100644 index 0000000..a96f752 --- /dev/null +++ b/.codex/skills/query-shz-highgo/references/connection.md @@ -0,0 +1,12 @@ +# Connection reference + +- SSH gateway: `root@124.243.245.42` +- HighGo client: `/opt/HighGo4.5.7-see/bin/psql` +- Database endpoint: `39.98.44.136:6023` +- Database: `highgo` +- User: `sysdba` +- Default schemas: `shz,public` +- Detected server: HighGo Security Enterprise Edition Database System 4.5.10 +- Password source: `../.password`, mode `600`; override with `HIGHGO_PASSWORD_FILE` + +The SSH gateway must be reachable with the workstation's existing SSH key. Keep credentials out of commands, logs, responses, and `SKILL.md`. diff --git a/.codex/skills/query-shz-highgo/scripts/query.sh b/.codex/skills/query-shz-highgo/scripts/query.sh new file mode 100755 index 0000000..2c88bde --- /dev/null +++ b/.codex/skills/query-shz-highgo/scripts/query.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +password_file="${HIGHGO_PASSWORD_FILE:-$skill_dir/.password}" + +ssh_host="root@124.243.245.42" +psql_path="/opt/HighGo4.5.7-see/bin/psql" +db_host="39.98.44.136" +db_port="6023" +db_name="highgo" +db_user="sysdba" + +if [[ ! -r "$password_file" ]]; then + echo "HighGo password file is missing or unreadable: $password_file" >&2 + exit 2 +fi + +if [[ $# -eq 0 ]]; then + echo "Usage: $0 'SELECT ...'" >&2 + exit 2 +fi + +sql="$*" +if [[ ! "$sql" =~ ^[[:space:]]*(SELECT|select|WITH|with|SHOW|show|EXPLAIN|explain)([[:space:]]|$) ]]; then + echo "Rejected: only SELECT, WITH, SHOW, and EXPLAIN queries are allowed." >&2 + exit 3 +fi + +sql_without_final_semicolon="${sql%;}" +if [[ "$sql_without_final_semicolon" == *';'* ]]; then + echo "Rejected: multiple SQL statements are not allowed." >&2 + exit 3 +fi + +password="$(<"$password_file")" +sql_base64="$(printf '%s' "$sql" | base64 | tr -d '\n')" + +{ + printf '%s\n' "$password" + printf '%s\n' "$sql_base64" +} | ssh -o BatchMode=yes -o ConnectTimeout=10 "$ssh_host" \ + "read -r PGPASSWORD; read -r SQL_BASE64; export PGPASSWORD; export PGOPTIONS='-c default_transaction_read_only=on -c search_path=shz,public -c statement_timeout=30000'; SQL=\$(printf '%s' \"\$SQL_BASE64\" | base64 -d); exec '$psql_path' -X --no-psqlrc -h '$db_host' -p '$db_port' -U '$db_user' -d '$db_name' -v ON_ERROR_STOP=1 -P pager=off -c \"\$SQL\"" diff --git a/.gitignore b/.gitignore index a86bdd0..fb41a99 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,5 @@ nbdist/ # Local application runtime files .local/ -.codex/ -local.sh \ No newline at end of file + +local.sh diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java index e289dbe..f5b849d 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java @@ -1,23 +1,38 @@ package com.ruoyi.cms.controller.cms; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.cms.domain.FairCompany; import com.ruoyi.cms.domain.OutdoorFair; +import com.ruoyi.cms.domain.OutdoorFairBooth; +import com.ruoyi.cms.domain.OutdoorFairBoothBooking; import com.ruoyi.cms.domain.OutdoorFairDictDataRequest; import com.ruoyi.cms.domain.VenueInfo; +import com.ruoyi.cms.mapper.FairCompanyMapper; +import com.ruoyi.cms.mapper.OutdoorFairBoothMapper; +import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper; +import com.ruoyi.cms.mapper.CompanyMapper; import com.ruoyi.cms.service.IOutdoorFairService; import com.ruoyi.cms.service.IVenueInfoService; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.Company; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -45,6 +60,18 @@ public class OutdoorFairController extends BaseController @Autowired private IVenueInfoService venueInfoService; + @Autowired + private FairCompanyMapper fairCompanyMapper; + + @Autowired + private CompanyMapper companyMapper; + + @Autowired + private OutdoorFairBoothBookingMapper outdoorFairBoothBookingMapper; + + @Autowired + private OutdoorFairBoothMapper outdoorFairBoothMapper; + /** * 查询户外招聘会列表 */ @@ -75,11 +102,16 @@ public class OutdoorFairController extends BaseController @ApiOperation("新增户外招聘会") @PreAuthorize("@ss.hasPermi('cms:outdoorFair:add')") @Log(title = "户外招聘会", businessType = BusinessType.INSERT) + @Transactional(rollbackFor = Exception.class) @PostMapping public AjaxResult add(@RequestBody OutdoorFair outdoorFair) { fillVenueFields(outdoorFair); - return toAjax(outdoorFairService.save(outdoorFair)); + boolean saved = outdoorFairService.save(outdoorFair); + if (saved) { + resetBoothSnapshot(outdoorFair, true); + } + return toAjax(saved); } /** @@ -88,11 +120,18 @@ public class OutdoorFairController extends BaseController @ApiOperation("修改户外招聘会") @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") @Log(title = "户外招聘会", businessType = BusinessType.UPDATE) + @Transactional(rollbackFor = Exception.class) @PutMapping public AjaxResult edit(@RequestBody OutdoorFair outdoorFair) { + OutdoorFair oldFair = outdoorFair.getId() == null ? null : outdoorFairService.getById(outdoorFair.getId()); fillVenueFields(outdoorFair); - return toAjax(outdoorFairService.updateById(outdoorFair)); + boolean updated = outdoorFairService.updateById(outdoorFair); + if (updated) { + boolean venueChanged = oldFair == null || !java.util.Objects.equals(oldFair.getVenueId(), outdoorFair.getVenueId()); + resetBoothSnapshot(outdoorFair, venueChanged); + } + return toAjax(updated); } /** @@ -119,6 +158,120 @@ public class OutdoorFairController extends BaseController return toAjax(outdoorFairService.insertOutdoorFairDictData(request.getDictType(), request.getDictLabel(), getUsername())); } + @ApiOperation("查询户外招聘会参会企业") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')") + @GetMapping("/{fairId}/companies") + public AjaxResult listCompanies(@PathVariable Long fairId, String companyName, Integer current, Integer pageSize) + { + List relations = fairCompanyMapper.selectList(new LambdaQueryWrapper() + .eq(FairCompany::getJobFairId, fairId) + .orderByDesc(FairCompany::getId)); + if (relations.isEmpty()) { + AjaxResult result = AjaxResult.success(); + result.put("rows", new ArrayList<>()); + result.put("total", 0); + return result; + } + + List companyIds = relations.stream().map(FairCompany::getCompanyId).collect(Collectors.toList()); + Map companyMap = companyMapper.selectBatchIds(companyIds).stream() + .collect(Collectors.toMap(Company::getCompanyId, item -> item, (a, b) -> a)); + Map bookingMap = outdoorFairBoothBookingMapper.selectList(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getStatus, "reserved")) + .stream() + .filter(item -> item.getCompanyId() != null) + .collect(Collectors.toMap(OutdoorFairBoothBooking::getCompanyId, item -> item, (a, b) -> a)); + + List> rows = new ArrayList<>(); + for (FairCompany relation : relations) { + Company company = companyMap.get(relation.getCompanyId()); + if (company == null) { + continue; + } + if (StringUtils.isNotBlank(companyName) && (company.getName() == null || !company.getName().contains(companyName))) { + continue; + } + OutdoorFairBoothBooking booking = bookingMap.get(company.getCompanyId()); + Map row = new HashMap<>(); + row.put("id", relation.getId()); + row.put("fairId", fairId); + row.put("companyId", company.getCompanyId()); + row.put("companyName", company.getName()); + row.put("industry", company.getIndustry()); + row.put("scale", company.getScale()); + row.put("contactPerson", company.getContactPerson()); + row.put("contactPhone", company.getContactPersonPhone()); + row.put("boothId", booking == null ? null : booking.getBoothId()); + row.put("boothNumber", booking == null ? null : booking.getBoothNumber()); + row.put("jobList", new ArrayList<>()); + rows.add(row); + } + + int total = rows.size(); + int page = current == null || current < 1 ? 1 : current; + int size = pageSize == null || pageSize < 1 ? 10 : pageSize; + int fromIndex = Math.min((page - 1) * size, total); + int toIndex = Math.min(fromIndex + size, total); + AjaxResult result = AjaxResult.success(); + result.put("rows", rows.subList(fromIndex, toIndex)); + result.put("total", total); + return result; + } + + @ApiOperation("新增户外招聘会参会企业") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @PostMapping("/{fairId}/companies") + public AjaxResult addCompany(@PathVariable Long fairId, @RequestBody Map request) + { + Long companyId = Long.valueOf(String.valueOf(request.get("companyId"))); + Company company = companyMapper.selectById(companyId); + if (company == null || company.getStatus() == null || company.getStatus() != 1) { + return AjaxResult.error("只能添加审核通过的企业"); + } + ensureFairCompany(fairId, companyId); + return success(); + } + + @ApiOperation("移除户外招聘会参会企业") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @DeleteMapping("/{fairId}/companies/{id}") + public AjaxResult removeCompany(@PathVariable Long fairId, @PathVariable Long id) + { + return toAjax(fairCompanyMapper.delete(new LambdaQueryWrapper() + .eq(FairCompany::getJobFairId, fairId) + .eq(FairCompany::getId, id))); + } + + private void ensureFairCompany(Long fairId, Long companyId) + { + Long count = fairCompanyMapper.selectCount(new LambdaQueryWrapper() + .eq(FairCompany::getJobFairId, fairId) + .eq(FairCompany::getCompanyId, companyId)); + if (count != null && count > 0) { + return; + } + FairCompany fairCompany = new FairCompany(); + fairCompany.setJobFairId(fairId); + fairCompany.setCompanyId(companyId); + fairCompanyMapper.insert(fairCompany); + } + + private void resetBoothSnapshot(OutdoorFair outdoorFair, boolean forceRecreate) + { + if (outdoorFair.getId() == null) { + return; + } + if (forceRecreate) { + outdoorFairBoothBookingMapper.physicalDeleteByFairId(outdoorFair.getId()); + outdoorFairBoothMapper.physicalDeleteByFairId(outdoorFair.getId()); + } + Long boothCount = outdoorFairBoothMapper.selectCount(new LambdaQueryWrapper() + .eq(OutdoorFairBooth::getFairId, outdoorFair.getId())); + outdoorFair.setBoothCount(boothCount == null ? 0 : boothCount.intValue()); + outdoorFairService.updateById(outdoorFair); + } + private void fillVenueFields(OutdoorFair outdoorFair) { if (outdoorFair.getVenueId() == null) { @@ -129,6 +282,8 @@ public class OutdoorFairController extends BaseController throw new ServiceException("绑定场地不存在"); } outdoorFair.setVenueName(venueInfo.getVenueName()); - outdoorFair.setBoothCount(venueInfo.getFloorCount() == null ? 0 : venueInfo.getFloorCount()); + if (outdoorFair.getBoothCount() == null) { + outdoorFair.setBoothCount(0); + } } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java index c8e4414..d3ba639 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java @@ -12,14 +12,16 @@ import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.cms.domain.VenueInfo; import com.ruoyi.cms.domain.OutdoorFair; +import com.ruoyi.cms.domain.OutdoorFairBooth; import com.ruoyi.cms.domain.OutdoorFairBoothBooking; -import com.ruoyi.cms.domain.VenueBooth; +import com.ruoyi.cms.domain.FairCompany; import com.ruoyi.cms.domain.VenueInfoDictDataRequest; import com.ruoyi.cms.domain.VenueBoothSwapRequest; import com.ruoyi.cms.mapper.CompanyMapper; +import com.ruoyi.cms.mapper.FairCompanyMapper; +import com.ruoyi.cms.mapper.OutdoorFairBoothMapper; import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper; import com.ruoyi.cms.mapper.OutdoorFairMapper; -import com.ruoyi.cms.mapper.VenueBoothMapper; import com.ruoyi.cms.service.IVenueInfoService; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; @@ -57,18 +59,21 @@ public class VenueInfoController extends BaseController @Autowired private IVenueInfoService venueInfoService; - @Autowired - private VenueBoothMapper venueBoothMapper; - @Autowired private OutdoorFairMapper outdoorFairMapper; @Autowired private OutdoorFairBoothBookingMapper outdoorFairBoothBookingMapper; + @Autowired + private OutdoorFairBoothMapper outdoorFairBoothMapper; + @Autowired private CompanyMapper companyMapper; + @Autowired + private FairCompanyMapper fairCompanyMapper; + /** * 查询场地信息列表 */ @@ -143,78 +148,6 @@ public class VenueInfoController extends BaseController return toAjax(venueInfoService.insertVenueInfoDictData(request.getDictType(), request.getDictLabel(), getUsername())); } - @ApiOperation("查询场地展位图") - @PreAuthorize("@ss.hasAnyPermi('cms:venueInfo:query,cms:outdoorFair:query')") - @GetMapping("/{venueId}/booths") - public AjaxResult listBooths(@PathVariable Long venueId) - { - List list = venueBoothMapper.selectList(new LambdaQueryWrapper() - .eq(VenueBooth::getVenueId, venueId) - .orderByAsc(VenueBooth::getPositionY) - .orderByAsc(VenueBooth::getPositionX) - .orderByAsc(VenueBooth::getBoothNumber)); - return success(list); - } - - @ApiOperation("保存场地展位图") - @PreAuthorize("@ss.hasPermi('cms:venueInfo:edit')") - @Transactional(rollbackFor = Exception.class) - @PostMapping("/{venueId}/booths") - public AjaxResult saveBooths(@PathVariable Long venueId, @RequestBody List booths) - { - venueBoothMapper.physicalDeleteDeletedByVenueId(venueId); - List existingBooths = venueBoothMapper.selectList(new LambdaQueryWrapper() - .eq(VenueBooth::getVenueId, venueId)); - Map existingById = existingBooths.stream() - .filter(item -> item.getId() != null) - .collect(Collectors.toMap(VenueBooth::getId, item -> item, (a, b) -> a)); - Map existingByNumber = existingBooths.stream() - .filter(item -> StringUtils.isNotBlank(item.getBoothNumber())) - .collect(Collectors.toMap(VenueBooth::getBoothNumber, item -> item, (a, b) -> a)); - Set boothNumbers = new HashSet<>(); - Set savedIds = new HashSet<>(); - if (booths != null) { - for (VenueBooth booth : booths) { - if (StringUtils.isBlank(booth.getBoothNumber())) { - return AjaxResult.error("展位号不能为空"); - } - if (!boothNumbers.add(booth.getBoothNumber())) { - return AjaxResult.error("展位号不能重复:" + booth.getBoothNumber()); - } - } - for (VenueBooth booth : booths) { - VenueBooth existing = booth.getId() == null ? null : existingById.get(booth.getId()); - if (existing == null) { - existing = existingByNumber.get(booth.getBoothNumber()); - } - booth.setVenueId(venueId); - if (existing == null) { - booth.setId(null); - venueBoothMapper.insert(booth); - } else { - booth.setId(existing.getId()); - venueBoothMapper.updateById(booth); - } - if (booth.getId() != null) { - savedIds.add(booth.getId()); - } - } - VenueInfo venueInfo = venueInfoService.getById(venueId); - if (venueInfo != null) { - venueInfo.setFloorCount(booths.size()); - venueInfoService.updateById(venueInfo); - } - } - List removedIds = existingBooths.stream() - .map(VenueBooth::getId) - .filter(id -> id != null && !savedIds.contains(id)) - .collect(Collectors.toList()); - if (!removedIds.isEmpty()) { - venueBoothMapper.physicalDeleteByVenueIdAndIds(venueId, removedIds); - } - return success(); - } - @ApiOperation("查询户外招聘会展位图") @PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')") @GetMapping("/fair/{fairId}/booth-map") @@ -224,16 +157,16 @@ public class VenueInfoController extends BaseController if (fair == null || fair.getVenueId() == null) { return success(new ArrayList<>()); } - List booths = venueBoothMapper.selectList(new LambdaQueryWrapper() - .eq(VenueBooth::getVenueId, fair.getVenueId()) - .orderByAsc(VenueBooth::getPositionY) - .orderByAsc(VenueBooth::getPositionX) - .orderByAsc(VenueBooth::getBoothNumber)); + List booths = outdoorFairBoothMapper.selectList(new LambdaQueryWrapper() + .eq(OutdoorFairBooth::getFairId, fairId) + .orderByAsc(OutdoorFairBooth::getPositionY) + .orderByAsc(OutdoorFairBooth::getPositionX) + .orderByAsc(OutdoorFairBooth::getBoothNumber)); List bookings = outdoorFairBoothBookingMapper.selectList(new LambdaQueryWrapper() .eq(OutdoorFairBoothBooking::getFairId, fairId)); Map bookingMap = bookings.stream().collect(Collectors.toMap(OutdoorFairBoothBooking::getBoothId, item -> item, (a, b) -> a)); List> result = new ArrayList<>(); - for (VenueBooth booth : booths) { + for (OutdoorFairBooth booth : booths) { OutdoorFairBoothBooking booking = bookingMap.get(booth.getId()); Map row = new java.util.HashMap<>(); row.put("id", booth.getId()); @@ -250,8 +183,85 @@ public class VenueInfoController extends BaseController return success(result); } + @ApiOperation("保存户外招聘会展位图副本") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @Transactional(rollbackFor = Exception.class) + @PostMapping("/fair/{fairId}/booths") + public AjaxResult saveFairBooths(@PathVariable Long fairId, @RequestBody List booths) + { + OutdoorFair fair = outdoorFairMapper.selectById(fairId); + if (fair == null || fair.getVenueId() == null) { + return AjaxResult.error("当前招聘会未绑定场地"); + } + List existingBooths = outdoorFairBoothMapper.selectList(new LambdaQueryWrapper() + .eq(OutdoorFairBooth::getFairId, fairId)); + Map existingById = existingBooths.stream() + .filter(item -> item.getId() != null) + .collect(Collectors.toMap(OutdoorFairBooth::getId, item -> item, (a, b) -> a)); + Map existingByNumber = existingBooths.stream() + .filter(item -> StringUtils.isNotBlank(item.getBoothNumber())) + .collect(Collectors.toMap(OutdoorFairBooth::getBoothNumber, item -> item, (a, b) -> a)); + Set boothNumbers = new HashSet<>(); + Set savedIds = new HashSet<>(); + if (booths != null) { + for (OutdoorFairBooth booth : booths) { + if (StringUtils.isBlank(booth.getBoothNumber())) { + return AjaxResult.error("展位号不能为空"); + } + if (!boothNumbers.add(booth.getBoothNumber())) { + return AjaxResult.error("展位号不能重复:" + booth.getBoothNumber()); + } + } + for (OutdoorFairBooth booth : booths) { + OutdoorFairBooth existing = booth.getId() == null ? null : existingById.get(booth.getId()); + if (existing == null) { + existing = existingByNumber.get(booth.getBoothNumber()); + } + booth.setFairId(fairId); + booth.setSourceVenueId(fair.getVenueId()); + if (existing == null) { + booth.setId(null); + outdoorFairBoothMapper.insert(booth); + } else { + booth.setId(existing.getId()); + if (booth.getSourceBoothId() == null) { + booth.setSourceBoothId(existing.getSourceBoothId()); + } + outdoorFairBoothMapper.updateById(booth); + } + if (booth.getId() != null) { + savedIds.add(booth.getId()); + List bookings = outdoorFairBoothBookingMapper.selectList(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getBoothId, booth.getId())); + for (OutdoorFairBoothBooking booking : bookings) { + booking.setBoothNumber(booth.getBoothNumber()); + outdoorFairBoothBookingMapper.updateById(booking); + } + } + } + } + List removedIds = existingBooths.stream() + .map(OutdoorFairBooth::getId) + .filter(id -> id != null && !savedIds.contains(id)) + .collect(Collectors.toList()); + if (!removedIds.isEmpty()) { + Long bookedRemovedCount = outdoorFairBoothBookingMapper.selectCount(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .in(OutdoorFairBoothBooking::getBoothId, removedIds)); + if (bookedRemovedCount != null && bookedRemovedCount > 0) { + return AjaxResult.error("不能删除已预定展位,请先取消预定"); + } + outdoorFairBoothMapper.physicalDeleteByFairIdAndIds(fairId, removedIds); + } + fair.setBoothCount(booths == null ? 0 : booths.size()); + outdoorFairMapper.updateById(fair); + return success(); + } + @ApiOperation("预定户外招聘会展位") @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @Transactional(rollbackFor = Exception.class) @PostMapping("/fair/{fairId}/booth-booking") public AjaxResult bookBooth(@PathVariable Long fairId, @RequestBody OutdoorFairBoothBooking booking) { @@ -262,20 +272,28 @@ public class VenueInfoController extends BaseController if (company == null || company.getStatus() == null || company.getStatus() != 1) { return AjaxResult.error("只能预定审核通过的企业"); } - VenueBooth booth = venueBoothMapper.selectById(booking.getBoothId()); + OutdoorFairBooth booth = outdoorFairBoothMapper.selectById(booking.getBoothId()); + if (booth == null || !fairId.equals(booth.getFairId())) { + return AjaxResult.error("展位不存在或不属于当前招聘会"); + } booking.setFairId(fairId); - booking.setBoothNumber(booth == null ? booking.getBoothNumber() : booth.getBoothNumber()); + booking.setBoothNumber(booth.getBoothNumber()); booking.setCompanyName(company.getName()); booking.setStatus(StringUtils.isBlank(booking.getStatus()) ? "reserved" : booking.getStatus()); + outdoorFairBoothBookingMapper.physicalDeleteDeletedByFairIdAndBoothId(fairId, booking.getBoothId()); OutdoorFairBoothBooking existing = outdoorFairBoothBookingMapper.selectOne(new LambdaQueryWrapper() .eq(OutdoorFairBoothBooking::getFairId, fairId) .eq(OutdoorFairBoothBooking::getBoothId, booking.getBoothId()) .last("limit 1")); if (existing == null) { - return toAjax(outdoorFairBoothBookingMapper.insert(booking)); + int rows = outdoorFairBoothBookingMapper.insert(booking); + ensureFairCompany(fairId, booking.getCompanyId()); + return toAjax(rows); } booking.setId(existing.getId()); - return toAjax(outdoorFairBoothBookingMapper.updateById(booking)); + int rows = outdoorFairBoothBookingMapper.updateById(booking); + ensureFairCompany(fairId, booking.getCompanyId()); + return toAjax(rows); } @ApiOperation("取消户外招聘会展位预定") @@ -283,9 +301,7 @@ public class VenueInfoController extends BaseController @DeleteMapping("/fair/{fairId}/booth-booking/{boothId}") public AjaxResult cancelBooth(@PathVariable Long fairId, @PathVariable Long boothId) { - return toAjax(outdoorFairBoothBookingMapper.delete(new LambdaQueryWrapper() - .eq(OutdoorFairBoothBooking::getFairId, fairId) - .eq(OutdoorFairBoothBooking::getBoothId, boothId))); + return toAjax(outdoorFairBoothBookingMapper.physicalDeleteByFairIdAndBoothId(fairId, boothId)); } @ApiOperation("调换户外招聘会展位") @@ -313,6 +329,20 @@ public class VenueInfoController extends BaseController return success(); } + private void ensureFairCompany(Long fairId, Long companyId) + { + Long count = fairCompanyMapper.selectCount(new LambdaQueryWrapper() + .eq(FairCompany::getJobFairId, fairId) + .eq(FairCompany::getCompanyId, companyId)); + if (count != null && count > 0) { + return; + } + FairCompany fairCompany = new FairCompany(); + fairCompany.setJobFairId(fairId); + fairCompany.setCompanyId(companyId); + fairCompanyMapper.insert(fairCompany); + } + private void fillHandleFields(VenueInfo venueInfo) { if (venueInfo.getHandleDate() == null) { diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBooth.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairBooth.java similarity index 66% rename from ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBooth.java rename to ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairBooth.java index c948bd7..64ed82b 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBooth.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairBooth.java @@ -9,17 +9,23 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data -@ApiModel("场地展位") -@TableName("cms_venue_booth") -public class VenueBooth extends BaseEntity +@ApiModel("户外招聘会展位副本") +@TableName("cms_outdoor_fair_booth") +public class OutdoorFairBooth extends BaseEntity { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; - @ApiModelProperty("场地ID") - private Long venueId; + @ApiModelProperty("招聘会ID") + private Long fairId; + + @ApiModelProperty("来源场地ID") + private Long sourceVenueId; + + @ApiModelProperty("来源场地展位ID") + private Long sourceBoothId; @ApiModelProperty("展位号") private String boothNumber; diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java index 4736774..765fa1b 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java @@ -2,7 +2,17 @@ package com.ruoyi.cms.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.cms.domain.OutdoorFairBoothBooking; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; public interface OutdoorFairBoothBookingMapper extends BaseMapper { + @Delete("DELETE FROM cms_outdoor_fair_booth_booking WHERE fair_id = #{fairId} AND booth_id = #{boothId} AND del_flag <> '0'") + int physicalDeleteDeletedByFairIdAndBoothId(@Param("fairId") Long fairId, @Param("boothId") Long boothId); + + @Delete("DELETE FROM cms_outdoor_fair_booth_booking WHERE fair_id = #{fairId} AND booth_id = #{boothId}") + int physicalDeleteByFairIdAndBoothId(@Param("fairId") Long fairId, @Param("boothId") Long boothId); + + @Delete("DELETE FROM cms_outdoor_fair_booth_booking WHERE fair_id = #{fairId}") + int physicalDeleteByFairId(@Param("fairId") Long fairId); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothMapper.java new file mode 100644 index 0000000..1f7e6d9 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothMapper.java @@ -0,0 +1,24 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.OutdoorFairBooth; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface OutdoorFairBoothMapper extends BaseMapper +{ + @Delete("DELETE FROM cms_outdoor_fair_booth WHERE fair_id = #{fairId}") + int physicalDeleteByFairId(@Param("fairId") Long fairId); + + @Delete({ + "" + }) + int physicalDeleteByFairIdAndIds(@Param("fairId") Long fairId, @Param("ids") List ids); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueBoothMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueBoothMapper.java deleted file mode 100644 index f2fe589..0000000 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueBoothMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ruoyi.cms.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.ruoyi.cms.domain.VenueBooth; -import org.apache.ibatis.annotations.Delete; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -public interface VenueBoothMapper extends BaseMapper -{ - @Delete("DELETE FROM cms_venue_booth WHERE venue_id = #{venueId} AND del_flag <> '0'") - int physicalDeleteDeletedByVenueId(@Param("venueId") Long venueId); - - @Delete({ - "" - }) - int physicalDeleteByVenueIdAndIds(@Param("venueId") Long venueId, @Param("ids") List ids); -} diff --git a/sql/cms_outdoor_fair_booth_snapshot.sql b/sql/cms_outdoor_fair_booth_snapshot.sql new file mode 100644 index 0000000..e6c8df7 --- /dev/null +++ b/sql/cms_outdoor_fair_booth_snapshot.sql @@ -0,0 +1,46 @@ +-- 户外招聘会展位副本:招聘会绑定场地后,在招聘会详情内独立生成和维护展位图 +-- 瀚高数据库 / PostgreSQL 兼容 + +CREATE SEQUENCE IF NOT EXISTS "cms_outdoor_fair_booth_id_seq" START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +CREATE TABLE IF NOT EXISTS "cms_outdoor_fair_booth" ( + "id" BIGINT NOT NULL DEFAULT nextval('"cms_outdoor_fair_booth_id_seq"') PRIMARY KEY, + "fair_id" BIGINT NOT NULL, + "source_venue_id" BIGINT, + "source_booth_id" BIGINT, + "booth_number" VARCHAR(50) NOT NULL, + "position_x" INTEGER NOT NULL DEFAULT 0, + "position_y" INTEGER NOT NULL DEFAULT 0, + "create_by" VARCHAR(64) DEFAULT '', + "create_time" TIMESTAMP(0), + "update_by" VARCHAR(64) DEFAULT '', + "update_time" TIMESTAMP(0), + "remark" VARCHAR(500), + "del_flag" CHAR(1) DEFAULT '0' +); + +COMMENT ON TABLE "cms_outdoor_fair_booth" IS '户外招聘会展位图副本'; +COMMENT ON COLUMN "cms_outdoor_fair_booth"."fair_id" IS '招聘会ID'; +COMMENT ON COLUMN "cms_outdoor_fair_booth"."source_venue_id" IS '来源场地ID'; +COMMENT ON COLUMN "cms_outdoor_fair_booth"."source_booth_id" IS '来源场地展位ID'; +COMMENT ON COLUMN "cms_outdoor_fair_booth"."booth_number" IS '展位号'; +COMMENT ON COLUMN "cms_outdoor_fair_booth"."position_x" IS 'X坐标'; +COMMENT ON COLUMN "cms_outdoor_fair_booth"."position_y" IS 'Y坐标'; + +CREATE UNIQUE INDEX IF NOT EXISTS "uk_cms_outdoor_fair_booth_number" ON "cms_outdoor_fair_booth" ("fair_id", "booth_number"); +CREATE INDEX IF NOT EXISTS "idx_cms_outdoor_fair_booth_fair" ON "cms_outdoor_fair_booth" ("fair_id"); + +-- 兼容历史预定:如果已有副本保留了 source_booth_id,把旧 booking.booth_id 改为招聘会副本展位ID +UPDATE "cms_outdoor_fair_booth_booking" booking +SET "booth_id" = fair_booth."id", + "booth_number" = fair_booth."booth_number" +FROM "cms_outdoor_fair_booth" fair_booth +WHERE booking."fair_id" = fair_booth."fair_id" + AND booking."booth_id" = fair_booth."source_booth_id" + AND booking."del_flag" = '0' + AND NOT EXISTS ( + SELECT 1 + FROM "cms_outdoor_fair_booth" snapshot_booth + WHERE snapshot_booth."fair_id" = booking."fair_id" + AND snapshot_booth."id" = booking."booth_id" + ); diff --git a/sql/cms_venue_booth_map.sql b/sql/cms_venue_booth_map.sql deleted file mode 100644 index 51f719b..0000000 --- a/sql/cms_venue_booth_map.sql +++ /dev/null @@ -1,86 +0,0 @@ --- 场地展位图与户外招聘会展位预定 --- 瀚高数据库 / PostgreSQL 兼容 - -CREATE SEQUENCE IF NOT EXISTS "cms_venue_booth_id_seq" START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -CREATE TABLE IF NOT EXISTS "cms_venue_booth" ( - "id" BIGINT NOT NULL DEFAULT nextval('"cms_venue_booth_id_seq"') PRIMARY KEY, - "venue_id" BIGINT NOT NULL, - "booth_number" VARCHAR(50) NOT NULL, - "position_x" INTEGER NOT NULL DEFAULT 0, - "position_y" INTEGER NOT NULL DEFAULT 0, - "create_by" VARCHAR(64) DEFAULT '', - "create_time" TIMESTAMP(0), - "update_by" VARCHAR(64) DEFAULT '', - "update_time" TIMESTAMP(0), - "remark" VARCHAR(500), - "del_flag" CHAR(1) DEFAULT '0' -); - -COMMENT ON TABLE "cms_venue_booth" IS '场地展位图'; -COMMENT ON COLUMN "cms_venue_booth"."venue_id" IS '场地ID'; -COMMENT ON COLUMN "cms_venue_booth"."booth_number" IS '展位号'; -COMMENT ON COLUMN "cms_venue_booth"."position_x" IS 'X坐标'; -COMMENT ON COLUMN "cms_venue_booth"."position_y" IS 'Y坐标'; - -CREATE UNIQUE INDEX IF NOT EXISTS "uk_cms_venue_booth_number" ON "cms_venue_booth" ("venue_id", "booth_number"); -CREATE INDEX IF NOT EXISTS "idx_cms_venue_booth_venue" ON "cms_venue_booth" ("venue_id"); - -CREATE SEQUENCE IF NOT EXISTS "cms_outdoor_fair_booth_booking_id_seq" START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -CREATE TABLE IF NOT EXISTS "cms_outdoor_fair_booth_booking" ( - "id" BIGINT NOT NULL DEFAULT nextval('"cms_outdoor_fair_booth_booking_id_seq"') PRIMARY KEY, - "fair_id" BIGINT NOT NULL, - "booth_id" BIGINT NOT NULL, - "booth_number" VARCHAR(50) NOT NULL, - "company_id" BIGINT, - "company_name" VARCHAR(200), - "status" VARCHAR(30) DEFAULT 'reserved', - "create_by" VARCHAR(64) DEFAULT '', - "create_time" TIMESTAMP(0), - "update_by" VARCHAR(64) DEFAULT '', - "update_time" TIMESTAMP(0), - "remark" VARCHAR(500), - "del_flag" CHAR(1) DEFAULT '0' -); - -COMMENT ON TABLE "cms_outdoor_fair_booth_booking" IS '户外招聘会展位预定'; -CREATE UNIQUE INDEX IF NOT EXISTS "uk_cms_outdoor_fair_booth_booking" ON "cms_outdoor_fair_booth_booking" ("fair_id", "booth_id"); -CREATE INDEX IF NOT EXISTS "idx_cms_outdoor_fair_booth_booking_fair" ON "cms_outdoor_fair_booth_booking" ("fair_id"); - -WITH target_venue AS ( - SELECT "id" AS venue_id - FROM "cms_venue_info" - WHERE "venue_name" = '石河子市人民广场户外招聘区' - LIMIT 1 -), -seed AS ( - SELECT - target_venue.venue_id, - 'KJ' || LPAD(gs::text, 2, '0') AS booth_number, - 40 + ((gs - 1) % 20) * 54 AS position_x, - 40 + ((gs - 1) / 20) * 54 AS position_y - FROM target_venue - CROSS JOIN generate_series(1, 80) gs -) -INSERT INTO "cms_venue_booth" ("venue_id", "booth_number", "position_x", "position_y", "create_by", "create_time", "del_flag") -SELECT seed.venue_id, seed.booth_number, seed.position_x, seed.position_y, 'system', NOW(), '0' -FROM seed -WHERE NOT EXISTS ( - SELECT 1 - FROM "cms_venue_booth" existing - WHERE existing."venue_id" = seed.venue_id - AND existing."booth_number" = seed.booth_number -); - -UPDATE "cms_venue_info" venue -SET "floor_count" = booth_counts.total -FROM ( - SELECT "venue_id", COUNT(*) AS total - FROM "cms_venue_booth" - GROUP BY "venue_id" -) booth_counts -WHERE venue."id" = booth_counts."venue_id"; - -UPDATE "cms_outdoor_fair" fair -SET "booth_count" = venue."floor_count" -FROM "cms_venue_info" venue -WHERE fair."venue_id" = venue."id"; diff --git a/sql/drop_cms_venue_booth.sql b/sql/drop_cms_venue_booth.sql new file mode 100644 index 0000000..38f7a29 --- /dev/null +++ b/sql/drop_cms_venue_booth.sql @@ -0,0 +1,5 @@ +-- 移除场地信息维护中的展位图母本表 +-- 执行前请确认招聘会展位图已经迁移到 cms_outdoor_fair_booth + +DROP TABLE IF EXISTS "cms_venue_booth"; +DROP SEQUENCE IF EXISTS "cms_venue_booth_id_seq";