Pond Creation Simplification (Issue #159)

Problem

Creating a pond currently requires filling in areaId, name, code, surfaceArea,
depth, and type before the form can be submitted. Two issues:

  1. Validation mismatch on code. The DB column (ponds.code) is nullable and the
    frontend Zod schema/form already treat code as optional (no required asterisk shown),
    but CreatePondDto.code is @IsNotEmpty() on the backend. Submitting without a code
    throws a confusing 400 “Pond code is required” even though nothing in the UI told the
    user it was required.
  2. Too many required fields for a “simple” create flow. surfaceArea, depth, and
    type are all mandatory today, forcing users to know pond dimensions before they can
    even register a pond in the system.

code is not used as a real reference anywhere — no foreign key relies on it, only
display strings ("{pond.name} ({pond.code})") and an ILIKE search filter.

Goals

  1. Remove the code field from the Pond entity entirely (DB column, DTOs, entity,
    duplicate-check logic, search filter, and every display location) — not just make it
    optional.
  2. Make surfaceArea, depth, and type optional. Only areaId and name remain
    required to create a pond.
  3. Redesign PondForm Step 1 with progressive disclosure: areaId + name are shown as
    the only always-visible fields; surfaceArea, depth, type, status move into a
    collapsible “Tùy chọn nâng cao” (Advanced options) section, collapsed by default when
    creating a new pond, auto-expanded when editing a pond that already has these values
    set.
  4. Guard every place that reads surfaceArea/depth/type assuming a non-null value so
    nothing throws or silently computes NaN/0 once these fields can be null.
  5. Add an explicit validation guard when starting a farming cycle on a pond with no
    surfaceArea set, since stocking density (initialStock / surfaceArea) is computed
    at cycle creation.

Non-goals

  • No changes to PondForm Step 2 (device/staff assignment).
  • No changes to Area.code or Organization.code — different entities, not affected.
  • No auto-generation of a replacement pond code. The feature is removed, not replaced.
  • No new uniqueness constraint on pond name (none exists today; not adding one).

Backend changes

  • Migration (new file under backend/src/migrations/, following the
    <timestamp>-<PascalCaseDescription>.ts convention): drop the code column from
    ponds; alter surface_area and type to nullable (depth is already nullable).
  • backend/src/farm/entities/pond.entity.ts: remove the code column; type
    surfaceArea and type as nullable.
  • backend/src/farm/dto/create-pond.dto.ts / update-pond.dto.ts: remove
    code; add @IsOptional() to surfaceArea and type.
  • backend/src/farm/ponds.service.ts:
    • Remove the code-based duplicate-check in create() (currently ~line 136-145) and
      update() (currently ~line 202-214).
    • Remove code from the create/update payload construction and from the response DTO
      mapping.
    • Fix the unguarded Number(pond.surfaceArea) response mapping (currently ~line 464,
      which silently returns 0 for null) to follow the same null-safe pattern already
      used for depth on the next line.
    • Remove code from the ILIKE search filter (currently ~line 55).
  • backend/src/devices/devices.service.ts (~line 887): remove
    code: device.pond.code from the device response DTO.
  • backend/src/aquaculture/cycles.service.ts (~line 252): before computing
    stockingDensity = initialStock / pond.surfaceArea, check pond.surfaceArea is set;
    if not, throw BadRequestException with a message telling the user to set the pond’s
    surface area first.
  • backend/src/database/seeds/demo-data.seed.ts: remove the hardcoded pond code
    values (POND-1A, POND-1B, POND-1C, POND-2A, POND-2B, POND-2C). Area codes
    (NORTH-AREA, SOUTH-AREA) and organization code (BENTRE-SHRIMP-01) are untouched.

Frontend changes

  • frontend/src/schemas/farm.schema.ts / frontend/src/types/farm.types.ts:
    remove code from createPondSchema/updatePondSchema/CreatePondDto/
    UpdatePondDto/Pond; make surfaceArea and type optional in the create schema to
    match the backend.
  • frontend/src/components/farm/PondForm.tsx:
    • Remove the code form item, its inclusion in initialValues/form.setFieldsValue,
      and its inclusion in the create/update submit payloads.
    • Restructure BasicInfoStep: areaId and name remain always visible. Wrap
      surfaceArea, depth, type, status in an Ant Design Collapse panel labeled
      “Tùy chọn nâng cao”, collapsed by default for create, expanded by default when
      editing an existing pond that already has any of these fields set.
    • Keep the existing UI defaults (type defaults to lined, status defaults to
      active) inside the advanced section; the user is never forced to open it to submit.
  • Remove the {pond.name} ({pond.code}) display pattern, replacing it with just
    {pond.name}, in every file where it currently appears:
    PondCard.tsx, PondTable.tsx (also drop the “Mã ao” column definition),
    PondDetail.tsx (drop the “Mã ao” Descriptions.Item and the code subtitle),
    DeviceList.tsx, DeviceDashboard.tsx, DeviceDetail.tsx,
    DeviceOperationsPage.tsx, CycleDetail.tsx, CycleEdit.tsx, CycleList.tsx,
    CyclesDashboard.tsx.
  • Null-guard remaining surfaceArea/depth/type display sites that currently call
    .toLocaleString()/.toFixed() without a guard and would throw on null/undefined:
    PondCard.tsx:175, PondTable.tsx:167, PondDetail.tsx:743,
    CycleWizard.tsx:187,377. Use the same fallback pattern already used elsewhere (e.g.
    CycleEdit.tsx:190’s ?.toLocaleString() ?? '-').
  • frontend/src/schemas/cycle.schema.ts / frontend/src/types/cycle.types.ts:
    relax the surfaceArea requirement anywhere it’s derived from Pond, to match the
    entity change.

Tests

  • Backend (Jest, update before implementing per TDD):
    • ponds.service.spec.ts and ponds.controller.spec.ts: remove code from
      fixtures/assertions, add a case creating a pond with only areaId + name, and
      remove/replace the duplicate-code-rejection test cases.
    • devices.service.spec.ts: drop the code fixture value.
    • cycles.service.spec.ts: add a case asserting the new BadRequestException when
      starting a cycle on a pond with no surfaceArea.
  • Frontend: there is currently no test file for PondForm.tsx. This is a pre-existing
    gap, not something this change is expected to silently paper over — the implementation
    plan should call out whether to add coverage for the new advanced-section behavior or
    explicitly note the gap as out of scope.

Addendum: additional code call sites found during plan-writing

A full repo-wide audit (done while writing the implementation plan) found more
production call sites embedding pond.code than the “Backend changes” section above
lists — these are all instances of the same approved removal, not new decisions:

  • backend/src/aquaculture/cycles.service.ts:529 (toCycleResponse’s pond mapping)
    and backend/src/aquaculture/dto/cycle-response.dto.ts (CycleResponse interface +
    CycleResponseDto class, both declare pond.code).
  • backend/src/aquaculture/daily-logs.service.ts:521 (toDailyLogResponse’s pond
    mapping) and backend/src/aquaculture/dto/daily-log-response.dto.ts (pond?.code).
  • backend/src/devices/device-operations.service.ts:110 and
    backend/src/devices/dto/device-operations.dto.ts (DeviceOperationsWorkspaceRowDto.pond).
  • backend/src/devices/alert.service.ts:257 and
    backend/src/devices/dto/alert-response.dto.ts (pond?.code).
  • backend/src/devices/controllers/alert-config.controller.ts:181 and
    backend/src/devices/dto/alert-config-response.dto.ts (pond?.code).

None of these are displayed in the frontend today (confirmed by a repo-wide grep for
.code restricted to pond-shaped values), but all become TypeScript compile errors once
Pond.code is removed from the entity, so they must be fixed regardless. The
implementation plan covers all of them.

Risks

  • Dropping the code column is a destructive migration — any pond codes already stored
    are permanently lost. Accepted as part of this change (confirmed during design).
  • Making surfaceArea/type nullable touches stocking-density math in
    cycles.service.ts, which is why an explicit guard is included rather than relying on
    implicit NaN propagation.