diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/config/Constants.java b/backend/src/main/java/ca/bc/gov/backendstartapi/config/Constants.java index 8a1290165..d5c458468 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/config/Constants.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/config/Constants.java @@ -7,4 +7,5 @@ public final class Constants { public static final Integer CLASS_A_SEEDLOT_NUM_MAX = 69999; public static final String INCOMPLETE_SEEDLOT_STATUS = "INC"; public static final String PENDING_SEEDLOT_STATUS = "PND"; + public static final String SUBMITTED_SEEDLOT_STATUS = "SUB"; } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormService.java index aa6baaa93..27a01adbe 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormService.java @@ -58,6 +58,7 @@ public RevisionCountDto saveFormClassA( SaveSeedlotProgressEntityClassA entityToSave; // If an entity exist then update the values, otherwise make a new entity. + // The SUB status check is to create a draft for historical Oracle seedlots. if (optionalEntityToSave.isEmpty()) { SparLog.info( "First time saving A-class seedlot progress for seedlot number {}", seedlotNumber); diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotService.java index 4ebaa7160..90d1c512d 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotService.java @@ -591,18 +591,23 @@ public SeedlotAclassFormDto getAclassSeedlotFormInfo(@NonNull String seedlotNumb List seedlotOrchards = seedlotOrchardService.getAllSeedlotOrchardBySeedlotNumber(seedlotInfo.getId()); - List filteredPrimaryOrchard = - seedlotOrchards.stream().filter(so -> so.getIsPrimary()).toList(); - - String primaryOrchardId = - filteredPrimaryOrchard.isEmpty() - ? filteredPrimaryOrchard.get(0).getOrchardId() - : seedlotOrchards.get(0).getOrchardId(); - - Optional secondaryOrchardId = - seedlotOrchards.size() > 1 - ? Optional.of(seedlotOrchards.get(1).getOrchardId()) - : Optional.empty(); + String primaryOrchardId = null; + + if (!seedlotOrchards.isEmpty()) { + List filteredPrimaryOrchard = + seedlotOrchards.stream().filter(so -> so.getIsPrimary()).toList(); + + primaryOrchardId = + filteredPrimaryOrchard.isEmpty() + ? null + : filteredPrimaryOrchard.get(0).getOrchardId(); + } + + Optional secondaryOrchardId = Optional.empty(); + + if (seedlotOrchards.size() > 1 && !seedlotOrchards.get(1).getIsPrimary()) { + secondaryOrchardId = Optional.of(seedlotOrchards.get(1).getOrchardId()); + } SeedlotFormOrchardDto orchardStep = new SeedlotFormOrchardDto( diff --git a/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx index 2b3852659..242d9d9e6 100644 --- a/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx @@ -34,8 +34,8 @@ const InterimReviewRead = () => { cacheTime: THREE_HALF_HOURS }); - const getFacilityTypeLabel = (interimType: string) => { - if (facilityTypesQuery.data) { + const getFacilityTypeLabel = (interimType: string | null) => { + if (facilityTypesQuery.data && interimType) { const selectedType = facilityTypesQuery.data.filter((type) => type.code === interimType); return selectedType[0].label; } diff --git a/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx index 8576f9eae..8c078d5bc 100644 --- a/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx @@ -64,7 +64,7 @@ const OrchardReviewRead = () => { @@ -74,7 +74,7 @@ const OrchardReviewRead = () => { diff --git a/frontend/src/types/SeedlotType.ts b/frontend/src/types/SeedlotType.ts index 8be25af3e..a8bfd7139 100644 --- a/frontend/src/types/SeedlotType.ts +++ b/frontend/src/types/SeedlotType.ts @@ -234,7 +234,7 @@ export type InterimFormSubmitType = { } export type OrchardFormSubmitType = { - primaryOrchardId: string, + primaryOrchardId: string | null, secondaryOrchardId: string | null, femaleGameticMthdCode: string, maleGameticMthdCode: string, diff --git a/frontend/src/utils/DateUtils.ts b/frontend/src/utils/DateUtils.ts index ff8cca565..617c0c462 100644 --- a/frontend/src/utils/DateUtils.ts +++ b/frontend/src/utils/DateUtils.ts @@ -1,6 +1,6 @@ import { DateTime as luxon } from 'luxon'; import { PLACE_HOLDER } from '../shared-constants/shared-constants'; -import { MONTH_DAY_YEAR, ISO_YEAR_MONTH_DAY_DASH, ISO_YEAR_MONTH_DAY_SLASH } from '../config/DateFormat'; +import { MONTH_DAY_YEAR, ISO_YEAR_MONTH_DAY_SLASH, ISO_YEAR_MONTH_DAY_DASH } from '../config/DateFormat'; const DEFAULT_LOCAL_TIMEZONE = 'America/Vancouver'; @@ -33,7 +33,7 @@ export const utcToIsoSlashStyle = (utcDate: string | null | undefined): string = if (!utcDate) { return ''; } - return luxon.fromISO(utcDate, { zone: 'utc' }) + return luxon.fromISO(utcDate) .setZone(DEFAULT_LOCAL_TIMEZONE).toFormat(ISO_YEAR_MONTH_DAY_SLASH); }; @@ -44,6 +44,6 @@ export const localDateToUtcFormat = (localDate: string): string | null => { if (!localDate) { return null; } - return luxon.fromFormat(localDate, 'yyyy/MM/dd', { zone: 'America/Vancouver' }) - .toUTC().toFormat(ISO_YEAR_MONTH_DAY_DASH); + return luxon.fromFormat(localDate, 'yyyy/MM/dd', { zone: DEFAULT_LOCAL_TIMEZONE }) + .startOf('day').toUTC().toFormat(ISO_YEAR_MONTH_DAY_DASH); }; diff --git a/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts b/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts index a816ccaaa..17ed8dd1b 100644 --- a/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts +++ b/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts @@ -75,7 +75,7 @@ export const initCollectionState = ( }, locationCode: { id: 'collection-location-code', - value: collectionStepData.collectionLocnCode, + value: collectionStepData.collectionLocnCode ?? '', isInvalid: false }, startDate: { @@ -90,17 +90,17 @@ export const initCollectionState = ( }, numberOfContainers: { id: 'collection-num-of-container', - value: String(collectionStepData.noOfContainers), + value: collectionStepData.noOfContainers?.toString() ?? '', isInvalid: false }, volumePerContainers: { id: 'collection-vol-per-container', - value: String(collectionStepData.volPerContainer), + value: collectionStepData.volPerContainer?.toString() ?? '', isInvalid: false }, volumeOfCones: { id: 'collection-vol-of-cones', - value: String(collectionStepData.clctnVolume), + value: collectionStepData.clctnVolume?.toString() ?? '', isInvalid: false }, selectedCollectionCodes: { @@ -110,7 +110,7 @@ export const initCollectionState = ( }, comments: { id: 'collection-comments', - value: collectionStepData.seedlotComment, + value: collectionStepData.seedlotComment ?? '', isInvalid: false } }); @@ -125,14 +125,16 @@ export const initOwnershipState = ( const ownerState = createOwnerTemplate(index, curOwner); ownerState.ownerAgency.value = defaultAgencyNumber; - ownerState.ownerCode.value = curOwner.ownerLocnCode; - if (methodsOfPayment && fundingSource) { + + if (methodsOfPayment && methodsOfPayment.length > 0) { const payment = methodsOfPayment .filter((data: MultiOptionsObj) => data.code === curOwner.methodOfPaymentCode)[0]; + ownerState.methodOfPayment.value = payment; + } + if (fundingSource && fundingSource.length > 0) { const fundSource = fundingSource .filter((data: MultiOptionsObj) => data.code === curOwner.sparFundSrceCode)[0]; - ownerState.methodOfPayment.value = payment; ownerState.fundingSource.value = fundSource; } return ownerState; @@ -157,7 +159,7 @@ export const initInterimState = ( }, locationCode: { id: 'interim-location-code', - value: interimStepData.intermStrgLocnCode, + value: interimStepData.intermStrgLocnCode ?? '', isInvalid: false }, startDate: { @@ -172,12 +174,12 @@ export const initInterimState = ( }, facilityType: { id: 'storage-facility-type', - value: interimStepData.intermFacilityCode, + value: interimStepData.intermFacilityCode ?? '', isInvalid: false }, facilityOtherType: { id: 'storage-other-type-input', - value: interimStepData.intermOtherFacilityDesc, + value: interimStepData.intermOtherFacilityDesc ?? '', isInvalid: false } }); @@ -346,7 +348,7 @@ export const initExtractionStorageState = ( }, locationCode: { id: 'ext-location-code', - value: useTSCExtract ? tscLocationCode : extractionStepData.extractoryLocnCode, + value: useTSCExtract ? tscLocationCode : (extractionStepData.extractoryLocnCode ?? ''), isInvalid: false }, startDate: { @@ -373,7 +375,7 @@ export const initExtractionStorageState = ( }, locationCode: { id: 'str-location-code', - value: useTSCStorage ? tscLocationCode : extractionStepData.storageLocnCode, + value: useTSCStorage ? tscLocationCode : (extractionStepData.storageLocnCode ?? ''), isInvalid: false }, startDate: { @@ -479,28 +481,28 @@ export const verifyCollectionStepCompleteness = ( let isComplete = true; let idToFocus = ''; - if (!collectionData.collectorAgency.value.length) { + if (!collectionData.collectorAgency.value) { isComplete = false; idToFocus = collectionData.collectorAgency.id; - } else if (!collectionData.locationCode.value.length) { + } else if (!collectionData.locationCode.value) { isComplete = false; idToFocus = collectionData.locationCode.id; - } else if (!collectionData.startDate.value.length) { + } else if (!collectionData.startDate.value) { isComplete = false; idToFocus = collectionData.startDate.id; - } else if (!collectionData.endDate.value.length) { + } else if (!collectionData.endDate.value) { isComplete = false; idToFocus = collectionData.endDate.id; - } else if (!collectionData.numberOfContainers.value.length) { + } else if (!collectionData.numberOfContainers.value) { isComplete = false; idToFocus = collectionData.numberOfContainers.id; - } else if (!collectionData.volumePerContainers.value.length) { + } else if (!collectionData.volumePerContainers.value) { isComplete = false; idToFocus = collectionData.volumePerContainers.id; - } else if (!collectionData.volumeOfCones.value.length) { + } else if (!collectionData.volumeOfCones.value) { isComplete = false; idToFocus = collectionData.volumeOfCones.id; - } else if (!collectionData.selectedCollectionCodes.value.length) { + } else if (!collectionData.selectedCollectionCodes.value) { isComplete = false; // Have to hard code id to focus as they are generated dynamically, // assuming that there will always be a code 1 in the list of collection methods. @@ -525,19 +527,19 @@ export const verifyOwnershipStepCompleteness = ( let idToFocus = ''; for (let i = 0; i < ownershipData.length; i += 1) { - if (!ownershipData[i].ownerAgency.value.length) { + if (!ownershipData[i].ownerAgency.value) { isComplete = false; idToFocus = ownershipData[i].ownerAgency.id; - } else if (!ownershipData[i].ownerCode.value.length) { + } else if (!ownershipData[i].ownerCode.value) { isComplete = false; idToFocus = ownershipData[i].ownerCode.id; - } else if (!ownershipData[i].ownerPortion.value.length) { + } else if (!ownershipData[i].ownerPortion.value) { isComplete = false; idToFocus = ownershipData[i].ownerPortion.id; - } else if (!ownershipData[i].reservedPerc.value.length) { + } else if (!ownershipData[i].reservedPerc.value) { isComplete = false; idToFocus = ownershipData[i].reservedPerc.id; - } else if (!ownershipData[i].surplusPerc.value.length) { + } else if (!ownershipData[i].surplusPerc.value) { isComplete = false; idToFocus = ownershipData[i].surplusPerc.id; } else if ( diff --git a/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx b/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx index 0b6c89062..ce02a8532 100644 --- a/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx +++ b/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx @@ -53,6 +53,7 @@ import ClassAContext from '../ContextContainerClassA/context'; import { validateRegForm } from '../CreateAClass/utils'; import { getSeedlotPayload, + initOwnershipState, validateCollectionStep, validateExtractionStep, validateInterimStep, validateOrchardStep, validateOwnershipStep, validateParentStep, verifyCollectionStepCompleteness, verifyExtractionStepCompleteness, @@ -66,7 +67,8 @@ import { } from './utils'; import { GenWorthValType } from './definitions'; import { SaveStatusModalText } from './constants'; -import { completeProgressConfig } from '../ContextContainerClassA/constants'; +import { completeProgressConfig, emptyOwnershipStep, initialProgressConfig } from '../ContextContainerClassA/constants'; +import { AllStepData, ProgressIndicatorConfig } from '../ContextContainerClassA/definitions'; const SeedlotReviewContent = () => { const navigate = useNavigate(); @@ -327,35 +329,6 @@ const SeedlotReviewContent = () => { } }); - const updateDraftMutation = useMutation({ - mutationFn: ( - // It will be used later at onSuccess - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _variables: PutTscSeedlotMutationObj - ) => putAClassSeedlotProgress( - seedlotNumber ?? '', - { - allStepData, - progressStatus: completeProgressConfig, - // We don't know the previous revision count - revisionCount: -1 - } - ), - onSuccess: (_data, variables) => ( - tscSeedlotMutation.mutate(variables) - ), - onError: (err: AxiosError) => { - toast.error( - , - ErrToastOption - ); - }, - retry: 0 - }); - const statusOnlyMutaion = useMutation({ mutationFn: ( { seedlotNum, statusOnSave }: Omit @@ -379,6 +352,67 @@ const SeedlotReviewContent = () => { } }); + /** + * This is only used when we send SUB seedlots back to pending when + * we migrate historical data from Oracle to Postgres. PND seedlots on Oracle + * will come in as SUB, so we need to manually send them back to PND in order to + * generate a draft json object in the seedlot_registration_a_class_save table. + */ + const getAllStepDataForPayload = ():AllStepData => { + const allData = { ...allStepData }; + if (allData.ownershipStep.length === 0) { + const emptyOwner = initOwnershipState('', emptyOwnershipStep)[0]; + emptyOwner.ownerAgency.value = seedlotData?.applicantClientNumber ?? ''; + emptyOwner.ownerCode.value = seedlotData?.applicantLocationCode ?? ''; + allData.ownershipStep = [emptyOwner]; + } + return allData; + }; + + const getProgressStatus = (): ProgressIndicatorConfig => { + let progStatusPayload = completeProgressConfig; + if (allStepData.ownershipStep.length === 0) { + progStatusPayload = initialProgressConfig; + } + + return progStatusPayload; + }; + + const updateDraftMutation = useMutation({ + mutationFn: ( + // It will be used later at onSuccess + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _variables?: PutTscSeedlotMutationObj + ) => ( + putAClassSeedlotProgress( + seedlotNumber ?? '', + { + allStepData: getAllStepDataForPayload(), + progressStatus: getProgressStatus(), + // We don't know the previous revision count + revisionCount: -1 + } + ) + ), + onSuccess: (_data, variables) => { + if (variables) { + tscSeedlotMutation.mutate(variables); + } else { + statusOnlyMutaion.mutate({ seedlotNum: seedlotNumber!, statusOnSave: 'PND' }); + } + }, + onError: (err: AxiosError) => { + toast.error( + , + ErrToastOption + ); + }, + retry: 0 + }); + /** * The handler for the button that is floating on the bottom right. */ @@ -403,6 +437,13 @@ const SeedlotReviewContent = () => { * The handler for the send back to pending or approve buttons. */ const handleSaveAndStatus = (statusOnSave: StatusOnSaveType) => { + // This if statement is to deal with a special situation + // see getAllStepDataForDraftPayload's doc for more detail. + if (allStepData.ownershipStep.length === 0 && statusOnSave === 'PND') { + updateDraftMutation.mutate(undefined); + return; + } + if (isReadMode) { statusOnlyMutaion.mutate({ seedlotNum: seedlotNumber!, statusOnSave }); } else {