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:
- Validation mismatch on
code. The DB column (ponds.code) is nullable and the
frontend Zod schema/form already treatcodeas optional (no required asterisk shown),
butCreatePondDto.codeis@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. - Too many required fields for a “simple” create flow.
surfaceArea,depth, and
typeare 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
- Remove the
codefield from the Pond entity entirely (DB column, DTOs, entity,
duplicate-check logic, search filter, and every display location) — not just make it
optional. - Make
surfaceArea,depth, andtypeoptional. OnlyareaIdandnameremain
required to create a pond. - Redesign
PondFormStep 1 with progressive disclosure:areaId+nameare shown as
the only always-visible fields;surfaceArea,depth,type,statusmove 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. - Guard every place that reads
surfaceArea/depth/typeassuming a non-null value so
nothing throws or silently computesNaN/0once these fields can be null. - Add an explicit validation guard when starting a farming cycle on a pond with no
surfaceAreaset, since stocking density (initialStock / surfaceArea) is computed
at cycle creation.
Non-goals
- No changes to
PondFormStep 2 (device/staff assignment). - No changes to
Area.codeorOrganization.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>.tsconvention): drop thecodecolumn from
ponds; altersurface_areaandtypeto nullable (depthis already nullable). backend/src/farm/entities/pond.entity.ts: remove thecodecolumn; type
surfaceAreaandtypeas nullable.backend/src/farm/dto/create-pond.dto.ts/update-pond.dto.ts: remove
code; add@IsOptional()tosurfaceAreaandtype.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
codefrom the create/update payload construction and from the response DTO
mapping. - Fix the unguarded
Number(pond.surfaceArea)response mapping (currently ~line 464,
which silently returns0fornull) to follow the same null-safe pattern already
used fordepthon the next line. - Remove
codefrom theILIKEsearch filter (currently ~line 55).
- Remove the code-based duplicate-check in
backend/src/devices/devices.service.ts(~line 887): remove
code: device.pond.codefrom the device response DTO.backend/src/aquaculture/cycles.service.ts(~line 252): before computing
stockingDensity = initialStock / pond.surfaceArea, checkpond.surfaceAreais set;
if not, throwBadRequestExceptionwith a message telling the user to set the pond’s
surface area first.backend/src/database/seeds/demo-data.seed.ts: remove the hardcoded pondcode
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:
removecodefromcreatePondSchema/updatePondSchema/CreatePondDto/
UpdatePondDto/Pond; makesurfaceAreaandtypeoptional in the create schema to
match the backend.frontend/src/components/farm/PondForm.tsx:- Remove the
codeform item, its inclusion ininitialValues/form.setFieldsValue,
and its inclusion in the create/update submit payloads. - Restructure
BasicInfoStep:areaIdandnameremain always visible. Wrap
surfaceArea,depth,type,statusin an Ant DesignCollapsepanel 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 (
typedefaults tolined,statusdefaults to
active) inside the advanced section; the user is never forced to open it to submit.
- Remove the
- 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.Itemand thecodesubtitle),
DeviceList.tsx,DeviceDashboard.tsx,DeviceDetail.tsx,
DeviceOperationsPage.tsx,CycleDetail.tsx,CycleEdit.tsx,CycleList.tsx,
CyclesDashboard.tsx. - Null-guard remaining
surfaceArea/depth/typedisplay sites that currently call
.toLocaleString()/.toFixed()without a guard and would throw onnull/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 thesurfaceArearequirement anywhere it’s derived fromPond, to match the
entity change.
Tests
- Backend (Jest, update before implementing per TDD):
ponds.service.spec.tsandponds.controller.spec.ts: removecodefrom
fixtures/assertions, add a case creating a pond with onlyareaId+name, and
remove/replace the duplicate-code-rejection test cases.devices.service.spec.ts: drop thecodefixture value.cycles.service.spec.ts: add a case asserting the newBadRequestExceptionwhen
starting a cycle on a pond with nosurfaceArea.
- 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’spondmapping)
andbackend/src/aquaculture/dto/cycle-response.dto.ts(CycleResponseinterface +
CycleResponseDtoclass, both declarepond.code).backend/src/aquaculture/daily-logs.service.ts:521(toDailyLogResponse’spond
mapping) andbackend/src/aquaculture/dto/daily-log-response.dto.ts(pond?.code).backend/src/devices/device-operations.service.ts:110and
backend/src/devices/dto/device-operations.dto.ts(DeviceOperationsWorkspaceRowDto.pond).backend/src/devices/alert.service.ts:257and
backend/src/devices/dto/alert-response.dto.ts(pond?.code).backend/src/devices/controllers/alert-config.controller.ts:181and
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
codecolumn is a destructive migration — any pond codes already stored
are permanently lost. Accepted as part of this change (confirmed during design). - Making
surfaceArea/typenullable touches stocking-density math in
cycles.service.ts, which is why an explicit guard is included rather than relying on
implicitNaNpropagation.